diff --git a/docs/protocols/alpha.rst b/docs/protocols/alpha.rst index 0e02f084baf00c21e164e9d27617a538e3411e61..c9f0f457469fb86d6998767020ae38a63a98da62 100644 --- a/docs/protocols/alpha.rst +++ b/docs/protocols/alpha.rst @@ -75,6 +75,14 @@ Minor Changes - Relax (pre)endorsements branch condition and allow denunciations of a same endorsement on different branches. (MR :gl:`!7828`) +- Relax (pre)endorsement checks during mempool validation. The mempool + is now able to propagate (pre)endorsements for blocks in the near + past or future, and from close cousin branches. Notably, the + preendorsements that the baker is able to inject as soon as a block + has been validated (without waiting for its full application) can + now be immediately propagated by the mempool, allowing for a much + faster PQC. (MR :gl:`!7815`) + Internal -------- diff --git a/src/proto_alpha/lib_protocol/alpha_context.ml b/src/proto_alpha/lib_protocol/alpha_context.ml index 99ee157fa42adc48fd30a874bf1fae2b4a65cd56..2a83eb96f1328271166e6a870194219db55ed372 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.ml +++ b/src/proto_alpha/lib_protocol/alpha_context.ml @@ -592,17 +592,6 @@ module Consensus = struct let store_endorsement_branch ctxt branch = let ctxt = set_endorsement_branch ctxt branch in Storage.Tenderbake.Endorsement_branch.add ctxt branch - - let load_grand_parent_branch ctxt = - Storage.Tenderbake.Grand_parent_branch.find ctxt >>=? function - | Some grand_parent_branch -> - Raw_context.Consensus.set_grand_parent_branch ctxt grand_parent_branch - |> return - | None -> return ctxt - - let store_grand_parent_branch ctxt branch = - let ctxt = set_grand_parent_branch ctxt branch in - Storage.Tenderbake.Grand_parent_branch.add ctxt branch end let prepare_first_block = Init_storage.prepare_first_block @@ -611,7 +600,6 @@ let prepare ctxt ~level ~predecessor_timestamp ~timestamp = Init_storage.prepare ctxt ~level ~predecessor_timestamp ~timestamp >>=? fun (ctxt, balance_updates, origination_results) -> Consensus.load_endorsement_branch ctxt >>=? fun ctxt -> - Consensus.load_grand_parent_branch ctxt >>=? fun ctxt -> return (ctxt, balance_updates, origination_results) let finalize ?commit_message:message c fitness = diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 28d625f8e15adec7d4967d5ec8613064a2a49c77..b280770604c162ed0a5bd40f440fc08bfd7d6c86 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -5348,12 +5348,6 @@ module Consensus : sig storage and RAM. *) val store_endorsement_branch : context -> Block_hash.t * Block_payload_hash.t -> context Lwt.t - - (** [store_grand_parent_branch context branch] sets the "grand-parent branch" - (see {!Storage.Tenderbake.Grand_parent_branch} to [branch] in both the - disk storage and RAM. *) - val store_grand_parent_branch : - context -> Block_hash.t * Block_payload_hash.t -> context Lwt.t end (** This module re-exports definitions from {!Token}. *) diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index 3c2bf68c3d82c9a62c1243522cf0aee721a6709a..7c281963f85e32f0e20cd77a36adec646c0cc754 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -2184,9 +2184,16 @@ let record_operation (type kind) ctxt hash (operation : kind operation) : | Cons (Manager_operation _, _) -> record_non_consensus_operation_hash ctxt hash +let find_in_slot_map consensus_content slot_map = + match Slot.Map.find consensus_content.slot slot_map with + | Some (consensus_key, power) -> return (consensus_key, power) + | None -> + (* This should not happen: operation validation should have failed. *) + tzfail Faulty_validation_wrong_slot + let record_preendorsement ctxt (mode : mode) (content : consensus_content) : - (context * Kind.preendorsement contents_result_list) tzresult = - let open Result_syntax in + (context * Kind.preendorsement contents_result_list) tzresult Lwt.t = + let open Lwt_result_syntax in let ctxt = match mode with | Full_construction _ -> ( @@ -2195,34 +2202,59 @@ let record_preendorsement ctxt (mode : mode) (content : consensus_content) : | Some _ -> ctxt) | Application _ | Partial_construction _ -> ctxt in - match Slot.Map.find content.slot (Consensus.allowed_preendorsements ctxt) with - | None -> - (* This should not happen: operation validation should have failed. *) - error Faulty_validation_wrong_slot - | Some ({delegate; consensus_pkh; _}, preendorsement_power) -> - let* ctxt = + let mk_preendorsement_result {Consensus_key.delegate; consensus_pkh; _} + preendorsement_power = + Single_result + (Preendorsement_result + { + balance_updates = []; + delegate; + consensus_key = consensus_pkh; + preendorsement_power; + }) + in + match mode with + | Application _ | Full_construction _ -> + let* consensus_key, power = + find_in_slot_map content (Consensus.allowed_preendorsements ctxt) + in + let*? ctxt = Consensus.record_preendorsement ctxt ~initial_slot:content.slot - ~power:preendorsement_power + ~power content.round in return - ( ctxt, - Single_result - (Preendorsement_result - { - balance_updates = []; - delegate; - consensus_key = consensus_pkh; - preendorsement_power; - }) ) - -let is_grandparent_endorsement mode content = - match mode with + (ctxt, mk_preendorsement_result (Consensus_key.pkh consensus_key) power) | Partial_construction {predecessor_level; _} -> - Raw_level.(succ content.level = predecessor_level) - | _ -> false + (* In mempool mode, preendorsements are allowed for various levels + and rounds. We do not record preendorsements because we could get + false-positive conflicts for preendorsements with the same slot + but different levels/rounds. We could record just preendorsements + for the mempool head's level and round (the most usual + preendorsements), but we don't need to, because there is no block + to finalize anyway in this mode. *) + let* ctxt, consensus_key, power = + if Raw_level.(content.level = predecessor_level) then + (* We can use the pre-computed slot map, which contains the + consensus rights at the predecessor level. *) + let* consensus_key, power = + find_in_slot_map content (Consensus.allowed_preendorsements ctxt) + in + return (ctxt, consensus_key, power) + else + (* We retrieve the key directly, and return a fake voting + power of 0 because it doesn't matter for a past or future + preendorsement.*) + let level = Level.from_raw ctxt content.level in + let* ctxt, consensus_key = + Stake_distribution.slot_owner ctxt level content.slot + in + return (ctxt, consensus_key, 0) + in + return + (ctxt, mk_preendorsement_result (Consensus_key.pkh consensus_key) power) let record_endorsement ctxt (mode : mode) (content : consensus_content) : (context * Kind.endorsement contents_result_list) tzresult Lwt.t = @@ -2238,24 +2270,44 @@ let record_endorsement ctxt (mode : mode) (content : consensus_content) : endorsement_power; }) in - if is_grandparent_endorsement mode content then - let level = Level.from_raw ctxt content.level in - let* ctxt, ({delegate; _} as consensus_key) = - Stake_distribution.slot_owner ctxt level content.slot - in - let*? ctxt = Consensus.record_grand_parent_endorsement ctxt delegate in - return (ctxt, mk_endorsement_result (Consensus_key.pkh consensus_key) 0) - else - match Slot.Map.find content.slot (Consensus.allowed_endorsements ctxt) with - | None -> - (* This should not happen: operation validation should have failed. *) - tzfail Faulty_validation_wrong_slot - | Some (consensus_key, power) -> - let*? ctxt = - Consensus.record_endorsement ctxt ~initial_slot:content.slot ~power - in - return - (ctxt, mk_endorsement_result (Consensus_key.pkh consensus_key) power) + match mode with + | Application _ | Full_construction _ -> + let* consensus_key, power = + find_in_slot_map content (Consensus.allowed_endorsements ctxt) + in + let*? ctxt = + Consensus.record_endorsement ctxt ~initial_slot:content.slot ~power + in + return + (ctxt, mk_endorsement_result (Consensus_key.pkh consensus_key) power) + | Partial_construction {predecessor_level; _} -> + (* In mempool mode, endorsements are allowed for various levels + and rounds. We do not record endorsements because we could get + false-positive conflicts for endorsements with the same slot + but different levels/rounds. We could record just endorsements + for the predecessor's level and round (the most usual + endorsements), but we don't need to, because there is no block + to finalize anyway in this mode. *) + let* ctxt, consensus_key, power = + if Raw_level.(content.level = predecessor_level) then + (* We can use the pre-computed slot map, which contains the + consensus rights at the predecessor level. *) + let* consensus_key, power = + find_in_slot_map content (Consensus.allowed_endorsements ctxt) + in + return (ctxt, consensus_key, power) + else + (* We retrieve the key directly, and return a fake voting + power of 0 because it doesn't matter for a past or future + endorsement.*) + let level = Level.from_raw ctxt content.level in + let* ctxt, consensus_key = + Stake_distribution.slot_owner ctxt level content.slot + in + return (ctxt, consensus_key, 0) + in + return + (ctxt, mk_endorsement_result (Consensus_key.pkh consensus_key) power) let apply_manager_contents_list ctxt ~payload_producer chain_id fees_updated_contents_list = @@ -2357,7 +2409,7 @@ let apply_contents_list (type kind) ctxt chain_id (mode : mode) in match contents_list with | Single (Preendorsement consensus_content) -> - record_preendorsement ctxt mode consensus_content |> Lwt.return + record_preendorsement ctxt mode consensus_content | Single (Endorsement consensus_content) -> record_endorsement ctxt mode consensus_content | Single (Dal_attestation op) -> @@ -2845,12 +2897,6 @@ let finalize_application ctxt block_data_contents ~round ~predecessor_hash accessible. This will not be present before the first two blocks of tenderbake. *) let level = Level.current ctxt in - let*! ctxt = - match Consensus.endorsement_branch ctxt with - | Some predecessor_branch -> - Consensus.store_grand_parent_branch ctxt predecessor_branch - | None -> Lwt.return ctxt - in (* We mark the current payload hash as the predecessor one => this will only be accessed by the successor block now. *) let*! ctxt = @@ -3026,6 +3072,10 @@ let finalize_block (application_state : application_state) shell_header_opt = in return (result, receipt) | Partial_construction {predecessor_fitness; _} -> + (* Fake finalization to return a correct type, because there is no + block to finalize in mempool mode. If this changes in the + future, beware that consensus operations are not recorded by + {!record_preendorsement} and {!record_endorsement} in this mode. *) let* voting_period_info = Voting_period.get_rpc_current_info ctxt in let level_info = Level.current ctxt in let result = finalize ctxt predecessor_fitness in diff --git a/src/proto_alpha/lib_protocol/init_storage.ml b/src/proto_alpha/lib_protocol/init_storage.ml index 2e740d10f93ec2ace297751b932c00fad2f9f4a5..cd1924024a486bdbd77f5bb3892b6ed16b36d2cf 100644 --- a/src/proto_alpha/lib_protocol/init_storage.ml +++ b/src/proto_alpha/lib_protocol/init_storage.ml @@ -168,6 +168,7 @@ let prepare_first_block _chain_id ctxt ~typecheck ~level ~timestamp ~predecessor Raw_level_repr.of_int32 level >>?= fun level -> Storage.Tenderbake.First_level_of_protocol.update ctxt level >>=? fun ctxt -> + Storage.Legacy.Grand_parent_branch.remove ctxt >>= fun ctxt -> (* This needs to be kept in the migration code for every protocol, except Genesis. *) Sc_rollup_inbox_storage.add_protocol_migration ctxt >>= fun ctxt -> diff --git a/src/proto_alpha/lib_protocol/main.ml b/src/proto_alpha/lib_protocol/main.ml index ebb66598b1803e50c138e10c5adaa8dcd6dccf3b..bbc00994427c29da811a6f7bf7b5c9741565ccf8 100644 --- a/src/proto_alpha/lib_protocol/main.ml +++ b/src/proto_alpha/lib_protocol/main.ml @@ -156,9 +156,10 @@ let prepare_ctxt ctxt mode ~(predecessor : Block_header.shell_header) = (* During block (full or partial) application or full construction, endorsements must be for [predecessor_level] and preendorsements, if any, for the block's level. In the mempool (partial - construction), only consensus operations for [predecessor_level] - (that is, head's level) are allowed (except for grandparent - endorsements, which are handled differently). *) + construction), both endorsements and preendorsements are expected + to be for [predecessor_level] (that is, head's level) most of the + time, although operations that are one level before or after + [predecessor_level] are also accepted. *) let preendorsement_level = match mode with | Application _ | Partial_validation _ | Construction _ -> @@ -230,16 +231,12 @@ let begin_validation ctxt chain_id mode ~predecessor = block_header_data.contents | Partial_construction _ -> let*? predecessor_round = Fitness.round_from_raw predecessor_fitness in - let*? grandparent_round = - Fitness.predecessor_round_from_raw predecessor_fitness - in return (Validate.begin_partial_construction ctxt chain_id ~predecessor_level - ~predecessor_round - ~grandparent_round) + ~predecessor_round) let validate_operation = Validate.validate_operation @@ -411,15 +408,13 @@ module Mempool = struct ~predecessor:head in let*? predecessor_round = Fitness.round_from_raw head.fitness in - let*? grandparent_round = Fitness.predecessor_round_from_raw head.fitness in return (init ctxt chain_id ~predecessor_level:head_level ~predecessor_round - ~predecessor_hash:head_hash - ~grandparent_round) + ~predecessor_hash:head_hash) end (* Vanity nonce: TBD *) diff --git a/src/proto_alpha/lib_protocol/mempool_validation.ml b/src/proto_alpha/lib_protocol/mempool_validation.ml index 8493f18a7e13f9994ccc55028e453c8b73b2d7f8..e400de8a42bc1d1074ebafcded5dce78b55c5fca 100644 --- a/src/proto_alpha/lib_protocol/mempool_validation.ml +++ b/src/proto_alpha/lib_protocol/mempool_validation.ml @@ -64,15 +64,14 @@ let encoding : t Data_encoding.t = (Operation_hash.Map.encoding (dynamic_size ~kind:`Uint30 Operation.encoding))) -let init ctxt chain_id ~predecessor_level ~predecessor_round ~predecessor_hash - ~grandparent_round : validation_info * t = +let init ctxt chain_id ~predecessor_level ~predecessor_round ~predecessor_hash : + validation_info * t = let {info; operation_state; _} = begin_partial_construction ctxt chain_id ~predecessor_level ~predecessor_round - ~grandparent_round in ( info, {predecessor_hash; operation_state; operations = Operation_hash.Map.empty} diff --git a/src/proto_alpha/lib_protocol/mempool_validation.mli b/src/proto_alpha/lib_protocol/mempool_validation.mli index 6ed036b88545c2155cc00b92934456a720d238d5..dcadef04f5458b4a96597320a59fe01761a85757 100644 --- a/src/proto_alpha/lib_protocol/mempool_validation.mli +++ b/src/proto_alpha/lib_protocol/mempool_validation.mli @@ -131,7 +131,6 @@ val init : predecessor_level:Level.t -> predecessor_round:Round.t -> predecessor_hash:Block_hash.t -> - grandparent_round:Round.t -> validation_info * t (** Adds an operation to a [mempool] if and only if it is valid and diff --git a/src/proto_alpha/lib_protocol/raw_context.ml b/src/proto_alpha/lib_protocol/raw_context.ml index 374d81801ff082498a7cdf3f7ca59603c5955df1..297a17360ce99906e8a7348cf8855404365465b9 100644 --- a/src/proto_alpha/lib_protocol/raw_context.ml +++ b/src/proto_alpha/lib_protocol/raw_context.ml @@ -97,9 +97,6 @@ module Raw_consensus = struct for the lowest slot in the block can be recorded. The map associates to each initial slot the [pkh] associated to this slot with its power. *) - grand_parent_endorsements_seen : Signature.Public_key_hash.Set.t; - (** Record the endorsements already seen for the grand - parent. This only useful for the partial construction mode. *) endorsements_seen : Slot_repr.Set.t; (** Record the endorsements already seen. Only initial slots are indexed. *) preendorsements_seen : Slot_repr.Set.t; @@ -111,7 +108,6 @@ module Raw_consensus = struct (** in block construction mode, record the round of preendorsements included in a block. *) endorsement_branch : (Block_hash.t * Block_payload_hash.t) option; - grand_parent_branch : (Block_hash.t * Block_payload_hash.t) option; } (** Invariant: @@ -129,13 +125,11 @@ module Raw_consensus = struct current_endorsement_power = 0; allowed_endorsements = Slot_repr.Map.empty; allowed_preendorsements = Slot_repr.Map.empty; - grand_parent_endorsements_seen = Signature.Public_key_hash.Set.empty; endorsements_seen = Slot_repr.Set.empty; preendorsements_seen = Slot_repr.Set.empty; locked_round_evidence = None; preendorsements_quorum_round = None; endorsement_branch = None; - grand_parent_branch = None; } type error += Double_inclusion_of_consensus_operation @@ -153,17 +147,6 @@ module Raw_consensus = struct | Double_inclusion_of_consensus_operation -> Some () | _ -> None) (fun () -> Double_inclusion_of_consensus_operation) - let record_grand_parent_endorsement t pkh = - error_when - (Signature.Public_key_hash.Set.mem pkh t.grand_parent_endorsements_seen) - Double_inclusion_of_consensus_operation - >|? fun () -> - { - t with - grand_parent_endorsements_seen = - Signature.Public_key_hash.Set.add pkh t.grand_parent_endorsements_seen; - } - let record_endorsement t ~initial_slot ~power = error_when (Slot_repr.Set.mem initial_slot t.endorsements_seen) @@ -214,13 +197,8 @@ module Raw_consensus = struct let endorsement_branch t = t.endorsement_branch - let grand_parent_branch t = t.grand_parent_branch - let set_endorsement_branch t endorsement_branch = {t with endorsement_branch = Some endorsement_branch} - - let set_grand_parent_branch t grand_parent_branch = - {t with grand_parent_branch = Some grand_parent_branch} end type dal_committee = { @@ -1348,9 +1326,6 @@ module type CONSENSUS = sig allowed_preendorsements:(consensus_pk * int) slot_map -> t - val record_grand_parent_endorsement : - t -> Signature.Public_key_hash.t -> t tzresult - val record_endorsement : t -> initial_slot:slot -> power:int -> t tzresult val record_preendorsement : @@ -1367,10 +1342,6 @@ module type CONSENSUS = sig val set_endorsement_branch : t -> Block_hash.t * Block_payload_hash.t -> t val endorsement_branch : t -> (Block_hash.t * Block_payload_hash.t) option - - val set_grand_parent_branch : t -> Block_hash.t * Block_payload_hash.t -> t - - val grand_parent_branch : t -> (Block_hash.t * Block_payload_hash.t) option end module Consensus : @@ -1411,10 +1382,6 @@ module Consensus : ~allowed_endorsements ~allowed_preendorsements) - let[@inline] record_grand_parent_endorsement ctxt pkh = - update_consensus_with_tzresult ctxt (fun ctxt -> - Raw_consensus.record_grand_parent_endorsement ctxt pkh) - let[@inline] record_preendorsement ctxt ~initial_slot ~power round = update_consensus_with_tzresult ctxt @@ -1438,13 +1405,6 @@ module Consensus : let[@inline] set_endorsement_branch ctxt branch = update_consensus_with ctxt (fun ctxt -> Raw_consensus.set_endorsement_branch ctxt branch) - - let[@inline] grand_parent_branch ctxt = - Raw_consensus.grand_parent_branch ctxt.back.consensus - - let[@inline] set_grand_parent_branch ctxt branch = - update_consensus_with ctxt (fun ctxt -> - Raw_consensus.set_grand_parent_branch ctxt branch) end module Tx_rollup = struct diff --git a/src/proto_alpha/lib_protocol/raw_context.mli b/src/proto_alpha/lib_protocol/raw_context.mli index e8056a82633d51cc32af33b0e2f4ec83b0da6214..ba4398abf9bae8ad45de15d2505f2f1861a98ff0 100644 --- a/src/proto_alpha/lib_protocol/raw_context.mli +++ b/src/proto_alpha/lib_protocol/raw_context.mli @@ -322,12 +322,6 @@ module type CONSENSUS = sig allowed_preendorsements:(consensus_pk * int) slot_map -> t - (** [record_grand_parent_endorsement ctx pkh] records an - grand_parent_endorsement for the current block. This is only - useful for the partial construction mode. *) - val record_grand_parent_endorsement : - t -> Signature.Public_key_hash.t -> t tzresult - (** [record_endorsement ctx ~initial_slot ~power] records an endorsement for the current block. @@ -368,10 +362,6 @@ module type CONSENSUS = sig val set_endorsement_branch : t -> Block_hash.t * Block_payload_hash.t -> t val endorsement_branch : t -> (Block_hash.t * Block_payload_hash.t) option - - val set_grand_parent_branch : t -> Block_hash.t * Block_payload_hash.t -> t - - val grand_parent_branch : t -> (Block_hash.t * Block_payload_hash.t) option end module Consensus : diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index b6a09e116daa73fbd3bbf9f6dad1c3f51fa97604..2e1b31102f2ec7abcd7ad0095c5281635bcfe44f 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -129,13 +129,6 @@ module Tenderbake = struct let name = ["endorsement_branch"] end) (Branch) - - module Grand_parent_branch = - Make_single_data_storage (Registered) (Raw_context) - (struct - let name = ["grand_parent_branch"] - end) - (Branch) end (** Contracts handling *) @@ -2095,3 +2088,12 @@ module Zk_rollup = struct (option Ticket_hash_repr.encoding)) end) end + +module Legacy = struct + module Grand_parent_branch = + Make_single_data_storage (Registered) (Raw_context) + (struct + let name = ["grand_parent_branch"] + end) + (Tenderbake.Branch) +end diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index e1e6f88d7836f7d8e944afbdd399b9f215565319..29bf6cb5335285972bfba1970de37c4d51d208da 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -654,13 +654,6 @@ module Tenderbake : sig Single_data_storage with type value = Block_hash.t * Block_payload_hash.t and type t := Raw_context.t - - (** [Grand_parent_branch] stores a single value composed of the - great-grand parent hash and the grand parent's payload *) - module Grand_parent_branch : - Single_data_storage - with type value = Block_hash.t * Block_payload_hash.t - and type t := Raw_context.t end module Tx_rollup : sig @@ -921,3 +914,12 @@ module Zk_rollup : sig and type key = int64 and type value = Zk_rollup_operation_repr.t * Ticket_hash_repr.t option end + +module Legacy : sig + (** [Grand_parent_branch] stores a single value composed of the + great-grand parent hash and the grand parent's payload *) + module Grand_parent_branch : + Single_data_storage + with type value = Block_hash.t * Block_payload_hash.t + and type t := Raw_context.t +end diff --git a/src/proto_alpha/lib_protocol/test/helpers/consensus_helpers.ml b/src/proto_alpha/lib_protocol/test/helpers/consensus_helpers.ml index cd198517052d54790894a0689b2cc8ea690078cf..56dce872e73d52899150c8612fa9d0a38cc7f74d 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/consensus_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/consensus_helpers.ml @@ -178,10 +178,5 @@ let test_consensus_op_for_next ~genesis ~kind ~next = Incremental.add_operation inc operation >>=? fun inc -> delegate_of_slot ~different_slot:true slot (B b2) >>=? fun delegate -> dorsement ~endorsed_block:b2 ~delegate >>=? fun operation -> - Incremental.add_operation inc operation >>= fun res -> - let error_title = - match next with - | `Level -> "Consensus operation for future level" - | `Round -> "Consensus operation for future round" - in - Assert.proto_error_with_info ~loc:__LOC__ res error_title + Incremental.add_operation inc operation >>=? fun (_ : Incremental.t) -> + return_unit diff --git a/src/proto_alpha/lib_protocol/test/integration/consensus/test_endorsement.ml b/src/proto_alpha/lib_protocol/test/integration/consensus/test_endorsement.ml index a1d12e676c44f88685242058b94d2c832bdfe965..2758d3225de7f4b9c140c19eb7a9b6b7b554d9c0 100644 --- a/src/proto_alpha/lib_protocol/test/integration/consensus/test_endorsement.ml +++ b/src/proto_alpha/lib_protocol/test/integration/consensus/test_endorsement.ml @@ -229,16 +229,19 @@ let error_future_level = function (** Endorsement that is one level in the future (pointing to the same level as the block/mempool containing the endorsement instead of - its predecessor/head). It should fail in all modes. *) + its predecessor/head). It should fail in a block (application or + construction) but succeed in a mempool. *) let test_one_level_in_the_future () = let open Lwt_result_syntax in let* _genesis, predecessor = init_genesis () in let* next_level_block = Block.bake predecessor in - Consensus_helpers.test_consensus_operation_all_modes + Consensus_helpers.test_consensus_operation_all_modes_different_outcomes ~loc:__LOC__ ~endorsed_block:next_level_block ~predecessor - ~error:error_future_level + ~application_error:error_future_level + ~construction_error:error_future_level + ?mempool_error:None Endorsement (** Endorsement that is two levels in the future. It should fail in @@ -263,30 +266,36 @@ let error_old_round = function true | _ -> false -(** Endorsement that is one round too old. It should fail in all modes. *) +(** Endorsement that is one round too old. It should fail in a block + (application or construction) but succeed in a mempool. *) let test_one_round_too_old () = let open Lwt_result_syntax in let* _genesis, b = init_genesis () in let* round0_block = Block.bake b in let* predecessor = Block.bake ~policy:(By_round 1) b in - Consensus_helpers.test_consensus_operation_all_modes + Consensus_helpers.test_consensus_operation_all_modes_different_outcomes ~loc:__LOC__ ~endorsed_block:round0_block ~predecessor - ~error:error_old_round + ~application_error:error_old_round + ~construction_error:error_old_round + ?mempool_error:None Endorsement -(** Endorsement that is many rounds too old. It should fail in all modes. *) +(** Endorsement that is many rounds too old. It should fail in a block + (application or construction) but succeed in a mempool. *) let test_many_rounds_too_old () = let open Lwt_result_syntax in let* _genesis, b = init_genesis () in let* round5_block = Block.bake ~policy:(By_round 5) b in let* predecessor = Block.bake ~policy:(By_round 15) b in - Consensus_helpers.test_consensus_operation_all_modes + Consensus_helpers.test_consensus_operation_all_modes_different_outcomes ~loc:__LOC__ ~endorsed_block:round5_block ~predecessor - ~error:error_old_round + ~application_error:error_old_round + ~construction_error:error_old_round + ?mempool_error:None Endorsement let error_future_round = function @@ -295,49 +304,59 @@ let error_future_round = function true | _ -> false -(** Endorsement that is one round in the future. It should fail in all modes. *) +(** Endorsement that is one round in the future. It should fail in a + block (application or construction) but succeed in a mempool. *) let test_one_round_in_the_future () = let open Lwt_result_syntax in let* _genesis, b = init_genesis () in let* predecessor = Block.bake b in let* round1_block = Block.bake ~policy:(By_round 1) b in - Consensus_helpers.test_consensus_operation_all_modes + Consensus_helpers.test_consensus_operation_all_modes_different_outcomes ~loc:__LOC__ ~endorsed_block:round1_block ~predecessor - ~error:error_future_round + ~application_error:error_future_round + ~construction_error:error_future_round + ?mempool_error:None Endorsement -(** Endorsement that is many rounds in the future. It should fail in - all modes. *) +(** Endorsement that is many rounds in the future. It should fail in a + block (application or construction) but succeed in a mempool. *) let test_many_rounds_future () = let open Lwt_result_syntax in let* _genesis, b = init_genesis () in let* predecessor = Block.bake ~policy:(By_round 5) b in let* round15_block = Block.bake ~policy:(By_round 15) b in - Consensus_helpers.test_consensus_operation_all_modes + Consensus_helpers.test_consensus_operation_all_modes_different_outcomes ~loc:__LOC__ ~endorsed_block:round15_block ~predecessor - ~error:error_future_round + ~application_error:error_future_round + ~construction_error:error_future_round + ?mempool_error:None Endorsement (** {2 Wrong payload hash} *) -(** Endorsement with an incorrect payload hash. *) +(** Endorsement with an incorrect payload hash. It should fail in a + block (application or construction) but succeed in a mempool. *) let test_wrong_payload_hash () = let open Lwt_result_syntax in let* _genesis, endorsed_block = init_genesis () in - Consensus_helpers.test_consensus_operation_all_modes + let error_wrong_payload_hash = function + | Validate_errors.Consensus.Wrong_payload_hash_for_consensus_operation + {kind; _} + when kind = Validate_errors.Consensus.Endorsement -> + true + | _ -> false + in + Consensus_helpers.test_consensus_operation_all_modes_different_outcomes ~loc:__LOC__ ~endorsed_block ~block_payload_hash:Block_payload_hash.zero - ~error:(function - | Validate_errors.Consensus.Wrong_payload_hash_for_consensus_operation - {kind; _} - when kind = Validate_errors.Consensus.Endorsement -> - true - | _ -> false) + ~application_error:error_wrong_payload_hash + ~construction_error:error_wrong_payload_hash + ?mempool_error:None Endorsement (** {1 Conflict tests} @@ -353,7 +372,11 @@ let assert_conflict_error ~loc res = (** Test that endorsements conflict with: - an identical endorsement, and - - an endorsement on the same block with a different branch. *) + - an endorsement on the same block with a different branch. + + In mempool mode, also test that they conflict with an endorsement + on the same level and round but with a different payload hash + (such an endorsement is invalid in application and construction modes). *) let test_conflict () = let open Lwt_result_syntax in let* _genesis, b = init_genesis () in @@ -376,11 +399,16 @@ let test_conflict () = in let* () = assert_mempool_conflict __LOC__ op in let* () = assert_mempool_conflict __LOC__ op_different_branch in + let* op_different_payload_hash = + Op.endorsement ~block_payload_hash:Block_payload_hash.zero b + in + let* () = assert_mempool_conflict __LOC__ op_different_payload_hash in return_unit (** In mempool mode, test that grandparent endorsements conflict with: - - an identical endorsement, and - - an endorsement on the same block with a different branch. + - an identical endorsement, + - an endorsement on the same block with a different branch, and + - an endorsement on the same block with a different payload hash. This test would make no sense in application or construction modes, since grandparent endorsements fail anyway (as can be observed in @@ -393,6 +421,33 @@ let test_grandparent_conflict () = let* op_different_branch = Op.endorsement ~branch:Block_hash.zero grandparent in + let* op_different_payload_hash = + Op.endorsement ~block_payload_hash:Block_payload_hash.zero grandparent + in + let* inc = Incremental.begin_construction ~mempool_mode:true predecessor in + let* inc = Incremental.validate_operation inc op in + let assert_conflict loc tested_op = + Incremental.validate_operation inc tested_op >>= assert_conflict_error ~loc + in + let* () = assert_conflict __LOC__ op in + let* () = assert_conflict __LOC__ op_different_branch in + let* () = assert_conflict __LOC__ op_different_payload_hash in + return_unit + +(** In mempool mode, test that endorsements with the same future level + and same non-zero round conflict. This is not tested in application + and construction modes since such endorsements would be invalid. *) +let test_future_level_conflict () = + let open Lwt_result_syntax in + let* _genesis, predecessor = init_genesis () in + let* future_block = Block.bake ~policy:(By_round 10) predecessor in + let* op = Op.endorsement future_block in + let* op_different_branch = + Op.endorsement ~branch:Block_hash.zero future_block + in + let* op_different_payload_hash = + Op.endorsement ~block_payload_hash:Block_payload_hash.zero future_block + in let* inc = Incremental.begin_construction ~mempool_mode:true predecessor in let* inc = Incremental.validate_operation inc op in let assert_conflict loc tested_op = @@ -400,6 +455,7 @@ let test_grandparent_conflict () = in let* () = assert_conflict __LOC__ op in let* () = assert_conflict __LOC__ op_different_branch in + let* () = assert_conflict __LOC__ op_different_payload_hash in return_unit (** In mempool mode, test that there is no conflict between an @@ -440,20 +496,41 @@ let test_no_conflict_with_preendorsement_block () = let* (_ : Block.t) = bake_both_ops Baking in return_unit -(** In mempool mode, test that there is no conflict between a normal - endorsement (for the predecessor) and a grandparent endorsement, - both for the same slot (here the first slot). There is no similar - test in application and construction modes because grandparent - endorsements are not valid then. *) -let test_no_conflict_with_grandparent () = +(** In mempool mode, test that there is no conflict between + endorsements for the same slot (here the first slot) with various + allowed levels and rounds. + + There are no similar tests in application and construction modes + because valid endorsements always have the same level and round. *) +let test_no_conflict_various_levels_and_rounds () = let open Lwt_result_syntax in - let* _genesis, grandparent = init_genesis () in + let* genesis, grandparent = init_genesis () in let* predecessor = Block.bake grandparent in - let* op_normal = Op.endorsement predecessor in - let* op_grandparent = Op.endorsement grandparent in + let* future_block = Block.bake predecessor in + let* alt_grandparent = Block.bake ~policy:(By_round 1) genesis in + let* alt_predecessor = Block.bake ~policy:(By_round 1) grandparent in + let* alt_future = Block.bake ~policy:(By_round 10) alt_predecessor in let* inc = Incremental.begin_construction ~mempool_mode:true predecessor in - let* inc = Incremental.add_operation inc op_normal in - let* inc = Incremental.add_operation inc op_grandparent in + let add_endorsement inc endorsed_block = + let* (op : packed_operation) = Op.endorsement endorsed_block in + let (Operation_data protocol_data) = op.protocol_data in + let content = + match protocol_data.contents with + | Single (Endorsement content) -> content + | _ -> assert false + in + Format.eprintf + "level: %ld, round: %ld@." + (Raw_level.to_int32 content.level) + (Round.to_int32 content.round) ; + Incremental.add_operation inc op + in + let* inc = add_endorsement inc grandparent in + let* inc = add_endorsement inc predecessor in + let* inc = add_endorsement inc future_block in + let* inc = add_endorsement inc alt_grandparent in + let* inc = add_endorsement inc alt_predecessor in + let* inc = add_endorsement inc alt_future in let* _inc = Incremental.finalize_block inc in return_unit @@ -520,6 +597,7 @@ let tests = (* Conflict tests (some negative, some positive) *) Tztest.tztest "Conflict" `Quick test_conflict; Tztest.tztest "Grandparent conflict" `Quick test_grandparent_conflict; + Tztest.tztest "Future level conflict" `Quick test_future_level_conflict; Tztest.tztest "No conflict with preendorsement (mempool)" `Quick @@ -529,9 +607,9 @@ let tests = `Quick test_no_conflict_with_preendorsement_block; Tztest.tztest - "No conflict with grandparent endorsement" + "No conflict with various levels and rounds" `Quick - test_no_conflict_with_grandparent; + test_no_conflict_various_levels_and_rounds; (* Consensus threshold tests (one positive and one negative) *) Tztest.tztest "sufficient endorsement threshold" diff --git a/src/proto_alpha/lib_protocol/test/integration/consensus/test_preendorsement.ml b/src/proto_alpha/lib_protocol/test/integration/consensus/test_preendorsement.ml index 40b0695d7b54f4a6b3553bbfc3be88daac4085ba..88a60cead11f5a13f141d302aa91aad3dd710ff2 100644 --- a/src/proto_alpha/lib_protocol/test/integration/consensus/test_preendorsement.ml +++ b/src/proto_alpha/lib_protocol/test/integration/consensus/test_preendorsement.ml @@ -74,7 +74,8 @@ let test_consensus_operation_preendorsement_for_future_level () = (** Consensus operation for old level : apply a preendorsement with a level in the past *) let test_consensus_operation_preendorsement_for_old_level () = - init_genesis () >>=? fun (_genesis, pred) -> + init_genesis () >>=? fun (_genesis, grandparent) -> + Block.bake grandparent >>=? fun pred -> let raw_level = Raw_level.of_int32 (Int32.of_int 0) in let level = match raw_level with Ok l -> l | Error _ -> assert false in Consensus_helpers.test_consensus_operation @@ -97,11 +98,6 @@ let test_consensus_operation_preendorsement_for_future_round () = ~loc:__LOC__ ~endorsed_block:pred ~round - ~error:(function - | Validate_errors.Consensus.Consensus_operation_for_future_round {kind; _} - when kind = Validate_errors.Consensus.Preendorsement -> - true - | _ -> false) Preendorsement Mempool @@ -113,11 +109,6 @@ let test_consensus_operation_preendorsement_for_old_round () = ~loc:__LOC__ ~endorsed_block:pred ~round - ~error:(function - | Validate_errors.Consensus.Consensus_operation_for_old_round {kind; _} - when kind = Validate_errors.Consensus.Preendorsement -> - true - | _ -> false) Preendorsement Mempool @@ -128,12 +119,6 @@ let test_consensus_operation_preendorsement_on_competing_proposal () = ~loc:__LOC__ ~endorsed_block:pred ~block_payload_hash:Block_payload_hash.zero - ~error:(function - | Validate_errors.Consensus.Wrong_payload_hash_for_consensus_operation - {kind; _} - when kind = Validate_errors.Consensus.Preendorsement -> - true - | _ -> false) Preendorsement Mempool diff --git a/src/proto_alpha/lib_protocol/test/integration/validate/test_mempool.ml b/src/proto_alpha/lib_protocol/test/integration/validate/test_mempool.ml index 8174a9130213d15c62c5028ae2e21c9ed543bb2a..b4a3ca83dd5f82bb0898dd1b2b831894bf0cb5d3 100644 --- a/src/proto_alpha/lib_protocol/test/integration/validate/test_mempool.ml +++ b/src/proto_alpha/lib_protocol/test/integration/validate/test_mempool.ml @@ -47,8 +47,7 @@ let extract_values ctxt (b : Block.t) = in let predecessor_round = Fitness.round fitness in let predecessor_hash = b.header.shell.predecessor in - let grandparent_round = Fitness.predecessor_round fitness in - (predecessor_level, predecessor_round, predecessor_hash, grandparent_round) + (predecessor_level, predecessor_round, predecessor_hash) let op_with_hash op = (Operation.hash_packed op, op) @@ -102,8 +101,7 @@ let test_simple () = let+ incr = Incremental.begin_construction block in Incremental.alpha_ctxt incr in - let predecessor_level, predecessor_round, predecessor_hash, grandparent_round - = + let predecessor_level, predecessor_round, predecessor_hash = extract_values ctxt block in let vs, mempool = @@ -113,7 +111,6 @@ let test_simple () = ~predecessor_level ~predecessor_round ~predecessor_hash - ~grandparent_round in let* op1 = Op.transaction (B block) c1 c2 Tez.one_cent in let op1 = op_with_hash op1 in @@ -137,8 +134,7 @@ let test_imcompatible_mempool () = let+ incr = Incremental.begin_construction block in Incremental.alpha_ctxt incr in - let predecessor_level, predecessor_round, predecessor_hash, grandparent_round - = + let predecessor_level, predecessor_round, predecessor_hash = extract_values ctxt block in let (_vs : Mempool.validation_info), mempool1 = @@ -148,7 +144,6 @@ let test_imcompatible_mempool () = ~predecessor_level ~predecessor_round ~predecessor_hash - ~grandparent_round in (* Create a second mempool on a different block *) let* block2 = Block.bake block in @@ -156,8 +151,7 @@ let test_imcompatible_mempool () = let+ incr = Incremental.begin_construction block2 in Incremental.alpha_ctxt incr in - let predecessor_level, predecessor_round, predecessor_hash2, grandparent_round - = + let predecessor_level, predecessor_round, predecessor_hash2 = extract_values ctxt2 block2 in let (_vs : Mempool.validation_info), mempool2 = @@ -167,7 +161,6 @@ let test_imcompatible_mempool () = ~predecessor_level ~predecessor_round ~predecessor_hash:predecessor_hash2 - ~grandparent_round in let () = match Mempool.merge mempool1 mempool2 with @@ -188,8 +181,7 @@ let test_merge () = let+ incr = Incremental.begin_construction block in Incremental.alpha_ctxt incr in - let predecessor_level, predecessor_round, predecessor_hash, grandparent_round - = + let predecessor_level, predecessor_round, predecessor_hash = extract_values ctxt block in let vs, mempool_i = @@ -199,7 +191,6 @@ let test_merge () = ~predecessor_level ~predecessor_round ~predecessor_hash - ~grandparent_round in (* Build two mempool with a conflicting operation and check that the merge fails and succeeds when a conflict handler is provided *) @@ -283,8 +274,7 @@ let test_add_invalid_operation () = let+ incr = Incremental.begin_construction block in Incremental.alpha_ctxt incr in - let predecessor_level, predecessor_round, predecessor_hash, grandparent_round - = + let predecessor_level, predecessor_round, predecessor_hash = extract_values ctxt block in let vs, mempool_i = @@ -294,7 +284,6 @@ let test_add_invalid_operation () = ~predecessor_level ~predecessor_round ~predecessor_hash - ~grandparent_round in let* op1 = Op.transaction (B block) c1 c1 ~gas_limit:Zero Tez.one_cent in let op1 = op_with_hash op1 in @@ -311,8 +300,7 @@ let test_add_and_replace () = let+ incr = Incremental.begin_construction block in Incremental.alpha_ctxt incr in - let predecessor_level, predecessor_round, predecessor_hash, grandparent_round - = + let predecessor_level, predecessor_round, predecessor_hash = extract_values ctxt block in let info, mempool_i = @@ -322,7 +310,6 @@ let test_add_and_replace () = ~predecessor_level ~predecessor_round ~predecessor_hash - ~grandparent_round in (* Try adding a conflicting operation using both handler strategy *) let* op1 = Op.transaction (B block) c1 c2 Tez.one_cent in @@ -366,8 +353,7 @@ let test_remove_operation () = let+ incr = Incremental.begin_construction block in Incremental.alpha_ctxt incr in - let predecessor_level, predecessor_round, predecessor_hash, grandparent_round - = + let predecessor_level, predecessor_round, predecessor_hash = extract_values ctxt block in let info, mempool_i = @@ -377,7 +363,6 @@ let test_remove_operation () = ~predecessor_level ~predecessor_round ~predecessor_hash - ~grandparent_round in let* op1 = Op.transaction (B block) c1 c2 Tez.one_cent in let op1 = op_with_hash op1 in diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index 10515348a5b82bc56a2a0fb9710e66c76e877406..b612393f3aa1a503133112e4812fca94be0ca2c2 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -41,90 +41,63 @@ let init_consensus_info ctxt (predecessor_level, predecessor_round) = endorsement_slot_map = Consensus.allowed_endorsements ctxt; } -module Consensus_content_map = Map.Make (struct - type t = consensus_content - - let compare {slot; level; round; block_payload_hash} - { - slot = slot'; - level = level'; - round = round'; - block_payload_hash = block_payload_hash'; - } = - Compare.or_else (Raw_level.compare level level') @@ fun () -> - Compare.or_else (Slot.compare slot slot') @@ fun () -> - Compare.or_else (Round.compare round round') @@ fun () -> - Compare.or_else - (Block_payload_hash.compare block_payload_hash block_payload_hash') - @@ fun () -> 0 +(** Map used to detect consensus operation conflicts. Each delegate + may (pre)endorse at most once for each level and round, so two + endorsements (resp. two preendorsements) conflict when they have + the same slot, level, and round. + + Note that when validating a block, all endorsements (resp. all + preendorsements) must have the same level and round anyway, so only + the slot is relevant. Taking the level and round into account is + useful in mempool mode, because we want to be able to accept and + propagate consensus operations for multiple close + past/future/cousin blocks. *) +module Consensus_conflict_map = Map.Make (struct + type t = Slot.t * Raw_level.t * Round.t + + let compare (slot1, level1, round1) (slot2, level2, round2) = + Compare.or_else (Raw_level.compare level1 level2) @@ fun () -> + Compare.or_else (Slot.compare slot1 slot2) @@ fun () -> + Round.compare round1 round2 end) type consensus_state = { - (* The [predecessor_level] is needed here, despite being also in - {!consensus_info}, because it allows to identify grandparent - endorsements in [check_endorsement_conflict] where we only have - access to the [consensus_state]. *) - predecessor_level : Raw_level.t; - preendorsements_seen : Operation_hash.t Slot.Map.t; - endorsements_seen : Operation_hash.t Slot.Map.t; - grandparent_endorsements_seen : Operation_hash.t Slot.Map.t; + preendorsements_seen : Operation_hash.t Consensus_conflict_map.t; + endorsements_seen : Operation_hash.t Consensus_conflict_map.t; dal_attestation_seen : Operation_hash.t Signature.Public_key_hash.Map.t; } -let slot_map_encoding element_encoding = +let consensus_conflict_map_encoding = let open Data_encoding in conv - (fun slot_map -> Slot.Map.bindings slot_map) - (fun l -> Slot.Map.(List.fold_left (fun m (k, v) -> add k v m) empty l)) - (list (tup2 Slot.encoding element_encoding)) + (fun map -> Consensus_conflict_map.bindings map) + (fun l -> + Consensus_conflict_map.( + List.fold_left (fun m (k, v) -> add k v m) empty l)) + (list + (tup2 + (tup3 Slot.encoding Raw_level.encoding Round.encoding) + Operation_hash.encoding)) let consensus_state_encoding = let open Data_encoding in def "consensus_state" @@ conv - (fun { - predecessor_level; - preendorsements_seen; - endorsements_seen; - grandparent_endorsements_seen; - dal_attestation_seen; - } -> - ( predecessor_level, - preendorsements_seen, - endorsements_seen, - grandparent_endorsements_seen, - dal_attestation_seen )) - (fun ( predecessor_level, - preendorsements_seen, - endorsements_seen, - grandparent_endorsements_seen, - dal_attestation_seen ) -> - { - predecessor_level; - preendorsements_seen; - endorsements_seen; - grandparent_endorsements_seen; - dal_attestation_seen; - }) - (obj5 - (req "predecessor_level" Raw_level.encoding) - (req - "preendorsements_seen" - (slot_map_encoding Operation_hash.encoding)) - (req "endorsements_seen" (slot_map_encoding Operation_hash.encoding)) - (req - "grandparent_endorsements_seen" - (slot_map_encoding Operation_hash.encoding)) + (fun {preendorsements_seen; endorsements_seen; dal_attestation_seen} -> + (preendorsements_seen, endorsements_seen, dal_attestation_seen)) + (fun (preendorsements_seen, endorsements_seen, dal_attestation_seen) -> + {preendorsements_seen; endorsements_seen; dal_attestation_seen}) + (obj3 + (req "preendorsements_seen" consensus_conflict_map_encoding) + (req "endorsements_seen" consensus_conflict_map_encoding) (req "dal_attestation_seen" (Signature.Public_key_hash.Map.encoding Operation_hash.encoding))) -let init_consensus_state ~predecessor_level = +let empty_consensus_state = { - predecessor_level; - preendorsements_seen = Slot.Map.empty; - endorsements_seen = Slot.Map.empty; - grandparent_endorsements_seen = Slot.Map.empty; + preendorsements_seen = Consensus_conflict_map.empty; + endorsements_seen = Consensus_conflict_map.empty; dal_attestation_seen = Signature.Public_key_hash.Map.empty; } @@ -339,14 +312,6 @@ type construction_info = { header_contents : Block_header.contents; } -(** Information needed to validate grandparent endorsements in - [Mempool] mode. *) -type grandparent = { - round : Round.t; - hash : Block_hash.t; - payload_hash : Block_payload_hash.t; -} - (** Circumstances in which operations are validated, and corresponding specific information. @@ -364,13 +329,10 @@ type mode = consensus operations are validated in this mode. *) | Construction of construction_info (** Used for the construction of a new block. *) - | Mempool of grandparent option + | Mempool (** Used by the mempool ({!module:Mempool_validation}) and by the [Partial_construction] mode in {!module:Main}, which may itself be - used by RPCs or by another mempool implementation. - - If the option is [None], it means that there cannot be any - grandparent endorsements. *) + used by RPCs or by another mempool implementation. *) (** {2 Definition and initialization of [info] and [state]} *) @@ -444,9 +406,9 @@ let empty_voting_state = ballots_seen = Signature.Public_key_hash.Map.empty; } -let init_operation_conflict_state ~predecessor_level = +let empty_operation_conflict_state = { - consensus_state = init_consensus_state ~predecessor_level; + consensus_state = empty_consensus_state; voting_state = empty_voting_state; anonymous_state = empty_anonymous_state; manager_state = empty_manager_state; @@ -477,9 +439,9 @@ module Consensus = struct Tez.(frozen_deposits.current_amount > zero) (Zero_frozen_deposits delegate_pkh) - let get_delegate_details slot_map kind consensus_content = + let get_delegate_details slot_map kind slot = Result.of_option - (Slot.Map.find consensus_content.slot slot_map) + (Slot.Map.find slot slot_map) ~error:(trace_of_error (Wrong_slot_used_for_consensus_operation {kind})) (** When validating a block (ie. in [Application], @@ -512,31 +474,43 @@ module Consensus = struct (Block_payload_hash.equal expected provided) (Wrong_payload_hash_for_consensus_operation {kind; expected; provided}) - (** Check the preendorsement features for both [Application] and - [Partial_validation] modes. *) - let check_preendorsement_content_preexisting_block vi block_info - {level; round; block_payload_hash; _} = - let open Result_syntax in - let* locked_round = + (** Preendorsement checks for both [Application] and + [Partial_validation] modes. + + Return the slot owner's consensus key and voting power. *) + let check_preexisting_block_preendorsement vi consensus_info block_info + {level; round; block_payload_hash = bph; slot} = + let open Lwt_result_syntax in + let*? locked_round = match block_info.locked_round with - | Some locked_round -> return locked_round + | Some locked_round -> ok locked_round | None -> (* A preexisting block whose fitness has no locked round should contain no preendorsements. *) error Unexpected_preendorsement_in_block in let kind = Preendorsement in - let* () = check_round_before_block ~block_round:block_info.round round in - let* () = check_level kind vi.current_level.level level in - let* () = check_round kind locked_round round in + let*? () = check_round_before_block ~block_round:block_info.round round in + let*? () = check_level kind vi.current_level.level level in + let*? () = check_round kind locked_round round in let expected_payload_hash = block_info.header_contents.payload_hash in - check_payload_hash kind expected_payload_hash block_payload_hash + let*? () = check_payload_hash kind expected_payload_hash bph in + let*? consensus_key, voting_power = + get_delegate_details consensus_info.preendorsement_slot_map kind slot + in + let* () = + check_frozen_deposits_are_positive vi.ctxt consensus_key.delegate + in + return (consensus_key, voting_power) - let check_preendorsement_content_construction vi cons_info - {level; round; block_payload_hash; _} = - let open Result_syntax in + (** Preendorsement checks for Construction mode. + + Return the slot owner's consensus key and voting power. *) + let check_constructed_block_preendorsement vi consensus_info cons_info + {level; round; block_payload_hash = bph; slot} = + let open Lwt_result_syntax in let expected_payload_hash = cons_info.header_contents.payload_hash in - let* () = + let*? () = (* When the proposal is fresh, a fake [payload_hash] of [zero] has been provided. In this case, the block should not contain any preendorsements. *) @@ -545,27 +519,77 @@ module Consensus = struct Unexpected_preendorsement_in_block in let kind = Preendorsement in - let* () = check_round_before_block ~block_round:cons_info.round round in - let* () = check_level kind vi.current_level.level level in + let*? () = check_round_before_block ~block_round:cons_info.round round in + let*? () = check_level kind vi.current_level.level level in (* We cannot check the exact round here in construction mode, because there is no preexisting fitness to provide the locked_round. We do however check that all preendorments have the same round in [check_construction_preendorsement_round_consistency] further below. *) - check_payload_hash kind expected_payload_hash block_payload_hash - - let check_preendorsement_content_mempool vi (consensus_info : consensus_info) - {level; round; block_payload_hash; _} = - let open Result_syntax in - let kind = Preendorsement in - match Consensus.endorsement_branch vi.ctxt with - | None -> - let expected = consensus_info.predecessor_level in - let provided = level in + let*? () = check_payload_hash kind expected_payload_hash bph in + let*? consensus_key, voting_power = + get_delegate_details consensus_info.preendorsement_slot_map kind slot + in + let* () = + check_frozen_deposits_are_positive vi.ctxt consensus_key.delegate + in + return (consensus_key, voting_power) + + (** Preendorsement/endorsement checks for Mempool mode. + + We want this mode to be very permissive, to allow the mempool to + accept and propagate consensus operations even if they point to a + block which is not known to the mempool (e.g. because the block + has just been validated and the mempool has not had time to + switch its head to it yet, or because the block belongs to a + cousin branch). Therefore, we do not check the round nor the + payload, which may correspond to blocks that we do not know of + yet. As to the level, we only require it to be the + [predecessor_level] (aka the level of the mempool's head) plus or + minus one, that is: + [predecessor_level - 1 <= op_level <= predecessor_level + 1] + (note that [predecessor_level + 1] is also known as [current_level]). + + Return the slot owner's consensus key and voting power (the + latter may be fake because it doesn't matter in Mempool mode, but + it is included to mirror the check_..._block_preendorsement + functions). *) + let check_mempool_consensus vi consensus_info kind {level; slot; _} = + let open Lwt_result_syntax in + let*? () = + if Raw_level.(succ level < consensus_info.predecessor_level) then + let expected = consensus_info.predecessor_level and provided = level in + error (Consensus_operation_for_old_level {kind; expected; provided}) + else if Raw_level.(level > vi.current_level.level) then + let expected = consensus_info.predecessor_level and provided = level in error (Consensus_operation_for_future_level {kind; expected; provided}) - | Some (_, expected_payload_hash) -> - let* () = check_level kind consensus_info.predecessor_level level in - let* () = check_round kind consensus_info.predecessor_round round in - check_payload_hash kind expected_payload_hash block_payload_hash + else ok_unit + in + if Raw_level.(level = consensus_info.predecessor_level) then + (* The operation points to the mempool head's level, which is + the level for which slot maps have been pre-computed. *) + let slot_map = + match kind with + | Preendorsement -> consensus_info.preendorsement_slot_map + | Endorsement -> consensus_info.endorsement_slot_map + | Dal_attestation -> assert false + in + Lwt.return (get_delegate_details slot_map kind slot) + else + (* We don't have a pre-computed slot map for the operation's + level, so we retrieve the key directly from the context. We + return a fake voting power since it won't be used anyway in + Mempool mode. *) + let* (_ctxt : t), consensus_key = + Stake_distribution.slot_owner + vi.ctxt + (Level.from_raw vi.ctxt level) + slot + in + return (consensus_key, 0 (* Fake voting power *)) + (* We do not check that the frozen deposits are positive because this + only needs to be true in the context of a block that actually + contains the operation, which may not be the same as the current + mempool's context. *) let check_preendorsement vi ~check_signature (operation : Kind.preendorsement operation) = @@ -578,33 +602,27 @@ module Consensus = struct let (Single (Preendorsement consensus_content)) = operation.protocol_data.contents in - let*? () = + let* consensus_key, voting_power = match vi.mode with | Application block_info | Partial_validation block_info -> - check_preendorsement_content_preexisting_block + check_preexisting_block_preendorsement vi + consensus_info block_info consensus_content | Construction construction_info -> - check_preendorsement_content_construction + check_constructed_block_preendorsement vi + consensus_info construction_info consensus_content - | Mempool _ -> - check_preendorsement_content_mempool + | Mempool -> + check_mempool_consensus vi consensus_info + Preendorsement consensus_content in - let*? consensus_key, voting_power = - get_delegate_details - consensus_info.preendorsement_slot_map - Preendorsement - consensus_content - in - let* () = - check_frozen_deposits_are_positive vi.ctxt consensus_key.delegate - in let*? () = if check_signature then Operation.check_signature @@ -617,12 +635,12 @@ module Consensus = struct let check_preendorsement_conflict vs oph (op : Kind.preendorsement operation) = - let (Single (Preendorsement consensus_content)) = + let (Single (Preendorsement {slot; level; round; _})) = op.protocol_data.contents in match - Slot.Map.find_opt - consensus_content.slot + Consensus_conflict_map.find_opt + (slot, level, round) vs.consensus_state.preendorsements_seen with | Some existing -> @@ -637,12 +655,12 @@ module Consensus = struct Conflicting_consensus_operation {kind = Preendorsement; conflict}) let add_preendorsement vs oph (op : Kind.preendorsement operation) = - let (Single (Preendorsement consensus_content)) = + let (Single (Preendorsement {slot; level; round; _})) = op.protocol_data.contents in let preendorsements_seen = - Slot.Map.add - consensus_content.slot + Consensus_conflict_map.add + (slot, level, round) oph vs.consensus_state.preendorsements_seen in @@ -652,7 +670,7 @@ module Consensus = struct (consensus_content : consensus_content) voting_power = let locked_round_evidence = match mode with - | Mempool _ -> (* The block_state is not relevant in this mode. *) None + | Mempool -> (* The block_state is not relevant in this mode. *) None | Application _ | Partial_validation _ | Construction _ -> ( match block_state.locked_round_evidence with | None -> Some (consensus_content.round, voting_power) @@ -671,185 +689,45 @@ module Consensus = struct let remove_preendorsement vs (operation : Kind.preendorsement operation) = (* As we are in mempool mode, we do not update [locked_round_evidence]. *) - let (Single (Preendorsement consensus_content)) = + let (Single (Preendorsement {slot; level; round; _})) = operation.protocol_data.contents in let preendorsements_seen = - Slot.Map.remove - consensus_content.slot + Consensus_conflict_map.remove + (slot, level, round) vs.consensus_state.preendorsements_seen in {vs with consensus_state = {vs.consensus_state with preendorsements_seen}} - (** Validate an endorsement pointing to the grandparent block. This - function will only be called in [Mempool] mode. *) - let check_grandparent_endorsement vi ~check_signature grandparent - (operation : _ operation) {level; round; block_payload_hash = bph; slot} = - let open Lwt_result_syntax in - let* (_ctxt : t), consensus_key = - Stake_distribution.slot_owner vi.ctxt (Level.from_raw vi.ctxt level) slot - in - let kind = Grandparent_endorsement in - (* This function is only called on endorsements whose level is the - grandparent's, so there is no need to check the level. *) - let*? () = check_round kind grandparent.round round in - let*? () = check_payload_hash kind grandparent.payload_hash bph in - let*? () = - if check_signature then - Operation.check_signature - consensus_key.consensus_pk - vi.chain_id - operation - else ok_unit - in - return_unit - - let add_grandparent_endorsement vs oph (consensus_content : consensus_content) - = - { - vs with - consensus_state = - { - vs.consensus_state with - grandparent_endorsements_seen = - Slot.Map.add - consensus_content.slot - oph - vs.consensus_state.grandparent_endorsements_seen; - }; - } - - let check_grandparent_endorsement_conflict vs oph - (consensus_content : consensus_content) = - match - Slot.Map.find_opt - consensus_content.slot - vs.consensus_state.grandparent_endorsements_seen - with - | None -> ok_unit - | Some existing -> - Error (Operation_conflict {existing; new_operation = oph}) - - let remove_grandparent_endorsement vs (consensus_content : consensus_content) - = - let grandparent_endorsements_seen = - Slot.Map.remove - consensus_content.slot - vs.consensus_state.grandparent_endorsements_seen - in - { - vs with - consensus_state = {vs.consensus_state with grandparent_endorsements_seen}; - } - - type endorsement_kind = Grandparent_endorsement | Normal_endorsement of int - - (** Retrieve the expected branch and payload_hash for endorsements - from the context. + (** Endorsement checks for all modes that involve a block: + Application, Partial_validation, and Construction. - [Consensus.endorsement_branch] only returns [None] when the - predecessor is the protocol activation block, which is always - considered final and should not be endorsed. In that case, return - an error depending on the mode. *) - let expected_payload_hash vi (consensus_info : consensus_info) op_level = - match Consensus.endorsement_branch vi.ctxt with - | Some branch_and_payload_hash -> ok branch_and_payload_hash - | None -> - error - (match vi.mode with - | Application _ | Partial_validation _ | Construction _ -> - (* The block should not contain any endorsements. The - validation of the endorsement fails, and so will the - validation of the whole block. *) - Unexpected_endorsement_in_block - | Mempool _ -> - (* The block that will be built on top on the mempool head - should contain no endorsements, and this is not a - grandparent endorsement (otherwise - [check_normal_endorsement] would not have been called). It - is probably an early endorsement for the next level, hence - the error (and even if this is not the case, hopefully the - levels recorded in the error will provide a hint to what - happened). *) - Consensus_operation_for_future_level - { - kind = Endorsement; - expected = consensus_info.predecessor_level; - provided = op_level; - }) - - (** Validate an endorsement pointing to the predecessor, aka a - "normal" endorsement. Only this kind of endorsement may be found - during block validation or construction (ie. [Application], - [Partial_validation], or [Construction] modes). *) - let check_normal_endorsement vi consensus_info ~check_signature - (operation : Kind.endorsement operation) = + Return the slot owner's consensus key and voting power. *) + let check_block_endorsement vi consensus_info + {level; round; block_payload_hash = bph; slot} = let open Lwt_result_syntax in - let (Single (Endorsement consensus_content)) = - operation.protocol_data.contents - in - let {level; round; block_payload_hash = bph; _} = consensus_content in - let*? _expected_branch, expected_payload_hash = - expected_payload_hash vi consensus_info level + let*? expected_payload_hash = + match Consensus.endorsement_branch vi.ctxt with + | Some ((_branch : Block_hash.t), payload_hash) -> ok payload_hash + | None -> + (* [Consensus.endorsement_branch] only returns [None] when the + predecessor is the block that activates the first protocol + of the Tenderbake family; this block should not be + endorsed. This can only happen in tests and test + networks. *) + error Unexpected_endorsement_in_block in let kind = Endorsement in let*? () = check_level kind consensus_info.predecessor_level level in let*? () = check_round kind consensus_info.predecessor_round round in let*? () = check_payload_hash kind expected_payload_hash bph in let*? consensus_key, voting_power = - get_delegate_details - consensus_info.endorsement_slot_map - kind - consensus_content + get_delegate_details consensus_info.endorsement_slot_map kind slot in let* () = check_frozen_deposits_are_positive vi.ctxt consensus_key.delegate in - let*? () = - if check_signature then - Operation.check_signature - consensus_key.consensus_pk - vi.chain_id - operation - else ok_unit - in - return voting_power - - let check_normal_endorsement_conflict vs oph - (consensus_content : consensus_content) = - match - Slot.Map.find_opt - consensus_content.slot - vs.consensus_state.endorsements_seen - with - | None -> ok_unit - | Some existing -> - Error (Operation_conflict {existing; new_operation = oph}) - - let add_normal_endorsement vs oph (consensus_content : consensus_content) = - { - vs with - consensus_state = - { - vs.consensus_state with - endorsements_seen = - Slot.Map.add - consensus_content.slot - oph - vs.consensus_state.endorsements_seen; - }; - } - - (* Hypothesis: this function will only be called in mempool mode *) - let remove_normal_endorsement vs (consensus_content : consensus_content) = - (* We do not remove the endorsement power because it is not - relevant for the mempool mode. *) - let endorsements_seen = - Slot.Map.remove - consensus_content.slot - vs.consensus_state.endorsements_seen - in - {vs with consensus_state = {vs.consensus_state with endorsements_seen}} + return (consensus_key, voting_power) let check_endorsement vi ~check_signature (operation : Kind.endorsement operation) = @@ -862,37 +740,40 @@ module Consensus = struct let (Single (Endorsement consensus_content)) = operation.protocol_data.contents in - match vi.mode with - | Mempool (Some grandparent) - when Raw_level.( - succ consensus_content.level = consensus_info.predecessor_level) -> - let* () = - check_grandparent_endorsement + let* consensus_key, voting_power = + match vi.mode with + | Application _ | Partial_validation _ | Construction _ -> + check_block_endorsement vi consensus_info consensus_content + | Mempool -> + check_mempool_consensus vi - ~check_signature - grandparent - operation - (consensus_content : consensus_content) - in - return Grandparent_endorsement - | _ -> - let* voting_power = - check_normal_endorsement vi consensus_info ~check_signature operation - in - return (Normal_endorsement voting_power) - - let is_normal_endorsement_assuming_valid vs - (consensus_content : consensus_content) = - Raw_level.equal vs.consensus_state.predecessor_level consensus_content.level + consensus_info + Endorsement + consensus_content + in + let*? () = + if check_signature then + Operation.check_signature + consensus_key.consensus_pk + vi.chain_id + operation + else ok_unit + in + return voting_power let check_endorsement_conflict vs oph (operation : Kind.endorsement operation) = - let (Single (Endorsement consensus_content)) = + let (Single (Endorsement {slot; level; round; _})) = operation.protocol_data.contents in - if is_normal_endorsement_assuming_valid vs consensus_content then - check_normal_endorsement_conflict vs oph consensus_content - else check_grandparent_endorsement_conflict vs oph consensus_content + match + Consensus_conflict_map.find_opt + (slot, level, round) + vs.consensus_state.endorsements_seen + with + | None -> ok_unit + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) let wrap_endorsement_conflict = function | Ok () -> ok_unit @@ -901,28 +782,40 @@ module Consensus = struct Validate_errors.Consensus.( Conflicting_consensus_operation {kind = Endorsement; conflict}) - let add_endorsement vs oph (op : Kind.endorsement operation) endorsement_kind - = - let (Single (Endorsement consensus_content)) = op.protocol_data.contents in - match endorsement_kind with - | Grandparent_endorsement -> - add_grandparent_endorsement vs oph consensus_content - | Normal_endorsement _voting_power -> - add_normal_endorsement vs oph consensus_content - - let may_update_endorsement_power block_state = function - | Grandparent_endorsement -> block_state - | Normal_endorsement voting_power -> + let add_endorsement vs oph (op : Kind.endorsement operation) = + let (Single (Endorsement {slot; level; round; _})) = + op.protocol_data.contents + in + let endorsements_seen = + Consensus_conflict_map.add + (slot, level, round) + oph + vs.consensus_state.endorsements_seen + in + {vs with consensus_state = {vs.consensus_state with endorsements_seen}} + + let may_update_endorsement_power vi block_state voting_power = + match vi.mode with + | Mempool -> (* The block_state is not relevant. *) block_state + | Application _ | Partial_validation _ | Construction _ -> { block_state with endorsement_power = block_state.endorsement_power + voting_power; } - let remove_endorsement vs (op : Kind.endorsement operation) = - let (Single (Endorsement consensus_content)) = op.protocol_data.contents in - if is_normal_endorsement_assuming_valid vs consensus_content then - remove_normal_endorsement vs consensus_content - else remove_grandparent_endorsement vs consensus_content + (* Hypothesis: this function will only be called in mempool mode *) + let remove_endorsement vs (operation : Kind.endorsement operation) = + (* We do not remove the endorsement power because it is not + relevant for the mempool mode. *) + let (Single (Endorsement {slot; level; round; _})) = + operation.protocol_data.contents + in + let endorsements_seen = + Consensus_conflict_map.remove + (slot, level, round) + vs.consensus_state.endorsements_seen + in + {vs with consensus_state = {vs.consensus_state with endorsements_seen}} let check_dal_attestation vi (operation : Kind.dal_attestation operation) = (* DAL/FIXME https://gitlab.com/tezos/tezos/-/issues/3115 @@ -1014,7 +907,7 @@ module Consensus = struct (* Other preendorsements have already been validated: we check that the current operation has the same round as them. *) check_round Preendorsement expected consensus_content.round) - | Application _ | Partial_validation _ | Mempool _ -> return_unit + | Application _ | Partial_validation _ | Mempool -> return_unit let validate_preendorsement ~check_signature info operation_state block_state oph (operation : Kind.preendorsement operation) = @@ -1047,13 +940,13 @@ module Consensus = struct let validate_endorsement ~check_signature info operation_state block_state oph operation = let open Lwt_result_syntax in - let* kind = check_endorsement info ~check_signature operation in + let* power = check_endorsement info ~check_signature operation in let*? () = check_endorsement_conflict operation_state oph operation |> wrap_endorsement_conflict in - let block_state = may_update_endorsement_power block_state kind in - let operation_state = add_endorsement operation_state oph operation kind in + let block_state = may_update_endorsement_power info block_state power in + let operation_state = add_endorsement operation_state oph operation in return {info; operation_state; block_state} end @@ -2219,7 +2112,7 @@ module Manager = struct let may_trace_gas_limit_too_high info = match info.mode with | Application _ | Partial_validation _ | Construction _ -> fun x -> x - | Mempool _ -> + | Mempool -> (* [Gas.check_limit] will only raise a "temporary" error, however when {!validate_operation} is called on a batch in isolation @@ -2436,7 +2329,7 @@ module Manager = struct Gas.Arith.(sub block_state.remaining_block_gas operation_gas_used) in {block_state with remaining_block_gas} - | Mempool _ -> block_state + | Mempool -> block_state let remove_manager_operation (type kind) vs (operation : kind Kind.manager operation) = @@ -2474,15 +2367,7 @@ end let init_validation_state ctxt mode chain_id ~predecessor_level_and_round = let info = init_info ctxt mode chain_id ~predecessor_level_and_round in - let predecessor_level = - match predecessor_level_and_round with - | Some (predecessor_level, _) -> predecessor_level - | None -> - (* Fake predecessor level that will not be used since the - validation of all consensus operations will fail. *) - info.current_level.level - in - let operation_state = init_operation_conflict_state ~predecessor_level in + let operation_state = empty_operation_conflict_state in let block_state = init_block_state info in {info; operation_state; block_state} @@ -2617,17 +2502,11 @@ let begin_full_construction ctxt chain_id ~predecessor_level ~predecessor_round return validation_state let begin_partial_construction ctxt chain_id ~predecessor_level - ~predecessor_round ~grandparent_round = - let grandparent = - Option.map - (fun (hash, payload_hash) -> - {round = grandparent_round; hash; payload_hash}) - (Alpha_context.Consensus.grand_parent_branch ctxt) - in + ~predecessor_round = let validation_state = init_validation_state ctxt - (Mempool grandparent) + Mempool chain_id ~predecessor_level_and_round: (Some (predecessor_level.Level.level, predecessor_round)) @@ -2635,11 +2514,7 @@ let begin_partial_construction ctxt chain_id ~predecessor_level validation_state let begin_no_predecessor_info ctxt chain_id = - init_validation_state - ctxt - (Mempool None) - chain_id - ~predecessor_level_and_round:None + init_validation_state ctxt Mempool chain_id ~predecessor_level_and_round:None let check_operation ?(check_signature = true) info (type kind) (operation : kind operation) : unit tzresult Lwt.t = @@ -2651,7 +2526,7 @@ let check_operation ?(check_signature = true) info (type kind) in return_unit | Single (Endorsement _) -> - let* (_kind : Consensus.endorsement_kind) = + let* (_voting_power : int) = Consensus.check_endorsement info ~check_signature operation in return_unit @@ -2769,20 +2644,8 @@ let add_valid_operation operation_conflict_state oph (type kind) match operation.protocol_data.contents with | Single (Preendorsement _) -> Consensus.add_preendorsement operation_conflict_state oph operation - | Single (Endorsement consensus_content) -> - let endorsement_kind = - if - Consensus.is_normal_endorsement_assuming_valid - operation_conflict_state - consensus_content - then Consensus.Normal_endorsement 0 - else Grandparent_endorsement - in - Consensus.add_endorsement - operation_conflict_state - oph - operation - endorsement_kind + | Single (Endorsement _) -> + Consensus.add_endorsement operation_conflict_state oph operation | Single (Dal_attestation _) -> Consensus.add_dal_attestation operation_conflict_state oph operation | Single (Proposals _) -> @@ -2860,7 +2723,7 @@ let remove_operation operation_conflict_state (type kind) let check_validation_pass_consistency vi vs validation_pass = let open Lwt_result_syntax in match vi.mode with - | Mempool _ | Construction _ -> return vs + | Mempool | Construction _ -> return vs | Application _ | Partial_validation _ -> ( match (vs.last_op_validation_pass, validation_pass) with | None, validation_pass -> @@ -2908,7 +2771,7 @@ let validate_operation ?(check_signature = true) (* Do not validate non-consensus operations in [Partial_validation] mode. *) return {info; operation_state; block_state} - | (Application _ | Partial_validation _ | Construction _ | Mempool _), _ -> ( + | (Application _ | Partial_validation _ | Construction _ | Mempool), _ -> ( match operation.protocol_data.contents with | Single (Preendorsement _) -> Consensus.validate_preendorsement @@ -3162,6 +3025,6 @@ let finalize_block {info; block_state; _} = ~fitness_locked_round:(Option.map fst locked_round_evidence) in return_unit - | Mempool _ -> + | Mempool -> (* There is no block to finalize in mempool mode. *) return_unit diff --git a/src/proto_alpha/lib_protocol/validate.mli b/src/proto_alpha/lib_protocol/validate.mli index 19050496379bc8e815b0ec1cb98aacb6773f30b1..00afeaa8448ca3c8745a1fd531265b8b77eaa368 100644 --- a/src/proto_alpha/lib_protocol/validate.mli +++ b/src/proto_alpha/lib_protocol/validate.mli @@ -239,7 +239,6 @@ val begin_partial_construction : Chain_id.t -> predecessor_level:Level.t -> predecessor_round:Round.t -> - grandparent_round:Round.t -> validation_state (** Similar to [begin_partial_construction] but do not require diff --git a/src/proto_alpha/lib_protocol/validate_errors.ml b/src/proto_alpha/lib_protocol/validate_errors.ml index 0eb8bdee3343bde0533014ec0f3ca6372e4bfacd..b6026458fa9f223623acb746e32c328333493fc5 100644 --- a/src/proto_alpha/lib_protocol/validate_errors.ml +++ b/src/proto_alpha/lib_protocol/validate_errors.ml @@ -72,7 +72,6 @@ module Consensus = struct type consensus_operation_kind = | Preendorsement | Endorsement - | Grandparent_endorsement | Dal_attestation let consensus_operation_kind_encoding = @@ -80,14 +79,12 @@ module Consensus = struct [ ("Preendorsement", Preendorsement); ("Endorsement", Endorsement); - ("Grandparent_endorsement", Grandparent_endorsement); ("Dal_attestation", Dal_attestation); ] let consensus_operation_kind_pp fmt = function | Preendorsement -> Format.fprintf fmt "Preendorsement" | Endorsement -> Format.fprintf fmt "Endorsement" - | Grandparent_endorsement -> Format.fprintf fmt "Grandparent endorsement" | Dal_attestation -> Format.fprintf fmt "Dal_attestation" (** Errors for preendorsements and endorsements. *) @@ -308,7 +305,18 @@ module Consensus = struct (fun (block_round, provided) -> Preendorsement_round_too_high {block_round; provided}) ; register_error_kind - `Permanent + (* TODO: https://gitlab.com/tezos/tezos/-/issues/5061 + + The classification of this error has been changed to Branch + because it is possible for a previously valid operation to + trigger this error during reclassification on a new + head. This could lead to the unwarranted kicking of a peer if + this error were Permanent. + + The classification should be set back to Permanent once the + mempool checks the slot normalization more consistently + across all levels. *) + `Branch ~id:"validate.wrong_slot_for_consensus_operation" ~title:"Wrong slot for consensus operation" ~description:"Wrong slot used for a preendorsement or endorsement." diff --git a/src/proto_alpha/lib_protocol/validate_errors.mli b/src/proto_alpha/lib_protocol/validate_errors.mli index 4d1b3485afb6914a2cad819ac0f50cf48a8d19f5..ec07efbc44cf78466918de8761b9fc312210b64f 100644 --- a/src/proto_alpha/lib_protocol/validate_errors.mli +++ b/src/proto_alpha/lib_protocol/validate_errors.mli @@ -37,7 +37,6 @@ module Consensus : sig type consensus_operation_kind = | Preendorsement | Endorsement - | Grandparent_endorsement | Dal_attestation (** Errors for preendorsements and endorsements. *) diff --git a/tezt/tests/prevalidator.ml b/tezt/tests/prevalidator.ml index c00cb54bf4495e82c50604f712808eaac5b06547..2967a52bbffc174b78a23ff574746d30f521b0b8 100644 --- a/tezt/tests/prevalidator.ml +++ b/tezt/tests/prevalidator.ml @@ -2148,36 +2148,8 @@ let pp_mempool_count fmt outdated unprocessed -(** Matches events which contain an injection request. - For example: - - {[ - { "event": { - "request": { - "request": "inject", - "operation": { - "branch": "BL2FDpiSbzxkXpefiSRCpBHGhZ1kDpEUzWswSCABvGKr3hF6xre", - "data": "6c0002298c03ed7d454a101eb7022bc95f7e5f41ac78940a0280bd3f00e8070000e7670f32038107a59a2b9cfefae36ea21f5aa63c00cf958f834a8d89a88068d7da1209db3c8dc6f5a0c88fb7df0fc8b910f5e100c1179e0862993fd2abadcc47eb4710ad41b68603983559b5fb68bb98499aa1800d" - } - }, - "status": { - "pushed": "2021-05-03T17:16:03.826-00:00", - "treated": 3.0033e-05, - "completed": 0.00190934 - } - }, - "level": "info" - } - ]} - *) -let wait_for_injection node = - let filter json = - match JSON.(json |-> "view" |-> "request" |> as_string_opt) with - | Some s when s = "inject" -> Some s - | Some _ | None -> None - in - let* _ = Node.wait_for node "request_completed_info.v0" filter in - return () +(** Wait for an injection request event. *) +let wait_for_injection = Node.wait_for_request ~request:`Inject (** Matches events which contain an flush request. For example: @@ -2381,64 +2353,27 @@ let get_endorsement_as_bytes client = in Lwt.return (wrapped_bytes, hash) -let wait_for_synch node = - let filter json = - match JSON.(json |-> "view" |-> "request" |> as_string_opt) with - | Some s when s = "notify" -> Some s - | Some _ | None -> None - in - let* _ = Node.wait_for node "request_completed_debug.v0" filter in - return () - let mempool_synchronisation client node = - let waiter = wait_for_synch node in + let waiter = Node.wait_for_request ~request:`Notify node in let* _ = RPC.Client.call client @@ RPC.post_chain_mempool_request_operations () in waiter -(** This test checks that future endorsement are still propagated when - the head is incremented *) +(** This test checks that future endorsements are correctly + propagated, either immediately or when the head is sufficiently + incremented. *) let propagation_future_endorsement = - let step1_msg = - "Step 1: 3 nodes are initialised, chain connected and the protocol is \ - activated." - in - let step2_msg = "Step 2: disconnect the nodes" in - let step3_msg = "Step 3: bake one block on node_1" in - let step4_msg = "Step 4: Endorsement on node_1 injected" in - let step5_msg = - "Step 5: recover hash endorsement and bytes representing the endorsement" - in - let step6_msg = - "Step 6: ban the endorsement on node_1 to ensure it will not be propagated \ - from this node" - in - let step7_msg = "Step 7: Endorsement has been inject on node_2" in - let step8_msg = - "Step 8: Reconnect node_2 and node_3 and synchronise their mempool" - in - let step9_msg = - "Step 9: ensure that endorsement is in node_2 mempool and classified as \ - branch_delayed" - in - let step10_msg = - "Step 10: ensure that endorsement is not in node_3 mempool" - in - let step11_msg = "Step 11: Reconnect node_1 and node_2, new head on node_2" in - let step12_msg = - "Step 12: Synchronise mempool on node_2 and check that endorsement is now \ - applied" - in - let step13_msg = - "Step 13: Synchronise mempool on node_3 and check that endorsement has \ - been propagated" - in Protocol.register_test ~__FILE__ - ~title:"Ensure that future endorsement are propagated" + ~title:"Ensure that future endorsements are propagated" ~tags:["endorsement"; "mempool"; "branch_delayed"] @@ fun protocol -> + (* For this test to be moved to {!Revamped}, we need to update its + auxiliary functions. Notably, {!get_endorsement_as_bytes} should + use the {!Operation} module to build the endorsement bytes. *) + let log_step = Revamped.log_step in + log_step 1 "Initialize 3 nodes, connect them, and activate the protocol." ; let* node_1 = Node.init [Synchronisation_threshold 0; Private_mode] and* node_2 = Node.init @@ -2460,7 +2395,7 @@ let propagation_future_endorsement = and* () = Client.Admin.connect_address client_2 ~peer:node_3 in let* () = Client.activate_protocol_and_wait ~protocol client_1 in let* _ = Node.wait_for_level node_2 1 and* _ = Node.wait_for_level node_3 1 in - Log.info "%s" step1_msg ; + log_step 2 "Disconnect all the nodes from each other." ; let* node_1_id = Node.wait_for_identity node_1 and* node_2_id = Node.wait_for_identity node_2 and* node_3_id = Node.wait_for_identity node_3 in @@ -2468,56 +2403,107 @@ let propagation_future_endorsement = and* () = Client.Admin.kick_peer client_2 ~peer:node_1_id and* () = Client.Admin.kick_peer client_2 ~peer:node_3_id and* () = Client.Admin.kick_peer client_3 ~peer:node_2_id in - Log.info "%s" step2_msg ; + log_step + 3 + "Bake a block on node_1 then inject an endorsement, which is one level in \ + the future from the perspective of nodes 2 and 3. Retrieve the hash and \ + bytes representing this endorsement, called future1 from now on." ; let* () = Node_event_level.bake_wait_log node_1 client_1 in - Log.info "%s" step3_msg ; - let endorser_waiter = wait_for_injection node_1 in + let injection_waiter = wait_for_injection node_1 in let* () = Client.endorse_for client_1 ~force:true ~protocol in - let* () = endorser_waiter in - Log.info "%s" step4_msg ; - let* bytes, hash = get_endorsement_as_bytes client_1 in - Log.info "%s" step5_msg ; - let* _ = - RPC.Client.call client_1 - @@ RPC.post_chain_mempool_ban_operation ~data:(Data (`String hash)) () - in - Log.info "%s" step6_msg ; - let (`Hex bytes) = Hex.of_bytes bytes in - let injection_waiter = wait_for_injection node_2 in - let* _ = - RPC.Client.call client_2 - @@ RPC.post_private_injection_operation (Data (`String bytes)) - in let* () = injection_waiter in - Log.info "%s" step7_msg ; + let* bytes_future1, oph_future1 = get_endorsement_as_bytes client_1 in + log_step + 4 + "Bake another block on node_1 then inject another endorsement, which is \ + two levels in the future from the perspective of nodes 2 and 3. Retrieve \ + the hash and bytes representing this endorsement, called future2." ; + let* () = Node_event_level.bake_wait_log node_1 client_1 in + let injection_waiter2 = wait_for_injection node_1 in + let* () = Client.endorse_for client_1 ~force:true ~protocol in + let* () = injection_waiter2 in + let* bytes_future2, oph_future2 = get_endorsement_as_bytes client_1 in + log_step 5 "Inject both endorsements in node_2." ; + let inject_bytes_in_node_2 bytes = + let (`Hex bytes) = Hex.of_bytes bytes in + let injection_waiter = wait_for_injection node_2 in + let* (_ : JSON.t) = + RPC.Client.call client_2 + @@ RPC.post_private_injection_operation (Data (`String bytes)) + in + injection_waiter + in + let* () = inject_bytes_in_node_2 bytes_future1 in + let* () = inject_bytes_in_node_2 bytes_future2 in + log_step 6 "Reconnect node_2 and node_3, and synchronize their mempools." ; let* () = Client.Admin.trust_address client_2 ~peer:node_3 and* () = Client.Admin.trust_address client_3 ~peer:node_2 in let* () = Client.Admin.connect_address client_2 ~peer:node_3 in - let* _ = mempool_synchronisation client_3 node_3 in - Log.info "%s" step8_msg ; - let* _ = - check_if_op_is_in_mempool - client_2 - ~classification:(Some "branch_delayed") - hash + let* () = mempool_synchronisation client_3 node_3 in + log_step + 7 + "Check that both endorsements are in the mempool of node_2: future1 is \ + branch_delayed for protocols 016 and before, and applied for protocol 017 \ + on; future2 is branch_delayed for all protocols." ; + (* From protocol N on, consensus operations that are one level in + the future are accepted and propagated by the mempool. *) + let accept_one_level_in_future = Protocol.number protocol >= 017 in + let* () = + let classification = + Some (if accept_one_level_in_future then "applied" else "branch_delayed") + in + check_if_op_is_in_mempool client_2 ~classification oph_future1 in - Log.info "%s" step9_msg ; - let* _ = check_if_op_is_in_mempool client_3 ~classification:None hash in - Log.info "%s" step10_msg ; - let* () = Client.Admin.trust_address client_1 ~peer:node_2 - and* () = Client.Admin.trust_address client_2 ~peer:node_1 in - let* () = Client.Admin.connect_address client_1 ~peer:node_2 in - Log.info "%s" step11_msg ; - let* _ = mempool_synchronisation client_2 node_2 in - let* _ = - check_if_op_is_in_mempool client_2 ~classification:(Some "applied") hash + let* () = + let classification = Some "branch_delayed" in + check_if_op_is_in_mempool client_2 ~classification oph_future2 + in + log_step + 8 + "Check that none of the endorsements is in node_3 mempool for protocols \ + 016 and before. For protocol 017 on, only future1 is present (and \ + applied) in node_3 mempool." ; + let* () = + let classification = + if accept_one_level_in_future then Some "applied" else None + in + check_if_op_is_in_mempool client_3 ~classification oph_future1 in - Log.info "%s" step12_msg ; - let* _ = mempool_synchronisation client_3 node_3 in - let* _ = - check_if_op_is_in_mempool client_3 ~classification:(Some "applied") hash + let* () = + check_if_op_is_in_mempool client_3 ~classification:None oph_future2 + in + log_step + 9 + "Bake one block on node_2 and synchronize its mempool. Check that future1 \ + is now applied for all protocols, and future2 is also applied for \ + protocol 017 on." ; + let* () = Node_event_level.bake_wait_log node_2 client_2 in + let* () = mempool_synchronisation client_2 node_2 in + let* () = + let classification = Some "applied" in + check_if_op_is_in_mempool client_2 ~classification oph_future1 + in + let* () = + let classification = + Some (if accept_one_level_in_future then "applied" else "branch_delayed") + in + check_if_op_is_in_mempool client_2 ~classification oph_future2 + in + log_step + 10 + "Synchronize the mempool of node_3. Check that future1 is present and \ + applied for both protocols, and so is future2 for protocol 017 on." ; + let* () = mempool_synchronisation client_3 node_3 in + let* () = + let classification = Some "applied" in + check_if_op_is_in_mempool client_3 ~classification oph_future1 + in + let* () = + let classification = + if accept_one_level_in_future then Some "applied" else None + in + check_if_op_is_in_mempool client_3 ~classification oph_future2 in - Log.info "%s" step13_msg ; unit let check_empty_operation__ddb ddb =