1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2021 Nomadic Labs <contact@nomadic-labs.com> *)(* *)(* 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. *)(* *)(*****************************************************************************)(**
A Merkelized inbox represents a list of messages. This list
is decomposed into sublists of messages, one for each non-empty Tezos
level greater than the level of the Last Cemented Commitment (LCC).
This module is designed to:
1. provide a space-efficient representation for proofs of inbox
inclusions (only for inboxes obtained at the end of block
validation) ;
2. offer an efficient function to add a new batch of messages in the
inbox at the current level.
To solve (1), we use a proof tree H which is implemented by a sparse
merkelized skip list allowing for compact inclusion proofs (See
{!skip_list_repr.ml}).
To solve (2), we maintain a separate proof tree C witnessing the
contents of messages of the current level.
The protocol maintains the hashes of the head of H, and the root hash of C.
The rollup node needs to maintain a full representation for C and a
partial representation for H back to the level of the LCC.
*)typeerror+=Invalid_level_add_messagesofRaw_level_repr.ttypeerror+=Inbox_proof_errorofstringtypeerror+=Tried_to_add_zero_messagestypeerror+=Empty_upper_levelofRaw_level_repr.tlet()=letopenData_encodinginregister_error_kind`Permanent~id:"sc_rollup_inbox.invalid_level_add_messages"~title:"Internal error: Trying to add a message to an inbox from the past"~description:"An inbox can only accept messages for its current level or for the next \
levels."(obj1(req"level"Raw_level_repr.encoding))(functionInvalid_level_add_messageslevel->Somelevel|_->None)(funlevel->Invalid_level_add_messageslevel);register_error_kind`Permanent~id:"sc_rollup_inbox.inbox_proof_error"~title:"Internal error: error occurred during proof production or validation"~description:"An inbox proof error."~pp:(funppfe->Format.fprintfppf"Inbox proof error: %s"e)(obj1(req"error"string))(functionInbox_proof_errore->Somee|_->None)(fune->Inbox_proof_errore);register_error_kind`Permanent~id:"sc_rollup_inbox.add_zero_messages"~title:"Internal error: trying to add zero messages"~description:"Message adding functions must be called with a positive number of \
messages"~pp:(funppf_->Format.fprintfppf"Tried to add zero messages")empty(functionTried_to_add_zero_messages->Some()|_->None)(fun()->Tried_to_add_zero_messages);register_error_kind`Permanent~id:"sc_rollup_inbox.empty_upper_level"~title:"Internal error: No payload found in a [Level_crossing] proof"~description:"Failed to find any message in the [upper_level] of a [Level_crossing] \
proof"(obj1(req"upper_level"Raw_level_repr.encoding))(functionEmpty_upper_levelupper_level->Someupper_level|_->None)(funupper_level->Empty_upper_levelupper_level)moduleInt64_map=Map.Make(Int64)(* 32 *)lethash_prefix="\003\250\174\238\208"(* scib1(55) *)moduleHash=structletprefix="scib1"letencoded_size=55moduleH=Blake2B.Make(Base58)(structletname="inbox_hash"lettitle="The hash of an inbox of a smart contract rollup"letb58check_prefix=hash_prefix(* defaults to 32 *)letsize=Noneend)includeHlet()=Base58.check_encoded_prefixb58check_encodingprefixencoded_sizeletof_context_hashcontext_hash=Context_hash.to_bytescontext_hash|>of_bytes_exnletto_context_hashhash=to_byteshash|>Context_hash.of_bytes_exnincludePath_encoding.Make_hex(H)endmoduleSkip_list_parameters=structletbasis=2endmoduleSkip_list=Skip_list_repr.Make(Skip_list_parameters)lethash_skip_list_cellcell=letcurrent_level_hash=Skip_list.contentcellinletback_pointers_hashes=Skip_list.back_pointerscellinHash.to_bytescurrent_level_hash::List.mapHash.to_bytesback_pointers_hashes|>Hash.hash_bytesmoduleV1=structtypehistory_proof=(Hash.t,Hash.t)Skip_list.cellletequal_history_proof=Skip_list.equalHash.equalHash.equallethistory_proof_encoding:history_proofData_encoding.t=Skip_list.encodingHash.encodingHash.encodingletpp_history_prooffmthistory=lethistory_hash=hash_skip_list_cellhistoryinFormat.fprintffmt"@[hash : %a@;%a@]"Hash.pphistory_hash(Skip_list.pp~pp_content:Hash.pp~pp_ptr:Hash.pp)history(** Construct an inbox [history] with a given [capacity]. If you
are running a rollup node, [capacity] needs to be large enough to
remember any levels for which you may need to produce proofs. *)moduleHistory=Bounded_history_repr.Make(structletname="inbox_history"end)(Hash)(structtypet=history_proofletpp=pp_history_proofletequal=equal_history_proofletencoding=history_proof_encodingend)(*
At a given level, an inbox is composed of metadata of type [t] and
[current_level], a [tree] representing the messages of the current level
(held by the [Raw_context.t] in the protocol).
The metadata contains :
- [rollup] : the address of the rollup ;
- [level] : the inbox level ;
- [message_counter] : the number of messages in the [level]'s inbox ;
the number of messages that have not been consumed by a commitment cementing ;
- [nb_messages_in_commitment_period] :
the number of messages during the commitment period ;
- [starting_level_of_current_commitment_period] :
the level marking the beginning of the current commitment period ;
- [current_level_hash] : the root hash of [current_level] ;
- [old_levels_messages] : a witness of the inbox history.
When new messages are appended to the current level inbox, the
metadata stored in the context may be related to an older level.
In that situation, an archival process is applied to the metadata.
This process saves the [current_level_hash] in the
[old_levels_messages] and empties [current_level]. It then
initialises a new level tree for the new messages---note that any
intermediate levels are simply skipped. See
{!Make_hashing_scheme.archive_if_needed} for details.
*)typet={rollup:Sc_rollup_repr.t;level:Raw_level_repr.t;nb_messages_in_commitment_period:int64;starting_level_of_current_commitment_period:Raw_level_repr.t;message_counter:Z.t;(* Lazy to avoid hashing O(n^2) time in [add_messages] *)current_level_hash:unit->Hash.t;old_levels_messages:history_proof;}letequalinbox1inbox2=(* To be robust to addition of fields in [t]. *)let{rollup;level;nb_messages_in_commitment_period;starting_level_of_current_commitment_period;message_counter;current_level_hash;old_levels_messages;}=inbox1inSc_rollup_repr.Address.equalrollupinbox2.rollup&&Raw_level_repr.equallevelinbox2.level&&Compare.Int64.(equalnb_messages_in_commitment_periodinbox2.nb_messages_in_commitment_period)&&Raw_level_repr.(equalstarting_level_of_current_commitment_periodinbox2.starting_level_of_current_commitment_period)&&Z.equalmessage_counterinbox2.message_counter&&Hash.equal(current_level_hash())(inbox2.current_level_hash())&&equal_history_proofold_levels_messagesinbox2.old_levels_messagesletppfmt{rollup;level;nb_messages_in_commitment_period;starting_level_of_current_commitment_period;message_counter;current_level_hash;old_levels_messages;}=Format.fprintffmt"@[<hov 2>{ rollup = %a@;\
level = %a@;\
current messages hash = %a@;\
nb_messages_in_commitment_period = %s@;\
starting_level_of_current_commitment_period = %a@;\
message_counter = %a@;\
old_levels_messages = %a@;\
}@]"Sc_rollup_repr.Address.pprollupRaw_level_repr.pplevelHash.pp(current_level_hash())(Int64.to_stringnb_messages_in_commitment_period)Raw_level_repr.ppstarting_level_of_current_commitment_periodZ.pp_printmessage_counterpp_history_proofold_levels_messagesletinbox_levelinbox=inbox.levelletold_levels_messagesinbox=inbox.old_levels_messagesletcurrent_level_hashinbox=inbox.current_level_hash()letold_levels_messages_encoding=Skip_list.encodingHash.encodingHash.encodingletencoding=Data_encoding.(conv(fun{rollup;message_counter;nb_messages_in_commitment_period;starting_level_of_current_commitment_period;level;current_level_hash;old_levels_messages;}->(rollup,message_counter,nb_messages_in_commitment_period,starting_level_of_current_commitment_period,level,current_level_hash(),old_levels_messages))(fun(rollup,message_counter,nb_messages_in_commitment_period,starting_level_of_current_commitment_period,level,current_level_hash,old_levels_messages)->{rollup;message_counter;nb_messages_in_commitment_period;starting_level_of_current_commitment_period;level;current_level_hash=(fun()->current_level_hash);old_levels_messages;})(obj7(req"rollup"Sc_rollup_repr.encoding)(req"message_counter"n)(req"nb_messages_in_commitment_period"int64)(req"starting_level_of_current_commitment_period"Raw_level_repr.encoding)(req"level"Raw_level_repr.encoding)(req"current_level_hash"Hash.encoding)(req"old_levels_messages"old_levels_messages_encoding)))letnumber_of_messages_during_commitment_periodinbox=inbox.nb_messages_in_commitment_periodletstart_new_commitment_periodinboxlevel={inboxwithstarting_level_of_current_commitment_period=level;nb_messages_in_commitment_period=0L;}letstarting_level_of_current_commitment_periodinbox=inbox.starting_level_of_current_commitment_periodletrefresh_commitment_period~commitment_period~levelinbox=letstart=starting_level_of_current_commitment_periodinboxinletfreshness=Raw_level_repr.difflevelstartinletopenInt32inletopenCompare.Int32iniffreshness>=commitment_periodthen(letnb_periods=to_int((mul(divfreshnesscommitment_period))commitment_period)inletnew_starting_level=Raw_level_repr.(addstartnb_periods)inassert(Raw_level_repr.(new_starting_level<=level));assert(rem(Raw_level_repr.diffnew_starting_levelstart)commitment_period=0l);start_new_commitment_periodinboxnew_starting_level)elseinboxendtypeversioned=V1ofV1.tletversioned_encoding=letopenData_encodinginunion[case~title:"V1"(Tag0)V1.encoding(functionV1inbox->Someinbox)(funinbox->V1inbox);]includeV1letof_versioned=functionV1inbox->inbox[@@inline]letto_versionedinbox=V1inbox[@@inline]letkey_of_messageix=["message";Data_encoding.Binary.to_string_exnData_encoding.nix]letlevel_key=["level"]letnumber_of_messages_key=["number_of_messages"]typeserialized_proof=bytesletserialized_proof_encoding=Data_encoding.bytesmoduletypeMerkelized_operations=sigtypeinbox_contexttypetreevalhash_level_tree:tree->Hash.tvalnew_level_tree:inbox_context->Raw_level_repr.t->treeLwt.tvaladd_messages:inbox_context->History.t->t->Raw_level_repr.t->Sc_rollup_inbox_message_repr.serializedlist->treeoption->(tree*History.t*t)tzresultLwt.tvaladd_messages_no_history:inbox_context->t->Raw_level_repr.t->Sc_rollup_inbox_message_repr.serializedlist->treeoption->(tree*t)tzresultLwt.tvalget_message_payload:tree->Z.t->Sc_rollup_inbox_message_repr.serializedoptionLwt.tvalform_history_proof:inbox_context->History.t->t->treeoption->(History.t*history_proof)tzresultLwt.tvaltake_snapshot:t->history_prooftypeinclusion_proofvalinclusion_proof_encoding:inclusion_proofData_encoding.tvalpp_inclusion_proof:Format.formatter->inclusion_proof->unitvalnumber_of_proof_steps:inclusion_proof->intvalverify_inclusion_proof:inclusion_proof->history_proof->history_proof->booltypeproofvalpp_proof:Format.formatter->proof->unitvalto_serialized_proof:proof->serialized_proofvalof_serialized_proof:serialized_proof->proofoptionvalverify_proof:Raw_level_repr.t*Z.t->history_proof->proof->Sc_rollup_PVM_sig.inbox_messageoptiontzresultLwt.tvalproduce_proof:inbox_context->History.t->history_proof->Raw_level_repr.t*Z.t->(proof*Sc_rollup_PVM_sig.inbox_messageoption)tzresultLwt.tvalempty:inbox_context->Sc_rollup_repr.t->Raw_level_repr.t->tLwt.tmoduleInternal_for_tests:sigvaleq_tree:tree->tree->boolvalproduce_inclusion_proof:History.t->history_proof->history_proof->inclusion_proofoptiontzresultvalserialized_proof_of_string:string->serialized_proofendendmoduletypeP=sigmoduleTree:Context.TREEwithtypekey=stringlistandtypevalue=bytestypet=Tree.ttypetree=Tree.treevalcommit_tree:Tree.t->stringlist->Tree.tree->unitLwt.tvallookup_tree:Tree.t->Hash.t->treeoptionLwt.ttypeproofvalproof_encoding:proofData_encoding.tvalproof_before:proof->Hash.tvalverify_proof:proof->(tree->(tree*'a)Lwt.t)->(tree*'a)optionLwt.tvalproduce_proof:Tree.t->tree->(tree->(tree*'a)Lwt.t)->(proof*'a)optionLwt.tendmoduleMake_hashing_scheme(P:P):Merkelized_operationswithtypetree=P.treeandtypeinbox_context=P.t=structmoduleTree=P.Treetypeinbox_context=P.ttypetree=P.treelethash_level_treelevel_tree=Hash.of_context_hash(Tree.hashlevel_tree)letset_leveltreelevel=letlevel_bytes=Data_encoding.Binary.to_bytes_exnRaw_level_repr.encodinglevelinTree.addtreelevel_keylevel_bytesletfind_leveltree=letopenLwt_syntaxinlet+level_bytes=Tree.(findtreelevel_key)inOption.bindlevel_bytes(Data_encoding.Binary.of_bytes_optRaw_level_repr.encoding)letset_number_of_messagestreenumber_of_messages=letnumber_of_messages_bytes=Data_encoding.Binary.to_bytes_exnData_encoding.nnumber_of_messagesinTree.addtreenumber_of_messages_keynumber_of_messages_bytes(** Initialise the merkle tree for a new level in the inbox. We have
to include the [level] in this structure so that it cannot be
forged by a malicious rollup node. *)letnew_level_treectxtlevel=letopenLwt_syntaxinlettree=Tree.emptyctxtinlet*tree=set_number_of_messagestreeZ.zeroinset_leveltreelevelletadd_messageinboxpayloadlevel_tree=letopenLwt_tzresult_syntaxinletmessage_index=inbox.message_counterinletmessage_counter=Z.succmessage_indexinlet*!level_tree=Tree.addlevel_tree(key_of_messagemessage_index)(Bytes.of_string(payload:Sc_rollup_inbox_message_repr.serialized:>string))inlet*!level_tree=set_number_of_messageslevel_treemessage_counterinletnb_messages_in_commitment_period=Int64.succinbox.nb_messages_in_commitment_periodinletinbox={starting_level_of_current_commitment_period=inbox.starting_level_of_current_commitment_period;current_level_hash=inbox.current_level_hash;rollup=inbox.rollup;level=inbox.level;old_levels_messages=inbox.old_levels_messages;message_counter;nb_messages_in_commitment_period;}inreturn(level_tree,inbox)letget_message_payloadlevel_treemessage_index=letopenLwt_syntaxinletkey=key_of_messagemessage_indexinlet*bytes=Tree.(findlevel_treekey)inreturn@@Option.map(funbs->Sc_rollup_inbox_message_repr.unsafe_of_string(Bytes.to_stringbs))bytes(** [no_history] creates an empty history with [capacity] set to
zero---this makes the [remember] function a no-op. We want this
behaviour in the protocol because we don't want to store
previous levels of the inbox. *)letno_history=History.empty~capacity:0Llettake_snapshotinbox=letprev_cell=inbox.old_levels_messagesinletprev_cell_ptr=hash_skip_list_cellprev_cellinSkip_list.next~prev_cell~prev_cell_ptr(current_level_hashinbox)letkey_of_levellevel=letlevel_bytes=Data_encoding.Binary.to_bytes_exnRaw_level_repr.encodinglevelinBytes.to_stringlevel_bytesletcommit_treectxttreeinbox_level=letkey=[key_of_levelinbox_level]inP.commit_treectxtkeytreeletform_history_proofctxthistoryinboxlevel_tree=letopenLwt_tzresult_syntaxinlet*!()=let*!tree=matchlevel_treewith|Sometree->Lwt.returntree|None->new_level_treectxtinbox.levelincommit_treectxttreeinbox.levelinletprev_cell=inbox.old_levels_messagesinletprev_cell_ptr=hash_skip_list_cellprev_cellinlet*?history=History.rememberprev_cell_ptrprev_cellhistoryinletcell=Skip_list.next~prev_cell~prev_cell_ptr(current_level_hashinbox)inreturn(history,cell)(** [archive_if_needed ctxt history inbox new_level level_tree]
is responsible for ensuring that the {!add_messages} function
below has a correctly set-up [level_tree] to which to add the
messages. If [new_level] is a higher level than the current inbox,
we create a new inbox level tree at that level in which to start
adding messages, and archive the earlier levels depending on the
[history] parameter's [capacity]. If [level_tree] is [None] (this
happens when the inbox is first created) we similarly create a new
empty level tree with the right [level] key.
This function and {!form_history_proof} are the only places we
begin new level trees. *)letarchive_if_neededctxthistoryinboxnew_levellevel_tree=letopenLwt_result_syntaxinifRaw_level_repr.(inbox.level=new_level)thenmatchlevel_treewith|Sometree->return(history,inbox,tree)|None->let*!tree=new_level_treectxtnew_levelinreturn(history,inbox,tree)elselet*history,old_levels_messages=form_history_proofctxthistoryinboxlevel_treeinlet*!tree=new_level_treectxtnew_levelinletinbox={starting_level_of_current_commitment_period=inbox.starting_level_of_current_commitment_period;current_level_hash=inbox.current_level_hash;rollup=inbox.rollup;nb_messages_in_commitment_period=inbox.nb_messages_in_commitment_period;old_levels_messages;level=new_level;message_counter=Z.zero;}inreturn(history,inbox,tree)letadd_messagesctxthistoryinboxlevelpayloadslevel_tree=letopenLwt_tzresult_syntaxinlet*()=fail_when(matchpayloadswith[]->true|_->false)Tried_to_add_zero_messagesinlet*()=fail_whenRaw_level_repr.(level<inbox.level)(Invalid_level_add_messageslevel)inlet*history,inbox,level_tree=archive_if_neededctxthistoryinboxlevellevel_treeinlet*level_tree,inbox=List.fold_left_es(fun(level_tree,inbox)payload->add_messageinboxpayloadlevel_tree)(level_tree,inbox)payloadsinletcurrent_level_hash()=hash_level_treelevel_treeinreturn(level_tree,history,{inboxwithcurrent_level_hash})letadd_messages_no_historyctxtinboxlevelpayloadslevel_tree=letopenLwt_tzresult_syntaxinlet+level_tree,_,inbox=add_messagesctxtno_historyinboxlevelpayloadslevel_treein(level_tree,inbox)(* An [inclusion_proof] is a path in the Merkelized skip list
showing that a given inbox history is a prefix of another one.
This path has a size logarithmic in the difference between the
levels of the two inboxes.
[Irmin.Proof.{tree_proof, stream_proof}] could not be reused here
because there is no obvious encoding of sequences in these data
structures with the same guarantee about the size of proofs. *)typeinclusion_proof=history_prooflistletinclusion_proof_encoding=letopenData_encodinginlisthistory_proof_encodingletpp_inclusion_prooffmtproof=Format.pp_print_listpp_history_prooffmtproofletnumber_of_proof_stepsproof=List.lengthproofletlift_ptr_pathderefptr_path=letrecauxaccu=function|[]->Some(List.revaccu)|x::xs->Option.bind(derefx)@@func->aux(c::accu)xsinaux[]ptr_pathletverify_inclusion_proofproofab=letassoc=List.map(func->(hash_skip_list_cellc,c))proofinletpath=List.splitassoc|>fstinletderef=letopenHash.Mapinletmap=of_seq(List.to_seqassoc)infunptr->find_optptrmapinletcell_ptr=hash_skip_list_cellbinlettarget_ptr=hash_skip_list_cellainSkip_list.valid_back_path~equal_ptr:Hash.equal~deref~cell_ptr~target_ptrpathtypeproof=(* See the main docstring for this type (in the mli file) for
definitions of the three proof parameters [starting_point],
[message] and [snapshot]. In the below we deconstruct
[starting_point] into [(l, n)] where [l] is a level and [n] is a
message index.
In a [Single_level] proof, [level] is the skip list cell for the
level [l], [inc] is an inclusion proof of [level] into
[snapshot] and [message_proof] is a tree proof showing that
[exists level_tree .
(hash_level_tree level_tree = level.content)
AND (payload_and_level n level_tree = (_, (message, l)))]
Note: in the case that [message] is [None] this shows that
there's no value at the index [n]; in this case we also must
check that [level] equals [snapshot] (otherwise, we'd need a
[Level_crossing] proof instead. *)|Single_levelof{level:history_proof;inc:inclusion_proof;message_proof:P.proof;}(* See the main docstring for this type (in the mli file) for
definitions of the three proof parameters [starting_point],
[message] and [snapshot]. In the below we deconstruct
[starting_point] as [(l, n)] where [l] is a level and [n] is a
message index.
In a [Level_crossing] proof, [lower] is the skip list cell for
the level [l] and [upper] must be the skip list cell that comes
immediately after it in [snapshot]. If the inbox has been
constructed correctly using the functions in this module that
will be the next non-empty level in the inbox.
[inc] is an inclusion proof of [upper] into [snapshot].
[upper_level] is the level of [upper].
The tree proof [lower_message_proof] shows the following:
[exists level_tree .
(hash_level_tree level_tree = lower.content)
AND (payload_and_level n level_tree = (_, (None, l)))]
in other words, there is no message at index [n] in
level [l]. This means that level has been fully read.
The tree proof [upper_message_proof] shows the following:
[exists level_tree .
(hash_level_tree level_tree = upper.content)
AND (payload_and_level 0 level_tree = (_, (message, upper_level)))]
in other words, if we look in the next non-empty level the
message at index zero is [message]. *)|Level_crossingof{lower:history_proof;upper:history_proof;inc:inclusion_proof;lower_message_proof:P.proof;upper_message_proof:P.proof;upper_level:Raw_level_repr.t;}letpp_prooffmtproof=matchproofwith|Single_level{level;_}->lethash=Skip_list.contentlevelinFormat.fprintffmt"Single_level inbox proof at %a"Hash.pphash|Level_crossing{lower;upper;upper_level;_}->letlower_hash=Skip_list.contentlowerinletupper_hash=Skip_list.contentupperinFormat.fprintffmt"Level_crossing inbox proof between %a and %a (upper_level %a)"Hash.pplower_hashHash.ppupper_hashRaw_level_repr.ppupper_levelletproof_encoding=letopenData_encodinginunion~tag_size:`Uint8[case~title:"Single_level"(Tag0)(obj3(req"level"history_proof_encoding)(req"inclusion_proof"inclusion_proof_encoding)(req"message_proof"P.proof_encoding))(function|Single_level{level;inc;message_proof}->Some(level,inc,message_proof)|_->None)(fun(level,inc,message_proof)->Single_level{level;inc;message_proof});case~title:"Level_crossing"(Tag1)(obj6(req"lower"history_proof_encoding)(req"upper"history_proof_encoding)(req"inclusion_proof"inclusion_proof_encoding)(req"lower_message_proof"P.proof_encoding)(req"upper_message_proof"P.proof_encoding)(req"upper_level"Raw_level_repr.encoding))(function|Level_crossing{lower;upper;inc;lower_message_proof;upper_message_proof;upper_level;}->Some(lower,upper,inc,lower_message_proof,upper_message_proof,upper_level)|_->None)(fun(lower,upper,inc,lower_message_proof,upper_message_proof,upper_level)->Level_crossing{lower;upper;inc;lower_message_proof;upper_message_proof;upper_level;});]letof_serialized_proof=Data_encoding.Binary.of_bytes_optproof_encodingletto_serialized_proof=Data_encoding.Binary.to_bytes_exnproof_encodingletproof_errorreason=letopenLwt_tzresult_syntaxinfail(Inbox_proof_errorreason)letcheckpreason=unlessp(fun()->proof_errorreason)(** Utility function that checks the inclusion proof [inc] for any
inbox proof.
In the case of a [Single_level] proof this is just an inclusion
proof between [level] and the inbox snapshot targeted the proof.
In the case of a [Level_crossing] proof [inc] must be an inclusion
proof between [upper] and the inbox snapshot. In this case we must
additionally check that [lower] is the immediate predecessor of
[upper] in the inbox skip list. NB: there may be many 'inbox
levels' apart, but if the intervening levels are empty they will
be immediate neighbours in the skip list because it misses empty
levels out. *)letcheck_inclusionsproofsnapshot=check(matchproofwith|Single_level{inc;level;_}->verify_inclusion_proofinclevelsnapshot|Level_crossing{inc;lower;upper;_}->(letprev_cell=Skip_list.back_pointerupper0inmatchprev_cellwith|None->false|Somep->verify_inclusion_proofincuppersnapshot&&Hash.equalp(hash_skip_list_celllower)))"invalid inclusions"(** To construct or verify a tree proof we need a function of type
[tree -> (tree, result) Lwt.t]
where [result] is some data extracted from the tree that we care
about proving. [payload_and_level n] is such a function, used for
checking both the inbox level specified inside the tree and the
message at a particular index, [n].
For this function, the [result] is
[(payload, level) : string option * Raw_level_repr.t option]
where [payload] is [None] if there was no message at the index.
The [level] part of the result will only be [None] if the [tree]
is not in the correct format for an inbox level. This should not
happen if the [tree] was correctly initialised with
[new_level_tree]. *)letpayload_and_levelntree=letopenLwt_syntaxinlet*payload=get_message_payloadtreeninlet*level=find_leveltreeinreturn(tree,(payload,level))(** Utility function that handles all the verification needed for a
particular message proof at a particular level. It calls
[P.verify_proof], but also checks the proof has the correct
[P.proof_before] hash and the [level] stored inside the tree is
the expected one. *)letcheck_message_proofmessage_prooflevel_hash(l,n)label=letopenLwt_tzresult_syntaxinlet*()=check(Hash.equallevel_hash(P.proof_beforemessage_proof))(Format.sprintf"message_proof (%s) does not match history"label)inlet*!result=P.verify_proofmessage_proof(payload_and_leveln)inmatchresultwith|None->proof_error(Format.sprintf"message_proof is invalid (%s)"label)|Some(_,(_,None))->proof_error(Format.sprintf"badly encoded level in message_proof (%s)"label)|Some(_,(payload_opt,Someproof_level))->let*()=check(Raw_level_repr.equalproof_levell)(Format.sprintf"incorrect level in message_proof (%s)"label)inreturnpayload_optletverify_proof(l,n)snapshotproof=assert(Z.(geqnzero));letopenLwt_tzresult_syntaxinlet*()=check_inclusionsproofsnapshotinmatchproofwith|Single_levelp->(letlevel_hash=Skip_list.contentp.levelinlet*payload_opt=check_message_proofp.message_prooflevel_hash(l,n)"single level"inmatchpayload_optwith|None->ifequal_history_proofsnapshotp.levelthenreturnNoneelseproof_error"payload is None but proof.level not top level"|Somepayload->return@@SomeSc_rollup_PVM_sig.{inbox_level=l;message_counter=n;payload})|Level_crossingp->(letlower_level_hash=Skip_list.contentp.lowerinlet*should_be_none=check_message_proofp.lower_message_prooflower_level_hash(l,n)"lower"inlet*()=matchshould_be_nonewith|None->return()|Some_->proof_error"more messages to read in lower level"inletupper_level_hash=Skip_list.contentp.upperinlet*payload_opt=check_message_proofp.upper_message_proofupper_level_hash(p.upper_level,Z.zero)"upper"inmatchpayload_optwith|None->(* [check_inclusions] checks at least two important properties:
1. [p.lower_level] is different from [p.upper_level]
2. [p.upper_level] is included in the snapshot
If [p.upper_level] is included in the snapshot, the level was
created by the protocol. If the protocol created a level tree
at [p.upper_level] it *must* contain at least one message.
So, if [p.upper_level] exists, at the index [Z.zero] (fetched
here), a payload *must* exist.
This code is then dead as long as we store only the nonempty
inboxes.
*)fail(Empty_upper_levelp.upper_level)|Somepayload->return@@SomeSc_rollup_PVM_sig.{inbox_level=p.upper_level;message_counter=Z.zero;payload;})(** Utility function; we convert all our calls to be consistent with
[Lwt_tzresult_syntax]. *)letoption_to_resultelwt_opt=letopenLwt_syntaxinlet*opt=lwt_optinmatchoptwithNone->proof_errore|Somex->return(okx)letproduce_proofctxthistoryinbox(l,n)=letopenLwt_tzresult_syntaxinletderefptr=History.findptrhistoryinletcomparehash=let*!tree=P.lookup_treectxthashinmatchtreewith|None->Lwt.return(-1)|Sometree->(letopenLwt_syntaxinlet+level=find_leveltreeinmatchlevelwith|None->-1|Somelevel->Raw_level_repr.comparelevell)inlet*!result=Skip_list.search~deref~compare~cell:inboxinlet*inc,level=matchresultwith|Skip_list.{rev_path;last_cell=Foundlevel}->return(List.revrev_path,level)|{last_cell=Nearest_;_}|{last_cell=No_exact_or_lower_ptr;_}|{last_cell=Deref_returned_none;_}->(* We are only interested to the result where [search] than a
path to the cell we were looking for. All the other cases
should be considered as an error. *)proof_error(Format.asprintf"Skip_list.search failed to find a valid path: %a"(Skip_list.pp_search_result~pp_cell:pp_history_proof)result)inlet*level_tree=option_to_result"could not find level_tree in the inbox_context"(P.lookup_treectxt(Skip_list.contentlevel))inlet*message_proof,(payload_opt,_)=option_to_result"failed to produce message proof for level_tree"(P.produce_proofctxtlevel_tree(payload_and_leveln))inmatchpayload_optwith|Somepayload->return(Single_level{level;inc;message_proof},SomeSc_rollup_PVM_sig.{inbox_level=l;message_counter=n;payload})|None->(ifequal_history_proofinboxlevelthenreturn(Single_level{level;inc;message_proof},None)elselettarget_index=Skip_list.indexlevel+1inletcell_ptr=hash_skip_list_cellinboxinlet*?history=History.remembercell_ptrinboxhistoryinletderefptr=History.findptrhistoryinlet*inc=option_to_result"failed to find path to upper level"(Lwt.return(Skip_list.back_path~deref~cell_ptr~target_index|>Option.map(lift_ptr_pathderef)|>Option.join))inlet*upper=option_to_result"back_path returned empty list"(Lwt.return(List.last_optinc))inlet*upper_level_tree=option_to_result"could not find upper_level_tree in the inbox_context"(P.lookup_treectxt(Skip_list.contentupper))inlet*upper_message_proof,(payload_opt,upper_level_opt)=option_to_result"failed to produce message proof for upper_level_tree"(P.produce_proofctxtupper_level_tree(payload_and_levelZ.zero))inlet*upper_level=option_to_result"upper_level_tree was misformed---could not find level"(Lwt.returnupper_level_opt)inmatchpayload_optwith|None->proof_error"if upper_level_tree exists, the payload must exist"|Somepayload->letinput_given=SomeSc_rollup_PVM_sig.{inbox_level=upper_level;message_counter=Z.zero;payload;}inreturn(Level_crossing{lower=level;upper;inc;lower_message_proof=message_proof;upper_message_proof;upper_level;},input_given))letemptycontextrolluplevel=letopenLwt_syntaxinassert(Raw_level_repr.(level<>Raw_level_repr.root));letpre_genesis_level=Raw_level_repr.rootinlet*initial_level=new_level_treecontextpre_genesis_levelinlet*()=commit_treecontextinitial_levelpre_genesis_levelinletinitial_hash=hash_level_treeinitial_levelinreturn{rollup;level;message_counter=Z.zero;nb_messages_in_commitment_period=0L;starting_level_of_current_commitment_period=level;current_level_hash=(fun()->initial_hash);old_levels_messages=Skip_list.genesisinitial_hash;}moduleInternal_for_tests=structleteq_tree=Tree.equalletproduce_inclusion_proofhistoryab=letopenTzresult_syntaxinletcell_ptr=hash_skip_list_cellbinlettarget_index=Skip_list.indexainlet*history=History.remembercell_ptrbhistoryinletderefptr=History.findptrhistoryinSkip_list.back_path~deref~cell_ptr~target_index|>Option.map(lift_ptr_pathderef)|>Option.join|>returnletserialized_proof_of_stringx=Bytes.of_stringxendendinclude(Make_hashing_scheme(structmoduleTree=structincludeContext.Treetypet=Context.ttypetree=Context.treetypevalue=bytestypekey=stringlistendtypet=Context.ttypetree=Context.treeletcommit_tree_ctxt_key_tree=(* This is a no-op in the protocol inbox implementation *)Lwt.return()letlookup_tree_ctxt_hash=(* We cannot find the tree without a full inbox_context *)Lwt.returnNonetypeproof=Context.Proof.treeContext.Proof.tletproof_encoding=Context.Proof_encoding.V1.Tree32.tree_proof_encodingletproof_beforeproof=matchproof.Context.Proof.beforewith|`Valuehash|`Nodehash->Hash.of_context_hashhashletverify_proofpf=Lwt.mapResult.to_option(Context.verify_tree_proofpf)letproduce_proof___=(* We cannot produce a proof without full inbox_context *)Lwt.returnNoneend):Merkelized_operationswithtypetree=Context.treeandtypeinbox_context=Context.t)typeinbox=t