1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540(*****************************************************************************)(* *)(* 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. *)(* *)(*****************************************************************************)typeblock={rpc_context:Tezos_protocol_environment.rpc_context;protocol_data:Protocol.Alpha_context.Block_header.protocol_data;raw_protocol_data:Bytes.t;operations:Mockup.M.Block_services.operationlistlist;resulting_context_hash:Context_hash.t;}typechain=blocklist(** As new blocks and operations are received they are pushed to an Lwt_pipe
wrapped into this type. *)typebroadcast=|Broadcast_blockofBlock_hash.t*Block_header.t*Operation.tlistlist|Broadcast_opofOperation_hash.t*Alpha_context.packed_operation(** The state of a mockup node. *)typestate={instance_index:int;(** Index of this node. Indices go from 0 to N-1 where N is the total
number of bakers in the simulation. *)live_depth:int;(** How many blocks (counting from the head into the past) are considered live? *)mutablechain:chain;(** The chain as seen by this fake "node". *)mutablemempool:(Operation_hash.t*Mockup.M.Protocol.operation)list;(** Mempool of this fake "node". *)chain_table:chainBlock_hash.Table.t;(** The chain table of this fake "node". It maps from block hashes to
blocks. *)global_chain_table:blockBlock_hash.Table.t;(** The global chain table that allows us to look up blocks that may be
missing in [chain_table], i.e. not known to this particular node. This
is used to find unknown predecessors. The real node can ask about an
unknown block and receive it on request, this is supposed to emulate
that functionality. *)ctxt_table:Tezos_protocol_environment.rpc_contextContext_hash.Table.t;(** The context table allows us to look up rpc_context by its hash. *)validated_blocks_pipe:(Block_hash.t*Block_header.t*Operation.tlistlist)Lwt_pipe.Unbounded.t;(** [validated_blocks_pipe] is used to implement the
[monitor_validated_blocks] RPC. *)heads_pipe:(Block_hash.t*Block_header.t)Lwt_pipe.Unbounded.t;(** [heads_pipe] is used to implement the [monitor_heads]
RPC. *)mutableoperations_stream:(Operation_hash.t*Mockup.M.Protocol.operation)listLwt_stream.t;mutableoperations_stream_push:(Operation_hash.t*Mockup.M.Protocol.operation)listoption->unit;(** [operations_pipe] is used to implement the [operations_pipe] RPC. *)mutablestreaming_operations:bool;(** A helper flag used to implement the monitor operations RPC. *)broadcast_pipes:broadcastLwt_pipe.Unbounded.tlist;(** Broadcast pipes per node. *)genesis_block_true_hash:Block_hash.t;(** True hash of the genesis
block as calculated by the
[Block_header.hash] function. *)}letaccounts=Mockup.Protocol_parameters.default_value.bootstrap_accountsletchain_id=Chain_id.of_string_exn"main"letgenesis_block_hash=Block_hash.of_b58check_exn"BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU"letgenesis_predecessor_block_hash=Block_hash.zerotypepropagation=Block|Pass|Delayoffloattypepropagation_vector=propagationlistmoduletypeHooks=sigvalon_inject_block:level:int32->round:int32->block_hash:Block_hash.t->block_header:Block_header.t->operations:Operation.tlistlist->protocol_data:Alpha_context.Block_header.protocol_data->(Block_hash.t*Block_header.t*Operation.tlistlist*propagation_vector)tzresultLwt.tvalon_inject_operation:op_hash:Operation_hash.t->op:Alpha_context.packed_operation->(Operation_hash.t*Alpha_context.packed_operation*propagation_vector)tzresultLwt.tvalon_new_validated_block:block_hash:Block_hash.t->block_header:Block_header.t->operations:Operation.tlistlist->(Block_hash.t*Block_header.t*Operation.tlistlist)optionLwt.tvalon_new_head:block_hash:Block_hash.t->block_header:Block_header.t->(Block_hash.t*Block_header.t)optionLwt.tvalon_new_operation:Operation_hash.t*Alpha_context.packed_operation->(Operation_hash.t*Alpha_context.packed_operation)optionLwt.tvalcheck_block_before_processing:level:int32->round:int32->block_hash:Block_hash.t->block_header:Block_header.t->protocol_data:Alpha_context.Block_header.protocol_data->unittzresultLwt.tvalcheck_chain_after_processing:level:int32->round:int32->chain:chain->unittzresultLwt.tvalcheck_mempool_after_processing:mempool:(Operation_hash.t*Mockup.M.Protocol.operation)list->unittzresultLwt.tvalstop_on_event:Baking_state.event->boolvalon_start_baker:baker_position:int->delegates:Baking_state.consensus_keylist->cctxt:Protocol_client_context.full->unitLwt.tvalcheck_chain_on_success:chain:chain->unittzresultLwt.tend(** Return a series of blocks starting from the block with the given
identifier. *)letlocate_blocks(state:state)(block:Tezos_shell_services.Block_services.block):blocklisttzresultLwt.t=matchblockwith|`Hash(hash,rel)->(matchBlock_hash.Table.findstate.chain_tablehashwith|None->failwith"locate_blocks: can't find the block %a"Block_hash.pphash|Somechain0->let_,chain=List.split_nrelchain0inreturnchain)|`Headrel->let_,chain=List.split_nrelstate.chaininreturnchain|`Level_->failwith"locate_blocks: `Level block spec not handled"|`Genesis->failwith"locate_blocks: `Genesis block spec net handled"|`Alias_->failwith"locate_blocks: `Alias block spec not handled"(** Similar to [locate_blocks], but only returns the first block. *)letlocate_block(state:state)(block:Tezos_shell_services.Block_services.block):blocktzresultLwt.t=letopenLwt_result_syntaxinlet*blocks=locate_blocksstateblockinmatchblockswith|[]->failwith"locate_block: can't find the block"|x::_->returnx(** Return the collection of live blocks for a given block identifier. *)letlive_blocks(state:state)block=letopenLwt_result_syntaxinlet*chain=locate_blocksstateblockinletsegment,_=List.split_nstate.live_depthchaininreturn(List.fold_left(funset({rpc_context;_}:block)->lethash=rpc_context.Tezos_protocol_environment.block_hashinBlock_hash.Set.addhashset)(Block_hash.Set.of_list[state.genesis_block_true_hash;genesis_predecessor_block_hash])segment)(** Extract the round number from raw fitness. *)letround_from_raw_fitnessraw_fitness=matchProtocol.Alpha_context.Fitness.from_rawraw_fitnesswith|Okfitness->return(Alpha_context.Round.to_int32(Protocol.Alpha_context.Fitness.roundfitness))|Error_->failwith"round_from_raw_fitness: cannot parse fitness"(** Extract level from a block header. *)letget_block_level(block_header:Block_header.t)=returnblock_header.shell.level(** Extract round from a block header. *)letget_block_round(block_header:Block_header.t)=round_from_raw_fitnessblock_header.shell.fitness(** Parse protocol data. *)letparse_protocol_data(protocol_data:Bytes.t)=matchData_encoding.Binary.of_bytes_optProtocol.Alpha_context.Block_header.protocol_data_encodingprotocol_datawith|None->failwith"can't parse protocol data of a block"|Someparsed_protocol_data->returnparsed_protocol_data(** Broadcast an operation or block according to the given propagation
vector. *)lethandle_propagationmsgpropagation_vectorbroadcast_pipes=letopenLwt_result_syntaxinlet*!()=List.iter_s(fun(propagation,pipe)->matchpropagationwith|Block->Lwt.return_unit|Pass->Lwt_pipe.Unbounded.pushpipemsg;Lwt.return_unit|Delays->Lwt.dont_wait(fun()->let*!()=Lwt_unix.sleepsinLwt_pipe.Unbounded.pushpipemsg;Lwt.return_unit)(fun_exn->());Lwt.return_unit)(List.combine_droppropagation_vectorbroadcast_pipes)inreturn_unit(** Use the [user_hooks] to produce a module of functions that will perform
the heavy lifting for the RPC implementations. *)letmake_mocked_services_hooks(state:state)(user_hooks:(moduleHooks)):Faked_services.hooks=letopenLwt_result_syntaxinletmoduleUser_hooks=(valuser_hooks:Hooks)inletmoduleImpl:Faked_services.Mocked_services_hooks=structtypemempool=Mockup.M.Block_services.Mempool.tletmonitor_validated_blocks()=letnext()=letrecpop_until_ok()=let*!block_hash,block_header,operations=Lwt_pipe.Unbounded.popstate.validated_blocks_pipeinlet*!result=User_hooks.on_new_validated_block~block_hash~block_header~operationsinmatchresultwith|None->pop_until_ok()|Some(hash,head,operations)->Lwt.return_some(chain_id,hash,head,operations)inpop_until_ok()inletshutdown()=()inTezos_rpc.Answer.{next;shutdown}letmonitor_heads()=letnext()=letrecpop_until_ok()=let*!block_hash,block_header=Lwt_pipe.Unbounded.popstate.heads_pipein(* Sleep a 0.1s to simulate a block application delay *)let*!()=Lwt_unix.sleep0.1inlet*!head_opt=User_hooks.on_new_head~block_hash~block_headerinmatchhead_optwith|None->pop_until_ok()|Somehead->Lwt.return_someheadinpop_until_ok()inletshutdown()=()inTezos_rpc.Answer.{next;shutdown}letmonitor_bootstrapped()=letfirst_run=reftrueinletnext()=if!first_runthen(first_run:=false;letb=matchstate.chainwith[]->assertfalse|b::_->binlethead_hash=b.rpc_context.block_hashinlettimestamp=b.rpc_context.block_header.timestampinLwt.return_some(head_hash,timestamp))elseLwt.return_noneinletshutdown()=()inTezos_rpc.Answer.{next;shutdown}letprotocols(block:Tezos_shell_services.Block_services.block)=let*x=locate_blockstateblockinlethash=x.rpc_context.block_hashinletis_predecessor_of_genesis=matchblockwith|`Hash(requested_hash,rel)->Int.equalrel0&&Block_hash.equalrequested_hashgenesis_predecessor_block_hash|_->falsein(* It is important to tell the baker that the genesis block is not in
the alpha protocol (we use Protocol_hash.zero). This will make the
baker not try to propose alternatives to that block and just accept
it as final in that Protocol_hash.zero protocol. The same for
predecessor of genesis, it should be in Protocol_hash.zero. *)returnTezos_shell_services.Block_services.{current_protocol=(ifBlock_hash.equalhashgenesis_block_hash||is_predecessor_of_genesisthenProtocol_hash.zeroelseProtocol.hash);next_protocol=(ifis_predecessor_of_genesisthenProtocol_hash.zeroelseProtocol.hash);}letmay_lie_on_proto_levelblockx=(* As for ../protocols, the baker distinguishes activation
blocks from "normal" blocks by comparing the [proto_level] of
the shell header and its predecessor. If the predecessor's
one is different, it must mean that we are considering an
activation block and must not attest. Here, we do a bit of
hacking in order to return a different proto_level for the
predecessor of the genesis block which is considered as the
current protocol activation block. To perfectly mimic what is
supposed to happen, the first mocked up block created should
be made in the genesis protocol, however, it is not what's
done in the mockup mode. *)letis_predecessor_of_genesis=matchblockwith|`Hash(requested_hash,rel)->Int.equalrel0&&Block_hash.equalrequested_hashgenesis_predecessor_block_hash|_->falseinifis_predecessor_of_genesisthen{x.rpc_context.block_headerwithproto_level=predx.rpc_context.block_header.proto_level;}elsex.rpc_context.block_headerletraw_header(block:Tezos_shell_services.Block_services.block):bytestzresultLwt.t=let*x=locate_blockstateblockinletshell=may_lie_on_proto_levelblockxinletprotocol_data=Data_encoding.Binary.to_bytes_exnProtocol.block_header_data_encodingx.protocol_datainreturn(Data_encoding.Binary.to_bytes_exnTezos_base.Block_header.encoding{shell;protocol_data})letheader(block:Tezos_shell_services.Block_services.block):Mockup.M.Block_services.block_headertzresultLwt.t=let*x=locate_blockstateblockinletshell=may_lie_on_proto_levelblockxinreturn{Mockup.M.Block_services.hash=x.rpc_context.block_hash;chain_id;shell;protocol_data=x.protocol_data;}letresulting_context_hash(block:Tezos_shell_services.Block_services.block):Context_hash.ttzresultLwt.t=let*x=locate_blockstateblockinreturnx.resulting_context_hashletoperationsblock=let*x=locate_blockstateblockinreturnx.operationsletinject_blockblock_hash(block_header:Block_header.t)operations=let*protocol_data=parse_protocol_datablock_header.protocol_datainlet*level=get_block_levelblock_headerinlet*round=get_block_roundblock_headerinlet*block_hash1,block_header1,operations1,propagation_vector=User_hooks.on_inject_block~level~round~block_hash~block_header~operations~protocol_datainhandle_propagation(Broadcast_block(block_hash1,block_header1,operations1))propagation_vectorstate.broadcast_pipesletall_pipes_or_select=function|None->returnstate.broadcast_pipes|Somel->List.map_es(funn->matchList.nth_optstate.broadcast_pipesnwith|None->failwith"Node number %d is out of range (max is %d)"n(List.lengthstate.broadcast_pipes-1)|Somepipe->returnpipe)lletbroadcast_block?destsblock_hash(block_header:Block_header.t)operations=let*pipes=all_pipes_or_selectdestsinlet*!()=List.iter_s(funpipe->Lwt_pipe.Unbounded.pushpipe(Broadcast_block(block_hash,block_header,operations));Lwt.return_unit)pipesinreturn_unitletinject_operation(Operation.{shell;proto}asop)=letop_hash=Operation.hashopinletproto_op_opt=Data_encoding.Binary.of_bytesProtocol.operation_data_encodingprotoinmatchproto_op_optwith|Error_->failwith"inject_operation: cannot parse operation"|Okprotocol_data->letop:Protocol.Alpha_context.packed_operation={shell;protocol_data}inlet*op_hash1,op1,propagation_vector=User_hooks.on_inject_operation~op_hash~opinlet*()=handle_propagation(Broadcast_op(op_hash1,op1))propagation_vectorstate.broadcast_pipesinreturnop_hash1letbroadcast_operation?dests(op:Protocol.Alpha_context.packed_operation)=let*pipes=all_pipes_or_selectdestsinletop_hash=Alpha_context.Operation.hash_packedopinlet*!()=List.iter_s(funpipe->Lwt_pipe.Unbounded.pushpipe(Broadcast_op(op_hash,op));Lwt.return_unit)pipesinreturn_unitletpending_operations()=letops=state.mempoolinLwt.returnMockup.M.Block_services.Mempool.{validated=ops;refused=Operation_hash.Map.empty;outdated=Operation_hash.Map.empty;branch_refused=Operation_hash.Map.empty;branch_delayed=Operation_hash.Map.empty;unprocessed=Operation_hash.Map.empty;}letmonitor_operations~version~validated~branch_delayed~branch_refused~refused=ignorevalidated;ignorebranch_delayed;ignorebranch_refused;ignorerefused;letstreamed=reffalseinstate.streaming_operations<-true;letnext()=letrecloop()=let*!ops_opt=Lwt_stream.getstate.operations_streaminmatchops_optwith|Nonewhen!streamed->Lwt.return_none|None->streamed:=true;Lwt.return_some(version,[])|Someops->(let*!result=List.filter_map_sUser_hooks.on_new_operationopsinmatchresultwith|[]->loop()|l->Lwt.return_some(version,List.map(funx->(x,None))l))inloop()inletshutdown()=()inTezos_rpc.Answer.{next;shutdown}letrpc_context_callbackblock=let*x=locate_blockstateblockinreturnx.rpc_contextletlist_blocks~heads~length~min_date:_=letcompare_block_fitnessesblock0block1=Fitness.compareblock0.rpc_context.block_header.fitnessblock1.rpc_context.block_header.fitnessinlethash_of_blockblock=block.rpc_context.block_hashinletlookup_headhead=let*xs=locate_blocksstate(`Hash(head,0))inletsegment=matchlengthwithNone->xs|Somen->List.take_nnxsinreturn(List.maphash_of_block(List.sortcompare_block_fitnessessegment))inList.map_eslookup_headheadsletlive_blocksblock=live_blocksstateblockletraw_protocol_datablock=let*x=locate_blockstateblockinreturnx.raw_protocol_dataendin(moduleImpl)(** Return the current head. *)lethead{chain;_}=matchList.hdchainwith|None->failwith"mockup_simulator.ml: empty chain"|Somehd->returnhd(** Clear from the mempool operations whose branch does not point to
a live block with respect to the current head. *)letclear_mempoolstate=letopenLwt_result_syntaxinlet*head=headstateinletincluded_ops_hashes=List.map(fun(op:Mockup.M.Block_services.operation)->op.hash)(List.flattenhead.operations)inlet*live_set=live_blocksstate(`Head0)inletmempool=List.filter(fun(_oph,(op:Mockup.M.Protocol.operation))->letincluded_in_head=List.mem~equal:Operation_hash.equal(Alpha_context.Operation.hash_packedop)included_ops_hashesinBlock_hash.Set.memop.shell.branchlive_set&¬included_in_head)state.mempoolinstate.mempool<-mempool;return_unitletbegin_validation_and_applicationctxtchain_idmode~predecessor~cache=letopenLwt_result_syntaxinlet*validation_state=Mockup.M.Protocol.begin_validationctxtchain_idmode~predecessor~cacheinlet*application_state=Mockup.M.Protocol.begin_applicationctxtchain_idmode~predecessor~cacheinreturn(validation_state,application_state)letvalidate_and_apply_operation(validation_state,application_state)ophop=letopenLwt_result_syntaxinlet*validation_state=Mockup.M.Protocol.validate_operationvalidation_stateophopinlet*application_state,receipt=Mockup.M.Protocol.apply_operationapplication_stateophopinreturn((validation_state,application_state),receipt)letfinalize_validation_and_application(validation_state,application_state)shell_header=letopenLwt_result_syntaxinlet*()=Mockup.M.Protocol.finalize_validationvalidation_stateinMockup.M.Protocol.finalize_applicationapplication_stateshell_header(** Apply a block to the given [rpc_context]. *)letreconstruct_context(rpc_context:Tezos_protocol_environment.rpc_context)(operations:Operation.tlistlist)(block_header:Block_header.t)=letopenLwt_result_syntaxinletpredecessor=rpc_context.block_headerinletpredecessor_context=rpc_context.contextinlet*protocol_data=parse_protocol_datablock_header.protocol_datainlet*state=begin_validation_and_applicationpredecessor_contextchain_id(Application{shell=block_header.shell;protocol_data})~predecessor~cache:`Lazyinleti=ref0inlet*state,_=List.fold_left_es(List.fold_left_es(fun(state,results)op->incri;letoph=Operation.hashopinletoperation_data=Data_encoding.Binary.of_bytes_exnMockup.M.Protocol.operation_data_encodingop.Operation.protoinletop={Mockup.M.Protocol.shell=op.shell;protocol_data=operation_data;}inlet*state,receipt=validate_and_apply_operationstateophopinreturn(state,receipt::results)))(state,[])operationsinfinalize_validation_and_applicationstateNone(** Process an incoming block. If validation succeeds:
- update the current head to this new block
- cleanup outdated operations
- cleanup listener table
Note that this implementation does not handle concurrent branches. *)letrecprocess_blockstateblock_hash(block_header:Block_header.t)operations=letopenLwt_result_syntaxinletget_predecessor()=letpredecessor_hash=block_header.Block_header.shell.predecessorinlet*head=headstateinmatchBlock_hash.Table.findstate.chain_tablepredecessor_hashwith|None|Some[]->((* Even if the predecessor is not known locally, it might be known by
some node in the network. The code below "requests" information
about the block by its hash. *)matchBlock_hash.Table.findstate.global_chain_tablepredecessor_hashwith|None->failwith"get_predecessor: unknown predecessor block"|Somepredecessor->letpredecessor_block_header=Block_header.{shell=predecessor.rpc_context.block_header;protocol_data=predecessor.raw_protocol_data;}inletpredecessor_ops=List.map(funxs->List.map(fun(op:Mockup.M.Block_services.operation)->Operation.{shell=op.shell;proto=Data_encoding.Binary.to_bytes_exnProtocol.operation_data_encodingop.protocol_data;})xs)predecessor.operationsin(* If the block is found, apply it before proceeding. *)let*()=process_blockstatepredecessor.rpc_context.block_hashpredecessor_block_headerpredecessor_opsinreturnpredecessor)|Some(predecessor::_)->ifInt32.subhead.rpc_context.block_header.levelpredecessor.rpc_context.block_header.level<=2lthenreturnpredecessorelsefailwith"get_predecessor: the predecessor block is too old"inmatchBlock_hash.Table.findstate.chain_tableblock_hashwith|Some_->(* The block is already known. *)return_unit|None->let*predecessor=get_predecessor()inlet*head=headstateinlet*{context;message;_},_=reconstruct_contextpredecessor.rpc_contextoperationsblock_headerinletresulting_context_hash=Tezos_context_ops.Context_ops.hash~time:block_header.shell.timestamp?messagecontextinletrpc_context=Tezos_protocol_environment.{context;block_hash;block_header=block_header.shell}inletoperations=List.map(funpass->List.map(fun(Operation.{shell;proto}asop)->lethash:Operation_hash.t=Operation.hashopinletprotocol_data:Alpha_context.packed_protocol_data=Data_encoding.Binary.of_bytes_exnProtocol.operation_data_encodingprotoin{Mockup.M.Block_services.chain_id;hash;shell;protocol_data;receipt=Empty;})pass)operationsinlet*protocol_data=parse_protocol_datablock_header.protocol_datainletnew_block={rpc_context;protocol_data;raw_protocol_data=block_header.protocol_data;operations;resulting_context_hash;}inletpredecessor_hash=block_header.Block_header.shell.predecessorinlettail=Block_hash.Table.findstate.chain_tablepredecessor_hash|>WithExceptions.Option.get~loc:__LOC__inletnew_chain=new_block::tailinBlock_hash.Table.replacestate.chain_tableblock_hashnew_chain;Block_hash.Table.replacestate.global_chain_tableblock_hashnew_block;Context_hash.Table.replacestate.ctxt_tableresulting_context_hashrpc_context;ifFitness.(block_header.shell.fitness>head.rpc_context.block_header.fitness)then(state.chain<-new_chain;let*()=clear_mempoolstatein(* The head changed: notify that the stream ended. *)state.operations_stream_pushNone;state.streaming_operations<-false;(* Instanciate a new stream *)letoperations_stream,operations_stream_push=Lwt_stream.create()instate.operations_stream<-operations_stream;state.operations_stream_push<-operations_stream_push;state.operations_stream_push(Somestate.mempool);return_unit)elsereturn_unit(** This process listens to broadcast block and operations and incorporates
them in the context of the fake node. *)letreclistener~(user_hooks:(moduleHooks))~state~broadcast_pipe=letopenLwt_result_syntaxinletmoduleUser_hooks=(valuser_hooks:Hooks)inlet*!result=Lwt_pipe.Unbounded.popbroadcast_pipeinmatchresultwith|Broadcast_op(operation_hash,packed_operation)->let*()=ifList.mem_assoc~equal:Operation_hash.equaloperation_hashstate.mempoolthenreturn_unitelse(state.mempool<-(operation_hash,packed_operation)::state.mempool;state.operations_stream_push(Some[(operation_hash,packed_operation)]);User_hooks.check_mempool_after_processing~mempool:state.mempool)inlistener~user_hooks~state~broadcast_pipe|Broadcast_block(block_hash,block_header,operations)->let*level=get_block_levelblock_headerinlet*round=get_block_roundblock_headerinlet*protocol_data=parse_protocol_datablock_header.protocol_datainlet*()=User_hooks.check_block_before_processing~level~round~block_hash~block_header~protocol_datainlet*()=process_blockstateblock_hashblock_headeroperationsinlet*()=User_hooks.check_chain_after_processing~level~round~chain:state.chaininLwt_pipe.Unbounded.pushstate.validated_blocks_pipe(block_hash,block_header,operations);Lwt_pipe.Unbounded.pushstate.heads_pipe(block_hash,block_header);listener~user_hooks~state~broadcast_pipe(** Create a fake node state. *)letcreate_fake_node_state~i~live_depth~(genesis_block:Block_header.t*Tezos_protocol_environment.rpc_context)~global_chain_table~broadcast_pipes=letopenLwt_result_syntaxinletblock_header0,rpc_context0=genesis_blockinlet*protocol_data=parse_protocol_datablock_header0.protocol_datainletgenesis0={rpc_context=rpc_context0;protocol_data;raw_protocol_data=block_header0.protocol_data;operations=[[];[];[];[]];resulting_context_hash=block_header0.shell.context;}inletchain0=[genesis0]inletvalidated_blocks_pipe=Lwt_pipe.Unbounded.create()inletheads_pipe=Lwt_pipe.Unbounded.create()inletoperations_stream,operations_stream_push=Lwt_stream.create()inletgenesis_block_true_hash=Block_header.hash{shell=rpc_context0.block_header;protocol_data=block_header0.protocol_data;}in(* Only push genesis block as a new head, not a valid block: it is
the shell's semantics to not advertise "transition" blocks. *)Lwt_pipe.Unbounded.pushheads_pipe(rpc_context0.block_hash,block_header0);return{instance_index=i;live_depth;mempool=[];chain=chain0;chain_table=Block_hash.Table.of_seq(List.to_seq[(rpc_context0.block_hash,chain0);(genesis_block_true_hash,chain0);(genesis_predecessor_block_hash,chain0);]);global_chain_table;ctxt_table=Context_hash.Table.of_seq(List.to_seq[(rpc_context0.Tezos_protocol_environment.block_header.Block_header.context,rpc_context0);]);validated_blocks_pipe;heads_pipe;operations_stream;operations_stream_push;streaming_operations=false;broadcast_pipes;genesis_block_true_hash;}(** Start baker process. *)letbaker_process~(delegates:Baking_state.consensus_keylist)~base_dir~(genesis_block:Block_header.t*Tezos_protocol_environment.rpc_context)~i~global_chain_table~broadcast_pipes~(user_hooks:(moduleHooks))=letopenLwt_result_syntaxinletbroadcast_pipe=List.nthbroadcast_pipesi|>WithExceptions.Option.get~loc:__LOC__inlet*state=create_fake_node_state~i~live_depth:60~genesis_block~global_chain_table~broadcast_pipesinletfilesystem=String.Hashtbl.create10inletwallet=newFaked_client_context.faked_io_wallet~base_dir~filesysteminletcctxt=lethooks=make_mocked_services_hooksstateuser_hooksinnewProtocol_client_context.wrap_full(newFaked_client_context.unix_faked~base_dir~filesystem~chain_id~hooks)inletmoduleUser_hooks=(valuser_hooks:Hooks)inlet*!()=User_hooks.on_start_baker~baker_position:i~delegates~cctxtinlet*()=List.iter_es(fun({alias;public_key;public_key_hash;secret_key_uri}:Baking_state.consensus_key)->letopenTezos_client_baseinletname=alias|>WithExceptions.Option.get~loc:__LOC__inlet*public_key_uri=Client_keys.neuterizesecret_key_uriinClient_keys.register_keywallet~force:false(public_key_hash,public_key_uri,secret_key_uri)~public_keyname)delegatesinletcontext_index=letopenAbstract_context_indexin{sync_fun=Lwt.return;checkout_fun=(funhash->Context_hash.Table.findstate.ctxt_tablehash|>Option.map(funTezos_protocol_environment.{context;_}->context)|>Lwt.return);finalize_fun=Lwt.return;}inletmoduleUser_hooks=(valuser_hooks:Hooks)inletlistener_process()=listener~user_hooks~state~broadcast_pipeinletstop_on_eventevent=User_hooks.stop_on_eventeventinletbaker_process()=Faked_daemon.Baker.run~cctxt~stop_on_event~chain_id~context_index~delegatesinlet*()=Lwt.pick[listener_process();baker_process()]inUser_hooks.check_chain_on_success~chain:state.chainletgenesis_protocol_data(baker_sk:Signature.secret_key)(predecessor_hash:Block_hash.t)(block_header:Block_header.shell_header):Bytes.t=letproof_of_work_nonce=Bytes.createProtocol.Alpha_context.Constants.proof_of_work_nonce_sizeinletpayload_hash=Protocol.Alpha_context.Block_payload.hash~predecessor_hash~payload_round:Alpha_context.Round.zero[]inletper_block_votes={Protocol.Per_block_votes_repr.liquidity_baking_vote=Baking_configuration.default_votes_config.Baking_configuration.liquidity_baking_vote;adaptive_issuance_vote=Baking_configuration.default_votes_config.Baking_configuration.adaptive_issuance_vote;}inletcontents=Protocol.Alpha_context.Block_header.{payload_hash;payload_round=Alpha_context.Round.zero;proof_of_work_nonce;seed_nonce_hash=None;per_block_votes;}inletunsigned_header=Data_encoding.Binary.to_bytes_exnProtocol.Alpha_context.Block_header.unsigned_encoding(block_header,contents)inletsignature=Signature.sign~watermark:Alpha_context.Block_header.(to_watermark(Block_headerchain_id))baker_skunsigned_headerinData_encoding.Binary.to_bytes_exnProtocol.Alpha_context.Block_header.protocol_data_encoding{contents;signature}(** Figure out who should be the signer for the genesis block. *)letdeduce_baker_sk(accounts_with_secrets:(Protocol.Alpha_context.Parameters.bootstrap_account*Tezos_mockup_commands.Mockup_wallet.bootstrap_secret)list)(total_accounts:int)(level:int):Signature.secret_keytzresultLwt.t=letopenLwt_result_syntaxinlet*baker_index=match(total_accounts,level)with|_,0->return0(* apparently this doesn't really matter *)|_->failwith"cannot deduce baker for a genesis block, total accounts = %d, level \
= %d"total_accountslevelinlet_,secret=List.nthaccounts_with_secretsbaker_index|>WithExceptions.Option.get~loc:__LOC__inletsecret_key=Signature.Secret_key.of_b58check_exn(Uri.path(secret.sk_uri:>Uri.t))inreturnsecret_key(** Generate the two initial genesis blocks. *)letmake_genesis_context~delegate_selection~initial_seed~round0~round1~consensus_committee_size~consensus_thresholdaccounts_with_secrets(total_accounts:int)=letopenLwt_result_syntaxinletdefault_constants=Mockup.Protocol_parameters.default_value.constantsinletround_durations=letopenAlpha_contextinStdlib.Option.get(Round.Durations.create_opt~first_round_duration:(Period.of_seconds_exnround0)~delay_increment_per_round:(Period.of_seconds_exn(Int64.subround1round0)))inletconstants={default_constantswithinitial_seed;consensus_committee_size;consensus_threshold;minimal_block_delay=Alpha_context.Period.of_seconds_exn(max1Lround0);delay_increment_per_round=Alpha_context.Period.of_seconds_exnInt64.(max1L(subround1round0));}inletfrom_bootstrap_accounti((account:Protocol.Alpha_context.Parameters.bootstrap_account),(secret:Tezos_mockup_commands.Mockup_wallet.bootstrap_secret)):Mockup.Parsed_account.t={name=Format.sprintf"bootstrap%d"(i+1);sk_uri=secret.sk_uri;amount=account.amount;}inletbootstrap_accounts=Data_encoding.Json.construct(Data_encoding.listMockup.Parsed_account.encoding)(List.mapifrom_bootstrap_accountaccounts_with_secrets)inlet*?delegate_selection=letopenResult_syntaxinList.map_e(fun(level,round_delegates)->let*level=Raw_level_repr.of_int32levelinlet+round_delegates=List.map_e(fun(round,delegate)->let+round=Round_repr.of_int32roundin(round,delegate))round_delegatesin(level,round_delegates))delegate_selection|>Environment.wrap_tzresultinlet*initial_seed=match(delegate_selection,constants.initial_seed)with|[],seed_opt->returnseed_opt|selection,(Some_asseed)->(let*!()=Faked_client_context.logger#warning"Checking provided seed."inlet*result=Tenderbrute.check_seed~bootstrap_accounts_json:bootstrap_accounts~parameters:Mockup.Protocol_parameters.{default_valuewithconstants}~seedselectioninmatchresultwith|true->returnseed|false->failwith"Provided initial seed does not match delegate selection")|_,None->let*!()=Faked_client_context.logger#warning"No initial seed provided, bruteforcing."inTenderbrute.bruteforce~max:100_000_000_000~bootstrap_accounts_json:bootstrap_accounts~parameters:Mockup.Protocol_parameters.{default_valuewithconstants}delegate_selectioninlet*!()=matchinitial_seedwith|None->Lwt.return_unit|_wheninitial_seed=constants.initial_seed->Lwt.return_unit|Someseed->Faked_client_context.logger#warning"Bruteforced seed is %a, please save into your test."State_hash.ppseedinletconstants={constantswithinitial_seed}inletcommon_parameters=Mockup.Protocol_parameters.{default_valuewithconstants}inletmake_block0initial_timestamp=letparameters={common_parameterswithinitial_timestamp}inletreencoded_parameters=Data_encoding.Binary.of_bytes_exnMockup.M.parameters_encoding@@Data_encoding.Binary.to_bytes_exnMockup.Protocol_parameters.encodingparametersinlet*{chain=_;rpc_context=rpc_context0;protocol_data=_}=Mockup.M.init~cctxt:Faked_client_context.logger~parameters:reencoded_parameters~constants_overrides_json:None~bootstrap_accounts_json:(Somebootstrap_accounts)inletblock_header0={rpc_context0.block_headerwithpredecessor=genesis_predecessor_block_hash;}inletrpc_context={rpc_context0withblock_header=block_header0}inlet*baker_sk=deduce_baker_skaccounts_with_secretstotal_accounts0inletprotocol_data=genesis_protocol_databaker_skgenesis_predecessor_block_hashrpc_context.block_headerinletblock_header=Block_header.{shell=rpc_context.block_header;protocol_data}inreturn(block_header,rpc_context)inletlevel0_round0_duration=Protocol.Alpha_context.Round.round_durationround_durationsAlpha_context.Round.zeroinlettimestamp0=Time.Protocol.of_secondsInt64.(sub(of_float(Unix.time()))(Alpha_context.Period.to_secondslevel0_round0_duration))inmake_block0timestamp0(** By default, propagate every message everywhere. *)letdefault_propagation_vector=List.repeat5PassmoduleDefault_hooks:Hooks=structleton_inject_block~level:_~round:_~block_hash~block_header~operations~protocol_data:_=return(block_hash,block_header,operations,default_propagation_vector)leton_inject_operation~op_hash~op=return(op_hash,op,default_propagation_vector)leton_new_validated_block~block_hash~block_header~operations=Lwt.return_some(block_hash,block_header,operations)leton_new_head~block_hash~block_header=Lwt.return_some(block_hash,block_header)leton_new_operationx=Lwt.return_somexletcheck_block_before_processing~level:_~round:_~block_hash:_~block_header:_~protocol_data:_=return_unitletcheck_chain_after_processing~level:_~round:_~chain:_=return_unitletcheck_mempool_after_processing~mempool:_=return_unitletstop_on_event_=falseleton_start_baker~baker_position:_~delegates:_~cctxt:_=Lwt.return_unitletcheck_chain_on_success~chain:_=return_unitendtypeconfig={debug:bool;round0:int64;round1:int64;timeout:int;delegate_selection:(int32*(int32*Signature.public_key_hash)list)list;initial_seed:State_hash.toption;consensus_committee_size:int;consensus_threshold:int;}letdefault_config={debug=false;round0=2L;(* Rounds should be long enough for the bakers to
exchange all the necessary messages. *)round1=3L(* No real need to increase round durations. *);timeout=30;delegate_selection=[];initial_seed=None;consensus_committee_size=Default_parameters.constants_mainnet.consensus_committee_size;consensus_threshold=Default_parameters.constants_mainnet.consensus_threshold;}letmake_baking_delegate((account:Alpha_context.Parameters.bootstrap_account),(secret:Tezos_mockup_commands.Mockup_wallet.bootstrap_secret)):Baking_state.consensus_key=Baking_state.{alias=Somesecret.name;public_key=account.public_key|>WithExceptions.Option.get~loc:__LOC__;public_key_hash=account.public_key_hash;secret_key_uri=secret.sk_uri;}letrun?(config=default_config)bakers_spec=letopenLwt_result_syntaxinTezos_client_base.Client_keys.register_signer(moduleTezos_signer_backends.Unencrypted);lettotal_accounts=List.fold_left(funacc(n,_)->acc+n)0bakers_speciniftotal_accounts=0thenfailwith"the simulation should use at least one delegate"elseiftotal_accounts>5thenfailwith"only up to 5 bootstrap accounts are available"else(* When logging is enabled it may cause non-termination:
https://gitlab.com/nomadic-labs/tezos/-/issues/546
In particular, it seems that when logging is enabled the baker
process can get cancelled without executing its Lwt finalizer. *)let*!()=ifconfig.debugthenTezos_base_unix.Internal_event_unix.init()elseLwt.return_unitinlettotal_bakers=List.lengthbakers_specinlet*broadcast_pipes=List.init~when_negative_length:()total_bakers(fun_->Lwt_pipe.Unbounded.create())|>function|Error()->failwith"impossible: negative length of the baker spec"|Okxs->returnxsinletglobal_chain_table=Block_hash.Table.create10inlet*bootstrap_secrets=Tezos_mockup_commands.Mockup_wallet.default_bootstrap_accountsinletaccounts_with_secrets=List.combine_drop(List.take_ntotal_accountsaccounts)bootstrap_secretsinletall_delegates=List.mapmake_baking_delegateaccounts_with_secretsinlet*genesis_block=make_genesis_context~delegate_selection:config.delegate_selection~initial_seed:config.initial_seed~round0:config.round0~round1:config.round1~consensus_committee_size:config.consensus_committee_size~consensus_threshold:config.consensus_thresholdaccounts_with_secretstotal_accountsinlettake_third(_,_,x)=xinlettimeout_process()=let*!()=Lwt_unix.sleep(Float.of_intconfig.timeout)infailwith"the test is taking longer than %d seconds@."config.timeoutinLwt.pick[timeout_process();Lwt_result_syntax.tzjoin(take_third(List.fold_left(fun(i,delegates_acc,ms)(n,user_hooks)->letdelegates,leftover_delegates=List.split_nndelegates_accinletm=baker_process~delegates~base_dir:"dummy"~genesis_block~i~global_chain_table~broadcast_pipes~user_hooksin(i+1,leftover_delegates,m::ms))(0,all_delegates,[])bakers_spec));]letget_account_pki=matchList.nthaccountsiwith|None->assertfalse|Someacc->acc.public_key|>WithExceptions.Option.get~loc:__LOC__letbootstrap1=get_account_pk0letbootstrap2=get_account_pk1letbootstrap3=get_account_pk2letbootstrap4=get_account_pk3letbootstrap5=get_account_pk4letcheck_block_signature~block_hash~(block_header:Block_header.t)~public_key=let(protocol_data:Protocol.Alpha_context.Block_header.protocol_data)=Data_encoding.Binary.of_bytes_exnProtocol.Alpha_context.Block_header.protocol_data_encodingblock_header.protocol_datainletunsigned_header=Data_encoding.Binary.to_bytes_exnProtocol.Alpha_context.Block_header.unsigned_encoding(block_header.shell,protocol_data.contents)inifSignature.check~watermark:Alpha_context.Block_header.(to_watermark(Block_headerchain_id))public_keyprotocol_data.signatureunsigned_headerthenreturn_unitelsefailwith"unexpected signature for %a; tried with %a@."Block_hash.ppblock_hashSignature.Public_key.pppublic_keytypeop_predicate=Operation_hash.t->Alpha_context.packed_operation->booltzresultLwt.tletmempool_count_ops~mempool~predicate=letopenLwt_result_syntaxinlet*results=List.map_es(fun(op_hash,op)->predicateop_hashop)mempoolinreturn(List.fold_left(funaccresult->ifresultthenacc+1elseacc)0results)letmempool_has_op~mempool~predicate=letopenLwt_result_syntaxinlet*n=mempool_count_ops~mempool~predicateinreturn(n>0)letmempool_has_op_ref~mempool~predicate~var=letopenLwt_result_syntaxinlet*result=mempool_has_op~mempool~predicateinifresultthenvar:=true;return_unitletop_is_signed_by~public_key(op_hash:Operation_hash.t)(op:Alpha_context.packed_operation)=letopenLwt_result_syntaxinmatchop.protocol_datawith|Operation_datad->(let*watermark=matchd.contentswith|Singleop_contents->return(matchop_contentswith|Attestation_->Alpha_context.Operation.to_watermark(Attestationchain_id)|Preattestation_->Alpha_context.Operation.to_watermark(Preattestationchain_id)|_->Signature.Generic_operation)|_->failwith"unexpected contents in %a@."Operation_hash.ppop_hashinmatchd.signaturewith|None->failwith"did not find a signature for op %a@."Operation_hash.ppop_hash|Somesignature->letunsigned_operation_bytes=Data_encoding.Binary.to_bytes_exnProtocol.Alpha_context.Operation.unsigned_encoding(op.shell,Contents_listd.contents)inreturn(Signature.check~watermarkpublic_keysignatureunsigned_operation_bytes))letop_is_preattestation?level?round(op_hash:Operation_hash.t)(op:Alpha_context.packed_operation)=matchop.protocol_datawith|Operation_datad->(matchd.contentswith|Singleop_contents->(matchop_contentswith|Preattestationconsensus_content->letright_level=matchlevelwith|None->true|Someexpected_level->Int32.equal(Alpha_context.Raw_level.to_int32consensus_content.level)expected_levelinletright_round=matchroundwith|None->true|Someexpected_round->Int32.equal(Alpha_context.Round.to_int32consensus_content.round)expected_roundinreturn(right_level&&right_round)|_->return_false)|_->failwith"unexpected contents in %a@."Operation_hash.ppop_hash)letop_is_attestation?level?round(op_hash:Operation_hash.t)(op:Alpha_context.packed_operation)=matchop.protocol_datawith|Operation_datad->(matchd.contentswith|Singleop_contents->(matchop_contentswith|Attestationconsensus_content->letright_level=matchlevelwith|None->true|Someexpected_level->Int32.equal(Alpha_context.Raw_level.to_int32consensus_content.level)expected_levelinletright_round=matchroundwith|None->true|Someexpected_round->Int32.equal(Alpha_context.Round.to_int32consensus_content.round)expected_roundinreturn(right_level&&right_round)|_->return_false)|_->failwith"unexpected contents in %a@."Operation_hash.ppop_hash)letop_is_bothfgop_hashop=letopenLwt_result_syntaxinlet*f_result=fop_hashopiniff_resultthengop_hashopelsereturn_falseletsave_proposal_payload~(protocol_data:Alpha_context.Block_header.protocol_data)~var=var:=Some(protocol_data.contents.payload_hash,protocol_data.contents.payload_round);return_unitletverify_payload_hash~(protocol_data:Alpha_context.Block_header.protocol_data)~original_proposal~message=match!original_proposalwith|None->failwith"verify_payload_hash: expected to have observed a proposal by now"|Some(original_hash,original_round)->ifProtocol.Block_payload_hash.equaloriginal_hashprotocol_data.contents.payload_hash&&Protocol.Alpha_context.Round.equaloriginal_roundprotocol_data.contents.payload_roundthenreturn_unitelsefailwith"verify_payload_hash: %s"messageletget_block_roundblock=round_from_raw_fitnessblock.rpc_context.block_header.fitness