ligo/vendors/ligo-utils/tezos-protocol-alpha/test/helpers/block.ml
2019-10-17 11:45:27 +02:00

419 lines
15 KiB
OCaml

(*****************************************************************************)
(* *)
(* Open Source License *)
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* Permission is hereby granted, free of charge, to any person obtaining a *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)
(* and/or sell copies of the Software, and to permit persons to whom the *)
(* Software is furnished to do so, subject to the following conditions: *)
(* *)
(* The above copyright notice and this permission notice shall be included *)
(* in all copies or substantial portions of the Software. *)
(* *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)
(* DEALINGS IN THE SOFTWARE. *)
(* *)
(*****************************************************************************)
open Protocol
module Proto_Nonce = Nonce (* Renamed otherwise is masked by Alpha_context *)
open Alpha_context
(* This type collects a block and the context that results from its application *)
type t = {
hash : Block_hash.t ;
header : Block_header.t ;
operations : Operation.packed list ;
context : Tezos_protocol_environment.Context.t ;
}
type block = t
let rpc_context block = {
Environment.Updater.block_hash = block.hash ;
block_header = block.header.shell ;
context = block.context ;
}
let rpc_ctxt =
new Environment.proto_rpc_context_of_directory
rpc_context rpc_services
(******** Policies ***********)
(* Policies are functions that take a block and return a tuple
[(account, level, timestamp)] for the [forge_header] function. *)
(* This type is used only to provide a simpler interface to the exterior. *)
type baker_policy =
| By_priority of int
| By_account of public_key_hash
| Excluding of public_key_hash list
let get_next_baker_by_priority priority block =
Alpha_services.Delegate.Baking_rights.get rpc_ctxt
~all:true
~max_priority:(priority+1) block >>=? fun bakers ->
let { Alpha_services.Delegate.Baking_rights.delegate = pkh ;
timestamp; _ } = List.find (fun { Alpha_services.Delegate.Baking_rights.priority = p ; _ } -> p = priority) bakers in
return (pkh, priority, Option.unopt_exn (Failure "") timestamp)
let get_next_baker_by_account pkh block =
Alpha_services.Delegate.Baking_rights.get rpc_ctxt
~delegates:[pkh]
~max_priority:256 block >>=? fun bakers ->
let { Alpha_services.Delegate.Baking_rights.delegate = pkh ;
timestamp ; priority ; _ } = List.hd bakers in
return (pkh, priority, Option.unopt_exn (Failure "") timestamp)
let get_next_baker_excluding excludes block =
Alpha_services.Delegate.Baking_rights.get rpc_ctxt
~max_priority:256 block >>=? fun bakers ->
let { Alpha_services.Delegate.Baking_rights.delegate = pkh ;
timestamp ; priority ; _ } =
List.find
(fun { Alpha_services.Delegate.Baking_rights.delegate ; _ } ->
not (List.mem delegate excludes))
bakers in
return (pkh, priority, Option.unopt_exn (Failure "") timestamp)
let dispatch_policy = function
| By_priority p -> get_next_baker_by_priority p
| By_account a -> get_next_baker_by_account a
| Excluding al -> get_next_baker_excluding al
let get_next_baker ?(policy = By_priority 0) = dispatch_policy policy
let get_endorsing_power b =
fold_left_s (fun acc (op: Operation.packed) ->
let Operation_data data = op.protocol_data in
match data.contents with
| Single Endorsement _ ->
Alpha_services.Delegate.Endorsing_power.get
rpc_ctxt b op Chain_id.zero >>=? fun endorsement_power ->
return (acc + endorsement_power)
| _ -> return acc)
0 b.operations
module Forge = struct
type header = {
baker : public_key_hash ; (* the signer of the block *)
shell : Block_header.shell_header ;
contents : Block_header.contents ;
}
let default_proof_of_work_nonce =
MBytes.create Constants.proof_of_work_nonce_size
let make_contents
?(proof_of_work_nonce = default_proof_of_work_nonce)
~priority ~seed_nonce_hash () =
Block_header.{ priority ;
proof_of_work_nonce ;
seed_nonce_hash }
let make_shell
~level ~predecessor ~timestamp ~fitness ~operations_hash =
Tezos_base.Block_header.{
level ;
predecessor ;
timestamp ;
fitness ;
operations_hash ;
(* We don't care of the following values, only the shell validates them. *)
proto_level = 0 ;
validation_passes = 0 ;
context = Context_hash.zero ;
}
let set_seed_nonce_hash seed_nonce_hash { baker ; shell ; contents } =
{ baker ; shell ; contents = { contents with seed_nonce_hash } }
let set_baker baker header = { header with baker }
let sign_header { baker ; shell ; contents } =
Account.find baker >>=? fun delegate ->
let unsigned_bytes =
Data_encoding.Binary.to_bytes_exn
Block_header.unsigned_encoding
(shell, contents) in
let signature =
Signature.sign ~watermark:Signature.(Block_header Chain_id.zero) delegate.sk unsigned_bytes in
Block_header.{ shell ; protocol_data = { contents ; signature } } |>
return
let forge_header
?(policy = By_priority 0)
?timestamp
?(operations = []) pred =
dispatch_policy policy pred >>=? fun (pkh, priority, _timestamp) ->
Alpha_services.Delegate.Minimal_valid_time.get
rpc_ctxt pred priority 0 >>=? fun expected_timestamp ->
let timestamp = Option.unopt ~default:expected_timestamp timestamp in
let level = Int32.succ pred.header.shell.level in
begin
match Fitness_repr.to_int64 pred.header.shell.fitness with
| Ok old_fitness ->
return (Fitness_repr.from_int64
(Int64.add (Int64.of_int 1) old_fitness))
| Error _ -> assert false
end >>=? fun fitness ->
begin
Alpha_services.Helpers.current_level ~offset:1l (rpc_ctxt) pred >>|? function
| { expected_commitment = true ; _ } -> Some (fst (Proto_Nonce.generate ()))
| { expected_commitment = false ; _ } -> None
end >>=? fun seed_nonce_hash ->
let hashes = List.map Operation.hash_packed operations in
let operations_hash = Operation_list_list_hash.compute
[Operation_list_hash.compute hashes] in
let shell = make_shell ~level ~predecessor:pred.hash
~timestamp ~fitness ~operations_hash in
let contents = make_contents ~priority ~seed_nonce_hash () in
return { baker = pkh ; shell ; contents }
(* compatibility only, needed by incremental *)
let contents
?(proof_of_work_nonce = default_proof_of_work_nonce)
?(priority = 0) ?seed_nonce_hash () =
{
Block_header.priority ;
proof_of_work_nonce ;
seed_nonce_hash ;
}
end
(********* Genesis creation *************)
(* Hard-coded context key *)
let protocol_param_key = [ "protocol_parameters" ]
let check_constants_consistency constants =
let open Constants_repr in
let { blocks_per_cycle ; blocks_per_commitment ;
blocks_per_roll_snapshot ; _ } = constants in
Error_monad.unless (blocks_per_commitment <= blocks_per_cycle)
(fun () -> failwith "Inconsistent constants : blocks per commitment must be \
less than blocks per cycle") >>=? fun () ->
Error_monad.unless (blocks_per_cycle >= blocks_per_roll_snapshot)
(fun () -> failwith "Inconsistent constants : blocks per cycle \
must be superior than blocks per roll snapshot") >>=?
return
let initial_context
?(with_commitments = false)
constants
header
initial_accounts
=
let open Tezos_protocol_005_PsBabyM1_parameters in
let bootstrap_accounts =
List.map (fun (Account.{ pk ; pkh ; _ }, amount) ->
Default_parameters.make_bootstrap_account (pkh, pk, amount)
) initial_accounts
in
let parameters =
Default_parameters.parameters_of_constants
~bootstrap_accounts
~with_commitments
constants in
let json = Default_parameters.json_of_parameters parameters in
let proto_params =
Data_encoding.Binary.to_bytes_exn Data_encoding.json json
in
Tezos_protocol_environment.Context.(
let empty = Memory_context.empty in
set empty ["version"] (MBytes.of_string "genesis") >>= fun ctxt ->
set ctxt protocol_param_key proto_params
) >>= fun ctxt ->
Main.init ctxt header
>|= Environment.wrap_error >>=? fun { context; _ } ->
return context
let genesis_with_parameters parameters =
let hash =
Block_hash.of_b58check_exn "BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU"
in
let shell = Forge.make_shell
~level:0l
~predecessor:hash
~timestamp:Time.Protocol.epoch
~fitness: (Fitness_repr.from_int64 0L)
~operations_hash: Operation_list_list_hash.zero in
let contents = Forge.make_contents
~priority:0
~seed_nonce_hash:None () in
let open Tezos_protocol_005_PsBabyM1_parameters in
let json = Default_parameters.json_of_parameters parameters in
let proto_params =
Data_encoding.Binary.to_bytes_exn Data_encoding.json json
in
Tezos_protocol_environment.Context.(
let empty = Memory_context.empty in
set empty ["version"] (MBytes.of_string "genesis") >>= fun ctxt ->
set ctxt protocol_param_key proto_params
) >>= fun ctxt ->
Main.init ctxt shell
>|= Environment.wrap_error >>=? fun { context; _ } ->
let block = { hash ;
header = { shell ;
protocol_data = {
contents = contents ;
signature = Signature.zero ;
} } ;
operations = [] ;
context ;
} in
return block
(* if no parameter file is passed we check in the current directory
where the test is run *)
let genesis
?with_commitments
?endorsers_per_block
?initial_endorsers
?min_proposal_quorum
(initial_accounts : (Account.t * Tez_repr.t) list)
=
if initial_accounts = [] then
Pervasives.failwith "Must have one account with a roll to bake";
let open Tezos_protocol_005_PsBabyM1_parameters in
let constants = Default_parameters.constants_test in
let endorsers_per_block =
Option.unopt ~default:constants.endorsers_per_block endorsers_per_block in
let initial_endorsers =
Option.unopt ~default:constants.initial_endorsers initial_endorsers in
let min_proposal_quorum =
Option.unopt ~default:constants.min_proposal_quorum min_proposal_quorum in
let constants = { constants with endorsers_per_block ; initial_endorsers ; min_proposal_quorum } in
(* Check there is at least one roll *)
begin try
let open Test_utils in
fold_left_s (fun acc (_, amount) ->
Environment.wrap_error @@
Tez_repr.(+?) acc amount >>?= fun acc ->
if acc >= constants.tokens_per_roll then
raise Exit
else return acc
) Tez_repr.zero initial_accounts >>=? fun _ ->
failwith "Insufficient tokens in initial accounts to create one roll"
with Exit -> return_unit
end >>=? fun () ->
check_constants_consistency constants >>=? fun () ->
let hash =
Block_hash.of_b58check_exn "BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU"
in
let shell = Forge.make_shell
~level:0l
~predecessor:hash
~timestamp:Time.Protocol.epoch
~fitness: (Fitness_repr.from_int64 0L)
~operations_hash: Operation_list_list_hash.zero in
let contents = Forge.make_contents
~priority:0
~seed_nonce_hash:None () in
initial_context
?with_commitments
constants
shell
initial_accounts
>>=? fun context ->
let block =
{ hash ;
header = {
shell = shell ;
protocol_data = {
contents = contents ;
signature = Signature.zero ;
} ;
};
operations = [] ;
context ;
}
in
return block
(********* Baking *************)
let apply header ?(operations = []) pred =
begin
let open Environment.Error_monad in
Main.begin_application
~chain_id: Chain_id.zero
~predecessor_context: pred.context
~predecessor_fitness: pred.header.shell.fitness
~predecessor_timestamp: pred.header.shell.timestamp
header >>=? fun vstate ->
fold_left_s
(fun vstate op ->
apply_operation vstate op >>=? fun (state, _result) ->
return state)
vstate operations >>=? fun vstate ->
Main.finalize_block vstate >>=? fun (validation, _result) ->
return validation.context
end >|= Environment.wrap_error >>|? fun context ->
let hash = Block_header.hash header in
{ hash ; header ; operations ; context }
let bake ?policy ?timestamp ?operation ?operations pred =
let operations =
match operation,operations with
| Some op, Some ops -> Some (op::ops)
| Some op, None -> Some [op]
| None, Some ops -> Some ops
| None, None -> None
in
Forge.forge_header ?timestamp ?policy ?operations pred >>=? fun header ->
Forge.sign_header header >>=? fun header ->
apply header ?operations pred
(********** Cycles ****************)
(* This function is duplicated from Context to avoid a cyclic dependency *)
let get_constants b =
Alpha_services.Constants.all rpc_ctxt b
let bake_n ?policy n b =
Error_monad.fold_left_s
(fun b _ -> bake ?policy b) b (1 -- n)
let bake_until_cycle_end ?policy b =
get_constants b >>=? fun Constants.{ parametric = { blocks_per_cycle ; _ } ; _ } ->
let current_level = b.header.shell.level in
let current_level = Int32.rem current_level blocks_per_cycle in
let delta = Int32.sub blocks_per_cycle current_level in
bake_n ?policy (Int32.to_int delta) b
let bake_until_n_cycle_end ?policy n b =
Error_monad.fold_left_s
(fun b _ -> bake_until_cycle_end ?policy b) b (1 -- n)
let bake_until_cycle ?policy cycle (b:t) =
get_constants b >>=? fun Constants.{ parametric = { blocks_per_cycle ; _ } ; _ } ->
let rec loop (b:t) =
let current_cycle =
let current_level = b.header.shell.level in
let current_cycle = Int32.div current_level blocks_per_cycle in
current_cycle
in
if Int32.equal (Cycle.to_int32 cycle) current_cycle then
return b
else
bake_until_cycle_end ?policy b >>=? fun b ->
loop b
in
loop b