123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2023 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. *)(* *)(*****************************************************************************)typeconfig={max_operations:int;max_total_bytes:int}letdefault_max_operations=10_000letdefault_max_total_bytes=10_000_000letdefault_config={max_operations=default_max_operations;max_total_bytes=default_max_total_bytes;}letconfig_encoding:configData_encoding.t=letopenData_encodinginconv(fun{max_operations;max_total_bytes}->(max_operations,max_total_bytes))(fun(max_operations,max_total_bytes)->{max_operations;max_total_bytes})(obj2(dft"max_operations"uint16default_config.max_operations)(dft"max_total_bytes"(uint_like_n())default_config.max_total_bytes))openShell_operation(* Interface for a [Bounding] module. *)moduletypeT=sigtypestatevalempty:statetypeprotocol_operationvaladd_operation:state->config->protocol_operationoperation->(state*Operation_hash.tlist,protocol_operationoperationoption)resultvalremove_operation:state->Operation_hash.t->stateend(* Include [T] but additionally aware of the state's exact definition:
this is useful for the tests. *)moduletypeT_for_tests=sigtypeprotocol_operationtypeoperation:=protocol_operationShell_operation.operationmoduleOpset:Set.Swithtypeelt=operationtypestate={opset:Opset.t;ophmap:operationOperation_hash.Map.t;minop:operationoption;cardinal:int;total_bytes:int;}includeTwithtypeprotocol_operation:=protocol_operationandtypestate:=stateend(* Build a [Bounding] module. *)moduleMake(Proto:Tezos_protocol_environment.PROTOCOL):T_for_testswithtypeprotocol_operation=Proto.operation=structtypeprotocol_operation=Proto.operationtypeoperation=protocol_operationShell_operation.operationletcompare_opsop1op2=Proto.compare_operations(op1.hash,op1.protocol)(op2.hash,op2.protocol)moduleOpset=Set.Make(structtypet=operationletcompare=compare_opsend)(** Internal overview of all the valid operations present in the mempool.
Structural invariants:
- [opset] and [ophmap] contain the same operations.
- [minop] is the minimum of [opset] (or [None] when [opset] is empty).
- [cardinal] is the cardinal of [opset].
- [total_bytes] is the sum of the byte sizes of all elements in [opset].
Bound invariants:
- [cardinal <= config.max_operations]
- [total_bytes <= config.max_total_bytes] *)typestate={opset:Opset.t;(** Ordered set of valid operations in the mempool. Note that the
operations are ordered by the protocol's [compare_operations]
function, NOT by the size of their bytes. *)ophmap:operationOperation_hash.Map.t;(** Contain the same elements as [opset], indexed by their hash. *)minop:operationoption;(** The smallest operation in [opset] according to the protocol's
[compare_operations] function (not necessarily the one with the
least bytes). This is [None] if and only if [opset] is empty. *)cardinal:int;(** The number of operations in [opset]. *)total_bytes:int;(** The sum of the sizes in bytes of all the operations in [opset]. *)}letempty={opset=Opset.empty;ophmap=Operation_hash.Map.empty;minop=None;cardinal=0;total_bytes=0;}(* Precondition: [op] is present in the [state]. *)letremove_presentstateop=letopset=Opset.removeopstate.opsetinletminop=matchstate.minopwith|None->None(* This is impossible since [op] was in the [state]. *)|Someminop->ifcompare_opsopminop<=0then(* The removed [op] was the minimum. *)Opset.min_eltopsetelsestate.minopin{opset;ophmap=Operation_hash.Map.removeop.hashstate.ophmap;minop;cardinal=state.cardinal-1;total_bytes=state.total_bytes-op.size;}(* Remove [oph] if it is in the [state], otherwise do nothing. *)letremove_operationstateoph=matchOperation_hash.Map.findophstate.ophmapwith|Someop->remove_presentstateop|None->stateletcheck_bound_invariantsstateconfig=state.cardinal<=config.max_operations&&state.total_bytes<=config.max_total_bytes(* Remove the minimal operation until the bound invariants are restored.
Return the updated state and the list of removed operation hashes. *)letenforce_bound_invariantsstateconfig=letrecauxstateremoved=ifcheck_bound_invariantsstateconfigthen(state,removed)else(* Invariants are broken: remove the minimal operation. *)matchstate.minopwith|None->(* Should not happen: the empty set cannot break the invariants. *)(state,removed)|Someminop->aux(remove_presentstateminop)(minop.hash::removed)inauxstate[](* Remove the minimal operation until there is room for [op], and
return the last operation removed this way.
Precondition: the [state] satisfies the invariants but does not
already have room for [op].
We don't need to check the [config.max_operations] bound because
removing one operation is always enough to add [op] without
breaking it.
Note that this can only return [None] when removing all
operations is still not enough to make room for [op] -- ie., when
[op.size > config.max_total_bytes]. *)letrecfind_op_to_overtakeconfigstateop=matchstate.minopwith|None->None|Someminop->ifstate.total_bytes-minop.size+op.size<=config.max_total_bytesthenSomeminopelsefind_op_to_overtakeconfig(remove_presentstateminop)op(* Precondition: [op] is valid (otherwise calling
[Proto.compare_operations] on it may return an error). *)letadd_operationstateconfigop=ifOperation_hash.Map.memop.hashstate.ophmapthenOk(state,[])elseletstate={opset=Opset.addopstate.opset;ophmap=Operation_hash.Map.addop.hashopstate.ophmap;minop=(matchstate.minopwith|None->Someop|Someminop->ifcompare_opsopminop<0thenSomeopelsestate.minop);cardinal=state.cardinal+1;total_bytes=state.total_bytes+op.size;}inletstate,removed=enforce_bound_invariantsstateconfiginifList.mem~equal:Operation_hash.equalop.hashremovedthen(* If the new operation needs to be immediately removed in
order to maintain the mempool bound invariants, then it
should actually be rejected.
We feed to [find_op_to_overtake] the [state] returned by
[enforce_bound_invariants] to avoid handling again the
operations removed by it: we already know that removing
them is not enough to make room for [op]. *)letop_to_overtake=find_op_to_overtakeconfigstateopinErrorop_to_overtakeelseOk(state,removed)endmoduleInternal_for_tests=structmoduletypeT=T_for_testsmoduleMake=Makeend