123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929(*****************************************************************************)(* *)(* 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. *)(* *)(*****************************************************************************)openProtocolopenAlpha_contextmoduleSmart_contracts=Client_proto_stresstest_contractstypetransfer_strategy=|Fixed_amountof{mutez:Tez.t}(** Amount to transfer *)|Evaporationof{fraction:float}(** Maximum fraction of current wealth to transfer.
Minimum amount is 1 mutez regardless of total wealth. *)typelimit=|Absofint(** Absolute level at which we should stop *)|Relofint(** Relative number of levels before stopping *)typeparameters={seed:int;fresh_probability:float;(** Per-transfer probability that the destination will be fresh *)tps:float;(** Transaction per seconds target *)strategy:transfer_strategy;regular_transfer_fee:Tez.t;(** fees for each transfer (except for transfers to smart contracts), in mutez *)regular_transfer_gas_limit:Gas.Arith.integral;(** gas limit per operation (except for transfers to smart contracts) *)storage_limit:Z.t;(** storage limit per operation *)account_creation_storage:Z.t;(** upper bound on bytes consumed when creating a tz1 account *)total_transfers:intoption;(** total number of transfers to perform; unbounded if None *)level_limit:limitoption;(** total number of levels during which the stresstest is run; unbounded if None *)smart_contracts:Smart_contracts.t;(** An opaque type that stores all the information that is necessary for
efficient sampling of smart contract calls. *)}typeorigin=Explicit|Wallet_pkh|Wallet_aliasofstringtypesource={pkh:public_key_hash;pk:public_key;sk:Signature.secret_key;}typesource_with_uri={pkh:public_key_hash;pk:public_key;pk_uri:Client_keys.pk_uri;sk:Signature.secret_key;sk_uri:Client_keys.sk_uri;}typeinput_source=|Explicitofsource|Wallet_aliasofstring|Wallet_pkhofpublic_key_hashtypesource_origin={source:source;origin:origin}(** Destination of a call: either an implicit contract or an originated one
with all the necessary data (entrypoint and the argument). *)typedestination=|ImplicitofSignature.Public_key_hash.t|OriginatedofSmart_contracts.invocation_parameterstypetransfer={src:source;dst:destination;fee:Tez.t;gas_limit:Gas.Arith.integral;amount:Tez.t;counter:Manager_counter.toption;fresh_dst:bool;}typestate={rng_state:Random.State.t;current_head_on_start:Block_hash.t;mutablepool:source_originlist;mutablepool_size:int;mutableshuffled_pool:sourcelist;mutablerevealed:Signature.Public_key_hash.Set.t;mutablelast_block:Block_hash.t;mutablelast_level:int;mutabletarget_block:Block_hash.t;(** The block on top of which we are injecting transactions (HEAD~2). *)new_block_condition:unitLwt_condition.t;injected_operations:Operation_hash.tlistBlock_hash.Table.t;}(** Cost estimations for every kind of transaction used in the stress test.
*)typetransaction_costs={regular:Gas.Arith.integral;(** Cost of a regular transaction. *)smart_contracts:(string*Gas.Arith.integral)list;(** Cost of a smart contract call (per contract alias). *)}typeverbosity=Notice|Info|Debugletverbosity=refNoticeletloglevelmsg=match(level,!verbosity)with|Notice,_|Info,Info|Info,Debug|Debug,Debug->msg()|_->Lwt.return_unitletpp_sepppf()=Format.fprintfppf",@ "letdefault_parameters={seed=0x533D;fresh_probability=0.001;tps=5.0;strategy=Fixed_amount{mutez=Tez.one};regular_transfer_fee=Tez.of_mutez_exn2_000L;regular_transfer_gas_limit=Gas.Arith.integral_of_int_exn1_600;(* [gas_limit] corresponds to a slight overapproximation of the
gas needed to inject an operation. It was obtained by simulating
the operation using the client. *)storage_limit=Z.zero;account_creation_storage=Z.of_int300;(* [account_creation_storage] corresponds to a slight overapproximation
of the storage consumed when allocating a new implicit account.
It was obtained by simulating the operation using the client. *)total_transfers=None;level_limit=None;smart_contracts=Smart_contracts.no_contracts;}letinput_source_encoding=letopenData_encodinginunion[case~title:"explicit"(Tag0)(obj3(req"pkh"Signature.Public_key_hash.encoding)(req"pk"Signature.Public_key.encoding)(req"sk"Signature.Secret_key.encoding))(functionExplicit{pkh;pk;sk}->Some(pkh,pk,sk)|_->None)(fun(pkh,pk,sk)->Explicit{pkh;pk;sk});case~title:"alias"(Tag1)(obj1(req"alias"Data_encoding.string))(functionWallet_aliasalias->Somealias|_->None)(funalias->Wallet_aliasalias);case~title:"pkh"(Tag2)(obj1(req"pkh"Signature.Public_key_hash.encoding))(functionWallet_pkhpkh->Somepkh|_->None)(funpkh->Wallet_pkhpkh);]letinjected_operations_encoding=letopenData_encodinginlist(obj2(req"block_hash_when_injected"Block_hash.encoding)(req"operation_hashes"(listOperation_hash.encoding)))lettransaction_costs_encoding=letopenData_encodinginconv(fun{regular;smart_contracts}->(regular,smart_contracts))(fun(regular,smart_contracts)->{regular;smart_contracts})(obj2(req"regular"Gas.Arith.n_integral_encoding)(req"smart_contracts"(assocGas.Arith.n_integral_encoding)))letdestination_to_contractdst=matchdstwith|Implicitx->Contract.Implicitx|Originatedx->x.destinationletparse_strategys=matchString.split~limit:1':'swith|["fixed";parameter]->(matchint_of_stringparameterwith|exception_->Error"invalid integer literal"|mutezwhenmutez<=0->Error"negative amount"|mutez->(matchTez.of_mutez(Int64.of_intmutez)with|None->Error"invalid mutez"|Somemutez->Ok(Fixed_amount{mutez})))|["evaporation";parameter]->(matchfloat_of_stringparameterwith|exception_->Error"invalid float literal"|fractionwhenfraction<0.0||fraction>1.0->Error"invalid evaporation rate"|fraction->Ok(Evaporation{fraction}))|_->Error"invalid argument"(** This command uses two different data structures for sources:
- The in-output files one,
- The normalized one.
The data structure used for in-output files does not directly contain the
data required to forge operations. For efficiency purposes, the sources are
converted into a normalized data structure that contains all the required
data to forge operations and the format originally used to be able to
revert this conversion. *)(** [normalize_source cctxt src] converts [src] from in-output data structure
to normalized one. If the conversion fails, [None] is returned and a
warning message is printed in [cctxt].
Only unencrypted and encrypted sources from the wallet of [cctxt] are
supported. *)letnormalize_sourcecctxt=letopenLwt_syntaxinletsk_of_sk_urisk_uri=matchSignature.Secret_key.of_b58check(Uri.path(sk_uri:Client_keys.sk_uri:>Uri.t))with|Oksk->Lwt.return_somesk|Error_->let+r=Tezos_signer_backends.Encrypted.decryptcctxtsk_uriinletsk=Option.of_resultrinOption.bindskSignature.Of_V_latest.secret_keyinletkey_from_aliasalias=letwarningmsgalias=let*()=cctxt#warningmsgaliasinreturn_noneinlet*key=let*r=Client_keys.alias_keyscctxtaliasinmatchrwith|Error_|OkNone->warning"Alias \"%s\" not found in the wallet"alias|Ok(Some(_,None,_))|Ok(Some(_,_,None))->warning"Alias \"%s\" does not contain public or secret key and could not \
be used for stresstest"alias|Ok(Some(pkh,Somepk,Somesk_uri))->(let*o=sk_of_sk_urisk_uriinmatchowith|None->warning"Cannot extract the secret key form the alias \"%s\" of the \
wallet"alias|Somesk->Lwt.return_some{source={pkh;pk;sk};origin=Wallet_aliasalias})inmatchkeywith|None->warning"Source given as alias \"%s\" ignored"alias|key->Lwt.returnkeyinletkey_from_walletpkh=letwarningmsgpkh=let*()=cctxt#warningmsgSignature.Public_key_hash.pppkhinreturn_noneinlet*key=let*r=Client_keys.get_keycctxtpkhinmatchrwith|Error_->warning"Pkh \"%a\" not found in the wallet"pkh|Ok(alias,pk,sk_uri)->(let*o=sk_of_sk_urisk_uriinmatchowith|None->let*()=cctxt#warning"Cannot extract the secret key form the pkh \"%a\" (alias: \
\"%s\") of the wallet"Signature.Public_key_hash.pppkhaliasinLwt.return_none|Somesk->Lwt.return_some{source={pkh;pk;sk};origin=Wallet_pkh})inmatchkeywith|None->warning"Source given as pkh \"%a\" ignored"pkh|key->Lwt.returnkeyinfunction|Explicitsource->Lwt.return_some{source;origin=Explicit}|Wallet_aliasalias->key_from_aliasalias|Wallet_pkhpkh->key_from_walletpkh(** [unnormalize_source src_org] converts [src_org] from normalized data
structure to in-output one. *)letunnormalize_sourcesrc_org=matchsrc_org.originwith|Explicit->Explicitsrc_org.source|Wallet_pkh->Wallet_pkhsrc_org.source.pkh|Wallet_aliasalias->Wallet_aliasalias(** Samples from [state.pool]. Used to generate the destination of a
transfer. *)letsample_any_source_from_poolstate=letidx=Random.State.intstate.rng_statestate.pool_sizeinmatchList.nthstate.poolidxwith|None->assertfalse|Somesrc_org->Lwt.returnsrc_org.source(** Takes and returns a source from [state.shuffled_pool]. Waits for a
new block if no source is available. *)letrecget_source_from_shuffled_poolstate(cctxt:Protocol_client_context.full)=letopenLwt_syntaxinmatchstate.shuffled_poolwith|source::l->state.shuffled_pool<-l;let*()=logDebug(fun()->cctxt#message"sample_transfer: %d unused sources for the block next to %a"(List.lengthl)Block_hash.ppstate.last_block)inLwt.returnsource|[]->let*()=cctxt#message"all available sources have been used for block next to %a"Block_hash.ppstate.last_blockinlet*()=Lwt_condition.waitstate.new_block_conditioninget_source_from_shuffled_poolstatecctxtletrandom_seedrng=Bytes.init32(fun_->Char.chr(Random.State.intrng256))letgenerate_fresh_sourcestate=letseed=random_seedstate.rng_stateinletpkh,pk,sk=Signature.generate_key~seed()inletfresh={source={pkh;pk;sk};origin=Explicit}instate.pool<-fresh::state.pool;state.pool_size<-state.pool_size+1;fresh.source(* [heads_iter cctxt f] calls [f head] each time there is a new head received
by the streamed RPC /monitor/heads/main and returns [promise, stopper].
[promise] resolved when the stream is closed. [stopper ()] closes the
stream. *)letheads_iter(cctxt:Protocol_client_context.full)(f:Block_hash.t*Tezos_base.Block_header.t->unittzresultLwt.t):(unittzresultLwt.t*Tezos_rpc.Context.stopper)tzresultLwt.t=letopenLwt_result_syntaxinlet*heads_stream,stopper=Shell_services.Monitor.headscctxt`Maininletrecloop():unittzresultLwt.t=let*!block_hash_and_header=Lwt_stream.getheads_streaminmatchblock_hash_and_headerwith|None->cctxt#error"unexpected end of block stream@."|Some((new_block_hash,_block_header)asblock_hash_and_header)->Lwt.catch(fun()->let*!()=logDebug(fun()->cctxt#message"heads_iter: new block received %a@."Block_hash.ppnew_block_hash)inlet*protocols=Shell_services.Blocks.protocolscctxt~block:(`Hash(new_block_hash,0))()inifProtocol_hash.(protocols.current_protocol=Protocol.hash)thenlet*()=fblock_hash_and_headerinloop()elselet*!()=logDebug(fun()->cctxt#message"heads_iter: new block on protocol %a. Stopping \
iteration.@."Protocol_hash.ppprotocols.current_protocol)inreturn_unit)(funexn->cctxt#error"An exception occurred on a function bound on new heads : %s@."(Printexc.to_stringexn))inletpromise=loop()inlet*!()=logDebug(fun()->cctxt#message"head iteration for proto %a stopped@."Protocol_hash.ppProtocol.hash)inreturn(promise,stopper)letsample_smart_contractssmart_contractsrng_state=letsmart_contract=Smart_contracts.selectsmart_contracts(Random.State.floatrng_state1.0)inOption.map(funinvocation_parameters->(Originatedinvocation_parameters,invocation_parameters.fee,invocation_parameters.gas_limit))smart_contract(* We perform rejection sampling of valid sources.
We could maintain a local cache of existing contracts with sufficient balance. *)letrecsample_transfer(cctxt:Protocol_client_context.full)chainblock(parameters:parameters)(state:state)=letopenLwt_result_syntaxinlet*!src=get_source_from_shuffled_poolstatecctxtinlet*tez=Alpha_services.Contract.balancecctxt(chain,block)(Contract.Implicitsrc.pkh)inifTez.(tez=zero)thenlet*!()=logDebug(fun()->cctxt#message"sample_transfer: invalid balance %a"Signature.Public_key_hash.ppsrc.pkh)in(* Sampled source has zero balance: the transfer that created that
address was not included yet. Retry *)sample_transfercctxtchainblockparametersstateelseletfresh=Random.State.floatstate.rng_state1.0<parameters.fresh_probabilityinlet*dst,fee,gas_limit=matchsample_smart_contractsparameters.smart_contractsstate.rng_statewith|None->let*!dest=iffreshthenLwt.return(generate_fresh_sourcestate)elsesample_any_source_from_poolstateinreturn(Implicitdest.pkh,parameters.regular_transfer_fee,parameters.regular_transfer_gas_limit)|Somev->returnvinletamount=matchparameters.strategywith|Fixed_amount{mutez}->mutez|Evaporation{fraction}->letmutez=Int64.to_float(Tez.to_muteztez)inletmax_fraction=Int64.of_float(mutez*.fraction)inletamount=ifmax_fraction=0Lthen1Lelsemax1L(Random.State.int64state.rng_statemax_fraction)inTez.of_mutez_exnamountinreturn{src;dst;fee;gas_limit;amount;counter=None;fresh_dst=fresh}letinject_contents(cctxt:Protocol_client_context.full)branchskcontents=letbytes=Data_encoding.Binary.to_bytes_exnOperation.unsigned_encoding({branch},Contents_listcontents)inletsignature=Some(Signature.sign~watermark:Signature.Generic_operationskbytes)inletop:_Operation.t={shell={branch};protocol_data={contents;signature}}inletbytes=Data_encoding.Binary.to_bytes_exnOperation.encoding(Operation.packop)inShell_services.Injection.operationcctxtbytes(* counter _must_ be set before calling this function *)letmanager_op_of_transferparameters{src;dst;fee;gas_limit;amount;counter;fresh_dst}=letsource=src.pkhinletstorage_limit=iffresh_dstthenZ.addparameters.account_creation_storageparameters.storage_limitelseparameters.storage_limitinletoperation=letparameters=letopenTezos_michelineinScript.lazy_expr(matchdstwith|Implicit_->Micheline.strip_locations(Prim(0,Michelson_v1_primitives.D_Unit,[],[]))|Originatedx->x.arg)inletentrypoint=matchdstwith|Implicit_->Entrypoint.default|Originatedx->x.entrypointinletdestination=destination_to_contractdstinTransaction{amount;parameters;entrypoint;destination}inmatchcounterwith|None->assertfalse|Somecounter->Manager_operation{source;fee;counter;operation;gas_limit;storage_limit}letcost_of_manager_operation=Gas.Arith.integral_of_int_exn1_000letinject_transfer(cctxt:Protocol_client_context.full)parametersstatetransfer=letopenLwt_result_syntaxinlet*branch=Shell_services.Blocks.hashcctxt()inlet*current_counter=Alpha_services.Contract.countercctxt(`Main,`Head0)transfer.src.pkhinlet*already_revealed=ifSignature.Public_key_hash.Set.memtransfer.src.pkhstate.revealedthenreturntrueelse((* Either the [manager_key] RPC tells us the key is already
revealed, or we immediately inject a reveal operation: in any
case the key is revealed in the end. *)state.revealed<-Signature.Public_key_hash.Set.addtransfer.src.pkhstate.revealed;let*pk_opt=Alpha_services.Contract.manager_keycctxt(`Main,`Head0)transfer.src.pkhinreturn(Option.is_somepk_opt))inlet*!r=ifnotalready_revealedthenletreveal_counter=Manager_counter.succcurrent_counterinlettransf_counter=Manager_counter.succreveal_counterinletreveal=Manager_operation{source=transfer.src.pkh;fee=Tez.zero;counter=reveal_counter;gas_limit=cost_of_manager_operation;storage_limit=Z.zero;operation=Revealtransfer.src.pk;}inletmanager_op=manager_op_of_transferparameters{transferwithcounter=Sometransf_counter}inletlist=Cons(reveal,Singlemanager_op)inlet*!()=logInfo(fun()->cctxt#message"injecting reveal+transfer from %a (counters=%a,%a) to %a"Signature.Public_key_hash.pptransfer.src.pkhManager_counter.ppreveal_counterManager_counter.pptransf_counterContract.pp(destination_to_contracttransfer.dst))in(* NB: regardless of our best efforts to keep track of counters, injection can fail with
"counter in the future" if a block switch happens in between the moment we
get the branch and the moment we inject, and the new block does not include
all the operations we injected. *)inject_contentscctxtstate.target_blocktransfer.src.sklistelselettransf_counter=Manager_counter.succcurrent_counterinletmanager_op=manager_op_of_transferparameters{transferwithcounter=Sometransf_counter}inletlist=Singlemanager_opinlet*!()=logInfo(fun()->cctxt#message"injecting transfer from %a (counter=%a) to %a"Signature.Public_key_hash.pptransfer.src.pkhManager_counter.pptransf_counterContract.pp(destination_to_contracttransfer.dst))in(* See comment above. *)inject_contentscctxtstate.target_blocktransfer.src.sklistinmatchrwith|Okop_hash->let*!()=logDebug(fun()->cctxt#message"inject_transfer: op injected %a"Operation_hash.ppop_hash)inletops=Option.value~default:[](Block_hash.Table.findstate.injected_operationsbranch)inBlock_hash.Table.replacestate.injected_operationsbranch(op_hash::ops);return_unit|Errore->let*!()=logDebug(fun()->cctxt#message"inject_transfer: error, op not injected: %a"Error_monad.pp_print_tracee)inreturn_unitletsave_injected_operations(cctxt:Protocol_client_context.full)state=letopenLwt_syntaxinletjson=Data_encoding.Json.constructinjected_operations_encoding(Block_hash.Table.fold(funkvacc->(k,v)::acc)state.injected_operations[])inletpath=Filename.temp_file"client-stresstest-injected_operations-"".json"inlet*()=cctxt#message"writing injected operations in file %s"pathinlet*r=Lwt_utils_unix.Json.write_filepathjsoninmatchrwith|Errore->cctxt#message"could not write injected operations json file: %a"Error_monad.pp_print_tracee|Ok_->Lwt.return_unitletstat_on_exit(cctxt:Protocol_client_context.full)state=letopenLwt_result_syntaxinletratio_injected_included_op()=let*current_head_on_exit=Shell_services.Blocks.hashcctxt()inletinter_cardinals1s2=Operation_hash.Set.cardinal(Operation_hash.Set.inter(Operation_hash.Set.of_lists1)(Operation_hash.Set.of_lists2))inletget_included_opsolder_block=letrecget_included_opsblockacc_included_ops=ifblock=older_blockthenreturnacc_included_opselselet*included_ops=Shell_services.Chain.Blocks.Operation_hashes.operation_hashes_in_passcctxt~chain:`Main~block:(`Hash(block,0))3inlet*bs=Shell_services.Blocks.listcctxt~chain:`Main~heads:[block]~length:2()inmatchbswith|[[current;predecessor]]whencurrent=block->get_included_opspredecessor(List.appendacc_included_opsincluded_ops)|_->cctxt#error"Error while computing stats: invalid block list"inget_included_opscurrent_head_on_exit[]inletinjected_ops=Block_hash.Table.fold(funklacc->(* The operations injected during the last block are ignored because
they should not be currently included. *)ifcurrent_head_on_exit<>kthenList.appendacclelseacc)state.injected_operations[]inlet*included_ops=get_included_opsstate.current_head_on_startinletincluded_ops_count=inter_cardinalinjected_opsincluded_opsinlet*!()=logDebug(fun()->cctxt#message"injected : [%a]@.included: [%a]"(Format.pp_print_list~pp_sepOperation_hash.pp)injected_ops(Format.pp_print_list~pp_sepOperation_hash.pp)included_ops)inletinjected_ops_count=List.lengthinjected_opsinlet*!()=cctxt#message"%s of the injected operations have been included (%d injected, %d \
included). Note that the operations injected during the last block \
are ignored because they should not be currently included."(ifInt.equalinjected_ops_count0then"N/A"elseFormat.sprintf"%d%%"(included_ops_count*100/injected_ops_count))injected_ops_countincluded_ops_countinreturn_unitinratio_injected_included_op()letlaunch(cctxt:Protocol_client_context.full)(parameters:parameters)statesave_pool_callback=letinjected=ref0inlettarget_level=matchparameters.level_limitwith|None->None|Some(Abstarget)->Sometarget|Some(Reloffset)->Some(state.last_level+offset)inletdt=1./.parameters.tpsinletterminated()=letopenLwt_syntaxinifmatchparameters.total_transferswith|None->false|Somebound->bound<=!injectedthenlet*()=cctxt#message"Stopping after %d injections (target %a)."!injectedFormat.(pp_print_optionpp_print_int)parameters.total_transfersinLwt.return_trueelsematchtarget_levelwith|None->Lwt.return_false|Sometarget->iftarget<=state.last_levelthenlet*()=cctxt#message"Stopping at level %d (target level: %d)."state.last_leveltargetinLwt.return_trueelseLwt.return_falseinletrecloop()=letopenLwt_result_syntaxinlet*!terminated=terminated()inifterminatedthenlet*!()=save_pool_callback()inlet*!()=save_injected_operationscctxtstateinstat_on_exitcctxtstateelseletstart=Mtime_clock.counter()inlet*!()=logDebug(fun()->cctxt#message"launch.loop: invoke sample_transfer")inlet*transfer=sample_transfercctxtcctxt#chaincctxt#blockparametersstateinlet*!()=logDebug(fun()->cctxt#message"launch.loop: invoke inject_transfer")inlet*()=inject_transfercctxtparametersstatetransferinincrinjected;letelapsed=Time.Monotonic.Span.to_float_s(Mtime_clock.countstart)inletremaining=dt-.elapsedinlet*!()=ifremaining<=0.0thencctxt#warning"warning: tps target could not be reached, consider using a lower \
value for --tps"elseLwt_unix.sleepremaininginloop()inleton_new_head:Block_hash.t*Tezos_base.Block_header.t->unittzresultLwt.t=(* Because of how Tenderbake works the target block should stay 2
blocks in the past because this guarantees that we are targeting a
block that is decided. *)letopenLwt_result_syntaxinletupdate_target_block()=let*target_block=Shell_services.Blocks.hashcctxt~block:(`Head2)()instate.target_block<-target_block;return_unitinfun(new_block_hash,new_block_header)->let*()=update_target_block()inifnot(Block_hash.equalnew_block_hashstate.last_block)then(state.last_block<-new_block_hash;state.last_level<-Int32.to_intnew_block_header.shell.level;state.shuffled_pool<-List.shuffle~rng:state.rng_state(List.map(funsrc_org->src_org.source)state.pool));Lwt_condition.broadcaststate.new_block_condition();return_unitinletopenLwt_result_syntaxinlet*heads_iteration,stopper=heads_itercctxton_new_headin(* The head iteration stops at protocol change. *)let*()=Lwt.pick[loop();heads_iteration]in(matchLwt.stateheads_iterationwithLwt.Return_->()|_->stopper());return_unitletgroup=Tezos_clic.{name="stresstest";title="Commands for stress-testing the network"}letinput_source_list_encoding=Data_encoding.listinput_source_encodingletpool_source_param=Client_proto_args.json_encoded_with_origin_parameter~name:"input source list"input_source_list_encodingletseed_arg=letopenTezos_clicinarg~long:"seed"~placeholder:"int"~doc:"random seed"(parameter(fun(cctxt:Protocol_client_context.full)s->matchint_of_stringswith|exception_->cctxt#error"While parsing --seed: could not convert argument to int"|i->Lwt_result_syntax.returni))lettps_arg=letopenTezos_clicinarg~long:"tps"~placeholder:"float"~doc:"transactions per seconds target"(parameter(fun(cctxt:Protocol_client_context.full)s->matchfloat_of_stringswith|exception_->cctxt#error"While parsing --tps: could not convert argument to float"|fwhenf<0.0->cctxt#error"While parsing --tps: negative argument"|f->Lwt_result_syntax.returnf))letfresh_probability_arg=letopenTezos_clicinarg~long:"fresh-probability"~placeholder:"float in [0;1]"~doc:(Format.sprintf"Probability for each transaction's destination to be a fresh \
account. The default value is %g. This new account may then be used \
as source or destination of subsequent transactions, just like the \
accounts that were initially provided to the command. Note that when \
[--single-op-per-pkh-per-block] is set, the new account will not be \
used as source until the head changes."default_parameters.fresh_probability)(parameter(fun(cctxt:Protocol_client_context.full)s->matchfloat_of_stringswith|exception_->cctxt#error"While parsing --fresh-probability: could not convert argument \
to float"|fwhenf<0.0||f>1.0->cctxt#error"While parsing --fresh-probability: invalid argument"|f->Lwt_result_syntax.returnf))letsmart_contract_parameters_arg=letopenTezos_clicinarg~long:"smart-contract-parameters"~placeholder:"JSON file with smart contract parameters"~doc:(Format.sprintf"A JSON object that maps smart contract aliases to objects with three \
fields: probability in [0;1], invocation_fee, and \
invocation_gas_limit.")(Client_proto_args.json_encoded_parameter~name:"smart contract"Smart_contracts.contract_parameters_collection_encoding)letstrategy_arg=letopenTezos_clicinarg~long:"strategy"~placeholder:"fixed:mutez | evaporation:[0;1]"~doc:"wealth redistribution strategy"(parameter(fun(cctxt:Protocol_client_context.full)s->matchparse_strategyswith|Errormsg->cctxt#error"While parsing --strategy: %s"msg|Okstrategy->Lwt_result_syntax.returnstrategy))letgas_limit_arg=letopenTezos_clicinletgas_limit_kind=parameter(fun(cctxt:#Client_context.full)s->tryletv=Z.of_stringsinLwt_result_syntax.return(Gas.Arith.integral_exnv)with_->cctxt#error"invalid gas limit (must be a positive number)")inarg~long:"gas-limit"~short:'G'~placeholder:"amount"~doc:(Format.asprintf"Set the gas limit of the transaction instead of using the default \
value of %a"Gas.Arith.pp_integraldefault_parameters.regular_transfer_gas_limit)gas_limit_kindletstorage_limit_arg=letopenTezos_clicinletstorage_limit_kind=parameter(fun(cctxt:#Client_context.full)s->tryletv=Z.of_stringsinassert(Compare.Z.(v>=Z.zero));Lwt_result_syntax.returnvwith_->cctxt#error"invalid storage limit (must be a positive number of bytes)")inarg~long:"storage-limit"~short:'S'~placeholder:"amount"~doc:(Format.asprintf"Set the storage limit of the transaction instead of using the \
default value of %a"Z.pp_printdefault_parameters.storage_limit)storage_limit_kindlettransfers_arg=letopenTezos_clicinarg~long:"transfers"~placeholder:"integer"~doc:"total number of transfers to perform, unbounded if not specified"(parameter(fun(cctxt:Protocol_client_context.full)s->matchint_of_stringswith|exception_->cctxt#error"While parsing --transfers: invalid integer literal"|iwheni<=0->cctxt#error"While parsing --transfers: negative integer"|i->Lwt_result_syntax.returni))letlevel_limit_arg=letopenTezos_clicinarg~long:"level-limit"~placeholder:"integer | +integer"~doc:"Level at which the stresstest will stop (if prefixed by '+', the level \
is relative to the current head)"(parameter(fun(cctxt:Protocol_client_context.full)s->letopenLwt_result_syntaxinmatchint_of_stringswith|exception_->cctxt#error"While parsing --levels: invalid integer literal"|iwheni<=0->cctxt#error"While parsing --levels: negative integer or zero"|i->ifString.gets0='+'thenreturn(Reli)elsereturn(Absi)))letverbose_arg=Tezos_clic.switch~long:"verbose"~short:'v'~doc:"Display detailed logs of the injected operations"()letdebug_arg=Tezos_clic.switch~long:"debug"~short:'V'~doc:"Display debug logs"()letset_optionoptfx=Option.fold~none:x~some:(fx)optletsave_pool_callback(cctxt:Protocol_client_context.full)pool_sourcestate=letjson=Data_encoding.Json.constructinput_source_list_encoding(List.mapunnormalize_sourcestate.pool)inletcatch_write_error=function|Errore->cctxt#message"could not write back json file: %a"Error_monad.pp_print_tracee|Ok()->Lwt.return_unitinletopenLwt_syntaxinmatchpool_sourcewith|Client_proto_args.Text_->(* If the initial pool was given directly as json, save pool to
a temp file. *)letpath=Filename.temp_file"client-stresstest-pool-"".json"inlet*()=cctxt#message"writing back address pool in file %s"pathinlet*r=Lwt_utils_unix.Json.write_filepathjsonincatch_write_errorr|File{path;_}->(* If the pool specification was a json file, save pool to
the same file. *)let*()=cctxt#message"writing back address pool in file %s"pathinlet*r=Lwt_utils_unix.Json.write_filepathjsonincatch_write_errorrletgenerate_random_transactions=letopenTezos_clicincommand~group~desc:"Generate random transactions"(args12seed_argtps_argfresh_probability_argsmart_contract_parameters_argstrategy_argClient_proto_args.fee_arggas_limit_argstorage_limit_argtransfers_arglevel_limit_argverbose_argdebug_arg)(prefixes["stresstest";"transfer";"using"]@@param~name:"sources.json"~desc:{|List of accounts from which to perform transfers in JSON format. The input JSON must be an array of objects of the form {"pkh":"<pkh>","pk":"<pk>","sk":"<sk>"} or {"alias":"<alias from wallet>"} or {"pkh":"<pkh from wallet>"} with the pkh, pk and sk encoded in B58 form."|}pool_source_param@@stop)(fun(seed,tps,freshp,smart_contract_parameters,strat,fee,gas_limit,storage_limit,transfers,level_limit,verbose_flag,debug_flag)pool_source(cctxt:Protocol_client_context.full)->letopenLwt_result_syntaxin(verbosity:=match(debug_flag,verbose_flag)with|true,_->Debug|false,true->Info|false,false->Notice);let*smart_contracts=Smart_contracts.initcctxt(Option.value~default:[]smart_contract_parameters)inletparameters={default_parameterswithsmart_contracts}|>set_optionseed(funparameterseed->{parameterwithseed})|>set_optiontps(funparametertps->{parameterwithtps})|>set_optionfreshp(funparameterfresh_probability->{parameterwithfresh_probability})|>set_optionstrat(funparameterstrategy->{parameterwithstrategy})|>set_optionfee(funparameterregular_transfer_fee->{parameterwithregular_transfer_fee})|>set_optiongas_limit(funparameterregular_transfer_gas_limit->{parameterwithregular_transfer_gas_limit})|>set_optionstorage_limit(funparameterstorage_limit->{parameterwithstorage_limit})|>set_optiontransfers(funparametertransfers->{parameterwithtotal_transfers=Sometransfers})|>set_optionlevel_limit(funparameterlevel_limit->{parameterwithlevel_limit=Somelevel_limit})inmatchClient_proto_args.content_of_file_or_textpool_sourcewith|[]->cctxt#error"It is required to provide sources"|sources->let*!()=logInfo(fun()->cctxt#message"starting to normalize sources")inlet*!sources=List.filter_map_s(normalize_sourcecctxt)sourcesinlet*!()=logInfo(fun()->cctxt#message"all sources have been normalized")inletsources=List.sort_uniq(funsrc1src2->Signature.Secret_key.comparesrc1.source.sksrc2.source.sk)sourcesinletrng_state=Random.State.make[|parameters.seed|]inlet*current_head_on_start=Shell_services.Blocks.hashcctxt()inlet*header_on_start=Shell_services.Blocks.Header.shell_headercctxt()inlet*()=ifheader_on_start.level<=2lthencctxt#error"The level of the head (%a) needs to be greater than 2 and is \
actually %ld."Block_hash.ppcurrent_head_on_startheader_on_start.levelelsereturn_unitinlet*current_target_block=Shell_services.Blocks.hashcctxt~block:(`Head2)()inletstate={rng_state;current_head_on_start;pool=sources;pool_size=List.lengthsources;shuffled_pool=List.shuffle~rng:rng_state(List.map(funsrc_org->src_org.source)sources);revealed=Signature.Public_key_hash.Set.empty;last_block=current_head_on_start;last_level=Int32.to_intheader_on_start.level;target_block=current_target_block;new_block_condition=Lwt_condition.create();injected_operations=Block_hash.Table.create1023;}inletexit_callback_id=Lwt_exit.register_clean_up_callback~loc:__LOC__(fun_retcode->let*!r=stat_on_exitcctxtstateinmatchrwith|Ok()->Lwt.return_unit|Errore->cctxt#message"Error: %a"Error_monad.pp_print_tracee)inletsave_pool()=save_pool_callbackcctxtpool_sourcestatein(* Register a callback for saving the pool when the tool is interrupted
through ctrl-c *)letexit_callback_id=Lwt_exit.register_clean_up_callback~loc:__LOC__~after:[exit_callback_id](fun_retcode->save_pool())inletsave_injected_operations()=save_injected_operationscctxtstateinignore(Lwt_exit.register_clean_up_callback~loc:__LOC__~after:[exit_callback_id](fun_retcode->save_injected_operations()));launchcctxtparametersstatesave_pool)letestimate_transaction_cost?smart_contracts(cctxt:Protocol_client_context.full):Gas.Arith.integraltzresultLwt.t=letopenLwt_result_syntaxinlet*!src=normalize_sourcecctxt(Wallet_alias"bootstrap1")inlet*!dst=normalize_sourcecctxt(Wallet_alias"bootstrap2")inletrng_state=Random.State.make[|default_parameters.seed|]inlet*src,dst=match(src,dst)with|Somesrc,Somedst->return(src,dst)|_->cctxt#error"Cannot find bootstrap1 or bootstrap2 accounts in the wallet."inletchain=cctxt#chaininletblock=cctxt#blockinletselected_smart_contract=Option.bindsmart_contracts(funsmart_contracts->sample_smart_contractssmart_contractsrng_state)inletdst,fee,gas_limit=Option.valueselected_smart_contract~default:(Implicitdst.source.pkh,default_parameters.regular_transfer_fee,default_parameters.regular_transfer_gas_limit)inlet*current_counter=Alpha_services.Contract.countercctxt(chain,block)src.source.pkhinlettransf_counter=Manager_counter.succcurrent_counterinlettransfer={src=src.source;dst;fee;gas_limit;amount=Tez.of_mutez_exn(Int64.of_int1);counter=Sometransf_counter;fresh_dst=false;}inletmanager_op=manager_op_of_transfer{default_parameterswithregular_transfer_gas_limit=Default_parameters.constants_mainnet.hard_gas_limit_per_operation;}transferinlet*_oph,op,result=Injection.simulatecctxt~chain~block(Singlemanager_op)inmatchresult.contentswith|Single_result(Manager_operation_result{operation_result;_})->(matchoperation_resultwith|Applied(Transaction_result(Transaction_to_contract_result{consumed_gas;_}))->return(Gas.Arith.ceilconsumed_gas)|_->(matchoperation_resultwith|Failed(_,errors)->Error_monad.pp_print_traceFormat.err_formatter(Environment.wrap_tztraceerrors)|_->assertfalse);cctxt#error"@[<v 2>Simulation result:@,%a@]"Operation_result.pp_operation_result(op.protocol_data.contents,result.contents))letestimate_transaction_costs:Protocol_client_context.fullTezos_clic.command=letopenTezos_clicincommand~group~desc:"Output gas estimations for transactions that stresstest uses"no_options(prefixes["stresstest";"estimate";"gas"]@@stop)(fun()cctxt->letopenLwt_result_syntaxinlet*regular=estimate_transaction_costcctxtinlet*smart_contracts=Smart_contracts.with_every_known_smart_contractcctxt(funsmart_contracts->estimate_transaction_cost~smart_contractscctxt)inlettransaction_costs:transaction_costs={regular;smart_contracts}inletjson=Data_encoding.Json.constructtransaction_costs_encodingtransaction_costsinFormat.printf"%a"Data_encoding.Json.ppjson;return_unit)(* Returns a list of transfers from each element of [sources]. *)letgenerate_transfers~sources~amount~parameters~entrypoint~fee~gas_limit~storage_limit=List.map(fundst->letdestination=Contract.Implicitdst.pkhinlettransfer=Client_proto_context.build_transaction_operation~amount~parameters~entrypoint~fee~gas_limit~storage_limitdestinationinAnnotated_manager_operation.Annotated_manager_operationtransfer)sources(* Returns a list of reveals from each element of [sources]. *)letgenerate_reveals~sources~fee~gas_limit~storage_limit=List.map(fundst->letreveal=Client_proto_context.build_reveal_operation~fee~gas_limit~storage_limitdst.pkin(dst,Annotated_manager_operation.Annotated_manager_operationreveal))sources(* Given a list of [sources], it returns
- a list of batches of transfers where each batch has a maximum of
[batch_size] operation, for each element of [sources],
- a list of reveals, for each element of [sources].
[sources] is the list of "starter" accounts, used to fund all
accounts in a exponential way.
*)letgenerate_starter_ops~sources~amount~batch_size=letfee=Tez.of_mutez_exn1_000Linletgas_limit=Gas.Arith.integral_of_int_exn1_040inletstorage_limit=Z.of_int257inletparameters=letopenTezos_michelineinScript.lazy_expr(Micheline.strip_locations(Prim(0,Michelson_v1_primitives.D_Unit,[],[])))inletentrypoint=Entrypoint.defaultinlettxs_ops=generate_transfers~sources~amount~parameters~entrypoint~fee~gas_limit~storage_limitinletreveal_ops=generate_reveals~sources~fee~gas_limit~storage_limitinletrecsplitnacc=function|[]->acc|l->letcurrent,next=List.rev_split_nnlinletbatch=Annotated_manager_operation.manager_of_listcurrentinsplitn(batch::acc)nextin(* Split the list of transfers into N batches containing a maximum
of [batch_size] operations. *)lettxs_batch_l=splitbatch_size[]txs_opsin(txs_batch_l,reveal_ops)(* Returns a list of list of batch. A list of batch consists of N
batches, depending on the number of [starter_sources]. The top
level list can be seen a block partition, so that the 1M
restriction is ensured. *)letgenerate_account_funding_batches(starter_sources:source_with_urilist)(empty_accounts:source_with_urilist)~batch_size~amount=letopenLwt_result_syntaxinletnb_sources=List.lengthstarter_sourcesinletfee=Tez.of_mutez_exn1_000Linletgas_limit=Gas.Arith.integral_of_int_exn1_040inletstorage_limit=Z.of_int257inletparameters=letopenTezos_michelineinScript.lazy_expr(Micheline.strip_locations(Prim(0,Michelson_v1_primitives.D_Unit,[],[])))inletentrypoint=Entrypoint.defaultinletto_batchcandidatesemiters=(* For each [emiters], it generates [batch_size] transactions from
it, and to [batch_size] candidates.*)letrecauxacc(candidates:source_with_urilist)(emiters:source_with_urilist)=matchemiterswith|[]->returnacc|source::next_sources->letcurrent,next_candidates=List.rev_split_nbatch_sizecandidatesinlettxs=generate_transfers~sources:current~amount~parameters~entrypoint~fee~gas_limit~storage_limitinletbatch=Annotated_manager_operation.manager_of_listtxsin(*Avoid the generation of empty batches*)ifnext_candidates=[]thenreturn((source,batch)::acc)elseaux((source,batch)::acc)next_candidatesnext_sourcesinaux[]candidatesemitersinletrecauxacc=function|[]->returnacc|empty_accounts->letcandidates,rest=List.rev_split_n(batch_size*nb_sources)empty_accountsinlet*batch=to_batchcandidatesstarter_sourcesinaux(batch::acc)restinlet*res=aux[]empty_accountsinreturnres(* Loads a wallet by reading directly the files to speed up things. *)letload_walletcctxt~source_pkh=letopenLwt_result_syntaxinlet*keys=Client_keys.get_keyscctxtin(* Convert loaded and filter identities. We want to ban activator
and bootstrap<1-5> in sandbox, as well as the "faucet source" on
test networks. *)letto_ban=["activator";"bootstrap"]@WithExceptions.Result.get_ok~loc:__LOC__(List.init~when_negative_length:"error"5(funi->Format.sprintf"bootstrap%d"(i+1)))inletrecauxacc=function|[]->returnacc|(alias,pkh,_,_)::tlwhenList.exists(String.equalalias)to_ban||Signature.Public_key_hash.equalpkhsource_pkh->auxacctl|(_,pkh,pk,sk_uri)::tl->let*pk_uri=Client_keys.neuterizesk_uriinletpayload=Uri.path(sk_uri:Tezos_signer_backends.Unencrypted.sk_uri:>Uri.t)inletsk=Signature.Secret_key.of_b58check_exnpayloadinaux({pkh;pk;pk_uri;sk;sk_uri}::acc)tlinaux[]keysletsource_key_arg=letopenTezos_clicinparam~name:"source_key_arg"~desc:"Source key public key hash from which the tokens will be transferred to \
start the funding."(parameter(fun(cctxt:#Client_context.full)s->letr=Signature.Public_key_hash.of_b58checksinmatchrwith|Okpkh->Lwt_result_syntax.returnpkh|Errore->cctxt#error"Cannot read public key hash: %a"Error_monad.pp_print_tracee))letbatch_size_arg=letopenTezos_clicindefault_arg~long:"batch-size"~placeholder:"integer"~doc:"Maximum number of operations that can be put into a single batch (250 \
by default)"~default:"250"(parameter(fun(cctxt:#Client_context.full)s->matchint_of_string_optswith|Someiwheni>0->Lwt_result_syntax.returni|Some_->cctxt#error"Integer must be positive."|None->cctxt#error"Cannot read integer"))letbatches_per_block_arg=letopenTezos_clicindefault_arg~long:"batches-per-block"~placeholder:"integer"~doc:"Maximum number of batches that can be put into a single block (100 by \
default)"~default:"100"(parameter(fun(cctxt:#Client_context.full)s->matchint_of_string_optswith|Someiwheni>0->Lwt_result_syntax.returni|Some_->cctxt#error"Integer must be positive."|None->cctxt#error"Cannot read integer"))letinitial_amount_arg=letopenTezos_clicindefault_arg~long:"initial-amount"~placeholder:"integer"~doc:"Number of token, in μtz, that will be funded on each of the accounts to \
fund (1 by default)"~default:"1_000_000"(parameter(fun(cctxt:#Client_context.full)s->matchInt64.of_string_optswith|Someiwheni>0L->(tryLwt_result_syntax.return(Tez.of_mutez_exni)withe->cctxt#error"Cannot convert to Tez.t:%s"(Printexc.to_stringe))|Some_->cctxt#error"Integer must be positive."|None->cctxt#error"Cannot read integer"))(* Monitors the node's head to inject transaction batches. *)letinject_batched_txscctxt(source_pkh,source_pk,source_sk)~(starter_batch:Annotated_manager_operation.packed_annotated_listlist)~fee~gas_limit~storage_limit~fee_parameterbatches_per_block=letopenLwt_result_syntaxinletchain=cctxt#chaininlet*heads_stream,stop=Shell_services.Monitor.headscctxtchaininletrecauxstream(sources_ops:Annotated_manager_operation.packed_annotated_listlist)=let*!v=Lwt_stream.getstreaminmatchvwith|Some(_block_hash,_)->(matchsources_opswith|[]->stop();return[]|sources_ops->letnow,next=List.rev_split_nbatches_per_blocksources_opsinlet*()=List.iter_ep(funbatch->let(Annotated_manager_operation.Manager_listcontents)=batchinlet*_results=Injection.inject_manager_operationcctxt~chain:cctxt#chain~block:cctxt#block?confirmations:cctxt#confirmations~dry_run:false~verbose_signing:false~simulation:false~force:false~source:source_pkh~fee:(Limit.of_optionfee)~gas_limit:(Limit.of_optiongas_limit)~storage_limit:(Limit.of_optionstorage_limit)~src_pk:source_pk~src_sk:source_sk~replace_by_fees:false~fee_parametercontentsinreturn_unit)nowinauxstreamnext)|None->let*!()=Lwt_unix.sleep0.5inauxstreamsources_opsinlet*_=auxheads_streamstarter_batchinreturn_unit(* Monitors the node's head to inject reveal batches. *)letinject_batched_revealscctxt~(starter_reveals:(source_with_uri*Annotated_manager_operation.packed)list)~fee~gas_limit~storage_limit~fee_parameterbatches_per_block=letopenLwt_result_syntaxinletchain=cctxt#chaininlet*heads_stream,stop=Shell_services.Monitor.headscctxtchaininletrecauxstream(sources_ops:(source_with_uri*Annotated_manager_operation.packed)list)=let*!v=Lwt_stream.getstreaminmatchvwith|Some(_block_hash,_)->(matchsources_opswith|[]->stop();return[]|sources_ops->letnow,next=List.rev_split_nbatches_per_blocksources_opsinlet*()=List.iter_ep(fun(source,op)->let(Annotated_manager_operation.Manager_listcontents)=Annotated_manager_operation.manager_of_list[op]inlet*_=Injection.inject_manager_operationcctxt~chain:cctxt#chain~block:cctxt#block?confirmations:cctxt#confirmations~dry_run:false~verbose_signing:false~simulation:false~force:false~source:source.pkh~fee:(Limit.of_optionfee)~gas_limit:(Limit.of_optiongas_limit)~storage_limit:(Limit.of_optionstorage_limit)~src_pk:source.pk~src_sk:source.sk_uri~replace_by_fees:false~fee_parametercontentsinreturn_unit)nowinauxstreamnext)|None->let*!()=Lwt_unix.sleep0.5inauxstreamsources_opsinlet*_=auxheads_streamstarter_revealsinreturn_unit(* Monitors the node's head to inject transaction batches. *)letinject_funding_batchescctxt~(funding_batches:(source_with_uri*Annotated_manager_operation.packed_annotated_list)listlist)~fee~gas_limit~storage_limit~fee_parameterbatches_per_block=letopenLwt_result_syntaxinletchain=cctxt#chaininlet*heads_stream,stop=Shell_services.Monitor.headscctxtchaininletrecauxstream(sources_ops:(source_with_uri*Annotated_manager_operation.packed_annotated_list)listlist)=let*!v=Lwt_stream.getstreaminmatchvwith|Some(_block_hash,_)->(matchsources_opswith|[]->stop();return[]|block_ops::tl->letnow,next=List.rev_split_nbatches_per_blockblock_opsinlet*()=List.iter_ep(fun(source,batch)->let(Annotated_manager_operation.Manager_listcontents)=batchinlet*_results=Injection.inject_manager_operationcctxt~chain:cctxt#chain~block:cctxt#block?confirmations:cctxt#confirmations~dry_run:false~verbose_signing:false~simulation:false~force:false~source:source.pkh~fee:(Limit.of_optionfee)~gas_limit:(Limit.of_optiongas_limit)~storage_limit:(Limit.of_optionstorage_limit)~src_pk:source.pk~src_sk:source.sk_uri~replace_by_fees:false~fee_parametercontentsinreturn_unit)nowinifnext=[]thenauxstreamtlelseauxstream(next::tl))|None->let*!()=Lwt_unix.sleep0.5inauxstreamsources_opsinlet*_=auxheads_streamfunding_batchesinreturn_unit(* This command aims to fund accounts to be used in pair with the
stresstest transfer command. To do so, it will proceed in the
following steps:
- takes all the identities found in a given wallet,
- chooses [batch_size] identities as starters ,
- funds the starters with some funds (using source account),
- reveal the starters (using source account),
- makes and injects batches so that the starters uses their funds to
fund the [nb_identities - nb_starters] remaining accounts.
These steps allows to minimize the number of
transfers/operations/blocks to fund many accounts.
As parameters, it is possible to chose:
- batch_size: number of operations into a single batch,
- batches_per_block: number of batches/operations per block,
- initial_amount: number of token distributed to each accounts.
It also allows to define additional parameters, such as fee, gas
and storage limit.
*)letfund_accounts_from_source:Protocol_client_context.fullTezos_clic.command=letopenTezos_clicincommand~group~desc:"Funds all the given accounts"(args7batch_size_argbatches_per_block_arginitial_amount_argClient_proto_args.default_fee_argClient_proto_args.default_gas_limit_argClient_proto_args.default_storage_limit_argClient_proto_args.fee_parameter_args)(prefixes["stresstest";"fund";"accounts";"from"]@@source_key_arg@@stop)(fun(batch_size,batches_per_block,initial_amount,fee,gas_limit,storage_limit,fee_parameter)source_pkh(cctxt:Protocol_client_context.full)->letopenLwt_result_syntaxinlet*source_pk,source_sk=let*_,src_pk,src_sk=Client_keys.get_keycctxtsource_pkhinreturn(src_pk,src_sk)inlet*!()=logNotice(fun()->cctxt#message"@.")inlet*!()=logNotice(fun()->cctxt#message"Starting funding from %a with parameters:@.- batch_size %d@.- \
batches_per_block %d@.- initial_amount %a@."Signature.Public_key_hash.ppsource_pkhbatch_sizebatches_per_blockTez.ppinitial_amount)in(* All generated sources *)let*new_sources=load_walletcctxt~source_pkhin(* Starter sources used to initiate the "exponential"
funding. *)letnb_starters=letl=List.lengthnew_sourcesin(l/batch_size)+iflmodbatch_size=0then0else1inletstarter_sources,empty_accounts=List.rev_split_nnb_startersnew_sourcesinlet*!()=logNotice(fun()->cctxt#message"Funding %d accounts using %d starters@."(List.lengthnew_sources)nb_starters)in(* Initial amount that is sent to starters to allow them to fund
other accounts. This is an over approximation. *)letstarter_initial_amount=(* over approximation of the max number of operation that a
starter may inject. We add one to leave the starter account
with it's own initial amount. *)letmax_nb_transfers=batch_size+1in(* Fees are: reveal + max_nb_transfers * manager_fees
= reveal + max_nb_transfers * (storage_fees + tx fees)
= 0.001tz + max_nb_transfers * (0.06425tz + 0.001tz)
=~ max_nb_transfers * 0.1 tz *)letfees_approx=Tez.of_mutez_exn100_000Linletamount=WithExceptions.Result.get_ok~loc:__LOC__Tez.(initial_amount+?fees_approx)inTez.mul_exnamountmax_nb_transfersinlet*!()=logNotice(fun()->cctxt#message"Sending %a tz to starter accounts@."Tez.ppstarter_initial_amount)inlet*source_balance=Alpha_services.Contract.balancecctxt(cctxt#chain,cctxt#block)(Contract.Implicitsource_pkh)inlet*()=letreq_balance=Tez.mul_exnstarter_initial_amountnb_startersinifTez.(source_balance<req_balance)thencctxt#error"Not enough funds to init starter accounts: %a are needed, only %a \
is available on %a@."Tez.ppsource_balanceTez.ppreq_balanceSignature.Public_key_hash.ppsource_pkhelselet*!()=logNotice(fun()->cctxt#message"Transfering %a tz from %a (out of %a)@."Tez.ppreq_balanceSignature.Public_key_hash.ppsource_pkhTez.ppsource_balance)inreturn_unitinlet*!()=logNotice(fun()->cctxt#message"Generating starter transactions and reveals@.")inletstarter_batch,starter_reveals=generate_starter_ops~sources:starter_sources~amount:starter_initial_amount~batch_sizein(* Inject generated batches and reveals for the starters. *)let*!()=logNotice(fun()->cctxt#message"Injecting starter transfer batches@.")inlet*()=inject_batched_txscctxt(source_pkh,source_pk,source_sk)~starter_batch~fee~gas_limit~storage_limit~fee_parameterbatches_per_blockinlet*!()=logNotice(fun()->cctxt#message"Injecting starter reveal batches@.")inlet*()=inject_batched_revealscctxt~starter_reveals~fee~gas_limit~storage_limit~fee_parameterbatches_per_blockinlet*!()=logNotice(fun()->cctxt#message"Generating funding batches@.")inlet*funding_batches=generate_account_funding_batchesstarter_sourcesempty_accounts~batch_size~amount:initial_amountinlet*!()=logNotice(fun()->cctxt#message"Injecting funding batches@.")inlet*()=inject_funding_batchescctxt~funding_batches~fee~gas_limit~storage_limit~fee_parameterbatches_per_blockinlet*!()=logNotice(fun()->cctxt#message"Done.@.")inreturn_unit)letcommands=[generate_random_transactions;estimate_transaction_costs;Smart_contracts.originate_command;fund_accounts_from_source;]letcommandsnetwork()=matchnetworkwithSome`Mainnet->[]|Some`Testnet|None->commands