123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2021 Nomadic Labs, <contact@nomadic-labs.com> *)(* Copyright (c) 2023 TriliTech <contact@trili.tech> *)(* Copyright (c) 2023 Functori, <contact@functori.com> *)(* Copyright (c) 2023 Marigold <contact@marigold.dev> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)(** This file defines the services of the rollup node that do not depend on the
protocol. A top-level directory is built for them in the rollup node
library.
Protocol specific services are for RPCs under [/global/block/] and are
defined in the protocol specific libraries in
[src/proto_*/lib_sc_rollup_layer2/sc_rollup_services.ml].
*)(** We distinguish RPC endpoints served by the rollup node into [global] and
[local]. The difference between the two lies in whether the responses given
by different rollup nodes in the same state (see below for an exact
definition) must be the same (in the case of global endpoints) or can differ
(in the case of local endpoints).
More formally, two rollup nodes are in the same quiescent state if they are
subscribed to the same rollup address, and have processed the same set of
heads from the layer1. We only consider quiescent states, that is those
where rollup nodes are not actively processing a head received from layer1.
Examples of global endpoints are [current_tezos_head] and
[last_stored_commitment], as the responses returned by these endpoints is
expected to be consistent across rollup nodes in the same state.
An example of local endpoint is [last_published_commitment], as two rollup
nodes in the same state may either publish or not publish a commitment,
according to whether its inbox level is below the inbox level of the last
cemented commitment at the time they tried to publish the commitment. See
below for a more detailed explanation.
*)typemessage_status=|Unknown|Pending_batch|Pending_injectionofL1_operation.t|Injectedof{op:L1_operation.t;oph:Operation_hash.t;op_index:int}|Includedof{op:L1_operation.t;oph:Operation_hash.t;op_index:int;l1_block:Block_hash.t;l1_level:int32;finalized:bool;cemented:bool;}|Committedof{op:L1_operation.t;oph:Operation_hash.t;op_index:int;l1_block:Block_hash.t;l1_level:int32;finalized:bool;cemented:bool;commitment:Commitment.t;commitment_hash:Commitment.Hash.t;first_published_at_level:int32;published_at_level:int32;}typegc_info={last_gc_level:int32;first_available_level:int32}moduleEncodings=structopenData_encodingletcommitment_with_hash=obj2(req"commitment"Commitment.encoding)(req"hash"Commitment.Hash.encoding)letcommitment_with_hash_and_level_infos=obj4(req"commitment"Commitment.encoding)(req"hash"Commitment.Hash.encoding)(opt"first_published_at_level"int32)(opt"published_at_level"int32)letqueued_message=obj2(req"hash"L2_message.Hash.encoding)(req"message"L2_message.encoding)letbatcher_queue=listqueued_messageletmessage_status=union[case(Tag0)~title:"unknown"~description:"The message is not known by the batcher."(obj1(req"status"(constant"unknown")))(functionUnknown->Some()|_->None)(fun()->Unknown);case(Tag1)~title:"pending_batch"~description:"The message is in the batcher queue."(obj1(req"status"(constant"pending_batch")))(functionPending_batch->Some()|_->None)(fun()->Pending_batch);case(Tag2)~title:"pending_injection"~description:"The message is batched but not injected yet."(obj2(req"status"(constant"pending_injection"))(req"operation"L1_operation.encoding))(functionPending_injectionop->Some((),op)|_->None)(fun((),op)->Pending_injectionop);case(Tag3)~title:"injected"~description:"The message is injected as part of an L1 operation but it is not \
included in a block."(obj3(req"status"(constant"injected"))(req"operation"L1_operation.encoding)(req"layer1"(obj2(req"operation_hash"Operation_hash.encoding)(req"operation_index"int31))))(function|Injected{op;oph;op_index}->Some((),op,(oph,op_index))|_->None)(fun((),op,(oph,op_index))->Injected{op;oph;op_index});case(Tag4)~title:"included"~description:"The message is included in an inbox in an L1 block."(obj5(req"status"(constant"included"))(req"operation"L1_operation.encoding)(req"layer1"(obj4(req"operation_hash"Operation_hash.encoding)(req"operation_index"int31)(req"block_hash"Block_hash.encoding)(req"level"int32)))(req"finalized"bool)(req"cemented"bool))(function|Included{op;oph;op_index;l1_block;l1_level;finalized;cemented}->Some((),op,(oph,op_index,l1_block,l1_level),finalized,cemented)|_->None)(fun((),op,(oph,op_index,l1_block,l1_level),finalized,cemented)->Included{op;oph;op_index;l1_block;l1_level;finalized;cemented});case(Tag5)~title:"committed"~description:"The message is included in a committed inbox on L1."(obj9(req"status"(constant"committed"))(req"operation"L1_operation.encoding)(req"layer1"(obj4(req"operation_hash"Operation_hash.encoding)(req"operation_index"int31)(req"block_hash"Block_hash.encoding)(req"level"int32)))(req"finalized"bool)(req"cemented"bool)(req"commitment"Commitment.encoding)(req"hash"Commitment.Hash.encoding)(req"first_published_at_level"int32)(req"published_at_level"int32))(function|Committed{op;oph;op_index;l1_block;l1_level;finalized;cemented;commitment;commitment_hash;first_published_at_level;published_at_level;}->Some((),op,(oph,op_index,l1_block,l1_level),finalized,cemented,commitment,commitment_hash,first_published_at_level,published_at_level)|_->None)(fun((),op,(oph,op_index,l1_block,l1_level),finalized,cemented,commitment,commitment_hash,first_published_at_level,published_at_level)->Committed{op;oph;op_index;l1_block;l1_level;finalized;cemented;commitment;commitment_hash;first_published_at_level;published_at_level;});]letmessage_status_output=merge_objs(obj1(opt"content"(string'Hex)))message_statusletgc_info:gc_infoData_encoding.t=conv(fun{last_gc_level;first_available_level}->(last_gc_level,first_available_level))(fun(last_gc_level,first_available_level)->{last_gc_level;first_available_level})@@obj2(req"last_gc_level"int32)(req"first_available_level"int32)endmoduleArg=structtypeblock_id=[`Head|`HashofBlock_hash.t|`LevelofInt32.t|`Finalized|`Cemented]letconstruct_block_id=function|`Head->"head"|`Hashh->Block_hash.to_b58checkh|`Levell->Int32.to_stringl|`Finalized->"finalized"|`Cemented->"cemented"letdestruct_block_idh=matchhwith|"head"->Ok`Head|"finalized"->Ok`Finalized|"cemented"->Ok`Cemented|_->(matchInt32.of_string_opthwith|Somel->Ok(`Levell)|None->(matchBlock_hash.of_b58check_opthwith|Someb->Ok(`Hashb)|None->Error"Cannot parse block id"))letblock_id:block_idTezos_rpc.Arg.t=Tezos_rpc.Arg.make~descr:"An L1 block identifier."~name:"block_id"~construct:construct_block_id~destruct:destruct_block_id()letl2_message_hash:L2_message.hashTezos_rpc.Arg.t=Tezos_rpc.Arg.make~descr:"A L2 message hash."~name:"l2_message_hash"~construct:L2_message.Hash.to_b58check~destruct:(funs->L2_message.Hash.of_b58check_opts|>Option.to_result~none:"Invalid L2 message hash")()letcommitment_hash:Commitment.Hash.tTezos_rpc.Arg.t=Tezos_rpc.Arg.make~descr:"A commitment hash."~name:"commitment_hash"~construct:Commitment.Hash.to_b58check~destruct:(funs->Commitment.Hash.of_b58check_opts|>Option.to_result~none:"Invalid commitment hash")()endmoduletypePREFIX=sigtypeprefixvalprefix:(unit,prefix)Tezos_rpc.Path.tendmoduleMake_services(P:PREFIX)=structincludePletpath:prefixTezos_rpc.Path.context=Tezos_rpc.Path.open_rootletmake_calls=Tezos_rpc.Context.make_call(Tezos_rpc.Service.prefixprefixs)letmake_call1s=Tezos_rpc.Context.make_call1(Tezos_rpc.Service.prefixprefixs)letmake_call2s=Tezos_rpc.Context.make_call2(Tezos_rpc.Service.prefixprefixs)endmoduleGlobal=structopenTezos_rpc.PathincludeMake_services(structtypeprefix=unitletprefix=open_root/"global"end)letsc_rollup_address=Tezos_rpc.Service.get_service~description:"Smart rollup address"~query:Tezos_rpc.Query.empty~output:Address.encoding(path/"smart_rollup_address")letcurrent_tezos_head=Tezos_rpc.Service.get_service~description:"Tezos head known to the smart rollup node"~query:Tezos_rpc.Query.empty~output:(Data_encoding.optionBlock_hash.encoding)(path/"tezos_head")letcurrent_tezos_level=Tezos_rpc.Service.get_service~description:"Tezos level known to the smart rollup node"~query:Tezos_rpc.Query.empty~output:(Data_encoding.optionData_encoding.int32)(path/"tezos_level")letlast_stored_commitment=Tezos_rpc.Service.get_service~description:"Last commitment computed by the node"~query:Tezos_rpc.Query.empty~output:(Data_encoding.optionEncodings.commitment_with_hash)(path/"last_stored_commitment")letglobal_block_watcher=Tezos_rpc.Service.get_service~description:"Monitor and streaming the L2 blocks"~query:Tezos_rpc.Query.empty~output:Sc_rollup_block.encoding(path/"monitor_blocks")endmoduleLocal=structopenTezos_rpc.PathincludeMake_services(structtypeprefix=unitletprefix=open_root/"local"end)(* commitments are published only if their inbox level is above the last
cemented commitment level inbox level. Because this information is
fetched from the head of the tezos node to which the rollup node is
connected, it is possible that two rollup nodes that have processed
the same set of heads, but whose corresponding layer1 node has
different information about the last cemented commitment, will
decide to publish and not to publish a commitment, respectively.
As a consequence, the results returned by the endpoint below
in the rollup node will be different.
*)letlast_published_commitment=Tezos_rpc.Service.get_service~description:"Last commitment published by the node"~query:Tezos_rpc.Query.empty~output:(Data_encoding.optionEncodings.commitment_with_hash_and_level_infos)(path/"last_published_commitment")letcommitment=Tezos_rpc.Service.get_service~description:"Commitment computed and published by the node"~query:Tezos_rpc.Query.empty~output:(Data_encoding.optionEncodings.commitment_with_hash_and_level_infos)(path/"commitments"/:Arg.commitment_hash)letgc_info=Tezos_rpc.Service.get_service~description:"Information about garbage collection"~query:Tezos_rpc.Query.empty~output:Encodings.gc_info(path/"gc_info")letinjection=Tezos_rpc.Service.post_service~description:"Inject messages in the batcher's queue"~query:Tezos_rpc.Query.empty~input:Data_encoding.(def"messages"~description:"Messages to inject"(listL2_message.content_encoding))~output:Data_encoding.(def"message_hashes"~description:"Hashes of injected L2 messages"(listL2_message.Hash.encoding))(path/"batcher"/"injection")letbatcher_queue=Tezos_rpc.Service.get_service~description:"List messages present in the batcher's queue"~query:Tezos_rpc.Query.empty~output:Encodings.batcher_queue(path/"batcher"/"queue")letbatcher_message=Tezos_rpc.Service.get_service~description:"Retrieve an L2 message and its status"~query:Tezos_rpc.Query.empty~output:Encodings.message_status_output(path/"batcher"/"queue"/:Arg.l2_message_hash)end