12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2022 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. *)(* *)(*****************************************************************************)typeparameters=Dal.parameters={redundancy_factor:int;page_size:int;slot_size:int;number_of_shards:int;}letparameters_encoding=Dal.parameters_encodingmoduleCommitment=struct(* DAL/FIXME https://gitlab.com/tezos/tezos/-/issues/3389
It is not clear whether the size of the slot associated to the
commitment should be given here. *)typet=Dal.commitmentletequal=Dal.Commitment.equalletencoding=Dal.Commitment.encodingletpp=Dal.Commitment.ppletzero=Dal.Commitment.zeroletof_b58check_opt=Dal.Commitment.of_b58check_optendmoduleCommitment_proof=structtypet=Dal.commitment_proofletencoding=Dal.Commitment_proof.encodingletzero=Dal.Commitment_proof.zeroendmoduleHeader=structtypeid={published_level:Raw_level_repr.t;index:Dal_slot_index_repr.t}typet={id:id;commitment:Commitment.t}letslot_id_equal{published_level;index}s2=Raw_level_repr.equalpublished_levels2.published_level&&Dal_slot_index_repr.equalindexs2.indexletequal{id;commitment}s2=slot_id_equalids2.id&&Commitment.equalcommitments2.commitmentletid_encoding=letopenData_encodinginconv(fun{published_level;index}->(published_level,index))(fun(published_level,index)->{published_level;index})(obj2(req"level"Raw_level_repr.encoding)(req"index"Dal_slot_index_repr.encoding))letencoding=letopenData_encodinginconv(fun{id;commitment}->(id,commitment))(fun(id,commitment)->{id;commitment})(* A tag is added to ensure we can migrate from this encoding to
different version if we decide to change the encoding. *)(union[case~title:"v0"(Tag0)(merge_objs(obj1(req"version"(constant"0")))(merge_objsid_encoding(obj1(req"commitment"Commitment.encoding))))(funx->Some((),x))(fun((),x)->x);])letpp_idfmt{published_level;index}=Format.fprintffmt"published_level: %a, index: %a"Raw_level_repr.pppublished_levelDal_slot_index_repr.ppindexletppfmt{id;commitment=c}=Format.fprintffmt"id:(%a), commitment: %a"pp_ididCommitment.ppcletverify_commitmentcryptoboxcommitmentproof=Ok(Dal.verify_commitmentcryptoboxcommitmentproof)endmoduleSlot_index=Dal_slot_index_reprmodulePage=structtypecontent=Bytes.ttypeslot_index=Dal_slot_index_repr.tletpages_per_slot=Dal.pages_per_slotmoduleIndex=structtypet=intletzero=0letencoding=Data_encoding.int16letpp=Format.pp_print_intletcompare=Compare.Int.compareletequal=Compare.Int.equaltypeerror+=Invalid_page_index of{given:int;min:int;max:int}let()=letopenData_encodinginregister_error_kind`Permanent~id:"dal_page_index_repr.index.invalid_index"~title:"Invalid Dal page index"~description:"The given index is out of range of representable page indices"~pp:(funppf(given,min,max)->Format.fprintfppf"The given index %d is out of range of representable page indices \
[%d, %d]"givenminmax)(obj3(req"given"int31)(req"min"int31)(req"max"int31))(function|Invalid_page_index{given;min;max}->Some(given,min,max)|_->None)(fun(given,min,max)->Invalid_page_index{given;min;max})letcheck_is_in_range~number_of_pagespage_id=error_unlessCompare.Int.(0<=page_id&&page_id<number_of_pages)(Invalid_page_index{given=page_id;min=zero;max=number_of_pages-1})endtypet={slot_id:Header.id;page_index:Index.t}typeproof=Dal.page_proofletencoding=letopenData_encodinginconv(fun{slot_id={published_level;index};page_index}->(published_level,index,page_index))(fun(published_level,index,page_index)->{slot_id={published_level;index};page_index})(obj3(req"published_level"Raw_level_repr.encoding)(req"slot_index"Slot_index.encoding)(req"page_index"Index.encoding))letequal{slot_id;page_index}p=Header.slot_id_equalslot_idp.slot_id&&Index.equalpage_indexp.page_indexletproof_encoding=Dal.page_proof_encodingletcontent_encoding=Data_encoding.(bytesHex)letppfmt{slot_id={published_level;index};page_index}=Format.fprintffmt"(published_level: %a, slot_index: %a, page_index: %a)"Raw_level_repr.pppublished_levelSlot_index.ppindexIndex.pppage_indexletpp_prooffmtproof=Data_encoding.Json.ppfmt(Data_encoding.Json.constructproof_encodingproof)endmoduleSlot_market=struct(* DAL/FIXME https://gitlab.com/tezos/tezos/-/issues/3108
Think harder about this data structure and whether it can be
optimized. *)moduleSlot_index_map=Map.Make(Dal_slot_index_repr)typet={length:int;slot_headers:Header.tSlot_index_map.t}letinit~length=ifCompare.Int.(length<0)theninvalid_arg"Dal_slot_repr.Slot_market.init: length cannot be negative";letslot_headers=Slot_index_map.emptyin{length;slot_headers}letlength{length;_}=lengthletregistertnew_slot_header=letopenHeaderinifnotCompare.Int.(0<=Dal_slot_index_repr.to_intnew_slot_header.id.index&&Dal_slot_index_repr.to_intnew_slot_header.id.index<t.length)thenNoneelselethas_changed=reffalseinletupdate=function|None->has_changed:=true;Somenew_slot_header|Somex->Somexinletslot_headers=Slot_index_map.updatenew_slot_header.id.indexupdatet.slot_headersinlett={twithslot_headers}inSome(t,!has_changed)letcandidatest=t.slot_headers|>Slot_index_map.to_seq|>Seq.mapsnd|>List.of_seqendmoduleHistory=struct(* History is represented via a skip list. The content of the cell
is the hash of a merkle proof. *)moduleContent_prefix=structlet(_prefix:string)="dash1"(* 32 *)letb58check_prefix="\002\224\072\094\219"(* dash1(55) *)letsize=Some32letname="dal_skip_list_content"lettitle="A hash to represent the content of a cell in the skip list"endmoduleContent_hash=Blake2B.Make(Base58)(Content_prefix)(* Pointers of the skip lists are used to encode the content and the
backpointers. *)modulePointer_prefix=structlet(_prefix:string)="dask1"(* 32 *)letb58check_prefix="\002\224\072\115\035"(* dask1(55) *)letsize=Some32letname="dal_skip_list_pointer"lettitle="A hash that represents the skip list pointers"endmodulePointer_hash=Blake2B.Make(Base58)(Pointer_prefix)moduleSkip_list_parameters=structletbasis=4endtypeerror+=Add_element_in_slots_skip_list_violates_orderinglet()=register_error_kind`Temporary~id:"Dal_slot_repr.add_element_in_slots_skip_list_violates_ordering"~title:"Add an element in slots skip list that violates ordering"~description:"Attempting to add an element on top of the Dal confirmed slots skip \
list that violates the ordering."Data_encoding.unit(function|Add_element_in_slots_skip_list_violates_ordering->Some()|_->None)(fun()->Add_element_in_slots_skip_list_violates_ordering)moduleContent=struct(** Each cell of the skip list is either a slot header that has been
attested, or a published level and a slot index for which no slot header
is attested (so, no associated commitment). *)typet=UnattestedofHeader.id|AttestedofHeader.tletcontent_id=function|Unattestedslot_id->slot_id|Attested{id;_}->idletencoding=letopenData_encodinginunion~tag_size:`Uint8[case~title:"unattested"(Tag0)(merge_objs(obj1(req"kind"(constant"unattested")))Header.id_encoding)(function|Unattestedslot_id->Some((),slot_id)|Attested_->None)(fun((),slot_id)->Unattestedslot_id);case~title:"attested"(Tag1)(merge_objs(obj1(req"kind"(constant"attested")))Header.encoding)(function|Unattested_->None|Attestedslot_header->Some((),slot_header))(fun((),slot_header)->Attestedslot_header);]letequalt1t2=match(t1,t2)with|Unattestedsid1,Unattestedsid2->Header.slot_id_equalsid1sid2|Attestedsh1,Attestedsh2->Header.equalsh1sh2|Unattested_,_|Attested_,_->falseletzero,zero_level=letzero_level=Raw_level_repr.rootinletzero_index=Dal_slot_index_repr.zeroin(Unattested{published_level=zero_level;index=zero_index},zero_level)letppfmt=function|Unattestedslot_id->Format.fprintffmt"Unattested (%a)"Header.pp_idslot_id|Attestedslot_header->Format.fprintffmt"Attested (%a)"Header.ppslot_headerendmoduleSkip_list=structincludeSkip_list.Make(Skip_list_parameters)(** All Dal slot indices for all levels will be stored in a skip list
(with or without a commitment depending on attestation status of each
slot), where only the last cell is needed to be remembered in the L1
context. The skip list is used in the proof phase of a refutation game
to verify whether a given slot is inserted as [Attested] or not in the
skip list. The skip list is supposed to be sorted, as its 'search'
function explicitly uses a given `compare` function during the list
traversal to quickly (in log(size)) reach the target slot header id.
Two cells compare in lexicographic ordering of their levels and slot indexes.
Below, we redefine the [next] function (that allows adding elements
on top of the list) to enforce that the constructed skip list is
well-sorted. We also define a wrapper around the [search] function to
guarantee that it can only be called with the adequate compare function.
*)letnext~prev_cell~prev_cell_ptr~number_of_slotselt=letopenResult_syntaxinletwell_ordered=(* For each cell we insert in the skip list, we ensure that it complies
with the following invariant:
- Either the published levels are successive (no gaps). In this case:
* The last inserted slot's index for the previous level is
[number_of_slots - 1];
* The first inserted slot's index for the current level is 0
- Or, levels are equal, but slot indices are successive. *)letHeader.{published_level=l1;index=i1}=contentprev_cell|>Content.content_idinletHeader.{published_level=l2;index=i2}=Content.content_ideltin(Raw_level_repr.equall2(Raw_level_repr.succl1)&&Compare.Int.(Dal_slot_index_repr.to_inti1=number_of_slots-1)&&Compare.Int.(Dal_slot_index_repr.to_inti2=0))||Raw_level_repr.equall2l1&&Dal_slot_index_repr.is_succi1~succ:i2inlet*()=error_unlesswell_orderedAdd_element_in_slots_skip_list_violates_orderinginreturn@@next~prev_cell~prev_cell_ptreltletsearch=letcompare_with_slot_id(target_slot_id:Header.id)(content:Content.t)=letHeader.{published_level=target_level;index=target_index}=target_slot_idinletHeader.{published_level;index}=Content.content_idcontentinletc=Raw_level_repr.comparepublished_leveltarget_levelinifCompare.Int.(c<>0)thencelseDal_slot_index_repr.compareindextarget_indexinfun~deref~cell~target_slot_id->Lwt.search~deref~cell~compare:(compare_with_slot_idtarget_slot_id)endmoduleV1=structtypecontent=Content.t(* A pointer to a cell is the hash of its content and all the back
pointers. *)typehash=Pointer_hash.ttypehistory=(content,hash)Skip_list.celltypet=historyletgenesis,genesis_level=(Skip_list.genesisContent.zero,Content.zero_level)lethistory_encoding=letopenData_encodingin(* The history_encoding is given as a union of two versions of the skip
list. The legacy case is only used to deserialize the skip list cells
which may appear in refutation games started on a previous version of
the protocol, before the activation of the DAL. In this case, the
snapshotted cells are always the genesis one and cannot be used by the
players so we deserialize it on the fly to the new representation of
the genesis cell. *)union~tag_size:`Uint8[case~title:"dal_skip_list_legacy"(Tag0)(obj2(req"kind"(constant"dal_skip_list_legacy"))(req"skip_list"(Data_encoding.Fixed.bytesHex57)))(fun_->None)(fun((),_)->genesis);case~title:"dal_skip_list"(Tag1)(obj2(req"kind"(constant"dal_skip_list"))(req"skip_list"(Skip_list.encodingPointer_hash.encodingContent.encoding)))(funx->Some((),x))(fun((),x)->x);]letequal_history:history->history->bool=Skip_list.equalPointer_hash.equalContent.equalletencoding=history_encodingletequal:t->t->bool=equal_historylethashcell=letcurrent_slot=Skip_list.contentcellinletback_pointers_hashes=Skip_list.back_pointerscellinData_encoding.Binary.to_bytes_exnContent.encodingcurrent_slot::List.mapPointer_hash.to_bytesback_pointers_hashes|>Pointer_hash.hash_bytesletpp_historyfmt(history:history)=lethistory_hash=hashhistoryinFormat.fprintffmt"@[hash : %a@;%a@]"Pointer_hash.pphistory_hash(Skip_list.pp~pp_content:Content.pp~pp_ptr:Pointer_hash.pp)historyletpp=pp_historymoduleHistory_cache=Bounded_history_repr.Make(structletname="dal_slots_cache"end)(Pointer_hash)(structtypet=historyletencoding=history_encodingletpp=pp_historyletequal=equal_historyend)(* Insert a cell in the skip list [t] and the corresponding association [(hash(t),
t)] in the given [cache].
Note that if the given skip list contains the genesis cell, its content is
reset with the given content. This ensures the invariant that
there are no gaps in the successive cells of the list. *)letadd_cell(t,cache)next_cell_content~number_of_slots=letopenResult_syntaxinletprev_cell_ptr=hashtinletHeader.{published_level;_}=Skip_list.contentt|>Content.content_idinlet*new_head=ifRaw_level_repr.equalpublished_levelgenesis_levelthen(* If this is the first real cell of DAL, replace dummy genesis. *)return(Skip_list.genesisnext_cell_content)elseSkip_list.next~prev_cell:t~prev_cell_ptrnext_cell_content~number_of_slotsinletnew_head_hash=hashnew_headinlet*cache=History_cache.remembernew_head_hashnew_headcacheinreturn(new_head,cache)(* Given a list [attested_slot_headers] of well-ordered (wrt slots indices)
(attested) slot headers, this function builds an extension [l] of
[attested_slot_headers] such that:
- all elements in [attested_slot_headers] are in [l],
- for every slot index i in [0, number_of_slots - 1] that doesn't appear
in [attested_slot_headers], an unattested slot id is inserted in [l],
- [l] is well sorted wrt. slots indices. *)letfill_slot_headers~number_of_slots~published_levelattested_slot_headers=letopenResult_syntaxinletmoduleI=Dal_slot_index_reprinlet*all_indices=I.slots_range~number_of_slots~lower:0~upper:(number_of_slots-1)inletmk_unattestedindex=Content.UnattestedHeader.{published_level;index}in(* Hypothesis: both lists are sorted in increasing order w.r.t. slots
indices. *)letrecauxindicesslots=match(indices,slots)with|_,[]->List.mapmk_unattestedindices|>ok|[],_s::_->tzfailAdd_element_in_slots_skip_list_violates_ordering|i::indices',s::slots'->ifI.(i=s.Header.id.index)thenlet*res=auxindices'slots'inContent.Attesteds::res|>okelseifI.(i<s.Header.id.index)thenlet*res=auxindices'slotsinmk_unattestedi::res|>okelse(* i > s.Header.id.index *)tzfailAdd_element_in_slots_skip_list_violates_orderinginauxall_indicesattested_slot_headers(* Assuming a [number_of_slots] per L1 level, we will ensure below that we
insert exactly [number_of_slots] cells in the skip list per level. This
will simplify the shape of proofs and help bounding the history cache
required for their generation. *)letadd_confirmed_slot_headers(t:t)cachepublished_level~number_of_slotsattested_slot_headers=letopenResult_syntaxinlet*()=List.iter_e(funslot_header->error_unlessRaw_level_repr.(published_level=slot_header.Header.id.published_level)Add_element_in_slots_skip_list_violates_ordering)attested_slot_headersinlet*slot_headers=fill_slot_headers~number_of_slots~published_levelattested_slot_headersinList.fold_left_e(add_cell~number_of_slots)(t,cache)slot_headersletadd_confirmed_slot_headers_no_cache=letempty_cache=History_cache.empty~capacity:0Linfuntpublished_level~number_of_slotsslots->letopenResult_syntaxinlet+cell,(_:History_cache.t)=add_confirmed_slot_headerstempty_cachepublished_level~number_of_slotsslotsincell(* Dal proofs section *)(** An inclusion proof is a sequence (list) of cells from the Dal skip list,
represented as [c1; c2; ...; cn], that encodes a minimal path from the
head [c1] (referred to as the "reference" or "snapshot" cell below) to a
target cell [cn]. Thanks to the back-pointers, it can be demonstrated
that the successive elements of the sequence are indeed cells of the
skip list. *)typeinclusion_proof=historylist(** (See the documentation in the mli file to understand what we want to
prove in a refutation game involving Dal and why.)
A Dal proof is an algebraic datatype with two cases, where we basically
prove that a Dal page is confirmed on L1 or not. Being 'not confirmed'
here includes the case where the slot's header is not published and the
case where the slot's header is published, but the attesters didn't
confirm the availability of its data.
To produce a proof representation for a page (see function
{!produce_proof_repr} below), we assume given:
- [page_id], identifies the page;
- [slots_history], a current/recent cell of the slots history skip list.
Typically, it should be the skip list cell snapshotted when starting the
refutation game;
- [get_history], a sufficiently large slots history cache, encoded as a
function from pointer hashes to their corresponding skip lists cells, to
navigate back through the successive cells of the skip list. The cache
should at least contain the cells starting from the published level of
the page ID for which we want to generate a proof. Indeed, inclusion
proofs encode paths through skip lists' cells where the head is the
reference/snapshot cell and the last element is the target cell inserted
at the level corresponding to the page's published level). Note that, the
case where the level of the page is far in the past (i.e. the skip list
was not populated yet) should be handled by the caller ;
- [page_info], provides information for [page_id]. In case the page is
supposed to be confirmed, this argument should contain the page's
content and the proof that the page is part of the (confirmed) slot
whose ID is given in [page_id]. In case we want to show that the page is
not confirmed, the value [page_info] should be [None].
[dal_parameters] is used when verifying that/if the page is part of
the candidate slot (if any). *)typeproof_repr=|Page_confirmedof{target_cell:history;(** [target_cell] is a cell whose content contains the slot to
which the page belongs to. *)inc_proof:inclusion_proof;(** [inc_proof] is a (minimal) path in the skip list that proves
cells inclusion. The head of the list is the [slots_history]
provided to produce the proof. The last cell's content is
the slot containing the page identified by [page_id],
that is: [target_cell]. *)page_data:Page.content;(** [page_data] is the content of the page. *)page_proof:Page.proof;(** [page_proof] is the proof that the page whose content is
[page_data] is actually the [page_id.page_index]th page of
the slot stored in [target_cell] and identified by
[page_id.slot_id]. *)}(** The case where the slot's page is confirmed/attested on L1. *)|Page_unconfirmedof{target_cell:history;inc_proof:inclusion_proof}(** The case where the slot's page doesn't exist or is not confirmed
on L1. The fields are similar to {!Page_confirmed} case except
that we don't have a page data or proof to check.
As said above, in case the level of the page is far in the past
(for instance, the skip list was not populated yet or the slots of
that level are not valid to be imported by the DAL anymore) should
be handled by the caller. In fact, the [proof_repr] type here only
covers levels where a new cell has been added to the skip list. *)letproof_repr_encoding=letopenData_encodinginletcase_page_confirmed=case~title:"confirmed dal page proof representation"(Tag0)(obj5(req"kind"(constant"confirmed"))(req"target_cell"history_encoding)(req"inc_proof"(listhistory_encoding))(req"page_data"(bytesHex))(req"page_proof"Page.proof_encoding))(function|Page_confirmed{target_cell;inc_proof;page_data;page_proof}->Some((),target_cell,inc_proof,page_data,page_proof)|_->None)(fun((),target_cell,inc_proof,page_data,page_proof)->Page_confirmed{target_cell;inc_proof;page_data;page_proof})andcase_page_unconfirmed=case~title:"unconfirmed dal page proof representation"(Tag1)(obj3(req"kind"(constant"unconfirmed"))(req"target_cell"history_encoding)(req"inc_proof"(listhistory_encoding)))(function|Page_unconfirmed{target_cell;inc_proof}->Some((),target_cell,inc_proof)|_->None)(fun((),target_cell,inc_proof)->Page_unconfirmed{target_cell;inc_proof})inunion[case_page_confirmed;case_page_unconfirmed](** Proof's type is set to bytes and not a structural datatype because
when a proof appears in a tezos operation or in an rpc, a user can not
reasonably understand the proof, thus it eases the work of people decoding
the proof by only supporting bytes and not the whole structured proof. *)typeproof=bytes(** DAL/FIXME: https://gitlab.com/tezos/tezos/-/issues/4084
DAL proof's encoding should be bounded *)letproof_encoding=Data_encoding.(bytesHex)typeerror+=Dal_invalid_proof_serializationlet()=register_error_kind`Permanent~id:"Dal_slot_repr.invalid_proof_serialization"~title:"Dal invalid proof serialization"~description:"Error occured during dal proof serialization"Data_encoding.unit(functionDal_invalid_proof_serialization->Some()|_->None)(fun()->Dal_invalid_proof_serialization)letserialize_proofproof=letopenResult_syntaxinmatchData_encoding.Binary.to_bytes_optproof_repr_encodingproofwith|None->tzfailDal_invalid_proof_serialization|Someserialized_proof->returnserialized_prooftypeerror+=Dal_invalid_proof_deserializationlet()=register_error_kind`Permanent~id:"Dal_slot_repr.invalid_proof_deserialization"~title:"Dal invalid proof deserialization"~description:"Error occured during dal proof deserialization"Data_encoding.unit(functionDal_invalid_proof_deserialization->Some()|_->None)(fun()->Dal_invalid_proof_deserialization)letdeserialize_proofproof=letopenResult_syntaxinmatchData_encoding.Binary.of_bytes_optproof_repr_encodingproofwith|None->tzfailDal_invalid_proof_deserialization|Somedeserialized_proof->returndeserialized_proofletpp_inclusion_proof=Format.pp_print_listpp_historyletpp_proof~serializedfmtp=ifserializedthenFormat.pp_print_stringfmt(Bytes.to_stringp)elsematchdeserialize_proofpwith|Errormsg->Error_monad.pp_tracefmtmsg|Okproof->(matchproofwith|Page_confirmed{target_cell;inc_proof;page_data;page_proof}->Format.fprintffmt"Page_confirmed (target_cell=%a, data=%s,@ \
inc_proof:[size=%d |@ path=%a]@ page_proof:%a)"pp_historytarget_cell(Bytes.to_stringpage_data)(List.lengthinc_proof)pp_inclusion_proofinc_proofPage.pp_proofpage_proof|Page_unconfirmed{target_cell;inc_proof}->Format.fprintffmt"Page_unconfirmed (target_cell = %a | inc_proof:[size=%d@ | \
path=%a])"pp_historytarget_cell(List.lengthinc_proof)pp_inclusion_proofinc_proof)typeerror+=|Dal_proof_errorofstring|Unexpected_page_sizeof{expected_size:int;page_size:int}let()=letopenData_encodinginregister_error_kind`Permanent~id:"dal_slot_repr.slots_history.dal_proof_error"~title:"Dal proof error"~description:"Error occurred during Dal proof production or validation"~pp:(funppfe->Format.fprintfppf"Dal proof error: %s"e)(obj1(req"error"(stringPlain)))(functionDal_proof_errore->Somee|_->None)(fune->Dal_proof_errore)let()=letopenData_encodinginregister_error_kind`Permanent~id:"dal_slot_repr.slots_history.unexpected_page_size"~title:"Unexpected page size"~description:"The size of the given page content doesn't match the expected one."~pp:(funppf(expected,size)->Format.fprintfppf"The size of a Dal page is expected to be %d bytes. The given one \
has %d"expectedsize)(obj2(req"expected_size"int16)(req"page_size"int16))(function|Unexpected_page_size{expected_size;page_size}->Some(expected_size,page_size)|_->None)(fun(expected_size,page_size)->Unexpected_page_size{expected_size;page_size})letdal_proof_errorreason=Dal_proof_errorreasonletproof_errorreason=error@@dal_proof_errorreasonletcheck_page_proofdal_paramsproofdata({Page.page_index;_}aspid)commitment=letopenResult_syntaxinlet*dal=matchDal.makedal_paramswith|Okdal->returndal|Error(`Fails)->proof_errorsinletfail_with_error_msgwhat=Format.kasprintfproof_error"%s (page id=%a)."whatPage.pppidinmatchDal.verify_pagedalcommitment~page_indexdataproofwith|Oktrue->return_unit|Okfalse->fail_with_error_msg"Wrong page content for the given page index and slot commitment"|Error`Segment_index_out_of_range->fail_with_error_msg"Segment_index_out_of_range"|Error`Page_length_mismatch->tzfail@@Unexpected_page_size{expected_size=dal_params.page_size;page_size=Bytes.lengthdata;}(** The [produce_proof_repr] function assumes that some invariants hold, such as:
- The DAL has been activated,
- The level of [page_id] is after the DAL activation level.
Under these assumptions, we recall that we maintain an invariant
ensuring that we a have a cell per slot index in the skip list at every level
after DAL activation. *)letproduce_proof_reprdal_paramspage_id~page_info~get_historyslots_hist=letopenLwt_result_syntaxinletPage.{slot_id=target_slot_id;page_index=_}=page_idin(* We first search for the slots attested at level [published_level]. *)let*!search_result=Skip_list.search~deref:get_history~target_slot_id~cell:slots_histin(* The search should necessarily find a cell in the skip list (assuming
enough cache is given) under the assumptions made when calling
{!produce_proof_repr}. *)matchsearch_result.Skip_list.last_cellwith|Deref_returned_none->tzfail@@dal_proof_error"Skip_list.search returned 'Deref_returned_none': Slots history \
cache is ill-formed or has too few entries."|No_exact_or_lower_ptr->tzfail@@dal_proof_error"Skip_list.search returned 'No_exact_or_lower_ptr', while it is \
initialized with a min elt (slot zero)."|Nearest_->(* This should not happen: there is one cell at each level
after DAL activation. The case where the page's level is before DAL
activation level should be handled by the caller
({!Sc_refutation_proof.produce} in our case). *)tzfail@@dal_proof_error"Skip_list.search returned Nearest', while all given levels to \
produce proofs are supposed to be in the skip list."|Foundtarget_cell->(letinc_proof=List.revsearch_result.Skip_list.rev_pathinmatch(page_info,Skip_list.contenttarget_cell)with|Some(page_data,page_proof),Attested{commitment;id=_}->(* The case where the slot to which the page is supposed to belong
is found and the page's information are given. *)let*?()=(* We check the page's proof against the commitment. *)check_page_proofdal_paramspage_proofpage_datapage_idcommitmentin(* All checks succeeded. We return a `Page_confirmed` proof. *)return(Page_confirmed{target_cell;inc_proof;page_data;page_proof},Somepage_data)|None,Unattested_->(* The slot corresponding to the given page's index is not found in
the attested slots of the page's level, and no information is
given for that page. So, we produce a proof that the page is not
attested. *)return(Page_unconfirmed{target_cell;inc_proof},None)|None,Attested_->(* Mismatch: case where no page information are given, but the
slot is attested. *)tzfail@@dal_proof_error"The page ID's slot is confirmed, but no page content and \
proof are provided."|Some_,Unattested_->(* Mismatch: case where page information are given, but the slot
is not attested. *)tzfail@@dal_proof_error"The page ID's slot is not confirmed, but page content and \
proof are provided.")letproduce_proofdal_paramspage_id~page_info~get_historyslots_hist=letopenLwt_result_syntaxinlet*proof_repr,page_data=produce_proof_reprdal_paramspage_id~page_info~get_historyslots_histinlet*?serialized_proof=serialize_proofproof_reprinreturn(serialized_proof,page_data)(* Given a starting cell [snapshot] and a (final) [target], this function
checks that the provided [inc_proof] encodes a minimal path from
[snapshot] to [target]. *)letverify_inclusion_proofinc_proof~src:snapshot~dest:target=letassoc=List.map(func->(hashc,c))inc_proofinletpath=List.splitassoc|>fstinletderef=letopenMap.Make(Pointer_hash)inletmap=of_seq(List.to_seqassoc)infunptr->find_optptrmapinletsnapshot_ptr=hashsnapshotinlettarget_ptr=hashtargetinerror_unless(Skip_list.valid_back_path~equal_ptr:Pointer_hash.equal~deref~cell_ptr:snapshot_ptr~target_ptrpath)(dal_proof_error"verify_proof_repr: invalid inclusion Dal proof.")letverify_proof_reprdal_paramspage_idsnapshotproof=letopenResult_syntaxinletPage.{slot_id=Header.{published_level;index};page_index=_}=page_idinlet*target_cell,inc_proof,page_proof_check=matchproofwith|Page_confirmed{target_cell;inc_proof;page_data;page_proof}->letpage_proof_check=Some(funcommitment->(* We check that the page indeed belongs to the target slot at the
given page index. *)let*()=check_page_proofdal_paramspage_proofpage_datapage_idcommitmentin(* If the check succeeds, we return the data/content of the
page. *)returnpage_data)inreturn(target_cell,inc_proof,page_proof_check)|Page_unconfirmed{target_cell;inc_proof}->return(target_cell,inc_proof,None)inletcell_content=Skip_list.contenttarget_cellin(* We check that the target cell has the same level and index than the
page we're about to prove. *)letcell_id=Content.content_idcell_contentinlet*()=error_whenRaw_level_repr.(cell_id.published_level<>published_level)(dal_proof_error"verify_proof_repr: published_level mismatch.")inlet*()=error_when(not(Dal_slot_index_repr.equalcell_id.indexindex))(dal_proof_error"verify_proof_repr: slot index mismatch.")in(* We check that the given inclusion proof indeed links our L1 snapshot to
the target cell. *)let*()=verify_inclusion_proofinc_proof~src:snapshot~dest:target_cellinmatch(page_proof_check,cell_content)with|None,Unattested_->return_none|Somepage_proof_check,Attested{commitment;_}->let*page_data=page_proof_checkcommitmentinreturn_somepage_data|Some_,Unattested_->error@@dal_proof_error"verify_proof_repr: the unconfirmation proof contains the \
target slot."|None,Attested_->error@@dal_proof_error"verify_proof_repr: the confirmation proof doesn't contain the \
attested slot."letverify_proofdal_paramspage_idsnapshotserialized_proof=letopenResult_syntaxinlet*proof_repr=deserialize_proofserialized_proofinverify_proof_reprdal_paramspage_idsnapshotproof_reprmoduleInternal_for_tests=structtypecell_content=Content.t=|UnattestedofHeader.id|AttestedofHeader.tletcontent=Skip_list.contentletproof_statement_isserialized_proofexpected=matchdeserialize_proofserialized_proofwith|Error_->false|Okproof->(match(expected,proof)with|`Confirmed,Page_confirmed_|`Unconfirmed,Page_unconfirmed_->true|_->false)endendincludeV1end