Shell: Liveness of operations

Operations now include a block hash in their header. Such an operation
could only be included in a successor of this block.

Furthermore, when validating a block, the economic protocol now
returns---together with the context---an integer `max_operations_ttl`.
Then, when validating a successor, the shell will fail if it contains
an operation whose header's block hash is not one the
`max_operations_ttl` predecessors of the block.

As a bonus, the shell is now able to detect and forbid replayed
operations. Then, we might decide to remove some replay
detection-mechanism that we previously implemented in the economic
protocol.
This commit is contained in:
Grégoire Henry 2017-04-20 08:49:14 +02:00
parent 329c8b185a
commit 2bc63854a8
22 changed files with 280 additions and 183 deletions

View File

@ -53,12 +53,7 @@ val inject_protocol:
module Blocks : sig
type block = [
| `Genesis
| `Head of int | `Prevalidation
| `Test_head of int | `Test_prevalidation
| `Hash of Block_hash.t
]
type block = Node_rpc_services.Blocks.block
val net_id:
config ->

View File

@ -124,6 +124,7 @@ let inject_endorsement cctxt
Client_proto_rpcs.Helpers.Forge.Delegate.endorsement cctxt.rpc_config
block
~net_id:bi.net_id
~branch:bi.hash
~source
~block:bi.hash
~slot:slot

View File

@ -11,15 +11,16 @@ open Cli_entries
open Tezos_context
open Logging.Client.Revelation
let inject_seed_nonce_revelation cctxt block ?force ?async nonces =
let inject_seed_nonce_revelation rpc_config block ?force ?async nonces =
let operations =
List.map
(fun (level, nonce) ->
Seed_nonce_revelation { level ; nonce }) nonces in
Client_node_rpcs.Blocks.net_id cctxt block >>=? fun net_id ->
Client_proto_rpcs.Helpers.Forge.Anonymous.operations cctxt
block ~net_id operations >>=? fun bytes ->
Client_node_rpcs.inject_operation cctxt ?force ?async bytes >>=? fun oph ->
let block = Client_rpcs.last_mined_block block in
Client_node_rpcs.Blocks.info rpc_config block >>=? fun bi ->
Client_proto_rpcs.Helpers.Forge.Anonymous.operations rpc_config
block ~net_id:bi.net_id ~branch:bi.hash operations >>=? fun bytes ->
Client_node_rpcs.inject_operation rpc_config ?force ?async bytes >>=? fun oph ->
return oph
type Error_monad.error += Bad_revelation

View File

@ -17,11 +17,31 @@ module Ed25519 = Environment.Ed25519
let get_balance cctxt block contract =
Client_proto_rpcs.Context.Contract.balance cctxt block contract
let rec find_predecessor rpc_config h n =
if n <= 0 then
return (`Hash h)
else
Client_node_rpcs.Blocks.predecessor rpc_config (`Hash h) >>=? fun h ->
find_predecessor rpc_config h (n-1)
let get_branch rpc_config block branch =
let branch = Utils.unopt ~default:0 branch in (* TODO export parameter *)
let block = Client_rpcs.last_mined_block block in
begin
match block with
| `Head n -> return (`Head (n+branch))
| `Test_head n -> return (`Test_head (n+branch))
| `Hash h -> find_predecessor rpc_config h branch
| `Genesis -> return `Genesis
end >>=? fun block ->
Client_node_rpcs.Blocks.info rpc_config block >>=? fun { net_id ; hash } ->
return (net_id, hash)
let transfer rpc_config
block ?force
block ?force ?branch
~source ~src_pk ~src_sk ~destination ?arg ~amount ~fee () =
let open Cli_entries in
Client_node_rpcs.Blocks.net_id rpc_config block >>=? fun net_id ->
get_branch rpc_config block branch >>=? fun (net_id, branch) ->
begin match arg with
| Some arg ->
Client_proto_programs.parse_data arg >>=? fun arg ->
@ -33,7 +53,7 @@ let transfer rpc_config
let counter = Int32.succ pcounter in
Client_proto_rpcs.Helpers.Forge.Manager.transaction
rpc_config block
~net_id ~source ~sourcePubKey:src_pk ~counter ~amount
~net_id ~branch ~source ~sourcePubKey:src_pk ~counter ~amount
~destination ?parameters ~fee () >>=? fun bytes ->
Client_node_rpcs.Blocks.predecessor rpc_config block >>=? fun predecessor ->
let signature = Ed25519.sign src_sk bytes in
@ -66,22 +86,22 @@ let originate rpc_config ?force ~block ?signature bytes =
(List.length contracts)
let originate_account rpc_config
block ?force
block ?force ?branch
~source ~src_pk ~src_sk ~manager_pkh
?delegatable ?spendable ?delegate ~balance ~fee () =
Client_node_rpcs.Blocks.net_id rpc_config block >>=? fun net_id ->
get_branch rpc_config block branch >>=? fun (net_id, branch) ->
Client_proto_rpcs.Context.Contract.counter
rpc_config block source >>=? fun pcounter ->
let counter = Int32.succ pcounter in
Client_proto_rpcs.Helpers.Forge.Manager.origination rpc_config block
~net_id ~source ~sourcePubKey:src_pk ~managerPubKey:manager_pkh
~net_id ~branch ~source ~sourcePubKey:src_pk ~managerPubKey:manager_pkh
~counter ~balance ?spendable
?delegatable ?delegatePubKey:delegate ~fee () >>=? fun bytes ->
let signature = Ed25519.sign src_sk bytes in
originate rpc_config ?force ~block ~signature bytes
let originate_contract rpc_config
block ?force
block ?force ?branch
~source ~src_pk ~src_sk ~manager_pkh ~balance ?delegatable ?delegatePubKey
~(code:Script.code) ~init ~fee () =
Client_proto_programs.parse_data init >>=? fun storage ->
@ -89,33 +109,33 @@ let originate_contract rpc_config
Client_proto_rpcs.Context.Contract.counter
rpc_config block source >>=? fun pcounter ->
let counter = Int32.succ pcounter in
Client_node_rpcs.Blocks.net_id rpc_config block >>=? fun net_id ->
get_branch rpc_config block branch >>=? fun (net_id, branch) ->
Client_proto_rpcs.Helpers.Forge.Manager.origination rpc_config block
~net_id ~source ~sourcePubKey:src_pk ~managerPubKey:manager_pkh
~net_id ~branch ~source ~sourcePubKey:src_pk ~managerPubKey:manager_pkh
~counter ~balance ~spendable:!spendable
?delegatable ?delegatePubKey
~script:{ code ; storage } ~fee () >>=? fun bytes ->
let signature = Ed25519.sign src_sk bytes in
originate rpc_config ?force ~block ~signature bytes
let faucet rpc_config block ?force ~manager_pkh () =
Client_node_rpcs.Blocks.net_id rpc_config block >>=? fun net_id ->
let faucet rpc_config block ?force ?branch ~manager_pkh () =
get_branch rpc_config block branch >>=? fun (net_id, branch) ->
Client_proto_rpcs.Context.faucet_counter rpc_config block >>=? fun pcounter ->
let counter = Int32.succ pcounter in
Client_proto_rpcs.Helpers.Forge.Anonymous.faucet
rpc_config block ~net_id ~id:manager_pkh counter >>=? fun bytes ->
rpc_config block ~net_id ~branch ~id:manager_pkh counter >>=? fun bytes ->
originate rpc_config ?force ~block bytes
let delegate_contract rpc_config
block ?force
block ?force ?branch
~source ?src_pk ~manager_sk
~fee delegate_opt =
Client_node_rpcs.Blocks.net_id rpc_config block >>=? fun net_id ->
get_branch rpc_config block branch >>=? fun (net_id, branch) ->
Client_proto_rpcs.Context.Contract.counter
rpc_config block source >>=? fun pcounter ->
let counter = Int32.succ pcounter in
Client_proto_rpcs.Helpers.Forge.Manager.delegation rpc_config block
~net_id ~source ?sourcePubKey:src_pk ~counter ~fee delegate_opt
~net_id ~branch ~source ?sourcePubKey:src_pk ~counter ~fee delegate_opt
>>=? fun bytes ->
let signature = Environment.Ed25519.sign manager_sk bytes in
let signed_bytes = MBytes.concat bytes signature in
@ -125,18 +145,6 @@ let delegate_contract rpc_config
assert (Operation_hash.equal oph injected_oph) ;
return oph
let dictate rpc_config block command seckey =
Client_node_rpcs.Blocks.net_id rpc_config block >>=? fun net_id ->
Client_proto_rpcs.Helpers.Forge.Dictator.operation
rpc_config block ~net_id command >>=? fun bytes ->
let signature = Ed25519.sign seckey bytes in
let signed_bytes = MBytes.concat bytes signature in
let oph = Operation_hash.hash_bytes [ signed_bytes ] in
Client_node_rpcs.inject_operation
rpc_config signed_bytes >>=? fun injected_oph ->
assert (Operation_hash.equal oph injected_oph) ;
return oph
let list_contract_labels cctxt block =
Client_proto_rpcs.Context.Contract.list
cctxt.rpc_config block >>=? fun contracts ->
@ -207,9 +215,11 @@ let group =
title = "Block contextual commands (see option -block)" }
let dictate rpc_config block command seckey =
Client_node_rpcs.Blocks.net_id rpc_config block >>=? fun net_id ->
let block = Client_rpcs.last_mined_block block in
Client_node_rpcs.Blocks.info
rpc_config block >>=? fun { net_id ; hash = branch } ->
Client_proto_rpcs.Helpers.Forge.Dictator.operation
rpc_config block ~net_id command >>=? fun bytes ->
rpc_config block ~net_id ~branch command >>=? fun bytes ->
let signature = Ed25519.sign seckey bytes in
let signed_bytes = MBytes.concat bytes signature in
let oph = Operation_hash.hash_bytes [ signed_bytes ] in

View File

@ -19,6 +19,7 @@ val transfer:
Client_rpcs.config ->
Client_proto_rpcs.block ->
?force:bool ->
?branch:int ->
source:Contract.t ->
src_pk:public_key ->
src_sk:secret_key ->
@ -32,6 +33,7 @@ val originate_account:
Client_rpcs.config ->
Client_proto_rpcs.block ->
?force:bool ->
?branch:int ->
source:Contract.t ->
src_pk:public_key ->
src_sk:secret_key ->
@ -47,6 +49,7 @@ val originate_contract:
Client_rpcs.config ->
Client_proto_rpcs.block ->
?force:bool ->
?branch:int ->
source:Contract.t ->
src_pk:public_key ->
src_sk:secret_key ->
@ -63,6 +66,7 @@ val delegate_contract:
Client_rpcs.config ->
Client_proto_rpcs.block ->
?force:bool ->
?branch:int ->
source:Contract.t ->
?src_pk:public_key ->
manager_sk:secret_key ->

View File

@ -190,25 +190,25 @@ module Helpers = struct
module Manager = struct
let operations cctxt
block ~net_id ~source ?sourcePubKey ~counter ~fee operations =
block ~net_id ~branch ~source ?sourcePubKey ~counter ~fee operations =
let ops =
Manager_operations { source ; public_key = sourcePubKey ;
counter ; operations ; fee } in
(call_error_service1 cctxt Services.Helpers.Forge.operations block
({net_id}, Sourced_operations ops))
({net_id ; branch }, Sourced_operations ops))
let transaction cctxt
block ~net_id ~source ?sourcePubKey ~counter
block ~net_id ~branch ~source ?sourcePubKey ~counter
~amount ~destination ?parameters ~fee ()=
operations cctxt block ~net_id ~source ?sourcePubKey ~counter ~fee
operations cctxt block ~net_id ~branch ~source ?sourcePubKey ~counter ~fee
Tezos_context.[Transaction { amount ; parameters ; destination }]
let origination cctxt
block ~net_id
block ~net_id ~branch
~source ?sourcePubKey ~counter
~managerPubKey ~balance
?(spendable = true)
?(delegatable = true)
?delegatePubKey ?script ~fee () =
operations cctxt block ~net_id ~source ?sourcePubKey ~counter ~fee
operations cctxt block ~net_id ~branch ~source ?sourcePubKey ~counter ~fee
Tezos_context.[
Origination { manager = managerPubKey ;
delegate = delegatePubKey ;
@ -218,53 +218,53 @@ module Helpers = struct
credit = balance }
]
let delegation cctxt
block ~net_id ~source ?sourcePubKey ~counter ~fee delegate =
operations cctxt block ~net_id ~source ?sourcePubKey ~counter ~fee
block ~net_id ~branch ~source ?sourcePubKey ~counter ~fee delegate =
operations cctxt block ~net_id ~branch ~source ?sourcePubKey ~counter ~fee
Tezos_context.[Delegation delegate]
end
module Delegate = struct
let operations cctxt
block ~net_id ~source operations =
block ~net_id ~branch ~source operations =
let ops = Delegate_operations { source ; operations } in
(call_error_service1 cctxt Services.Helpers.Forge.operations block
({net_id}, Sourced_operations ops))
({net_id ; branch}, Sourced_operations ops))
let endorsement cctxt
b ~net_id ~source ~block ~slot () =
operations cctxt b ~net_id ~source
b ~net_id ~branch ~source ~block ~slot () =
operations cctxt b ~net_id ~branch ~source
Tezos_context.[Endorsement { block ; slot }]
let proposals cctxt
b ~net_id ~source ~period ~proposals () =
operations cctxt b ~net_id ~source
b ~net_id ~branch ~source ~period ~proposals () =
operations cctxt b ~net_id ~branch ~source
Tezos_context.[Proposals { period ; proposals }]
let ballot cctxt
b ~net_id ~source ~period ~proposal ~ballot () =
operations cctxt b ~net_id ~source
b ~net_id ~branch ~source ~period ~proposal ~ballot () =
operations cctxt b ~net_id ~branch ~source
Tezos_context.[Ballot { period ; proposal ; ballot }]
end
module Dictator = struct
let operation cctxt
block ~net_id operation =
block ~net_id ~branch operation =
let op = Dictator_operation operation in
(call_error_service1 cctxt Services.Helpers.Forge.operations block
({net_id}, Sourced_operations op))
({net_id ; branch}, Sourced_operations op))
let activate cctxt
b ~net_id hash =
operation cctxt b ~net_id (Activate hash)
b ~net_id ~branch hash =
operation cctxt b ~net_id ~branch (Activate hash)
let activate_testnet cctxt
b ~net_id hash =
operation cctxt b ~net_id (Activate_testnet hash)
b ~net_id ~branch hash =
operation cctxt b ~net_id ~branch (Activate_testnet hash)
end
module Anonymous = struct
let operations cctxt block ~net_id operations =
let operations cctxt block ~net_id ~branch operations =
(call_error_service1 cctxt Services.Helpers.Forge.operations block
({net_id}, Anonymous_operations operations))
({net_id ; branch}, Anonymous_operations operations))
let seed_nonce_revelation cctxt
block ~net_id ~level ~nonce () =
operations cctxt block ~net_id [Seed_nonce_revelation { level ; nonce }]
block ~net_id ~branch ~level ~nonce () =
operations cctxt block ~net_id ~branch [Seed_nonce_revelation { level ; nonce }]
let faucet cctxt
block ~net_id ~id counter =
block ~net_id ~branch ~id counter =
let nonce = Sodium.Random.Bigbytes.generate 16 in
operations cctxt block ~net_id [Faucet { id ; counter ; nonce }]
operations cctxt block ~net_id ~branch [Faucet { id ; counter ; nonce }]
end
let empty_proof_of_work_nonce =
MBytes.of_string

View File

@ -10,12 +10,7 @@
val string_of_errors: error list -> string
val handle_error: Client_commands.context -> 'a tzresult -> 'a Lwt.t
type block = [
| `Genesis
| `Head of int | `Prevalidation
| `Test_head of int | `Test_prevalidation
| `Hash of Block_hash.t
]
type block = Node_rpc_services.Blocks.block
val header:
Client_rpcs.config -> block -> Block_header.t tzresult Lwt.t
@ -208,6 +203,7 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
source:Contract.t ->
?sourcePubKey:public_key ->
counter:int32 ->
@ -218,6 +214,7 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
source:Contract.t ->
?sourcePubKey:public_key ->
counter:int32 ->
@ -230,6 +227,7 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
source:Contract.t ->
?sourcePubKey:public_key ->
counter:int32 ->
@ -246,6 +244,7 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
source:Contract.t ->
?sourcePubKey:public_key ->
counter:int32 ->
@ -258,18 +257,21 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
dictator_operation ->
MBytes.t tzresult Lwt.t
val activate:
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
Protocol_hash.t ->
MBytes.t tzresult Lwt.t
val activate_testnet:
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
Protocol_hash.t ->
MBytes.t tzresult Lwt.t
end
@ -278,6 +280,7 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
source:public_key ->
delegate_operation list ->
MBytes.t tzresult Lwt.t
@ -285,6 +288,7 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
source:public_key ->
block:Block_hash.t ->
slot:int ->
@ -293,6 +297,7 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
source:public_key ->
period:Voting_period.t ->
proposals:Hash.Protocol_hash.t list ->
@ -301,6 +306,7 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
source:public_key ->
period:Voting_period.t ->
proposal:Hash.Protocol_hash.t ->
@ -312,12 +318,14 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
anonymous_operation list ->
MBytes.t tzresult Lwt.t
val seed_nonce_revelation:
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
level:Raw_level.t ->
nonce:Nonce.t ->
unit -> MBytes.t tzresult Lwt.t
@ -325,6 +333,7 @@ module Helpers : sig
Client_rpcs.config ->
block ->
net_id:Net_id.t ->
branch:Block_hash.t ->
id:public_key_hash ->
int32 -> MBytes.t tzresult Lwt.t
end
@ -335,17 +344,6 @@ module Helpers : sig
seed_nonce_hash: Nonce_hash.t ->
?proof_of_work_nonce: MBytes.t ->
unit -> MBytes.t tzresult Lwt.t
(** [block cctxt root ~net ~predecessor ~timestamp ~fitness
~operations ~level ~priority ~seed_nonce_hash
~proof_of_work_nonce ()] returns the binary serialization of
a block header (comprising the shell and protocol-specific
part), rooted at [root], belonging to [net], with
predecessor [predecessor], [timestamp], [fitness],
associated operations [operations], level [level] (the
protocol cannot deduce it from [predecessor] on its own),
priority [priority] (the priority of this miner in the
mining queue associated to [level]), [seed_nonce_hash] (the
chosen seed that we will reveal in the next cycle). *)
end
module Parse : sig

View File

@ -230,7 +230,7 @@ module RPC = struct
Lwt.return v
else
State.Block.predecessor v >>= function
| None -> Lwt.fail Not_found
| None -> Lwt.return v
| Some v -> predecessor net_db (n-1) v
let block_info node (block: block) =

View File

@ -77,6 +77,12 @@ let create net_db =
let pending = Operation_hash.Table.create 53 in
let head = ref head in
let operations = ref empty_result in
Chain_traversal.live_blocks
!head
(State.Block.max_operations_ttl !head)
>>= fun (live_blocks, live_operations) ->
let live_blocks = ref live_blocks in
let live_operations = ref live_operations in
let running_validation = ref Lwt.return_unit in
let unprocessed = ref Operation_hash.Set.empty in
let broadcast_unprocessed = ref false in
@ -98,22 +104,24 @@ let create net_db =
if Operation_hash.Set.is_empty !unprocessed then
Lwt.return ()
else
(* We assume that `!unprocessed` does not contain any operations
from `!operations`. *)
let ops = !unprocessed in
let broadcast = !broadcast_unprocessed in
unprocessed := Operation_hash.Set.empty ;
broadcast_unprocessed := false ;
let ops = Operation_hash.Set.diff ops !live_operations in
live_operations := Operation_hash.Set.(fold add) !live_operations ops ;
running_validation := begin
begin
Lwt_list.map_p
Lwt_list.filter_map_p
(fun h ->
Distributed_db.Operation.read_opt net_db h >>= function
| Some po ->
| Some po when Block_hash.Set.mem po.shell.branch !live_blocks ->
(* FIXME add the operation on a bounded set of
to-be-ignored operations.*)
Distributed_db.Operation.clear net_db h ;
Lwt.return_some (h, po)
| None -> Lwt.return_none)
| Some _ | None -> Lwt.return_none)
(Operation_hash.Set.elements ops) >>= fun rops ->
let rops = Utils.unopt_list rops in
(Lwt.return !validation_state >>=? fun validation_state ->
(prevalidate validation_state ~sort:true rops >>= return)) >>= function
| Ok (state, r) -> Lwt.return (Ok state, r)
@ -165,9 +173,6 @@ let create net_db =
let prevalidation_worker =
let rec worker_loop () =
(* TODO cleanup the mempool from outdated operation (1h like
Bitcoin ?). And log the removal in some statistic associated
to then peers that informed us of the operation. *)
(* TODO lookup in `!pending` for 'outdated' ops and re-add them
in `unprocessed` (e.g. if the previous tentative was
more 5 seconds ago) *)
@ -229,13 +234,12 @@ let create net_db =
Lwt.return_unit
end
| `Register (gid, ops) ->
Lwt_list.filter_p
(fun op ->
Distributed_db.Operation.known net_db op >|= not)
ops >>= fun new_ops ->
let known_ops, unknown_ops =
List.partition
(fun op -> Operation_hash.Table.mem pending op) new_ops in
(fun op ->
Operation_hash.Table.mem pending op
|| Operation_hash.Set.mem op !live_operations)
ops in
let fetch op =
Distributed_db.Operation.fetch
net_db ~peer:gid op () >>= fun _op ->
@ -260,6 +264,10 @@ let create net_db =
| `Flush (new_head : State.Block.t) ->
list_pendings ~from_block:!head ~to_block:new_head
(preapply_result_operations !operations) >>= fun new_mempool ->
Chain_traversal.live_blocks
new_head
(State.Block.max_operations_ttl new_head)
>>= fun (new_live_blocks, new_live_operations) ->
lwt_debug "flush %a (mempool: %d)"
Block_hash.pp_short (State.Block.hash new_head)
(Operation_hash.Set.cardinal new_mempool) >>= fun () ->
@ -269,6 +277,8 @@ let create net_db =
broadcast_unprocessed := false ;
unprocessed := new_mempool ;
timestamp := Time.now () ;
live_blocks := new_live_blocks ;
live_operations := new_live_operations ;
(* Reset the prevalidation context. *)
reset_validation_state new_head !timestamp)
q >>= fun () ->

View File

@ -296,6 +296,8 @@ module Block = struct
let message { contents = { message } } = message
let operation_list_count { contents = { operation_list_count } } =
operation_list_count
let max_operations_ttl { contents = { max_operations_ttl } } =
max_operations_ttl
let known_valid net_state hash =
Shared.use net_state.block_store begin fun store ->

View File

@ -120,6 +120,7 @@ module Block : sig
val net_id: t -> Net_id.t
val level: t -> Int32.t
val message: t -> string
val max_operations_ttl: t -> int
val predecessor: t -> block option Lwt.t

View File

@ -155,6 +155,8 @@ type error +=
| Non_increasing_fitness
| Wrong_level of Int32.t * Int32.t
| Wrong_proto_level of int * int
| Replayed_operation of Operation_hash.t
| Outdated_operation of Operation_hash.t * Block_hash.t
let () =
Error_monad.register_error_kind
@ -204,7 +206,35 @@ let () =
(req "expected" uint8)
(req "provided" uint8))
(function Wrong_proto_level (e, g) -> Some (e, g) | _ -> None)
(fun (e, g) -> Wrong_proto_level (e, g))
(fun (e, g) -> Wrong_proto_level (e, g)) ;
register_error_kind
`Permanent
~id:"validator.replayed_operation"
~title:"Replayed operation"
~description:"The block contains an operation that was previously \
included in the chain"
~pp:(fun ppf oph ->
Format.fprintf ppf
"The operation %a was previously included in the chain."
Operation_hash.pp oph)
Data_encoding.(obj1 (req "hash" Operation_hash.encoding))
(function Replayed_operation oph -> Some oph | _ -> None)
(function oph -> Replayed_operation oph) ;
register_error_kind
`Permanent
~id:"validator.outdated_operations"
~title:"Outdated operation"
~description:"The block contains an operation which is outdated."
~pp:(fun ppf (oph, bh)->
Format.fprintf ppf
"The operation %a is outdated (%a)"
Operation_hash.pp oph
Block_hash.pp bh)
Data_encoding.(obj2
(req "operation" Operation_hash.encoding)
(req "block" Block_hash.encoding))
(function Outdated_operation (oph, bh) -> Some (oph, bh) | _ -> None)
(function (oph, bh) -> Outdated_operation (oph, bh))
let apply_block net_state db
(pred: State.Block.t) hash (block: Block_header.t) =
@ -215,8 +245,7 @@ let apply_block net_state db
lwt_log_notice "validate block %a (after %a), net %a"
Block_hash.pp_short hash
Block_hash.pp_short block.shell.predecessor
Net_id.pp id
>>= fun () ->
Net_id.pp id >>= fun () ->
fail_unless
(Int32.succ pred_header.shell.level = block.shell.level)
(Wrong_level (Int32.succ pred_header.shell.level,
@ -246,6 +275,29 @@ let apply_block net_state db
else
return ()
end >>=? fun () ->
begin
Chain_traversal.live_blocks
pred (State.Block.max_operations_ttl pred) >>= fun (live_blocks,
live_operations) ->
let rec assert_no_duplicates live_operations = function
| [] -> return ()
| oph :: ophs ->
if Operation_hash.Set.mem oph live_operations then
fail (Replayed_operation oph)
else
assert_no_duplicates
(Operation_hash.Set.add oph live_operations) ophs in
let assert_live operations =
List.fold_left
(fun acc op ->
acc >>=? fun () ->
fail_unless
(Block_hash.Set.mem op.Operation.shell.branch live_blocks)
(Outdated_operation (Operation.hash op, op.shell.branch)))
(return ()) operations in
assert_no_duplicates live_operations operation_hashes >>=? fun () ->
assert_live operations
end >>=? fun () ->
Context.get_protocol pred_context >>= fun pred_protocol_hash ->
begin
match Updater.get pred_protocol_hash with
@ -295,6 +347,13 @@ let apply_block net_state db
expected = block.shell.fitness ;
found = new_context.fitness ;
}) >>=? fun () ->
let max_operations_ttl =
max 0
(min
((State.Block.max_operations_ttl pred)+1)
new_context.max_operations_ttl) in
let new_context =
{ new_context with max_operations_ttl } in
lwt_log_info "validation of %a: success"
Block_hash.pp_short hash >>= fun () ->
return new_context

View File

@ -364,8 +364,7 @@ let parse hash (op: Operation.t) =
Encoding.signed_proto_operation_encoding
op.proto with
| Some (contents, signature) ->
let shell = { Operation.net_id = op.shell.net_id } in
ok { hash ; shell ; contents ; signature }
ok { hash ; shell = op.shell ; contents ; signature }
| None -> error Cannot_parse_operation
type error += Invalid_signature (* `Permanent *)

View File

@ -40,6 +40,7 @@ module Operation : sig
type shell_header = {
net_id: Net_id.t ;
branch: Block_hash.t ;
}
val shell_header_encoding: shell_header Data_encoding.t

View File

@ -93,14 +93,17 @@ module Operation = struct
type shell_header = {
net_id: Net_id.t ;
branch: Block_hash.t ;
}
let shell_header_encoding =
let open Data_encoding in
conv
(fun { net_id } -> net_id)
(fun net_id -> { net_id })
(obj1 (req "net_id" Net_id.encoding))
(fun { net_id ; branch } -> net_id, branch)
(fun (net_id, branch) -> { net_id ; branch })
(obj2
(req "net_id" Net_id.encoding)
(req "branch" Block_hash.encoding))
type t = {
shell: shell_header ;

View File

@ -40,6 +40,7 @@ module Operation : sig
type shell_header = {
net_id: Net_id.t ;
branch: Block_hash.t ;
}
val shell_header_encoding: shell_header Data_encoding.t

View File

@ -17,6 +17,8 @@ S ../../src/proto
B ../../src/proto
S ../../src/client
B ../../src/client
S ../../src/client/embedded
B ../../src/client/embedded
S ../../src/client/embedded/alpha
B ../../src/client/embedded/alpha
S ../../src/client/embedded/alpha/baker

View File

@ -250,6 +250,7 @@ module Protocol = struct
Client_proto_rpcs.Context.next_level rpc_config block >>=? fun next_level ->
Client_proto_rpcs.Helpers.Forge.Delegate.proposals rpc_config block
~net_id:block_info.net_id
~branch:block_info.hash
~source:pk
~period:next_level.voting_period
~proposals
@ -262,6 +263,7 @@ module Protocol = struct
Client_proto_rpcs.Context.next_level rpc_config block >>=? fun next_level ->
Client_proto_rpcs.Helpers.Forge.Delegate.ballot rpc_config block
~net_id:block_info.net_id
~branch:block_info.hash
~source:pk
~period:next_level.voting_period
~proposal
@ -440,6 +442,7 @@ module Endorse = struct
Client_proto_rpcs.Helpers.Forge.Delegate.endorsement rpc_config
block
~net_id:net_id
~branch:hash
~source
~block:hash
~slot:slot
@ -480,7 +483,7 @@ module Endorse = struct
forge_endorsement block contract.sk contract.pk slot
(* FIXME @vb: I don't understand this function, copied from @cago. *)
let endorsers_list block { Account.b1 ; b2 ; b3 ; b4 ; b5 } =
let endorsers_list block =
let get_endorser_list result (account : Account.t) level block =
Client_proto_rpcs.Helpers.Rights.endorsement_rights_for_delegate
rpc_config block account.pkh
@ -489,6 +492,7 @@ module Endorse = struct
~last_level:level () >>|? fun slots ->
List.iter (fun (_,slot) -> result.(slot) <- account) slots
in
let { Account.b1 ; b2 ; b3 ; b4 ; b5 } = Account.bootstrap_accounts in
let result = Array.make 16 b1 in
Client_proto_rpcs.Context.level rpc_config block >>=? fun level ->
let level = Raw_level.succ @@ level.level in

View File

@ -114,7 +114,6 @@ module Endorse : sig
val endorsers_list :
Client_alpha.Client_proto_rpcs.block ->
Account.bootstrap_accounts ->
Account.t array tzresult Lwt.t
val endorsement_rights :

View File

@ -14,6 +14,12 @@ open Client_alpha
module Helpers = Proto_alpha_helpers
module Assert = Helpers.Assert
let { Helpers.Account.b1 ; b2 ; b3 ; b4 ; b5 } =
Helpers.Account.bootstrap_accounts
let default_account =
Helpers.Account.create "default_account"
let test_double_endorsement contract block =
(* Double endorsement for the same level *)
@ -59,8 +65,7 @@ let contain_tzerror ?(msg="") ~f t =
failwith "@[<v 2>Unexpected error@ %a@]" pp_print_error error
| _ -> return ()
let test_wrong_delegate ~miner contract head =
let block = `Hash head in
let test_wrong_delegate ~miner contract block =
begin
Helpers.Endorse.endorse ~slot:1 contract block >>=? fun op ->
Helpers.Mining.mine block miner [ op ] >>=? fun _ ->
@ -95,8 +100,8 @@ let test_invalid_endorsement_slot contract block =
end res ;
return ()
let test_endorsement_rewards
block ({ Helpers.Account.b5 = b1 ; _ } as baccounts) =
let test_endorsement_rewards block0 =
let get_endorser_except bs accounts =
let account, cpt = ref accounts.(0), ref 0 in
while List.mem !account bs do
@ -109,75 +114,74 @@ let test_endorsement_rewards
(* Endorsement Rights *)
(* #1 endorse & inject in a block *)
Helpers.Endorse.endorsers_list block baccounts >>=? fun accounts ->
Helpers.Endorse.endorsers_list block0 >>=? fun accounts ->
get_endorser_except [ b1 ] accounts >>=? fun (account0, slot0) ->
Helpers.Account.balance ~block account0 >>=? fun balance0 ->
Helpers.Endorse.endorse ~slot:slot0 account0 block >>=? fun ops ->
Helpers.Mining.mine block b1 [ ops ] >>=? fun head0 ->
Helpers.display_level (`Hash head0) >>=? fun () ->
Assert.balance_equal ~block:(`Hash head0) ~msg:__LOC__ account0
Helpers.Account.balance ~block:block0 account0 >>=? fun balance0 ->
Helpers.Endorse.endorse ~slot:slot0 account0 block0 >>=? fun op ->
Helpers.Mining.mine block0 b1 [ op ] >>=? fun hash1 ->
Helpers.display_level (`Hash hash1) >>=? fun () ->
Assert.balance_equal ~block:(`Hash hash1) ~msg:__LOC__ account0
(Int64.sub (Tez.to_cents balance0) bond) >>=? fun () ->
(* #2 endorse & inject in a block *)
let block0 = `Hash head0 in
Helpers.Endorse.endorsers_list block0 baccounts >>=? fun accounts ->
let block1 = `Hash hash1 in
Helpers.Endorse.endorsers_list block1 >>=? fun accounts ->
get_endorser_except [ b1 ; account0 ] accounts >>=? fun (account1, slot1) ->
Helpers.Account.balance ~block:block0 account1 >>=? fun balance1 ->
Helpers.Endorse.endorse ~slot:slot1 account1 block0 >>=? fun ops ->
Helpers.Mining.mine block0 b1 [ ops ] >>=? fun head1 ->
Helpers.display_level (`Hash head1) >>=? fun () ->
Assert.balance_equal ~block:(`Hash head1) ~msg:__LOC__ account1
Helpers.Account.balance ~block:block1 account1 >>=? fun balance1 ->
Helpers.Endorse.endorse ~slot:slot1 account1 block1 >>=? fun op ->
Helpers.Mining.mine block1 b1 [ op ] >>=? fun hash2 ->
Helpers.display_level (`Hash hash2) >>=? fun () ->
Assert.balance_equal ~block:(`Hash hash2) ~msg:__LOC__ account1
(Int64.sub (Tez.to_cents balance1) bond) >>=? fun () ->
(* Check rewards after one cycle for account0 *)
Helpers.Mining.mine (`Hash head1) b1 [] >>=? fun head2 ->
Helpers.display_level (`Hash head2) >>=? fun () ->
Helpers.Mining.mine (`Hash head2) b1 [] >>=? fun head3 ->
Helpers.display_level (`Hash head3) >>=? fun () ->
Helpers.Mining.mine (`Hash head3) b1 [] >>=? fun head4 ->
Helpers.display_level (`Hash head4) >>=? fun () ->
Helpers.Mining.endorsement_reward block0 >>=? fun rw0 ->
Assert.balance_equal ~block:(`Hash head4) ~msg:__LOC__ account0
Helpers.Mining.mine (`Hash hash2) b1 [] >>=? fun hash3 ->
Helpers.display_level (`Hash hash3) >>=? fun () ->
Helpers.Mining.mine (`Hash hash3) b1 [] >>=? fun hash4 ->
Helpers.display_level (`Hash hash4) >>=? fun () ->
Helpers.Mining.mine (`Hash hash4) b1 [] >>=? fun hash5 ->
Helpers.display_level (`Hash hash5) >>=? fun () ->
Helpers.Mining.endorsement_reward block1 >>=? fun rw0 ->
Assert.balance_equal ~block:(`Hash hash5) ~msg:__LOC__ account0
(Int64.add (Tez.to_cents balance0) rw0) >>=? fun () ->
(* Check rewards after one cycle for account1 *)
Helpers.Mining.endorsement_reward (`Hash head1) >>=? fun rw1 ->
Assert.balance_equal ~block:(`Hash head4) ~msg:__LOC__ account1
Helpers.Mining.endorsement_reward (`Hash hash2) >>=? fun rw1 ->
Assert.balance_equal ~block:(`Hash hash5) ~msg:__LOC__ account1
(Int64.add (Tez.to_cents balance1) rw1) >>=? fun () ->
(* #2 endorse and check reward only on the good chain *)
Helpers.Mining.mine (`Hash head4) b1 []>>=? fun head ->
Helpers.display_level (`Hash head) >>=? fun () ->
Helpers.Mining.mine (`Hash head4) b1 [] >>=? fun fork ->
Helpers.display_level (`Hash fork) >>=? fun () ->
Helpers.Mining.mine (`Hash hash5) b1 []>>=? fun hash6a ->
Helpers.display_level (`Hash hash6a) >>=? fun () ->
Helpers.Mining.mine (`Hash hash5) b1 [] >>=? fun hash6b ->
Helpers.display_level (`Hash hash6b) >>=? fun () ->
(* working on head *)
Helpers.Endorse.endorsers_list (`Hash head) baccounts >>=? fun accounts ->
Helpers.Endorse.endorsers_list (`Hash hash6a) >>=? fun accounts ->
get_endorser_except [ b1 ] accounts >>=? fun (account3, slot3) ->
Helpers.Account.balance ~block:(`Hash head) account3 >>=? fun balance3 ->
Helpers.Account.balance ~block:(`Hash hash6a) account3 >>=? fun balance3 ->
Helpers.Endorse.endorse
~slot:slot3 account3 (`Hash head) >>=? fun ops ->
Helpers.Mining.mine (`Hash head) b1 [ ops ] >>=? fun new_head ->
Helpers.display_level (`Hash new_head) >>=? fun () ->
~slot:slot3 account3 (`Hash hash6a) >>=? fun ops ->
Helpers.Mining.mine (`Hash hash6a) b1 [ ops ] >>=? fun hash7a ->
Helpers.display_level (`Hash hash7a) >>=? fun () ->
(* working on fork *)
Helpers.Endorse.endorsers_list (`Hash fork) baccounts >>=? fun accounts ->
Helpers.Endorse.endorsers_list (`Hash hash6b) >>=? fun accounts ->
get_endorser_except [ b1 ] accounts >>=? fun (account4, slot4) ->
Helpers.Account.balance ~block:(`Hash new_head) account4 >>=? fun _balance4 ->
Helpers.Endorse.endorse ~slot:slot4 account4 (`Hash fork) >>=? fun ops ->
Helpers.Mining.mine (`Hash fork) b1 [ ops ] >>=? fun _new_fork ->
Helpers.Account.balance ~block:(`Hash hash7a) account4 >>=? fun _balance4 ->
Helpers.Endorse.endorse ~slot:slot4 account4 (`Hash hash6b) >>=? fun ops ->
Helpers.Mining.mine (`Hash hash6b) b1 [ ops ] >>=? fun _new_fork ->
Helpers.display_level (`Hash _new_fork) >>=? fun () ->
Helpers.Account.balance ~block:(`Hash new_head) account4 >>=? fun balance4 ->
Helpers.Account.balance ~block:(`Hash hash7a) account4 >>=? fun balance4 ->
Helpers.Mining.mine (`Hash new_head) b1 [] >>=? fun head ->
Helpers.display_level (`Hash head) >>=? fun () ->
Helpers.Mining.mine (`Hash head) b1 [] >>=? fun head ->
Helpers.display_level (`Hash head) >>=? fun () ->
Helpers.Mining.mine (`Hash hash7a) b1 [] >>=? fun hash8a ->
Helpers.display_level (`Hash hash8a) >>=? fun () ->
Helpers.Mining.mine (`Hash hash8a) b1 [] >>=? fun hash9a ->
Helpers.display_level (`Hash hash9a) >>=? fun () ->
(* Check rewards after one cycle *)
Helpers.Mining.endorsement_reward (`Hash new_head) >>=? fun reward ->
Assert.balance_equal ~block:(`Hash head) ~msg:__LOC__ account3
Helpers.Mining.endorsement_reward (`Hash hash7a) >>=? fun reward ->
Assert.balance_equal ~block:(`Hash hash9a) ~msg:__LOC__ account3
(Int64.add (Tez.to_cents balance3) reward) >>=? fun () ->
(* Check no reward for the fork *)
@ -185,57 +189,60 @@ let test_endorsement_rewards
if account3 = account4 then return ()
(* if account4 is different from account3, we need to check that there
is no reward for him since the endorsement was in the fork branch *)
else Assert.balance_equal ~block:(`Hash head) ~msg:__LOC__ account4 (Tez.to_cents balance4)
else Assert.balance_equal ~block:(`Hash hash9a) ~msg:__LOC__ account4 (Tez.to_cents balance4)
end >>=? fun () ->
return head
return ()
let test_endorsement_rights contract block =
Helpers.Endorse.endorsement_rights contract block >>|? fun possibilities ->
possibilities <> []
let run head (({ b1 ; b2 ; b3 ; b4 ; b5 } : Helpers.Account.bootstrap_accounts) as baccounts) =
let run genesis =
let default_account = Helpers.Account.create "default_account" in
test_endorsement_rights default_account head >>=? fun has_right_to_endorse ->
test_endorsement_rights
default_account genesis >>=? fun has_right_to_endorse ->
Assert.equal_bool ~msg:__LOC__ has_right_to_endorse false ;
test_endorsement_rights b1 head >>=? fun has_right_to_endorse ->
test_endorsement_rights b1 genesis >>=? fun has_right_to_endorse ->
Assert.equal_bool ~msg:__LOC__ has_right_to_endorse true ;
test_endorsement_rights b1 head >>=? fun has_right_to_endorse ->
test_endorsement_rights b1 genesis >>=? fun has_right_to_endorse ->
Assert.equal_bool ~msg:__LOC__ has_right_to_endorse true ;
Assert.balance_equal ~block:head ~msg:__LOC__ b1 4_000_000_00L >>=? fun () ->
Assert.balance_equal ~block:head ~msg:__LOC__ b2 4_000_000_00L >>=? fun () ->
Assert.balance_equal ~block:head ~msg:__LOC__ b3 4_000_000_00L >>=? fun () ->
Assert.balance_equal ~block:head ~msg:__LOC__ b4 4_000_000_00L >>=? fun () ->
Assert.balance_equal ~block:head ~msg:__LOC__ b5 4_000_000_00L >>=? fun () ->
Assert.balance_equal
~block:genesis ~msg:__LOC__ b1 4_000_000_00L >>=? fun () ->
Assert.balance_equal
~block:genesis ~msg:__LOC__ b2 4_000_000_00L >>=? fun () ->
Assert.balance_equal
~block:genesis ~msg:__LOC__ b3 4_000_000_00L >>=? fun () ->
Assert.balance_equal
~block:genesis ~msg:__LOC__ b4 4_000_000_00L >>=? fun () ->
Assert.balance_equal
~block:genesis ~msg:__LOC__ b5 4_000_000_00L >>=? fun () ->
(* Check Rewards *)
test_endorsement_rewards head baccounts >>=? fun head ->
test_endorsement_rewards genesis >>=? fun () ->
(* Endorse with a contract with wrong delegate:
- contract with no endorsement rights
- contract which signs at every available slots *)
test_wrong_delegate ~miner:b1 default_account head >>= fun () ->
test_wrong_delegate ~miner:b1 b5 head >>= fun () ->
test_wrong_delegate ~miner:b1 default_account genesis >>= fun () ->
test_wrong_delegate ~miner:b1 b5 genesis >>= fun () ->
(* Endorse with a wrong slot : -1 and max (16) *)
test_invalid_endorsement_slot b3 (`Hash head) >>=? fun () ->
test_invalid_endorsement_slot b3 genesis >>=? fun () ->
(* FIXME: Mining.Invalid_signature is still unclassified *)
test_invalid_signature (`Hash head) >>=? fun _ ->
test_invalid_signature genesis >>=? fun _ ->
(* FIXME: cannot inject double endorsement operation yet, but the
code is still here
Double endorsement *)
test_double_endorsement b4 (`Hash head) >>=? fun new_head ->
test_double_endorsement b4 genesis >>=? fun _ ->
return new_head
return ()
let main () =
Helpers.init () >>=? fun (_node_pid, hash) ->
run (`Hash hash) Helpers.Account.bootstrap_accounts >>=? fun _blkh ->
return ()
Helpers.init () >>=? fun (_node_pid, genesis) ->
run (`Hash genesis)
let tests = [

View File

@ -54,7 +54,7 @@ let incr_timestamp timestamp =
let operation op =
let op : Operation.t = {
shell = { net_id } ;
shell = { net_id ; branch = genesis_block } ;
proto = MBytes.of_string op ;
} in
Operation.hash op,

View File

@ -63,7 +63,7 @@ let net_id = Net_id.of_block_hash genesis_block
(** Operation store *)
let make proto : Tezos_data.Operation.t =
{ shell = { net_id } ; proto }
{ shell = { net_id ; branch = genesis_block } ; proto }
let op1 = make (MBytes.of_string "Capadoce")
let oph1 = Tezos_data.Operation.hash op1