diff --git a/src/lib_store/unix/test/alpha_utils.ml b/src/lib_store/unix/test/alpha_utils.ml index 9ca1930ca7ebd9ab56f1b08fc44f51540aa747bc..bca99a3659634a037bcff7205ee64b388c8fc193 100644 --- a/src/lib_store/unix/test/alpha_utils.ml +++ b/src/lib_store/unix/test/alpha_utils.ml @@ -588,11 +588,10 @@ let apply ctxt chain_id ~policy ?(operations = empty_operations) pred = let payload_hash = let non_consensus_operations = Stdlib.List.tl operations |> List.concat in let hashes = List.map Operation.hash_packed non_consensus_operations in - let non_consensus_operations_hash = Operation_list_hash.compute hashes in Block_payload.hash - ~predecessor:shell.predecessor - contents.payload_round - non_consensus_operations_hash + ~predecessor_hash:shell.predecessor + ~payload_round:contents.payload_round + hashes in let contents = {contents with payload_hash} in let shell = {shell with context = context_hash} in diff --git a/src/proto_alpha/lib_delegate/block_forge.ml b/src/proto_alpha/lib_delegate/block_forge.ml index a367b6b655c8952d2139f50afbfe78f279d10ba9..82ca809b9136f9cc616240505ed0300b758facc6 100644 --- a/src/proto_alpha/lib_delegate/block_forge.ml +++ b/src/proto_alpha/lib_delegate/block_forge.ml @@ -165,15 +165,14 @@ let forge (cctxt : #Protocol_client_context.full) ~chain_id ~pred_info List.map (fun l -> List.map snd l.Preapply_result.applied) preapply_result in let payload_hash = - let operation_list_hash = + let operation_hashes = Stdlib.List.tl operations |> List.flatten |> List.map Tezos_base.Operation.hash - |> Operation_list_hash.compute in Block_payload.hash - ~predecessor:shell_header.predecessor - payload_round - operation_list_hash + ~predecessor_hash:shell_header.predecessor + ~payload_round + operation_hashes in return (shell_header, operations, payload_hash) in @@ -239,15 +238,14 @@ let forge (cctxt : #Protocol_client_context.full) ~chain_id ~pred_info >>=? fun shell_header -> let operations = List.map (List.map convert_operation) operations in let payload_hash = - let operation_list_hash = + let operation_hashes = Stdlib.List.tl operations |> List.flatten |> List.map Tezos_base.Operation.hash - |> Operation_list_hash.compute in Block_payload.hash - ~predecessor:shell_header.predecessor - payload_round - operation_list_hash + ~predecessor_hash:shell_header.predecessor + ~payload_round + operation_hashes in return (shell_header, operations, payload_hash) in 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 ca26b7547a25be9e0eed13dcd7e9b175ebf20388..7f6af037ab7302e6121d92870a4209496258b7cf 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 @@ -828,17 +828,16 @@ let baker_process ~(delegates : Baking_state.consensus_key list) ~base_dir User_hooks.check_chain_on_success ~chain:state.chain let genesis_protocol_data (baker_sk : Signature.secret_key) - (predecessor_block_hash : Block_hash.t) - (block_header : Block_header.shell_header) : Bytes.t = + (predecessor_hash : Block_hash.t) (block_header : Block_header.shell_header) + : Bytes.t = let proof_of_work_nonce = Bytes.create Protocol.Alpha_context.Constants.proof_of_work_nonce_size in - let operation_list_hash = Operation_list_hash.compute [] in let payload_hash = Protocol.Alpha_context.Block_payload.hash - ~predecessor:predecessor_block_hash - Alpha_context.Round.zero - operation_list_hash + ~predecessor_hash + ~payload_round:Alpha_context.Round.zero + [] in let contents = Protocol.Alpha_context.Block_header. diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index c541736fe99eb9d4d48068ef7918c9b9ffaa9dee..f7bc9ebfaa60a1c11e42872fb2f981da7b025f04 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -3841,11 +3841,13 @@ module Destination : sig type error += Invalid_destination_b58check of string end +(** See {!Block_payload_repr}. *) module Block_payload : sig + (** See {!Block_payload_repr.hash}. *) val hash : - predecessor:Block_hash.t -> - Round.t -> - Operation_list_hash.t -> + predecessor_hash:Block_hash.t -> + payload_round:Round.t -> + Operation_list_hash.elt list -> Block_payload_hash.t end diff --git a/src/proto_alpha/lib_protocol/amendment.ml b/src/proto_alpha/lib_protocol/amendment.ml index 07f6ff809c9c9b0ae0ade0fd53c337919496dcb2..b295020014ee5caf511944104053f45589a02609 100644 --- a/src/proto_alpha/lib_protocol/amendment.ml +++ b/src/proto_alpha/lib_protocol/amendment.ml @@ -148,6 +148,8 @@ let may_start_new_voting_period ctxt = Voting_period.is_last_block ctxt >>=? fun is_last -> if is_last then start_new_voting_period ctxt else return ctxt +(** {2 Application of voting operations} *) + let get_testnet_dictator ctxt chain_id = (* This function should always, ALWAYS, return None on mainnet!!!! *) match Constants.testnet_dictator ctxt with @@ -160,48 +162,39 @@ let is_testnet_dictator ctxt chain_id delegate = | Some pkh -> Signature.Public_key_hash.equal pkh delegate | _ -> false -(** {2 Application of voting operations} *) - -(** Helpers to apply [Proposals] operations from a - registered dictator of a test chain. These operations let the - dictator immediately change the current voting period's kind, and - the current proposal if applicable. Of course, there must never be - such a dictator on mainnet. *) -module Testnet_dictator = struct - (** Forcibly update the voting period according to a voting - dictator's Proposals operation. - - {!check_proposals} should guarantee that this function cannot - return an error. *) - let record_proposals ctxt chain_id proposals = - let open Lwt_tzresult_syntax in - let*! ctxt = Vote.clear_ballots ctxt in - let*! ctxt = Vote.clear_proposals ctxt in - let*! ctxt = Vote.clear_current_proposal ctxt in - let ctxt = record_dictator_proposal_seen ctxt in - match proposals with - | [] -> - Voting_period.Testnet_dictator.overwrite_current_kind - ctxt - chain_id - Proposal - | [proposal] -> - let* ctxt = Vote.init_current_proposal ctxt proposal in - Voting_period.Testnet_dictator.overwrite_current_kind - ctxt - chain_id - Adoption - | _ :: _ :: _ -> - (* This does not fail if validate proposal was previously - called. *) - fail Validate_errors.Voting.Testnet_dictator_multiple_proposals -end +(** Apply a [Proposals] operation from a registered dictator of a test + chain. This forcibly updates the voting period, changing the + current voting period kind and the current proposal if + applicable. Of course, there must never be such a dictator on + mainnet: see {!is_testnet_dictator}. *) +let apply_testnet_dictator_proposals ctxt chain_id proposals = + let open Lwt_tzresult_syntax in + let*! ctxt = Vote.clear_ballots ctxt in + let*! ctxt = Vote.clear_proposals ctxt in + let*! ctxt = Vote.clear_current_proposal ctxt in + let ctxt = record_dictator_proposal_seen ctxt in + match proposals with + | [] -> + Voting_period.Testnet_dictator.overwrite_current_kind + ctxt + chain_id + Proposal + | [proposal] -> + let* ctxt = Vote.init_current_proposal ctxt proposal in + Voting_period.Testnet_dictator.overwrite_current_kind + ctxt + chain_id + Adoption + | _ :: _ :: _ -> + (* This case should not be possible if the operation has been + previously validated by {!Validate.validate_operation}. *) + fail Validate_errors.Voting.Testnet_dictator_multiple_proposals let apply_proposals ctxt chain_id (Proposals {source; period = _; proposals}) = let open Lwt_tzresult_syntax in let* ctxt = if is_testnet_dictator ctxt chain_id source then - Testnet_dictator.record_proposals ctxt chain_id proposals + apply_testnet_dictator_proposals ctxt chain_id proposals else if dictator_proposal_seen ctxt then (* Noop if dictator voted *) return ctxt diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index ad56812ae19d27c08026d5c4084fab8dd1f08ac8..3481fe2d425c817d0eb5dfba05f38184b8ae3fba 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -1957,10 +1957,10 @@ type mode = predecessor_round : Round.t; } | Full_construction of { - predecessor : Block_hash.t; + block_data_contents : Block_header.contents; + predecessor_hash : Block_hash.t; payload_producer : Consensus_key.t; block_producer : Consensus_key.t; - block_data_contents : Block_header.contents; round : Round.t; predecessor_level : Level.t; predecessor_round : Round.t; @@ -2245,7 +2245,7 @@ let apply_contents_list (type kind) ctxt chain_id (mode : mode) {balance_updates; allocated_destination_contract}) ) | Single (Failing_noop _) -> (* This operation always fails. It should already have been - rejected by {!Validate_operation.validate_operation}. *) + rejected by {!Validate.validate_operation}. *) fail Validate_errors.Failing_noop_error | Single (Manager_operation _) -> apply_manager_operations @@ -2464,12 +2464,6 @@ let apply_liquidity_baking_subsidy ctxt ~toggle_vote = let ctxt = Gas.set_unlimited backtracking_ctxt in Ok (ctxt, [])) -let compute_payload_hash (ctxt : context) ~(predecessor : Block_hash.t) - ~(payload_round : Round.t) : Block_payload_hash.t = - let non_consensus_operations = non_consensus_operations ctxt in - let operations_hash = Operation_list_hash.compute non_consensus_operations in - Block_payload.hash ~predecessor payload_round operations_hash - let are_endorsements_required ctxt ~level = First_level_of_protocol.get ctxt >|=? fun first_level -> (* NB: the first level is the level of the migration block. There @@ -2549,7 +2543,7 @@ let begin_application ctxt chain_id ~migration_balance_updates let begin_full_construction ctxt chain_id ~migration_balance_updates ~migration_operation_results ~predecessor_timestamp ~predecessor_level - ~predecessor_round ~predecessor ~timestamp + ~predecessor_round ~predecessor_hash ~timestamp (block_data_contents : Block_header.contents) = let open Lwt_tzresult_syntax in let round_durations = Constants.round_durations ctxt in @@ -2579,11 +2573,11 @@ let begin_full_construction ctxt chain_id ~migration_balance_updates let mode = Full_construction { - predecessor; + block_data_contents; + predecessor_hash; payload_producer = Consensus_key.pkh payload_producer; block_producer = Consensus_key.pkh block_producer; round; - block_data_contents; predecessor_round; predecessor_level; } @@ -2625,21 +2619,21 @@ let begin_partial_construction ctxt chain_id ~migration_balance_updates @ liquidity_baking_operations_results; } -let finalize_application ctxt block_data_contents ~round ~predecessor +let finalize_application ctxt block_data_contents ~round ~predecessor_hash ~liquidity_baking_toggle_ema ~implicit_operations_results ~migration_balance_updates ~(block_producer : Consensus_key.t) ~(payload_producer : Consensus_key.t) = - let open Lwt_result_syntax in + let open Lwt_tzresult_syntax in let level = Level.current ctxt in let endorsing_power = Consensus.current_endorsement_power ctxt in let* required_endorsements = are_endorsements_required ctxt ~level:level.level in let block_payload_hash = - compute_payload_hash - ctxt - ~predecessor + Block_payload.hash + ~predecessor_hash ~payload_round:block_data_contents.Block_header.payload_round + (non_consensus_operations ctxt) in (* from this point nothing should fail *) (* We mark the endorsement branch as the grand parent branch when @@ -2655,7 +2649,9 @@ let finalize_application ctxt block_data_contents ~round ~predecessor (* We mark the current payload hash as the predecessor one => this will only be accessed by the successor block now. *) let*! ctxt = - Consensus.store_endorsement_branch ctxt (predecessor, block_payload_hash) + Consensus.store_endorsement_branch + ctxt + (predecessor_hash, block_payload_hash) in let* ctxt = Round.update ctxt round in (* end of level *) @@ -2776,9 +2772,9 @@ let finalize_block (application_state : application_state) shell_header_opt = match application_state.mode with | Full_construction { - predecessor; - predecessor_level = _; block_data_contents; + predecessor_hash; + predecessor_level = _; predecessor_round; block_producer; payload_producer; @@ -2812,7 +2808,7 @@ let finalize_block (application_state : application_state) shell_header_opt = ctxt block_data_contents ~round - ~predecessor + ~predecessor_hash ~liquidity_baking_toggle_ema ~implicit_operations_results ~migration_balance_updates @@ -2860,7 +2856,7 @@ let finalize_block (application_state : application_state) shell_header_opt = ctxt protocol_data.contents ~round - ~predecessor:shell.predecessor + ~predecessor_hash:shell.predecessor ~liquidity_baking_toggle_ema ~implicit_operations_results ~migration_balance_updates diff --git a/src/proto_alpha/lib_protocol/apply.mli b/src/proto_alpha/lib_protocol/apply.mli index 3b5094d2b19ef9d8fdb20aebbd779d00b6bd9031..dc3f212ae160c3d8c982eb7bbe3076019a6c8e55 100644 --- a/src/proto_alpha/lib_protocol/apply.mli +++ b/src/proto_alpha/lib_protocol/apply.mli @@ -53,10 +53,10 @@ type mode = predecessor_round : Round.t; } | Full_construction of { - predecessor : Block_hash.t; + block_data_contents : Block_header.contents; + predecessor_hash : Block_hash.t; payload_producer : Consensus_key.t; block_producer : Consensus_key.t; - block_data_contents : Block_header.contents; round : Round.t; predecessor_level : Level.t; predecessor_round : Round.t; @@ -98,7 +98,7 @@ val begin_full_construction : predecessor_timestamp:Time.t -> predecessor_level:Level.t -> predecessor_round:Round.t -> - predecessor:Block_hash.t -> + predecessor_hash:Block_hash.t -> timestamp:Time.t -> Block_header.contents -> application_state tzresult Lwt.t diff --git a/src/proto_alpha/lib_protocol/block_payload_repr.ml b/src/proto_alpha/lib_protocol/block_payload_repr.ml index ad46807466a505476ebcb958683fabe20687b09a..1765e1612958e51915b54f53125eba78a3b06488 100644 --- a/src/proto_alpha/lib_protocol/block_payload_repr.ml +++ b/src/proto_alpha/lib_protocol/block_payload_repr.ml @@ -31,10 +31,11 @@ include the hash of the block that precedes the block where these operations should be included. *) -let hash ~predecessor round operations_hash = +let hash ~predecessor_hash ~payload_round operations = + let operations_hash = Operation_list_hash.compute operations in let open Data_encoding in - let predecessor = Binary.to_bytes_exn Block_hash.encoding predecessor in - let round = Binary.to_bytes_exn Round_repr.encoding round in + let predecessor = Binary.to_bytes_exn Block_hash.encoding predecessor_hash in + let round = Binary.to_bytes_exn Round_repr.encoding payload_round in let operations_hash = Binary.to_bytes_exn Operation_list_hash.encoding operations_hash in diff --git a/src/proto_alpha/lib_protocol/block_payload_repr.mli b/src/proto_alpha/lib_protocol/block_payload_repr.mli index 82c672e280cba5748558341a33b00a06f4787f97..8f65862e6f557b688635221b70ac424ee1c9a4a6 100644 --- a/src/proto_alpha/lib_protocol/block_payload_repr.mli +++ b/src/proto_alpha/lib_protocol/block_payload_repr.mli @@ -31,11 +31,11 @@ include the hash of the block that precedes the block where these operations should be included. *) -(** [hash ~predecessor:block_hash round oplh] creates a payload hash given a - [block_hash], the first [round] at which the payload was proposed - and the hash [oplh] of the non-consensus operations. *) +(** Create a payload hash from the predecessor block hash, the first + round at which the payload was proposed, and the hashes of + non-consensus operations. *) val hash : - predecessor:Block_hash.t -> - Round_repr.t -> - Operation_list_hash.t -> + predecessor_hash:Block_hash.t -> + payload_round:Round_repr.t -> + Operation_list_hash.elt list -> Block_payload_hash.t diff --git a/src/proto_alpha/lib_protocol/contract_storage.ml b/src/proto_alpha/lib_protocol/contract_storage.ml index 3b712faaf2e6516c31040b93f441052a08d7e13b..ff38861ab5e99162d3792771e44c4ed442b30cca 100644 --- a/src/proto_alpha/lib_protocol/contract_storage.ml +++ b/src/proto_alpha/lib_protocol/contract_storage.ml @@ -547,12 +547,12 @@ let get_balance_carbonated c contract = get_balance c contract >>=? fun balance -> return (c, balance) let check_allocated_and_get_balance c pkh = - let open Lwt_result_syntax in + let open Lwt_tzresult_syntax in let* balance_opt = Storage.Contract.Spendable_balance.find c (Contract_repr.Implicit pkh) in match balance_opt with - | None -> Error_monad.fail (Empty_implicit_contract pkh) + | None -> fail (Empty_implicit_contract pkh) | Some balance -> return balance let update_script_storage c contract storage lazy_storage_diff = @@ -572,7 +572,7 @@ let spend_from_balance contract balance amount = Tez_repr.(balance -? amount) let check_emptiable c contract = - let open Lwt_result_syntax in + let open Lwt_tzresult_syntax in match contract with | Contract_repr.Originated _ -> return_unit | Implicit pkh -> ( @@ -586,7 +586,7 @@ let check_emptiable c contract = | None -> return_unit) let spend_only_call_from_token c contract amount = - let open Lwt_result_syntax in + let open Lwt_tzresult_syntax in let* balance = Storage.Contract.Spendable_balance.find c contract in let balance = Option.value balance ~default:Tez_repr.zero in let*? new_balance = spend_from_balance contract balance amount in @@ -714,7 +714,7 @@ let fold_on_bond_ids ctxt contract = (** Indicate whether the given implicit contract should avoid deletion when it is emptied. *) let should_keep_empty_implicit_contract ctxt contract = - let open Lwt_result_syntax in + let open Lwt_tzresult_syntax in let* has_frozen_bonds = has_frozen_bonds ctxt contract in if has_frozen_bonds then return_true else @@ -732,7 +732,7 @@ let should_keep_empty_implicit_contract ctxt contract = return_false let ensure_deallocated_if_empty ctxt contract = - let open Lwt_result_syntax in + let open Lwt_tzresult_syntax in match contract with | Contract_repr.Originated _ -> return ctxt (* Never delete originated contracts *) @@ -753,7 +753,7 @@ let ensure_deallocated_if_empty ctxt contract = if keep_contract then return ctxt else delete ctxt contract) let simulate_spending ctxt ~balance ~amount source = - let open Lwt_result_syntax in + let open Lwt_tzresult_syntax in let contract = Contract_repr.Implicit source in let*? new_balance = spend_from_balance contract balance amount in let* still_allocated = diff --git a/src/proto_alpha/lib_protocol/main.ml b/src/proto_alpha/lib_protocol/main.ml index c627d1ee4d51955c865e6d413ba44d757e2c1a70..1abe8b2569cd334303b2f54490ae310e8736aa4e 100644 --- a/src/proto_alpha/lib_protocol/main.ml +++ b/src/proto_alpha/lib_protocol/main.ml @@ -295,7 +295,7 @@ let begin_application ctxt chain_id mode ~predecessor = ~predecessor_timestamp ~predecessor_level ~predecessor_round - ~predecessor:predecessor_hash + ~predecessor_hash ~timestamp block_header_data.contents | Partial_construction _ -> diff --git a/src/proto_alpha/lib_protocol/test/helpers/block.ml b/src/proto_alpha/lib_protocol/test/helpers/block.ml index 78ccd9f88bd4414cc94a69d94ac8cfbd34e4c08a..3291ae729c4fafaa9b3e1ac0f82998c00a1d3a2e 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/block.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/block.ml @@ -253,15 +253,14 @@ module Forge = struct List.concat (match List.tl operations with None -> [] | Some l -> l) in let hashes = List.map Operation.hash_packed non_consensus_operations in - let non_consensus_operations_hash = Operation_list_hash.compute hashes in let payload_round = match payload_round with None -> round | Some r -> r in let payload_hash = Block_payload.hash - ~predecessor:shell.predecessor - payload_round - non_consensus_operations_hash + ~predecessor_hash:shell.predecessor + ~payload_round + hashes in let contents = make_contents diff --git a/src/proto_alpha/lib_protocol/test/helpers/op.ml b/src/proto_alpha/lib_protocol/test/helpers/op.ml index 8276637eaa4d15a8ec42e66f4a0ef140c694c927..274dc0b5e92df34dbb16df999cc236938f2225cf 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/op.ml @@ -44,17 +44,13 @@ let sign ?(watermark = Signature.Generic_operation) sk ctxt 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 pred_hash payload_round (b : Block.t) = +let mk_block_payload_hash predecessor_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 - let non_consensus_operations_hash = Operation_list_hash.compute hashes in - Block_payload.hash - ~predecessor:pred_hash - payload_round - non_consensus_operations_hash + Block_payload.hash ~predecessor_hash ~payload_round hashes (* ctxt is used for getting the branch in sign *) let endorsement ?delegate ?slot ?level ?round ?block_payload_hash diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index 26cd81643ad4de114d414430657a17f98fcdd5d7..71aad10a61ffe256e17cbb068e60af3fbfb83784 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -48,10 +48,10 @@ type expected_preendorsement = | Expected_preendorsement of { expected_features : expected_features; block_round : Round.t option; + (** During block validation or construction, we must also check that + the preendorsement round is lower than the block round. In + mempool mode, this field is [None]. *) } - (** During block validation or construction, we must also check - that the preendorsement round is lower than the block - round. In mempool mode, this field is [None]. *) | No_locked_round_for_block_validation_preendorsement (** A preexisting block whose fitness indicates no locked round should contain no preendorsements. *) @@ -191,9 +191,10 @@ let init_consensus_state ~predecessor_level = type voting_state = { proposals_seen : Operation_hash.t Signature.Public_key_hash.Map.t; - (** Summary of all Proposals operations validated in the current - block/mempool, indexed by the operation's source aka - proposer. This includes Testnet dictators proposals. *) + (** To each delegate that has submitted a Proposals operation in a + previously validated operation, associates the hash of this + operation. This includes Proposals from a potential Testnet + Dictator. *) ballots_seen : Operation_hash.t Signature.Public_key_hash.Map.t; (** To each delegate that has submitted a ballot in a previously validated operation, associates the hash of this operation. *) @@ -378,7 +379,7 @@ let manager_state_encoding = let empty_manager_state = {managers_seen = Signature.Public_key_hash.Map.empty} (** Mode-dependent information needed in final checks. *) -type application_info = { +type block_finalization_info = { fitness : Fitness.t; block_producer : Consensus_key.pk; payload_producer : Consensus_key.pk; @@ -386,27 +387,30 @@ type application_info = { block_data_contents : Block_header.contents; } -(** Circumstances in which operations are validated: +(** Circumstances in which operations are validated, and corresponding + information. - - [Application] is used for the validation of preexisting block. - Corresponds to [Application] of {!Main.validation_mode}. + - [Application] is used for the validation of a preexisting block, + often in preparation for its future application. - - [Partial_validation] is used to partially validate preexisting - block. Corresponds to [Partial_validation] of - {!Main.validation_mode}. + - [Partial_validation] is used to quickly but partially validate a + preexisting block, e.g. to quickly decide whether an alternate + branch seems viable. In this mode, the initial {!type:context} may + come from an ancestor block instead of the predecessor block. Only + consensus operations are validated in this mode. - [Construction] is used for the construction of a new block. - Corresponds to [Full_construction] of {!Main.validation_mode}. - - [Mempool] is used by the mempool (either directly or through the - plugin). Corresponds to [Partial_construction] of - {!Main.validation_mode}. + - [Mempool] is used by the {!module:Mempool} and by the + [Partial_construction] mode in {!module:Main}, which may itself be + used by RPCs or by another mempool implementation. (The [Mempool] + mode is also used by the plugin.) If you add a new mode, please make sure that it has a way to bound - the size of the map {!recfield:managers_seen}. *) + the size of the map {!recfield:manager_state.managers_seen}. *) type mode = - | Application of application_info - | Partial_validation of application_info + | Application of block_finalization_info + | Partial_validation of block_finalization_info | Construction of { predecessor_round : Round.t; predecessor_hash : Block_hash.t; @@ -516,6 +520,8 @@ module Consensus = struct payload_hash; } + (** Expected endorsement features for all modes in which a block is + considered: application, partial validation, and construction. *) let expected_endorsement_for_block ctxt ~predecessor_level ~predecessor_round : expected_endorsement = match Consensus.endorsement_branch ctxt with @@ -530,6 +536,8 @@ module Consensus = struct in Expected_endorsement {expected_features} + (** Retrieve the expected consensus features for both application and + partial validation modes. *) let expected_features_for_application ctxt fitness payload_hash ~predecessor_level ~predecessor_round ~predecessor_hash = let expected_preendorsement = @@ -757,8 +765,8 @@ module Consensus = struct consensus_content.slot vs.consensus_state.preendorsements_seen with - | Some oph' -> - Error (Operation_conflict {existing = oph'; new_operation = oph}) + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) | None -> ok_unit let wrap_preendorsement_conflict = function @@ -789,12 +797,11 @@ module Consensus = struct match block_state.locked_round_evidence with | None -> Some (consensus_content.round, voting_power) | Some (_stored_round, evidences) -> - (* [_stored_round] is always equal to - [consensus_content.round]: this is ensured by - {!check_round_equal} in application and partial - application modes, and by - {!check_locked_round_evidence} in construction - mode. *) + (* [_stored_round] is always equal to [consensus_content.round]. + Indeed, this is ensured by {!check_round_equal} in + application and partial validation modes, and by + {!check_construction_preendorsement_round_consistency} in + construction mode. *) Some (consensus_content.round, evidences + voting_power)) in {block_state with locked_round_evidence} @@ -1125,7 +1132,7 @@ module Consensus = struct match expected_features.round with | Some _ -> (* When [expected_features.round] has a value (ie. in - application and partial application modes when the block + application and partial validation modes when the block fitness has a [locked_round], and always in mempool mode), [check_preendorsement] already checks that all preendorsements have this expected round, so checking @@ -1206,34 +1213,6 @@ end module Voting = struct open Validate_errors.Voting - (** Check that [record_proposals] below will not fail. - - This function is designed to be exclusively called by - [validate_proposals] further down this file. - - @return [Error Multiple_proposals] if [proposals] has more than - one element. *) - let check_testnet_dictator_proposals chain_id proposals = - (* This assertion should be ensured by the fact that - {!is_testnet_dictator} cannot be [true] on mainnet, but we - double check it because it is critical. *) - assert (Chain_id.(chain_id <> Constants.mainnet_id)) ; - match proposals with - | [] | [_] -> - (* In [record_proposals] below, the call to - {!Vote.init_current_proposal} (in the singleton list case) - cannot fail because {!Vote.clear_current_proposal} is called - right before. - - The calls to - {!Voting_period.Testnet_dictator.overwrite_current_kind} may - usually fail when the voting period is not - initialized. However, this cannot happen because the current - function is only called in [validate_proposals] after a - successful call to {!Voting_period.get_current}. *) - ok_unit - | _ :: _ :: _ -> error Testnet_dictator_multiple_proposals - let check_period_index ~expected period_index = error_unless Compare.Int32.(expected = period_index) @@ -1294,72 +1273,70 @@ module Voting = struct fail_when already_proposed (Already_proposed {proposal})) proposals - let check_period_kind_for_ballot current_period = - match current_period.Voting_period.kind with - | Exploration | Promotion -> ok_unit - | (Cooldown | Proposal | Adoption) as current -> - error - (Wrong_voting_period_kind - {current; expected = [Exploration; Promotion]}) + (** Check that the [apply_testnet_dictator_proposals] function in + {!module:Amendment} will not fail. - let check_current_proposal ctxt op_proposal = - let open Lwt_tzresult_syntax in - let* current_proposal = Vote.get_current_proposal ctxt in - fail_unless - (Protocol_hash.equal op_proposal current_proposal) - (Ballot_for_wrong_proposal - {current = current_proposal; submitted = op_proposal}) + The current function is designed to be exclusively called by + [check_proposals] right below. - let check_source_has_not_already_voted ctxt source = - let open Lwt_tzresult_syntax in - let*! has_ballot = Vote.has_recorded_ballot ctxt source in - fail_when has_ballot Already_submitted_a_ballot + @return [Error Testnet_dictator_multiple_proposals] if + [proposals] has more than one element. *) + let check_testnet_dictator_proposals chain_id proposals = + (* This assertion should be ensured by the fact that + {!Amendment.is_testnet_dictator} cannot be [true] on mainnet + (so the current function cannot be called there). However, we + still double check it because of its criticality. *) + assert (Chain_id.(chain_id <> Constants.mainnet_id)) ; + match proposals with + | [] | [_] -> + (* In [Amendment.apply_testnet_dictator_proposals], the call to + {!Vote.init_current_proposal} (in the singleton list case) + cannot fail because {!Vote.clear_current_proposal} is called + right before. - let check_ballot_source_is_registered ctxt source = - let open Lwt_tzresult_syntax in - let*! is_registered = Delegate.registered ctxt source in - fail_unless is_registered (Ballot_from_unregistered_delegate source) + The calls to + {!Voting_period.Testnet_dictator.overwrite_current_kind} may + usually fail when the voting period is not + initialized. However, this cannot happen here because the + current function is only called in [check_proposals] after a + successful call to {!Voting_period.get_current}. *) + ok_unit + | _ :: _ :: _ -> error Testnet_dictator_multiple_proposals (** Check that a Proposals operation can be safely applied. - @return [Error Wrong_voting_period_index] if the operation's - period and the [context]'s current period do not have the same - index. - - @return [Error Proposals_from_unregistered_delegate] if the - source is not a registered delegate. + @return [Error Wrong_voting_period_index] if the operation's + period and the current period in the {!type:context} do not have + the same index. - @return [Error Empty_proposals] if the list of proposals is empty. + @return [Error Proposals_from_unregistered_delegate] if the + source is not a registered delegate. - @return [Error Proposals_contain_duplicate] if the list of - proposals contains a duplicate element. + @return [Error Empty_proposals] if the list of proposals is empty. - @return [Error Wrong_voting_period_kind] if the voting period is - not of the Proposal kind. + @return [Error Proposals_contain_duplicate] if the list of + proposals contains a duplicate element. - @return [Error Source_not_in_vote_listings] if the source is not - in the vote listings. + @return [Error Wrong_voting_period_kind] if the voting period is + not of the Proposal kind. - @return [Error Already_proposed] if one of the proposals has - already been proposed by the source. + @return [Error Source_not_in_vote_listings] if the source is not + in the vote listings. - @return [Error Too_many_proposals] if the total count of - proposals submitted by the source in previous blocks, in previously - validated operations of the current block/mempool, and in the - operation to validate, exceeds - {!Constants.max_proposals_per_delegate}. + @return [Error Too_many_proposals] if the operation causes the + source's total number of proposals during the current voting + period to exceed {!Constants.max_proposals_per_delegate}. - @return [Error Conflict_already_proposed] if one of the - operation's proposals has already been submitted by the source in - the current block/mempool. + @return [Error Already_proposed] if one of the proposals has + already been proposed by the source in the current voting period. - @return [Error Testnet_dictator_multiple_proposals] if the source - is a testnet dictator and the operation contains more than one - proposal. + @return [Error Testnet_dictator_multiple_proposals] if the + source is a testnet dictator and the operation contains more than + one proposal. - @return [Error Operation.Missing_signature] or [Error - Operation.Invalid_signature] if the operation is unsigned or - incorrectly signed. *) + @return [Error Operation.Missing_signature] or [Error + Operation.Invalid_signature] if the operation is unsigned or + incorrectly signed. *) let check_proposals vi ~check_signature (operation : Kind.proposals operation) = let open Lwt_tzresult_syntax in @@ -1392,11 +1369,12 @@ module Voting = struct else return_unit (** Check that a Proposals operation is compatible with previously - validated voting operations in the current block/mempool.. + validated operations in the current block/mempool. - @return [Error Conflicting_proposals] if the current - block/mempool already contains a same source Proposals - operation. *) + @return [Error Operation_conflict] if the current block/mempool + already contains a Proposals operation from the same source + (regardless of whether this source is a testnet dictator or an + ordinary manager). *) let check_proposals_conflict vs oph (operation : Kind.proposals operation) = let open Tzresult_syntax in let (Single (Proposals {source; _})) = operation.protocol_data.contents in @@ -1432,33 +1410,56 @@ module Voting = struct in {vs with voting_state = {vs.voting_state with proposals_seen}} - (** Check that a Ballot operation can be safely applied. + let check_ballot_source_is_registered ctxt source = + let open Lwt_tzresult_syntax in + let*! is_registered = Delegate.registered ctxt source in + fail_unless is_registered (Ballot_from_unregistered_delegate source) + + let check_period_kind_for_ballot current_period = + match current_period.Voting_period.kind with + | Exploration | Promotion -> ok_unit + | (Cooldown | Proposal | Adoption) as current -> + error + (Wrong_voting_period_kind + {current; expected = [Exploration; Promotion]}) + + let check_current_proposal ctxt op_proposal = + let open Lwt_tzresult_syntax in + let* current_proposal = Vote.get_current_proposal ctxt in + fail_unless + (Protocol_hash.equal op_proposal current_proposal) + (Ballot_for_wrong_proposal + {current = current_proposal; submitted = op_proposal}) - @return [Error Ballot_from_unregistered_delegate] if the - source is not a registered delegate. + let check_source_has_not_already_voted ctxt source = + let open Lwt_tzresult_syntax in + let*! has_ballot = Vote.has_recorded_ballot ctxt source in + fail_when has_ballot Already_submitted_a_ballot - @return [Error Conflicting_ballot] if the source has already - submitted a ballot in the current block/mempool. + (** Check that a Ballot operation can be safely applied. + + @return [Error Ballot_from_unregistered_delegate] if the source + is not a registered delegate. - @return [Error Wrong_voting_period_index] if the operation's - period and the [context]'s current period do not have the same - index. + @return [Error Wrong_voting_period_index] if the operation's + period and the current period in the {!type:context} do not have + the same index. - @return [Error Wrong_voting_period_kind] if the voting period is - not of the Exploration or Promotion kind. + @return [Error Wrong_voting_period_kind] if the voting period is + not of the Exploration or Promotion kind. - @return [Error Ballot_for_wrong_proposal] if the operation's - proposal is different from the [context]'s current proposal. + @return [Error Ballot_for_wrong_proposal] if the operation's + proposal is different from the current proposal in the context. - @return [Error Already_submitted_a_ballot] if the source has - already voted. + @return [Error Already_submitted_a_ballot] if the source has + already voted during the current voting period. - @return [Error Source_not_in_vote_listings] if the source is not - in the vote listings. + @return [Error Source_not_in_vote_listings] if the source is not + in the vote listings. - @return [Error Operation.Missing_signature] or [Error - Operation.Invalid_signature] if the operation is unsigned or - incorrectly signed. *) + @return [Error Operation.Missing_signature] or [Error + Operation.Invalid_signature] if the operation is unsigned or + incorrectly signed. *) let check_ballot vi ~check_signature (operation : Kind.ballot operation) = let open Lwt_tzresult_syntax in let (Single (Ballot {source; period; proposal; ballot = _})) = @@ -1479,18 +1480,18 @@ module Voting = struct Lwt.return (Operation.check_signature public_key vi.chain_id operation)) (** Check that a Ballot operation is compatible with previously - validated voting operations in the current block/mempool. + validated operations in the current block/mempool. - @return [Error Conflicting_ballot] if the [delegate] has already - submitted a ballot in the current block/mempool. *) + @return [Error Operation_conflict] if the current block/mempool + already contains a Ballot operation from the same source. *) let check_ballot_conflict vs oph (operation : Kind.ballot operation) = let (Single (Ballot {source; _})) = operation.protocol_data.contents in match Signature.Public_key_hash.Map.find_opt source vs.voting_state.ballots_seen with | None -> ok_unit - | Some oph' -> - Error (Operation_conflict {existing = oph'; new_operation = oph}) + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) let wrap_ballot_conflict = function | Ok () -> ok_unit @@ -1538,8 +1539,8 @@ module Anonymous = struct vs.anonymous_state.activation_pkhs_seen with | None -> ok_unit - | Some oph' -> - Error (Operation_conflict {existing = oph'; new_operation = oph}) + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) let wrap_activate_account_conflict (operation : Kind.activate_account operation) = function @@ -1673,8 +1674,8 @@ module Anonymous = struct vs.anonymous_state.double_endorsing_evidences_seen with | None -> ok_unit - | Some oph' -> - Error (Operation_conflict {existing = oph'; new_operation = oph})) + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph})) let check_double_preendorsement_evidence_conflict vs oph (operation : Kind.double_preendorsement_evidence operation) = @@ -1828,8 +1829,8 @@ module Anonymous = struct vs.anonymous_state.double_baking_evidences_seen with | None -> ok_unit - | Some oph' -> - Error (Operation_conflict {existing = oph'; new_operation = oph}) + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) let add_double_baking_evidence vs oph (operation : Kind.double_baking_evidence operation) = @@ -1954,8 +1955,8 @@ module Anonymous = struct state.manager_state.managers_seen with | None -> ok_unit - | Some oph' -> - Error (Operation_conflict {existing = oph'; new_operation = oph}) + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) let wrap_drain_delegate_conflict (operation : Kind.drain_delegate Operation.t) = @@ -2012,8 +2013,8 @@ module Anonymous = struct vs.anonymous_state.seed_nonce_levels_seen with | None -> ok_unit - | Some oph' -> - Error (Operation_conflict {existing = oph'; new_operation = oph}) + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) let wrap_seed_nonce_revelation_conflict = function | Ok () -> ok_unit @@ -2057,8 +2058,8 @@ module Anonymous = struct let check_vdf_revelation_conflict vs oph = match vs.anonymous_state.vdf_solution_seen with | None -> ok_unit - | Some oph' -> - Error (Operation_conflict {existing = oph'; new_operation = oph}) + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) let wrap_vdf_revelation_conflict = function | Ok () -> ok_unit @@ -2446,7 +2447,7 @@ module Manager = struct (* Gas should no longer be consumed below this point, because it would not take into account any gas consumed during the pattern matching right above. If you really need to consume gas here, then you - need to make this pattern matching return the [remaining_gas].*) + must make this pattern matching return the [remaining_gas].*) let* balance, is_allocated = Contract.simulate_spending vi.ctxt @@ -2511,8 +2512,8 @@ module Manager = struct vs.manager_state.managers_seen with | None -> ok_unit - | Some oph' -> - Error (Operation_conflict {existing = oph'; new_operation = oph}) + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) let wrap_check_manager_operation_conflict (type kind) (operation : kind Kind.manager operation) = @@ -2644,7 +2645,7 @@ let begin_any_application ctxt chain_id ~predecessor_level in let payload_hash = block_header.protocol_data.contents.payload_hash in let predecessor_hash = block_header.shell.predecessor in - let application_info = + let block_finalization_info = { fitness; block_producer; @@ -2654,8 +2655,8 @@ let begin_any_application ctxt chain_id ~predecessor_level } in let mode = - if is_partial then Partial_validation application_info - else Application application_info + if is_partial then Partial_validation block_finalization_info + else Application block_finalization_info in let all_expected_consensus_features = Consensus.expected_features_for_application @@ -3050,14 +3051,12 @@ let validate_operation ?(check_signature = true) let {shell; protocol_data = Operation_data protocol_data} = packed_operation in - let validation_pass_opt = - Alpha_context.Operation.acceptable_pass packed_operation - in + let validation_pass_opt = Operation.acceptable_pass packed_operation in let* block_state = check_validation_pass_consistency info block_state validation_pass_opt in let block_state = record_operation block_state oph validation_pass_opt in - let operation : _ Alpha_context.operation = {shell; protocol_data} in + let operation : _ operation = {shell; protocol_data} in match (info.mode, validation_pass_opt) with | Partial_validation _, Some n when Compare.Int.(n <> Operation_repr.consensus_pass) -> @@ -3230,8 +3229,7 @@ let check_endorsement_power vi bs = (Validate_errors.Block.Not_enough_endorsements {required; provided}) let finalize_validate_block_header vi vs checkable_payload_hash - (block_header_contents : Alpha_context.Block_header.contents) round fitness - = + (block_header_contents : Block_header.contents) round fitness = let locked_round_evidence = Option.map (fun (preendorsement_round, preendorsement_count) -> @@ -3247,14 +3245,11 @@ let finalize_validate_block_header vi vs checkable_payload_hash ~consensus_threshold:(Constants.consensus_threshold vi.ctxt) let compute_payload_hash block_state - (block_header_contents : Alpha_context.Block_header.contents) predecessor = - let operations_hash = - Operation_list_hash.compute (List.rev block_state.recorded_operations_rev) - in + (block_header_contents : Block_header.contents) ~predecessor_hash = Block_payload.hash - ~predecessor - block_header_contents.payload_round - operations_hash + ~predecessor_hash + ~payload_round:block_header_contents.payload_round + (List.rev block_state.recorded_operations_rev) let finalize_block {info; block_state; _} = let open Lwt_tzresult_syntax in @@ -3267,7 +3262,7 @@ let finalize_block {info; block_state; _} = else ok_unit in let block_payload_hash = - compute_payload_hash block_state block_data_contents predecessor_hash + compute_payload_hash block_state block_data_contents ~predecessor_hash in let round = Fitness.round fitness in let*? () = @@ -3291,7 +3286,7 @@ let finalize_block {info; block_state; _} = | Construction {predecessor_round; predecessor_hash; round; block_data_contents; _} -> let block_payload_hash = - compute_payload_hash block_state block_data_contents predecessor_hash + compute_payload_hash block_state block_data_contents ~predecessor_hash in let locked_round_evidence = block_state.locked_round_evidence in let checkable_payload_hash = diff --git a/src/proto_alpha/lib_protocol/validate.mli b/src/proto_alpha/lib_protocol/validate.mli index 5bf2650d14f11a49149d79d31800d1589ffdedfd..ccfaaeac9596f096786460077537f245ee776de1 100644 --- a/src/proto_alpha/lib_protocol/validate.mli +++ b/src/proto_alpha/lib_protocol/validate.mli @@ -23,16 +23,136 @@ (* *) (*****************************************************************************) -(** The purpose of this module is to provide the {!validate_operation} - function, that decides quickly whether an operation may safely be - included in a block. See the function's description for further - information. - - This module also provide functions to check the validity of a - block and the consistency of its block_header. - - Most elements in this module are either used or wrapped in the - {!Main} module. *) +(** This module provides functions pertaining to the validation of + blocks and operations. Most elements in this module are either used + or wrapped in the {!Main} module (though some of them are also + directly used by the plugin). + + The purpose of validation is to decide quickly whether a block or + an operation is valid, with minimal computations and without + writing anything in the storage. A block is considered valid if it + can be applied without failure (see {!Apply}). An operation is + valid if it can be safely included in a block without causing it to + fail. Therefore, the current module is responsible for ensuring + that calling functions from {!Apply} on validated blocks and + operations will not fail. + + {2 Block validation} + + The process of validation of a block may be started by calling one + of the following functions, depending on the circumstances (aka + mode): + + - [begin_application] is used for the validation of a preexisting + block, typically received through the network, and usually in + preparation for its future application. + + - [begin_partial_validation] is used to quickly but partially + validate an existing block. It is intended for quickly assessing a + series of blocks in an alternate branch (multipass validation). For + this reason, in this mode, the initial {!Alpha_context.t} may be + based on an ancestor block of the block to validate, instead of + necessarily its predecessor as in other modes. + + - [begin_full_construction] is used for the construction of a new + block, typically by a baker. + + Then, [validate_operation] should be called on every operation in + the block (in order of validation pass: see + {!Operation_repr.acceptable_pass}). Lastly, [finalize_block] + performs final checks on the block; if this function succeeds then + the block is valid. + + {2 Validation state} + + The process of block validation relies on a [validation_state] + transmitted throughout the aforementioned function calls. More + precisely, this immutable functional state is initialized by the + [begin_...] functions, read and updated by [validate_operation] + (as in, a slightly different [validation_state] is returned), and + required by [finalize_block]. It consists in three fields: + + - [info] contains static information required by + [validate_operation] and [finalize_block], notably the initial + {!Alpha_context.t}. It is fully filled in by the [begin_...] + functions, then only read, never updated. + + - [operation_conflict_state] keeps track of every validated + operation in the block, so that it can detect any conflict between + operations (e.g. two manager operations from the same + source). Consequently, it is both filled in and read by + [validate_operation], but not used at all by [finalize_block]. + + - [block_state] registers global block metrics such as total gas + used or endorsement power. It is filled in by [validate_operation], + which also uses it, e.g. to immediately return an error if the + block gas limit is exceeded. It is also essential to several checks + in [finalize_block]. + + The immutability of the [validation_state] allows the caller to + pause, replay, or backtrack throughout the steps of the validation + process. + + {2 Operation validation} + + Operations may be validated either as part of the validation of a + block in which they are included (see above), or on their own: + + - [begin_partial_construction] allows to initialize a + [validation_state] for the validation of operations outside of the + process of validation of a block. It is intended for mempools (see + {!Mempool_validation}) and for some RPCs. The global block + properties such as total block gas and endorsement power are not + checked. Calling [finalize_block] on such a [validation_state] does + not make much sense and simply returns unit. + + - [begin_no_predecessor_info] is a special weaker version of + [begin_partial_construction]: see its own documentation below. + + Even outside of the context of a given block validation, the + validation of operations aims at deciding whether they could + theoretically be included in a future block. Indeed, for a mempool, + this means that they are worth transmitting to a baker and + propagating to peers; or for the caller of an RPC, it means that + the tested operations may be injected in the node. + + An important property to maintain is that applying (see + {!Apply.apply_operation}) any subset of validated operations should + always succeed, even if they are not applied in the same order as + they were validated (as long as the order of application respects + the validation passes ordering). In other words, for all operations + A and B that have both been validated: if A has an earlier or the + same validation pass as B, then applying A then B must succeed; and + if B has an earlier or the same validation pass as A, then applying + B then A must succeed. Some restrictions, such as + one-operation-per-manager-per-block (1M), have been introduced to + preserve this property, and are enforced with the help of the + [operation_conflict_state]. An important consequence of this + property is that a baker may select any subset of validated + operations to bake into a new block, which is then guaranteed to be + applicable (provided that it verifies some additional global + properties such as including enough (pre)endorsing power; the baker + is responsible for ensuring this). + + For a manager operation, validity is mainly solvability, ie. the + operation must be well-formed and we must be able to take its + fees. Indeed, this is sufficient for the safe inclusion of the + operation in a block: even if there is an error during the + subsequent application of the manager operation, this will cause + the operation to have no further effects, but won't impact the + success of the block's application. The solvability of a manager + operation notably requires that it is correctly signed: indeed, we + can't take anything from a manager without having checked their + signature. + + A non-manager operation is only valid if its effects can be fully + applied in an {!Alpha_context.t} without failure. Indeed, any error + during the application of such an operation would cause the whole + block to fail; unlike manager operations, there is no notion of + failing to have an effect without impacting the application of the + whole block. More detailled documentation on checks performed and + potential errors can be found in the [validate.ml] file for some + non-manager operations. *) open Alpha_context open Validate_errors @@ -40,35 +160,35 @@ open Validate_errors (** Static information required to validate blocks and operations. *) type info -(** State used to register operations effects used to establish - potential conflicts. This state is serializable which allows it to - be exchanged with another source. See {Mempool_validation} *) +(** State used to keep track of previously validated operations and + detect potential conflicts. This state is serializable which allows + it to be exchanged with another source. See {!Mempool_validation}. *) type operation_conflict_state (** Encoding for the [operation_conflict_state]. *) val operation_conflict_state_encoding : operation_conflict_state Data_encoding.t -(** State used to register global block validity dependent - effects. This state is used and updated by the - [validate_operation] function and will also be used during the - [finalize_block]. For instance, it registers inter-operations - checks (e.g. total gas used in the block so far). *) +(** State used to register global block properties which are relevant + to the validity of a block, e.g. the total gas used in the block so + far. This state is both used and updated by the [validate_operation] + function, and is also required by [finalize_block]. *) type block_state -(** Validation state *) +(** Validation state (see above). *) type validation_state = { info : info; operation_state : operation_conflict_state; block_state : block_state; } -(** Return the context stored in the state. Note that this is the - context at the beginning of the block / mempool: indeed, it is not - modified by [validate_operation]. *) +(** Return the context stored in the state. + + Note that this is the context at the beginning of the block / + mempool: indeed, it is not modified by [validate_operation]. *) val get_initial_ctxt : validation_state -> context -(** Initialize the {!info} and {!state} for the validation of an - existing block (in preparation for its future application). *) +(** Initialize the {!validation_state} for the validation of an + existing block, usually in preparation for its future application. *) val begin_application : context -> Chain_id.t -> @@ -78,13 +198,16 @@ val begin_application : Fitness.t -> validation_state tzresult Lwt.t -(** Initialize the {!info} and {!state} for the partial validation of +(** Initialize the {!validation_state} for the partial validation of an existing block. - Note that the given context may be based on an ancestor - block. Indeed, we may not have access to the predecessor context - when trying to quickly assess a series of blocks in a cousin branch - (multipass validation). *) + The partial validation mode is intended for quickly assessing a + series of blocks in a cousin branch (multipass + validation). Therefore, it is the only mode in which the given + {!type:context} may be based on any recent ancestor block of the + block to validate, instead of only its predecessor (where recent + means having a greater level than the [last_allowed_fork_level] of + the current head). *) val begin_partial_validation : context -> Chain_id.t -> @@ -94,8 +217,8 @@ val begin_partial_validation : Fitness.t -> validation_state tzresult Lwt.t -(** Initialize the {!info} and {!state} for the full - construction of a fresh block. *) +(** Initialize the {!validation_state} for the full construction of a + fresh block. *) val begin_full_construction : context -> Chain_id.t -> @@ -107,8 +230,10 @@ val begin_full_construction : Block_header.contents -> validation_state tzresult Lwt.t -(** Initialize the {!info} and {!state} for the partial - construction use mainly to implement the mempool. *) +(** Initialize the {!validation_state} for the validation of + operations outside of the process of validation of a block. The + partial construction mode is mainly used to implement the mempool + (see {!Mempool_validation}), but may also be used by some RPCs. *) val begin_partial_construction : context -> Chain_id.t -> @@ -117,72 +242,30 @@ val begin_partial_construction : grandparent_round:Round.t -> validation_state -(** Initialize the {!info} and {!state} without providing any - predecessor information. This will cause any preendorsement or - endorsement operation to fail, since we lack the information needed - to validate it. *) +(** Similar to [begin_partial_construction] but do not require + predecessor information that is essential to the validation of + preendorsement and endorsement operations. As a consequence, the + validation of these operations will always fail. + + This function is used by the plugin RPC [run_operation], which + does not support consensus operations anyway. *) val begin_no_predecessor_info : context -> Chain_id.t -> validation_state -(** Check the validity of the given operation; return an updated - {!state}. - - An operation is valid if it may be included in a block without - causing the block's application to fail. The purpose of this - function is to decide validity quickly, that is, without trying to - actually apply the operation (ie. compute modifications to the - context: see {!Apply.apply_operation}) and see whether it causes - an error. - - An operation's validity may be checked in different situations: - when we receive a block from a peer or we are constructing a fresh - block, we validate each operation in the block right before trying - to apply it; when a mempool receives an operation, it validates it - to decide whether the operation should be propagated (note that - for now, this only holds for manager operations, since - [validate_operation] is not implemented yet for other operations: - see below). See {!type:mode}. - - The [info] contains every information we need - about the status of the chain to validate an operation, notably the - context (of type {!Alpha_context.t}) at the end of the previous - block. This context is never updated by the validation of - operations, since validation is separate from application. Yet - sometimes, the presence of some previous operations in a block or a - mempool may render the current operation invalid. E.g. the - one-operation-per-manager-per-block restriction (1M) states that a - block is invalid if it contains two separate operations from the - same manager; therefore the validation of an operation will return - [Error Manager_restriction] if another operation by the same - manager has already been validated in the same block or mempool. In - order to track this kind of operation incompatibilities, we use a - [state] with minimal information that gets - updated during validation. - - For a manager operation, validity is solvability, ie. it must be - well-formed, and we need to be able to take its fees. Indeed, this - is sufficient for the safe inclusion of the operation in a block: - even if there is an error during the subsequent application of the - manager operation, this will cause the operation to have no further - effects, but won't impact the success of the block's - application. The solvability of a manager operation notably - includes it being correctly signed: indeed, we can't take anything - from a manager without having checked their signature. - - For non-manager operations, any error during the operation - application causes the whole block to fail. Therefore, the - validation of such an operation must ensure that its application - will fully succeed. - - @param check_signature indicates whether the signature - check should happen. It defaults to [true] because the signature - needs to be correct for the operation to be valid. This argument - exists for special cases where it is acceptable to bypass this - check, e.g.: - - - The mempool may keep track of operations whose signatures have - already been checked: if such an operation needs to be validated - again (typically when the head block changes), then the mempool may - call [validate_operation] with [check_signature:false]. +(** Check the validity of the given operation and return the updated + {!validation_state}. + + See the documentation at the top of this module on operation validation. + + @param check_signature indicates whether the signature check + should happen. It defaults to [true] because the signature needs to + be correct for the operation to be valid. This argument exists for + special cases where it is acceptable to bypass this check, e.g.: + + - A mempool implementation may keep track of operations whose + signatures have already been checked: if such an operation needs to + be validated again (typically when the head block changes), then + the mempool may call [validate_operation] with + [check_signature:false]. - The [run_operation] RPC provided by the plugin explicitly excludes signature checks: see its documentation in @@ -194,43 +277,63 @@ val validate_operation : packed_operation -> validation_state tzresult Lwt.t -(** Check the operation validity, see {!validate_operation} for - more information +(** Finish the validation of a block. + + This function should only be used after {!validate_operation} has + been called on every operation in the block. It checks the + consistency of the block_header with the information computed while + validating the block's operations (Endorsement power, payload hash, + etc.) Checks vary depending on the mode (ie. which of the + [begin_...] functions above was used to initialize the + [validation_state]). *) +val finalize_block : validation_state -> unit tzresult Lwt.t + +(** The remaining functions are intended for the mempool. + See {!Mempool_validation}. *) + +(** Check the operation validity, similarly to {!validate_operation}. + + However, this function does not check for conflicts with + previously validated operations, nor global block properties such + as the respect of the block gas limit. This allows the function to + only take an {!info} as input rather than a full {!validation_state}. - Note: Should only be called in mempool mode *) + This function is intended for {!Mempool_validation} exclusively. *) val check_operation : ?check_signature:bool -> info -> 'kind operation -> unit tzresult Lwt.t (** Check that the operation does not conflict with other operations - already validated and included in the {!operation_conflict_state} + already validated and recorded in the {!operation_conflict_state}. - Note: Should only be called in mempool mode *) + This function is intended for {!Mempool_validation} exclusively. *) val check_operation_conflict : operation_conflict_state -> Operation_hash.t -> 'kind operation -> (unit, operation_conflict) result -(** Add the operation in the {!operation_conflict_state}. The - operation should be validated before being added +(** Add a valid operation to the {!operation_conflict_state}. - Note: Should only be called in mempool mode *) + The operation should have been previously validated by calling + both {!check_operation} and {!check_operation_conflict}. + + This function is intended for {!Mempool_validation} exclusively. *) val add_valid_operation : operation_conflict_state -> Operation_hash.t -> 'kind operation -> operation_conflict_state -(** Remove the operation from the {!operation_conflict_state}. +(** Remove a valid operation from the {!operation_conflict_state}. + + Preconditions: + - The operation has already been validated and added to the + [operation_conflict_state]. + - The [operation_conflict_state] and other states used to validate + the operation have been initialized by calling + {!begin_partial_construction}. - Hypothesis: - - the [operation] has been validated and added to - [operation_conflict_state]; - - this function is only valid for the mempool mode. *) + This function is intended for {!Mempool_validation}, though it is + also called by the plugin. *) val remove_operation : operation_conflict_state -> 'kind operation -> operation_conflict_state - -(** Check the consistency of the block_header information with the one - computed (Endorsement power, payload hash, etc) while validating - the block operations. Checks vary depending on the mode. *) -val finalize_block : validation_state -> unit tzresult Lwt.t