From b5e7806c3f8ce16e7c612e887315e44cb8e3857d Mon Sep 17 00:00:00 2001 From: vbot Date: Wed, 22 Feb 2023 16:25:12 +0100 Subject: [PATCH 1/6] Alpha/Protocol: remove endorsement branch requirement --- .../lib_protocol/test/helpers/op.ml | 9 +++-- .../integration/consensus/test_endorsement.ml | 27 ++++++--------- .../consensus/test_preendorsement.ml | 13 +++++++ .../consensus/test_preendorsement_functor.ml | 21 ------------ src/proto_alpha/lib_protocol/validate.ml | 28 ++++----------- .../lib_protocol/validate_errors.ml | 34 ------------------- .../lib_protocol/validate_errors.mli | 5 --- 7 files changed, 37 insertions(+), 100 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/helpers/op.ml b/src/proto_alpha/lib_protocol/test/helpers/op.ml index 1102b5b42f0f..bb04d5fbb9b6 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/op.ml @@ -43,13 +43,16 @@ let sign ?(watermark = Signature.Generic_operation) sk branch contents = (** Generates the block payload hash based on the hash [pred_hash] of the predecessor block and the hash of non-consensus operations of the current block [b]. *) -let mk_block_payload_hash predecessor_hash payload_round (b : Block.t) = +let mk_block_payload_hash payload_round (b : Block.t) = let ops = Block.Forge.classify_operations b.operations in let non_consensus_operations = List.concat (match List.tl ops with None -> [] | Some l -> l) in let hashes = List.map Operation.hash_packed non_consensus_operations in - Block_payload.hash ~predecessor_hash ~payload_round hashes + Block_payload.hash + ~predecessor_hash:b.header.shell.predecessor + ~payload_round + hashes let mk_consensus_content_signer_and_pred_branch ?delegate ?slot ?level ?round ?block_payload_hash ?pred_branch endorsed_block = @@ -87,7 +90,7 @@ let mk_consensus_content_signer_and_pred_branch ?delegate ?slot ?level ?round in let block_payload_hash = match block_payload_hash with - | None -> mk_block_payload_hash pred_branch round endorsed_block + | None -> mk_block_payload_hash round endorsed_block | Some block_payload_hash -> block_payload_hash in let consensus_content = {slot; level; round; block_payload_hash} in 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 3646a977e447..549f19572291 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 @@ -56,6 +56,13 @@ let inject_the_first_endorsement () = let test_simple_endorsement () = inject_the_first_endorsement () >>=? fun (_, _) -> return_unit +(** Test that the endorsement's branch does not affect its + validity. *) +let test_endorsement_with_arbitrary_branch () = + init_genesis () >>=? fun (_genesis, blk) -> + Op.endorsement ~pred_branch:Block_hash.zero blk >>=? fun operation -> + Block.bake ~operation blk >>=? fun _blk -> return_unit + (****************************************************************) (* The following test scenarios are supposed to raise errors. *) (****************************************************************) @@ -121,18 +128,6 @@ let test_non_normalized_slot () = true | _ -> false) -(** Wrong endorsement predecessor : apply an endorsement with an - incorrect block predecessor. *) -let test_wrong_endorsement_predecessor () = - init_genesis () >>=? fun (_genesis, b) -> - Op.endorsement ~pred_branch:(Context.branch (B b)) b >>=? fun operation -> - Block.bake ~operation b >>= fun res -> - Assert.proto_error ~loc:__LOC__ res (function - | Validate_errors.Consensus.Wrong_consensus_operation_branch {kind; _} - when kind = Validate_errors.Consensus.Endorsement -> - true - | _ -> false) - (** Invalid_endorsement_level: apply an endorsement with an incorrect level (i.e. the predecessor level). *) let test_invalid_endorsement_level () = @@ -519,6 +514,10 @@ let test_endorsement_grandparent_full_construction () = let tests = [ Tztest.tztest "Simple endorsement" `Quick test_simple_endorsement; + Tztest.tztest + "Endorsement with arbitrary branch" + `Quick + test_endorsement_with_arbitrary_branch; Tztest.tztest "Endorsement with slot -1" `Quick test_negative_slot; Tztest.tztest "Endorsement wrapped with non-normalized slot" @@ -526,10 +525,6 @@ let tests = test_non_normalized_slot; Tztest.tztest "Fitness gap" `Quick test_fitness_gap; (* Fail scenarios *) - Tztest.tztest - "Wrong endorsement predecessor" - `Quick - test_wrong_endorsement_predecessor; Tztest.tztest "Invalid endorsement level" `Quick 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 54e62f8b256a..aab600a17130 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 @@ -46,6 +46,15 @@ let init_genesis ?policy () = (* Tests *) (****************************************************************) +(** Test that the preendorsement's branch does not affect its + validity. *) +let test_preendorsement_with_arbitrary_branch () = + Context.init1 () >>=? fun (genesis, _contract) -> + Block.bake genesis >>=? fun blk -> + Op.preendorsement ~pred_branch:Block_hash.zero blk >>=? fun operation -> + Incremental.begin_construction ~mempool_mode:true blk >>=? fun inc -> + Incremental.validate_operation inc operation >>=? fun _inc -> return_unit + (** Consensus operation for future level : apply a preendorsement with a level in the future *) let test_consensus_operation_preendorsement_for_future_level () = init_genesis () >>=? fun (_genesis, pred) -> @@ -206,6 +215,10 @@ let tests = end) in AppMode.tests @ ConstrMode.tests @ [ + Tztest.tztest + "Preendorsement with arbitrary branch" + `Quick + test_preendorsement_with_arbitrary_branch; Tztest.tztest "Preendorsement for future level" `Quick diff --git a/src/proto_alpha/lib_protocol/test/integration/consensus/test_preendorsement_functor.ml b/src/proto_alpha/lib_protocol/test/integration/consensus/test_preendorsement_functor.ml index 1b70fc2779f5..14d283a6ee9f 100644 --- a/src/proto_alpha/lib_protocol/test/integration/consensus/test_preendorsement_functor.ml +++ b/src/proto_alpha/lib_protocol/test/integration/consensus/test_preendorsement_functor.ml @@ -96,23 +96,6 @@ end = struct let include_preendorsement_in_block_with_locked_round () = aux_simple_preendorsement_inclusion ~loc:__LOC__ () - (** KO: bake a block "_b2_1" at round 1, containing a PQC and a locked - round of round 0. But the preendorsement is on a bad branch *) - let test_preendorsement_with_bad_branch () = - aux_simple_preendorsement_inclusion - (* preendorsement should be on branch _pred to be valid *) - ~preend_branch:(fun predpred _pred _curr -> predpred) - ~loc:__LOC__ - ~post_process: - (Error - (function - | Validate_errors.Consensus.Wrong_consensus_operation_branch - {kind; _} - when kind = Validate_errors.Consensus.Preendorsement -> - true - | _ -> false)) - () - (** KO: The same preendorsement injected twice in the PQC *) let duplicate_preendorsement_in_pqc () = aux_simple_preendorsement_inclusion (* inject the op twice *) @@ -260,10 +243,6 @@ end = struct "ok: include_preendorsement_in_block_with_locked_round" `Quick include_preendorsement_in_block_with_locked_round; - my_tztest - "ko: test_preendorsement_with_bad_branch" - `Quick - test_preendorsement_with_bad_branch; my_tztest "ko: duplicate_preendorsement_in_pqc" `Quick diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index ec71a224a49a..cbfd46a79191 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -507,11 +507,6 @@ module Consensus = struct error (Consensus_operation_for_old_round {kind; expected; provided}) else error (Consensus_operation_for_future_round {kind; expected; provided}) - let check_branch kind expected provided = - error_unless - (Block_hash.equal expected provided) - (Wrong_consensus_operation_branch {kind; expected; provided}) - let check_payload_hash kind expected provided = error_unless (Block_payload_hash.equal expected provided) @@ -519,7 +514,7 @@ module Consensus = struct (** Check the preendorsement features for both [Application] and [Partial_validation] modes. *) - let check_preendorsement_content_preexisting_block vi block_info branch + let check_preendorsement_content_preexisting_block vi block_info {level; round; block_payload_hash; _} = let open Result_syntax in let* locked_round = @@ -534,11 +529,10 @@ module Consensus = struct 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_branch kind block_info.predecessor_hash branch in let expected_payload_hash = block_info.header_contents.payload_hash in check_payload_hash kind expected_payload_hash block_payload_hash - let check_preendorsement_content_construction vi cons_info branch + let check_preendorsement_content_construction vi cons_info {level; round; block_payload_hash; _} = let open Result_syntax in let expected_payload_hash = cons_info.header_contents.payload_hash in @@ -557,11 +551,10 @@ module Consensus = struct 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. *) - let* () = check_branch kind cons_info.predecessor_hash branch in check_payload_hash kind expected_payload_hash block_payload_hash let check_preendorsement_content_mempool vi (consensus_info : consensus_info) - branch {level; round; block_payload_hash; _} = + {level; round; block_payload_hash; _} = let open Result_syntax in let kind = Preendorsement in match Consensus.endorsement_branch vi.ctxt with @@ -569,10 +562,9 @@ module Consensus = struct let expected = consensus_info.predecessor_level in let provided = level in error (Consensus_operation_for_future_level {kind; expected; provided}) - | Some (expected_branch, expected_payload_hash) -> + | Some (_, expected_payload_hash) -> let* () = check_level kind consensus_info.predecessor_level level in let* () = check_round kind consensus_info.predecessor_round round in - let* () = check_branch kind expected_branch branch in check_payload_hash kind expected_payload_hash block_payload_hash let check_preendorsement vi ~check_signature @@ -592,19 +584,16 @@ module Consensus = struct check_preendorsement_content_preexisting_block vi block_info - operation.shell.branch consensus_content | Construction construction_info -> check_preendorsement_content_construction vi construction_info - operation.shell.branch consensus_content | Mempool _ -> check_preendorsement_content_mempool vi consensus_info - operation.shell.branch consensus_content in let*? consensus_key, voting_power = @@ -704,7 +693,6 @@ module Consensus = struct (* 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_branch kind grandparent.hash operation.shell.branch in let*? () = check_payload_hash kind grandparent.payload_hash bph in let*? () = if check_signature then @@ -763,8 +751,7 @@ module Consensus = struct 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_branch_and_payload_hash vi (consensus_info : consensus_info) - op_level = + 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 -> @@ -802,13 +789,12 @@ module Consensus = struct operation.protocol_data.contents in let {level; round; block_payload_hash = bph; _} = consensus_content in - let*? expected_branch, expected_payload_hash = - expected_branch_and_payload_hash vi consensus_info level + let*? _expected_branch, expected_payload_hash = + expected_payload_hash vi consensus_info level 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_branch kind expected_branch operation.shell.branch in let*? () = check_payload_hash kind expected_payload_hash bph in let*? consensus_key, voting_power = get_delegate_details diff --git a/src/proto_alpha/lib_protocol/validate_errors.ml b/src/proto_alpha/lib_protocol/validate_errors.ml index 53dfc99e17c4..0eb8bdee3343 100644 --- a/src/proto_alpha/lib_protocol/validate_errors.ml +++ b/src/proto_alpha/lib_protocol/validate_errors.ml @@ -112,11 +112,6 @@ module Consensus = struct expected : Round.t; provided : Round.t; } - | Wrong_consensus_operation_branch of { - kind : consensus_operation_kind; - expected : Block_hash.t; - provided : Block_hash.t; - } | Wrong_payload_hash_for_consensus_operation of { kind : consensus_operation_kind; expected : Block_payload_hash.t; @@ -242,35 +237,6 @@ module Consensus = struct | _ -> None) (fun (kind, expected, provided) -> Consensus_operation_for_future_round {kind; expected; provided}) ; - register_error_kind - `Temporary - ~id:"validate.wrong_consensus_operation_branch" - ~title:"Wrong consensus operation branch" - ~description: - "Trying to include an endorsement or preendorsement which points to \ - the wrong block. It should be the predecessor for preendorsements and \ - the grandfather for endorsements." - ~pp:(fun ppf (kind, expected, provided) -> - Format.fprintf - ppf - "%a with wrong branch (expected: %a, provided: %a)." - consensus_operation_kind_pp - kind - Block_hash.pp - expected - Block_hash.pp - provided) - Data_encoding.( - obj3 - (req "kind" consensus_operation_kind_encoding) - (req "expected" Block_hash.encoding) - (req "provided" Block_hash.encoding)) - (function - | Wrong_consensus_operation_branch {kind; expected; provided} -> - Some (kind, expected, provided) - | _ -> None) - (fun (kind, expected, provided) -> - Wrong_consensus_operation_branch {kind; expected; provided}) ; register_error_kind (* Note: in Mempool mode this used to be Consensus_operation_on_competing_proposal (which was diff --git a/src/proto_alpha/lib_protocol/validate_errors.mli b/src/proto_alpha/lib_protocol/validate_errors.mli index e26eafc0bba6..4d1b3485afb6 100644 --- a/src/proto_alpha/lib_protocol/validate_errors.mli +++ b/src/proto_alpha/lib_protocol/validate_errors.mli @@ -64,11 +64,6 @@ module Consensus : sig expected : Round.t; provided : Round.t; } - | Wrong_consensus_operation_branch of { - kind : consensus_operation_kind; - expected : Block_hash.t; - provided : Block_hash.t; - } | Wrong_payload_hash_for_consensus_operation of { kind : consensus_operation_kind; expected : Block_payload_hash.t; -- GitLab From b8f42a6698aa728200187a8b04a547cba5de7473 Mon Sep 17 00:00:00 2001 From: vbot Date: Wed, 22 Feb 2023 16:45:48 +0100 Subject: [PATCH 2/6] Alpha/Protocol: different branches endorsements are punishable --- .../consensus/test_double_endorsement.ml | 27 +++++++++++++++ src/proto_alpha/lib_protocol/validate.ml | 33 ++++++++++++++----- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/integration/consensus/test_double_endorsement.ml b/src/proto_alpha/lib_protocol/test/integration/consensus/test_double_endorsement.ml index 4db83c0171ad..911051eb3c36 100644 --- a/src/proto_alpha/lib_protocol/test/integration/consensus/test_double_endorsement.ml +++ b/src/proto_alpha/lib_protocol/test/integration/consensus/test_double_endorsement.ml @@ -61,6 +61,10 @@ let double_endorsement ctxt ?(correct_order = true) op1 op2 = let e1, e2 = order_endorsements ~correct_order op1 op2 in Op.double_endorsement ctxt e1 e2 +let double_preendorsement ctxt ?(correct_order = true) op1 op2 = + let e1, e2 = order_endorsements ~correct_order op1 op2 in + Op.double_preendorsement ctxt e1 e2 + (** This test verifies that when a "cheater" double endorses and doesn't have enough tokens to re-freeze of full deposit, we only freeze what we can (i.e. the remaining balance) but we check that @@ -126,6 +130,25 @@ let test_valid_double_endorsement_evidence () = let real_reward = Test_tez.(full_balance_with_rewards -! full_balance) in Assert.equal_tez ~loc:__LOC__ expected_reward real_reward +(** Check that a double (pre)endorsement evidence with equivalent + endorsements but on different branches succeeds. *) +let test_different_branch () = + Context.init2 ~consensus_threshold:0 () >>=? fun (genesis, _contracts) -> + Block.bake genesis >>=? fun blk -> + Context.get_endorser (B blk) >>=? fun (endorser, _slots) -> + Op.raw_endorsement ~delegate:endorser blk >>=? fun endorsement_a -> + Op.raw_endorsement ~pred_branch:Block_hash.zero ~delegate:endorser blk + >>=? fun endorsement_b -> + let operation = double_endorsement (B blk) endorsement_a endorsement_b in + Block.bake ~operation blk >>=? fun _blk -> + Op.raw_preendorsement ~delegate:endorser blk >>=? fun preendorsement_a -> + Op.raw_preendorsement ~pred_branch:Block_hash.zero ~delegate:endorser blk + >>=? fun preendorsement_b -> + let operation = + double_preendorsement (B blk) preendorsement_a preendorsement_b + in + Block.bake ~operation blk >>=? fun _blk -> return_unit + (** Say a delegate double-endorses twice and say the 2 evidences are timely included. Then the delegate can no longer bake. *) let test_two_double_endorsement_evidences_leadsto_no_bake () = @@ -472,6 +495,10 @@ let tests = "valid double endorsement evidence" `Quick test_valid_double_endorsement_evidence; + Tztest.tztest + "valid evidence with same (pre)endorsements on different branches" + `Quick + test_different_branch; Tztest.tztest "2 valid double endorsement evidences lead to not being able to bake" `Quick diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index cbfd46a79191..10515348a5b8 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -1461,17 +1461,32 @@ module Anonymous = struct | Single (Endorsement e1), Single (Endorsement e2) -> let op1_hash = Operation.hash op1 in let op2_hash = Operation.hash op2 in + let same_levels = Raw_level.(e1.level = e2.level) in + let same_rounds = Round.(e1.round = e2.round) in + let same_payload = + Block_payload_hash.(e1.block_payload_hash = e2.block_payload_hash) + in + let same_branches = Block_hash.(op1.shell.branch = op2.shell.branch) in + let ordered_hashes = Operation_hash.(op1_hash < op2_hash) in + let is_denunciation_consistent = + same_levels && same_rounds + (* Either the payloads or the branches must differ for the + double (pre)endorsement to be punishable. Indeed, + different payloads would endanger the consensus process, + while different branches could be used to spam mempools + with a lot of valid operations. On the other hand, if the + operations have identical levels, rounds, payloads, and + branches (and of course delegates), then only their + signatures are different, which is not considered the + delegate's fault and therefore is not punished. *) + && ((not same_payload) || not same_branches) + && (* we require an order on hashes to avoid the existence of + equivalent evidences *) + ordered_hashes + in let*? () = error_unless - (Raw_level.(e1.level = e2.level) - && Round.(e1.round = e2.round) - && (not - (Block_payload_hash.equal - e1.block_payload_hash - e2.block_payload_hash)) - && (* we require an order on hashes to avoid the existence of - equivalent evidences *) - Operation_hash.(op1_hash < op2_hash)) + is_denunciation_consistent (Invalid_denunciation denunciation_kind) in (* Disambiguate: levels are equal *) -- GitLab From b276c108c82d4079b39e661d829e73b2049390cb Mon Sep 17 00:00:00 2001 From: vbot Date: Mon, 27 Feb 2023 13:53:29 +0100 Subject: [PATCH 3/6] Alpha/Baker: anchor (pre)endorsements on the latest finalized block --- src/proto_alpha/lib_delegate/baking_actions.ml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/proto_alpha/lib_delegate/baking_actions.ml b/src/proto_alpha/lib_delegate/baking_actions.ml index 07cefe422396..9948c93bef0e 100644 --- a/src/proto_alpha/lib_delegate/baking_actions.ml +++ b/src/proto_alpha/lib_delegate/baking_actions.ml @@ -379,9 +379,10 @@ let inject_preendorsements state ~preendorsements = (fun (((consensus_key, _) as delegate), consensus_content) -> Events.(emit signing_preendorsement delegate) >>= fun () -> let shell = + (* The branch is the latest finalized block. *) { Tezos_base.Operation.branch = - state.level_state.latest_proposal.predecessor.hash; + state.level_state.latest_proposal.predecessor.shell.predecessor; } in let contents = Single (Preendorsement consensus_content) in @@ -466,9 +467,10 @@ let sign_endorsements state endorsements = (fun (((consensus_key, _) as delegate), consensus_content) -> Events.(emit signing_endorsement delegate) >>= fun () -> let shell = + (* The branch is the latest finalized block. *) { Tezos_base.Operation.branch = - state.level_state.latest_proposal.predecessor.hash; + state.level_state.latest_proposal.predecessor.shell.predecessor; } in let contents = -- GitLab From a820138b3c7703f072b3d18d2387ee26d502fabe Mon Sep 17 00:00:00 2001 From: vbot Date: Mon, 27 Feb 2023 16:38:09 +0100 Subject: [PATCH 4/6] Alpha/Baker: adapt mockup live blocks test RPC semantics --- .../lib_delegate/test/mockup_simulator/mockup_simulator.ml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_delegate/test/mockup_simulator/mockup_simulator.ml b/src/proto_alpha/lib_delegate/test/mockup_simulator/mockup_simulator.ml index 01f139d8f7ba..7f7b31ba4400 100644 --- a/src/proto_alpha/lib_delegate/test/mockup_simulator/mockup_simulator.ml +++ b/src/proto_alpha/lib_delegate/test/mockup_simulator/mockup_simulator.ml @@ -192,7 +192,8 @@ let live_blocks (state : state) block = (fun set ({rpc_context; _} : block) -> let hash = rpc_context.Tezos_protocol_environment.block_hash in Block_hash.Set.add hash set) - (Block_hash.Set.singleton state.genesis_block_true_hash) + (Block_hash.Set.of_list + [state.genesis_block_true_hash; genesis_predecessor_block_hash]) segment) (** Extract the round number from raw fitness. *) -- GitLab From 43abaa93e017d541146b6531bd1c2a87dde1b4ef Mon Sep 17 00:00:00 2001 From: vbot Date: Mon, 27 Feb 2023 14:22:40 +0100 Subject: [PATCH 5/6] Alpha/Accuser: adapt the denunciation condition to the new semantics --- src/proto_alpha/lib_delegate/client_baking_denunciation.ml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/proto_alpha/lib_delegate/client_baking_denunciation.ml b/src/proto_alpha/lib_delegate/client_baking_denunciation.ml index 2ebc4af5961e..692d06364648 100644 --- a/src/proto_alpha/lib_delegate/client_baking_denunciation.ml +++ b/src/proto_alpha/lib_delegate/client_baking_denunciation.ml @@ -153,8 +153,10 @@ let process_consensus_op (type kind) cctxt | Some existing_op when Block_payload_hash.( get_payload_hash op_kind existing_op - <> get_payload_hash op_kind new_op) -> - (* same level and round, and different payload hash for this slot *) + <> get_payload_hash op_kind new_op) + || Block_hash.(existing_op.shell.branch <> new_op.shell.branch) -> + (* same slot, level, and round, and: + different payload hash OR different branch *) let new_op_hash, existing_op_hash = (Operation.hash new_op, Operation.hash existing_op) in -- GitLab From 561d70e356e21a82ab7858068fba8550ef85d9f8 Mon Sep 17 00:00:00 2001 From: vbot Date: Mon, 27 Feb 2023 14:16:38 +0100 Subject: [PATCH 6/6] Alpha/Doc: update changelog --- docs/protocols/alpha.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/protocols/alpha.rst b/docs/protocols/alpha.rst index ff4cbf44d486..d0d5f7397b4b 100644 --- a/docs/protocols/alpha.rst +++ b/docs/protocols/alpha.rst @@ -69,6 +69,9 @@ Minor Changes - Adapt new mempool with proto add_operation. (MR :gl:`!6749`) +- Relax (pre)endorsements branch condition and allow denunciations of + a same endorsement on different branches. (MR :gl:`!7828`) + Internal -------- -- GitLab