From 77b9ecbb8a8a63e712d02f47d11eead25a0a9929 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Thu, 27 Dec 2018 14:07:35 +0100 Subject: [PATCH 01/22] Added `randomness_contract` field. --- ethcore/src/engines/authority_round/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index d2dd261d4be..7778af1b8fe 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -85,6 +85,8 @@ pub struct AuthorityRoundParams { pub strict_empty_steps_transition: u64, /// Sets whether Aura will use Proof of Authority (PoA) or Proof of Stake (PoS) consensus. pub consensus_kind: ConsensusKind, + /// If set, enables random number contract integration. + pub randomness_contract: Option
, } const U16_MAX: usize = ::std::u16::MAX as usize; @@ -116,6 +118,7 @@ impl From for AuthorityRoundParams { maximum_empty_steps: p.maximum_empty_steps.map_or(0, Into::into), strict_empty_steps_transition: p.strict_empty_steps_transition.map_or(0, Into::into), consensus_kind: p.consensus_kind.unwrap_or(ConsensusKind::Poa), + randomness_contract: None, } } } From 6fb927f88531bb613734a8d9304d4bbe08114c61 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Thu, 27 Dec 2018 15:28:08 +0100 Subject: [PATCH 02/22] Added initial `engines::authority_round::randomness` module. --- ethcore/src/engines/authority_round/mod.rs | 1 + .../src/engines/authority_round/randomness.rs | 94 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 ethcore/src/engines/authority_round/randomness.rs diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 7778af1b8fe..3ff2e11c49b 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -48,6 +48,7 @@ use types::ancestry_action::AncestryAction; use unexpected::{Mismatch, OutOfBounds}; mod finality; +mod randomness; /// `AuthorityRound` params. pub struct AuthorityRoundParams { diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs new file mode 100644 index 00000000000..75533cae92d --- /dev/null +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -0,0 +1,94 @@ +use ethereum_types::Address; + +/// Validated randomness phase state. +/// +/// The process of generating random numbers is a simple finite state machine: +/// +/// + +/// | +/// | +/// | +/// +--------------+ +-------v-------+ +/// | | | | +/// | BeforeCommit <------------------------------+ Waiting | +/// | | enter commit phase | | +/// +------+-------+ +-------^-------+ +/// | | +/// | call | +/// | `commitHash()` | call +/// | | `revealSecret` +/// | | +/// +------v-------+ +-------+-------+ +/// | | | | +/// | Committed +------------------------------> Reveal | +/// | | enter reveal phase | | +/// +--------------+ +---------------+ +/// +/// +/// Phase transitions are performed by the smart contract and simply queried by the engine, while +/// all calls are made directly. +#[derive(Clone, Copy, Debug)] +pub enum RandomnessPhase { + /// Waiting for the next phase. + /// + /// This state indicates either the successful revelation in this round or having missed the + /// window to make a commitment. + Waiting, + /// Indicates a commitment is possible, but still missing. + BeforeCommit, + /// Indicates a successful commitment, waiting for the commit phase to end. + Committed, + /// Indicates revealing is expected as the next step. + Reveal, +} + +/// Phase loading error for randomness generation state machine. +/// +/// This error usually indicates a bug in either the smart contract or the phase loading function. +#[derive(Clone, Copy, Debug)] +pub enum PhaseError { + /// The smart contract reported a phase as both commitment and reveal phase. + PhaseConflict, + /// The smart contract reported that we already revealed something while still being in the + /// commit phase. + RevealedInCommit, +} + +impl RandomnessPhase { + /// Determine randomness generation state from the contract. + fn load(contract_address: Address, our_address: Address) -> Result { + let is_reveal_phase: bool = unimplemented!(); // call `isRevealPhase()`. + let is_commit_phase: bool = unimplemented!(); // call `isCommitPhase()`. + + let committed: bool = unimplemented!(); // call `isCommitted()`. + let revealed: bool = unimplemented!(); // call `sendReveal()`. + + if is_reveal_phase && is_commit_phase { + return Err(PhaseError::PhaseConflict); + } + + if is_commit_phase { + if revealed { + return Err(PhaseError::RevealedInCommit); + } + + if !committed { + Ok(RandomnessPhase::BeforeCommit) + } else { + Ok(RandomnessPhase::Committed) + } + } else { + if !committed { + // We apparently entered too late to make a committment, wait until we get a chance + // again. + return Ok(RandomnessPhase::Waiting) + } + + if !revealed { + Ok(RandomnessPhase::Reveal) + } else { + Ok(RandomnessPhase::Waiting) + } + } + } +} From ff80715a1c557f8c822894c07521ea09752d2d02 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Sun, 30 Dec 2018 14:54:57 +0100 Subject: [PATCH 03/22] Added `authority_round_random.json`, based on `pos-contracts@8d10a85ad87633a54a7444883b32687faf5b80bf`. The contract was compiled using solc `0.4.25+commit.59dbf8f1.Linux.g++` and formatted using [json_pp](https://packages.debian.org/stretch/libjson-pp-perl). --- .../res/contracts/authority_round_random.json | 336 ++++++++++++++++++ .../src/engines/authority_round/randomness.rs | 2 + 2 files changed, 338 insertions(+) create mode 100644 ethcore/res/contracts/authority_round_random.json diff --git a/ethcore/res/contracts/authority_round_random.json b/ethcore/res/contracts/authority_round_random.json new file mode 100644 index 00000000000..8e5f17a8790 --- /dev/null +++ b/ethcore/res/contracts/authority_round_random.json @@ -0,0 +1,336 @@ +[ + { + "type" : "function", + "outputs" : [], + "name" : "commitHash", + "stateMutability" : "nonpayable", + "constant" : false, + "payable" : false, + "inputs" : [ + { + "type" : "bytes32", + "name" : "_secretHash" + }, + { + "type" : "bytes", + "name" : "_signature" + } + ] + }, + { + "stateMutability" : "view", + "type" : "function", + "name" : "blocksProducers", + "outputs" : [ + { + "type" : "address[]", + "name" : "" + } + ], + "payable" : false, + "inputs" : [ + { + "type" : "uint256", + "name" : "_collectRound" + } + ], + "constant" : true + }, + { + "type" : "function", + "name" : "COMMIT_PHASE_LENGTH", + "outputs" : [ + { + "name" : "", + "type" : "uint256" + } + ], + "stateMutability" : "view", + "constant" : true, + "payable" : false, + "inputs" : [] + }, + { + "constant" : true, + "payable" : false, + "inputs" : [], + "type" : "function", + "name" : "currentRandom", + "outputs" : [ + { + "type" : "uint256[]", + "name" : "" + } + ], + "stateMutability" : "view" + }, + { + "stateMutability" : "view", + "name" : "committedValidators", + "type" : "function", + "outputs" : [ + { + "type" : "address[]", + "name" : "" + } + ], + "inputs" : [ + { + "name" : "_collectRound", + "type" : "uint256" + } + ], + "payable" : false, + "constant" : true + }, + { + "payable" : false, + "inputs" : [ + { + "type" : "uint256", + "name" : "_collectRound" + }, + { + "type" : "address", + "name" : "_validator" + } + ], + "constant" : true, + "stateMutability" : "view", + "outputs" : [ + { + "name" : "", + "type" : "bool" + } + ], + "type" : "function", + "name" : "sentReveal" + }, + { + "stateMutability" : "view", + "type" : "function", + "outputs" : [ + { + "name" : "", + "type" : "bool" + } + ], + "name" : "isCommitPhase", + "inputs" : [], + "payable" : false, + "constant" : true + }, + { + "stateMutability" : "view", + "name" : "VALIDATOR_SET_CONTRACT", + "type" : "function", + "outputs" : [ + { + "name" : "", + "type" : "address" + } + ], + "payable" : false, + "inputs" : [], + "constant" : true + }, + { + "name" : "revealSecret", + "type" : "function", + "outputs" : [], + "stateMutability" : "nonpayable", + "constant" : false, + "inputs" : [ + { + "name" : "_secret", + "type" : "uint256" + }, + { + "name" : "_signature", + "type" : "bytes" + } + ], + "payable" : false + }, + { + "payable" : false, + "inputs" : [], + "constant" : true, + "stateMutability" : "view", + "type" : "function", + "name" : "currentCollectRound", + "outputs" : [ + { + "name" : "", + "type" : "uint256" + } + ] + }, + { + "type" : "function", + "name" : "createdBlockOnRevealsPhase", + "outputs" : [ + { + "type" : "bool", + "name" : "" + } + ], + "stateMutability" : "view", + "constant" : true, + "payable" : false, + "inputs" : [ + { + "type" : "uint256", + "name" : "_collectRound" + }, + { + "type" : "address", + "name" : "_validator" + } + ] + }, + { + "constant" : true, + "inputs" : [], + "payable" : false, + "outputs" : [ + { + "type" : "uint256", + "name" : "" + } + ], + "type" : "function", + "name" : "COLLECT_ROUND_LENGTH", + "stateMutability" : "view" + }, + { + "name" : "isCommitted", + "type" : "function", + "outputs" : [ + { + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "constant" : true, + "inputs" : [ + { + "type" : "uint256", + "name" : "_collectRound" + }, + { + "type" : "address", + "name" : "_validator" + } + ], + "payable" : false + }, + { + "payable" : false, + "inputs" : [ + { + "name" : "_currentValidator", + "type" : "address" + } + ], + "constant" : false, + "stateMutability" : "nonpayable", + "type" : "function", + "name" : "onBlockClose", + "outputs" : [] + }, + { + "stateMutability" : "view", + "type" : "function", + "outputs" : [ + { + "type" : "bool", + "name" : "" + } + ], + "name" : "isRevealPhase", + "payable" : false, + "inputs" : [], + "constant" : true + }, + { + "payable" : false, + "inputs" : [], + "constant" : true, + "stateMutability" : "view", + "type" : "function", + "name" : "getCurrentSecret", + "outputs" : [ + { + "type" : "uint256", + "name" : "" + } + ] + }, + { + "payable" : false, + "inputs" : [ + { + "name" : "_collectRound", + "type" : "uint256" + }, + { + "name" : "_validator", + "type" : "address" + } + ], + "constant" : true, + "stateMutability" : "view", + "outputs" : [ + { + "type" : "bool", + "name" : "" + } + ], + "type" : "function", + "name" : "createdBlockOnCommitsPhase" + }, + { + "stateMutability" : "view", + "name" : "revealsCount", + "type" : "function", + "outputs" : [ + { + "type" : "uint256", + "name" : "" + } + ], + "payable" : false, + "inputs" : [ + { + "type" : "uint256", + "name" : "_collectRound" + } + ], + "constant" : true + }, + { + "inputs" : [ + { + "name" : "_collectRound", + "type" : "uint256" + }, + { + "name" : "_validator", + "type" : "address" + } + ], + "payable" : false, + "constant" : true, + "stateMutability" : "view", + "type" : "function", + "outputs" : [ + { + "type" : "bytes32", + "name" : "" + } + ], + "name" : "getCommit" + } +] diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 75533cae92d..53ffd3604af 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -1,5 +1,7 @@ use ethereum_types::Address; +use_contract!(foo, "res/contracts/authority_round_random.json"); + /// Validated randomness phase state. /// /// The process of generating random numbers is a simple finite state machine: From 3df2d0f1a0674dcfdbeb204ea02970000b4fdbe0 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Sun, 30 Dec 2018 21:50:47 +0100 Subject: [PATCH 04/22] Added phase skeleton for determination through constant contract calls. --- .../src/engines/authority_round/randomness.rs | 82 +++++++++++++++++-- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 53ffd3604af..9605c34034c 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -1,6 +1,10 @@ +use std::fmt; + use ethereum_types::Address; -use_contract!(foo, "res/contracts/authority_round_random.json"); +use client::{BlockId, CallContract, Client}; + +use_contract!(aura_random, "res/contracts/authority_round_random.json"); /// Validated randomness phase state. /// @@ -47,24 +51,90 @@ pub enum RandomnessPhase { /// Phase loading error for randomness generation state machine. /// /// This error usually indicates a bug in either the smart contract or the phase loading function. -#[derive(Clone, Copy, Debug)] +#[derive(Debug)] pub enum PhaseError { /// The smart contract reported a phase as both commitment and reveal phase. PhaseConflict, /// The smart contract reported that we already revealed something while still being in the /// commit phase. RevealedInCommit, + /// Calling the EVM failed. + MachineError(ethabi::Error) +} + + +struct BoundContract<'a> { + client: &'a Client, + block_id: BlockId, + contract_addr: Address +} + +// The debug implementation is helpful when, well, debugging. It allows verifying that the correct +// contract is bound. +impl<'a> fmt::Debug for BoundContract<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("BoundContract") + .field("client", &(self.client as *const Client)) + .field("block_id", &self.block_id) + .field("contract_addr", &self.contract_addr) + .finish() + } +} + +impl<'a> BoundContract<'a> { + #[inline] + fn bind(client: &Client, block_id: BlockId, contract_addr: Address) -> BoundContract { + BoundContract{ + client, + block_id, + contract_addr, + } + } + + /// Performs a function call to an ethereum machine. + /// + /// Runs a constant function call on `client`. The `call` value can be serialized by calling any + /// api function generated by the `use_contract!` macro. + fn call_const(&self, call: (ethabi::Bytes, D)) -> Result where + D: ethabi::FunctionOutputDecoder + { + // FIXME: Add address to call. + let (data, output_decoder) = call; + + // TODO: Implement this step. Given a `data`, the call should be passed onto the + // the ethereum machine and evaluated. + let call_return = self.client.call_contract(self.block_id, self.contract_addr, data).expect("FIXME"); + + // Decode the result and return it. + // NOTE: We return an already wrapped error here, ideally this function could be generalized by + // removing the `map_err` and sticking it elsewhere. + output_decoder.decode(call_return.as_slice()).map_err(PhaseError::MachineError) + } } impl RandomnessPhase { /// Determine randomness generation state from the contract. + /// + /// Calls various constant contract functions to determine the precise state that needs to be + /// handled (that is, the phase and whether or not the current validator still needs to send + /// commitments or reveal secrets). fn load(contract_address: Address, our_address: Address) -> Result { - let is_reveal_phase: bool = unimplemented!(); // call `isRevealPhase()`. - let is_commit_phase: bool = unimplemented!(); // call `isCommitPhase()`. + let contract = BoundContract::bind(unimplemented!(), unimplemented!(), contract_address); + + // Determine the current round and which phase we are in. + let round = contract.call_const(aura_random::functions::current_collect_round::call())?; + let is_reveal_phase = contract.call_const(aura_random::functions::is_reveal_phase::call())?; + let is_commit_phase = contract.call_const(aura_random::functions::is_commit_phase::call())?; - let committed: bool = unimplemented!(); // call `isCommitted()`. - let revealed: bool = unimplemented!(); // call `sendReveal()`. + // Ensure we are not committing or revealing twice. + let committed = contract.call_const(aura_random::functions::is_committed::call( + round, our_address + ))?; + let revealed: bool = contract.call_const(aura_random::functions::sent_reveal::call( + round, our_address + ))?; + // With all the information known, we can determine the actual state we are in. if is_reveal_phase && is_commit_phase { return Err(PhaseError::PhaseConflict); } From b18a9a04308d75d8c4c66f2cc218c4a018998a9a Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Mon, 31 Dec 2018 11:07:25 +0100 Subject: [PATCH 05/22] Run `rustfmt` on `randomness` module. --- .../src/engines/authority_round/randomness.rs | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 9605c34034c..7debceb1565 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -59,14 +59,13 @@ pub enum PhaseError { /// commit phase. RevealedInCommit, /// Calling the EVM failed. - MachineError(ethabi::Error) + MachineError(ethabi::Error), } - struct BoundContract<'a> { client: &'a Client, block_id: BlockId, - contract_addr: Address + contract_addr: Address, } // The debug implementation is helpful when, well, debugging. It allows verifying that the correct @@ -84,7 +83,7 @@ impl<'a> fmt::Debug for BoundContract<'a> { impl<'a> BoundContract<'a> { #[inline] fn bind(client: &Client, block_id: BlockId, contract_addr: Address) -> BoundContract { - BoundContract{ + BoundContract { client, block_id, contract_addr, @@ -95,20 +94,26 @@ impl<'a> BoundContract<'a> { /// /// Runs a constant function call on `client`. The `call` value can be serialized by calling any /// api function generated by the `use_contract!` macro. - fn call_const(&self, call: (ethabi::Bytes, D)) -> Result where - D: ethabi::FunctionOutputDecoder + fn call_const(&self, call: (ethabi::Bytes, D)) -> Result + where + D: ethabi::FunctionOutputDecoder, { // FIXME: Add address to call. let (data, output_decoder) = call; // TODO: Implement this step. Given a `data`, the call should be passed onto the // the ethereum machine and evaluated. - let call_return = self.client.call_contract(self.block_id, self.contract_addr, data).expect("FIXME"); + let call_return = self + .client + .call_contract(self.block_id, self.contract_addr, data) + .expect("FIXME"); // Decode the result and return it. // NOTE: We return an already wrapped error here, ideally this function could be generalized by // removing the `map_err` and sticking it elsewhere. - output_decoder.decode(call_return.as_slice()).map_err(PhaseError::MachineError) + output_decoder + .decode(call_return.as_slice()) + .map_err(PhaseError::MachineError) } } @@ -118,20 +123,27 @@ impl RandomnessPhase { /// Calls various constant contract functions to determine the precise state that needs to be /// handled (that is, the phase and whether or not the current validator still needs to send /// commitments or reveal secrets). - fn load(contract_address: Address, our_address: Address) -> Result { - let contract = BoundContract::bind(unimplemented!(), unimplemented!(), contract_address); + fn load( + contract_address: Address, + our_address: Address, + ) -> Result { + let contract = BoundContract::bind(client, unimplemented!(), contract_address); // Determine the current round and which phase we are in. let round = contract.call_const(aura_random::functions::current_collect_round::call())?; - let is_reveal_phase = contract.call_const(aura_random::functions::is_reveal_phase::call())?; - let is_commit_phase = contract.call_const(aura_random::functions::is_commit_phase::call())?; + let is_reveal_phase = + contract.call_const(aura_random::functions::is_reveal_phase::call())?; + let is_commit_phase = + contract.call_const(aura_random::functions::is_commit_phase::call())?; // Ensure we are not committing or revealing twice. let committed = contract.call_const(aura_random::functions::is_committed::call( - round, our_address + round, + our_address, ))?; let revealed: bool = contract.call_const(aura_random::functions::sent_reveal::call( - round, our_address + round, + our_address, ))?; // With all the information known, we can determine the actual state we are in. @@ -153,7 +165,7 @@ impl RandomnessPhase { if !committed { // We apparently entered too late to make a committment, wait until we get a chance // again. - return Ok(RandomnessPhase::Waiting) + return Ok(RandomnessPhase::Waiting); } if !revealed { From 74a118bd6f174084effae8907382315aa2133e30 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Mon, 31 Dec 2018 11:10:19 +0100 Subject: [PATCH 06/22] Fixed path for `authority_round_random.json`. --- ethcore/res/{contracts => }/authority_round_random.json | 0 ethcore/src/engines/authority_round/randomness.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename ethcore/res/{contracts => }/authority_round_random.json (100%) diff --git a/ethcore/res/contracts/authority_round_random.json b/ethcore/res/authority_round_random.json similarity index 100% rename from ethcore/res/contracts/authority_round_random.json rename to ethcore/res/authority_round_random.json diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 7debceb1565..c1ed7d05cf7 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -4,7 +4,7 @@ use ethereum_types::Address; use client::{BlockId, CallContract, Client}; -use_contract!(aura_random, "res/contracts/authority_round_random.json"); +use_contract!(aura_random, "res/authority_round_random.json"); /// Validated randomness phase state. /// From b08741024550aec18386b4de160d2921d4bfee6b Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Mon, 31 Dec 2018 11:30:04 +0100 Subject: [PATCH 07/22] Fixed indentation in `randomness.rs`. --- .../src/engines/authority_round/randomness.rs | 241 +++++++++--------- 1 file changed, 121 insertions(+), 120 deletions(-) diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index c1ed7d05cf7..de269b75202 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -35,17 +35,17 @@ use_contract!(aura_random, "res/authority_round_random.json"); /// all calls are made directly. #[derive(Clone, Copy, Debug)] pub enum RandomnessPhase { - /// Waiting for the next phase. - /// - /// This state indicates either the successful revelation in this round or having missed the - /// window to make a commitment. - Waiting, - /// Indicates a commitment is possible, but still missing. - BeforeCommit, - /// Indicates a successful commitment, waiting for the commit phase to end. - Committed, - /// Indicates revealing is expected as the next step. - Reveal, + /// Waiting for the next phase. + /// + /// This state indicates either the successful revelation in this round or having missed the + /// window to make a commitment. + Waiting, + /// Indicates a commitment is possible, but still missing. + BeforeCommit, + /// Indicates a successful commitment, waiting for the commit phase to end. + Committed, + /// Indicates revealing is expected as the next step. + Reveal, } /// Phase loading error for randomness generation state machine. @@ -53,126 +53,127 @@ pub enum RandomnessPhase { /// This error usually indicates a bug in either the smart contract or the phase loading function. #[derive(Debug)] pub enum PhaseError { - /// The smart contract reported a phase as both commitment and reveal phase. - PhaseConflict, - /// The smart contract reported that we already revealed something while still being in the - /// commit phase. - RevealedInCommit, - /// Calling the EVM failed. - MachineError(ethabi::Error), + /// The smart contract reported a phase as both commitment and reveal phase. + PhaseConflict, + /// The smart contract reported that we already revealed something while still being in the + /// commit phase. + RevealedInCommit, + /// Calling a contract to determine the phase failed. + CallError(String), + /// Decoding a called contract's return value failed while determinig the phase. + DecodeError(ethabi::Error), } -struct BoundContract<'a> { - client: &'a Client, - block_id: BlockId, - contract_addr: Address, +pub struct BoundContract<'a> { + client: &'a Client, + block_id: BlockId, + contract_addr: Address, } // The debug implementation is helpful when, well, debugging. It allows verifying that the correct // contract is bound. impl<'a> fmt::Debug for BoundContract<'a> { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("BoundContract") - .field("client", &(self.client as *const Client)) - .field("block_id", &self.block_id) - .field("contract_addr", &self.contract_addr) - .finish() - } + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("BoundContract") + .field("client", &(self.client as *const Client)) + .field("block_id", &self.block_id) + .field("contract_addr", &self.contract_addr) + .finish() + } } impl<'a> BoundContract<'a> { - #[inline] - fn bind(client: &Client, block_id: BlockId, contract_addr: Address) -> BoundContract { - BoundContract { - client, - block_id, - contract_addr, - } - } - - /// Performs a function call to an ethereum machine. - /// - /// Runs a constant function call on `client`. The `call` value can be serialized by calling any - /// api function generated by the `use_contract!` macro. - fn call_const(&self, call: (ethabi::Bytes, D)) -> Result - where - D: ethabi::FunctionOutputDecoder, - { - // FIXME: Add address to call. - let (data, output_decoder) = call; - - // TODO: Implement this step. Given a `data`, the call should be passed onto the - // the ethereum machine and evaluated. - let call_return = self - .client - .call_contract(self.block_id, self.contract_addr, data) - .expect("FIXME"); - - // Decode the result and return it. - // NOTE: We return an already wrapped error here, ideally this function could be generalized by - // removing the `map_err` and sticking it elsewhere. - output_decoder - .decode(call_return.as_slice()) - .map_err(PhaseError::MachineError) - } + #[inline] + pub fn bind(client: &Client, block_id: BlockId, contract_addr: Address) -> BoundContract { + BoundContract { + client, + block_id, + contract_addr, + } + } + + /// Performs a function call to an ethereum machine. + /// + /// Runs a constant function call on `client`. The `call` value can be serialized by calling any + /// api function generated by the `use_contract!` macro. + pub fn call_const(&self, call: (ethabi::Bytes, D)) -> Result + where + D: ethabi::FunctionOutputDecoder, + { + let (data, output_decoder) = call; + + let call_return = self + .client + .call_contract(self.block_id, self.contract_addr, data) + .map_err(PhaseError::CallError)?; + + // Decode the result and return it. + // NOTE: We return an already wrapped error here, ideally this function could be generalized by + // removing the `map_err` and sticking it elsewhere. + output_decoder + .decode(call_return.as_slice()) + .map_err(PhaseError::DecodeError) + } } impl RandomnessPhase { - /// Determine randomness generation state from the contract. - /// - /// Calls various constant contract functions to determine the precise state that needs to be - /// handled (that is, the phase and whether or not the current validator still needs to send - /// commitments or reveal secrets). - fn load( - contract_address: Address, - our_address: Address, - ) -> Result { - let contract = BoundContract::bind(client, unimplemented!(), contract_address); - - // Determine the current round and which phase we are in. - let round = contract.call_const(aura_random::functions::current_collect_round::call())?; - let is_reveal_phase = - contract.call_const(aura_random::functions::is_reveal_phase::call())?; - let is_commit_phase = - contract.call_const(aura_random::functions::is_commit_phase::call())?; - - // Ensure we are not committing or revealing twice. - let committed = contract.call_const(aura_random::functions::is_committed::call( - round, - our_address, - ))?; - let revealed: bool = contract.call_const(aura_random::functions::sent_reveal::call( - round, - our_address, - ))?; - - // With all the information known, we can determine the actual state we are in. - if is_reveal_phase && is_commit_phase { - return Err(PhaseError::PhaseConflict); - } - - if is_commit_phase { - if revealed { - return Err(PhaseError::RevealedInCommit); - } - - if !committed { - Ok(RandomnessPhase::BeforeCommit) - } else { - Ok(RandomnessPhase::Committed) - } - } else { - if !committed { - // We apparently entered too late to make a committment, wait until we get a chance - // again. - return Ok(RandomnessPhase::Waiting); - } - - if !revealed { - Ok(RandomnessPhase::Reveal) - } else { - Ok(RandomnessPhase::Waiting) - } - } - } + /// Determine randomness generation state from the contract. + /// + /// Calls various constant contract functions to determine the precise state that needs to be + /// handled (that is, the phase and whether or not the current validator still needs to send + /// commitments or reveal secrets). + fn load( + client: &Client, + block_id: BlockId, + contract_address: Address, + our_address: Address, + ) -> Result { + let contract = BoundContract::bind(client, block_id, contract_address); + + // Determine the current round and which phase we are in. + let round = contract.call_const(aura_random::functions::current_collect_round::call())?; + let is_reveal_phase = + contract.call_const(aura_random::functions::is_reveal_phase::call())?; + let is_commit_phase = + contract.call_const(aura_random::functions::is_commit_phase::call())?; + + // Ensure we are not committing or revealing twice. + let committed = contract.call_const(aura_random::functions::is_committed::call( + round, + our_address, + ))?; + let revealed: bool = contract.call_const(aura_random::functions::sent_reveal::call( + round, + our_address, + ))?; + + // With all the information known, we can determine the actual state we are in. + if is_reveal_phase && is_commit_phase { + return Err(PhaseError::PhaseConflict); + } + + if is_commit_phase { + if revealed { + return Err(PhaseError::RevealedInCommit); + } + + if !committed { + Ok(RandomnessPhase::BeforeCommit) + } else { + Ok(RandomnessPhase::Committed) + } + } else { + if !committed { + // We apparently entered too late to make a committment, wait until we get a chance + // again. + return Ok(RandomnessPhase::Waiting); + } + + if !revealed { + Ok(RandomnessPhase::Reveal) + } else { + Ok(RandomnessPhase::Waiting) + } + } + } } From cb80ccc15720277bd2037e1217e86ba2af00bb77 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Mon, 31 Dec 2018 11:40:27 +0100 Subject: [PATCH 08/22] Updated docs for `randomness.rs`. --- .../src/engines/authority_round/randomness.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index de269b75202..53d73923e6d 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -1,3 +1,7 @@ +//! On-chain randomness generation for authority round +//! +//! This module contains the support code for the on-chain randomness generation used by AuRa. + use std::fmt; use ethereum_types::Address; @@ -48,6 +52,7 @@ pub enum RandomnessPhase { Reveal, } + /// Phase loading error for randomness generation state machine. /// /// This error usually indicates a bug in either the smart contract or the phase loading function. @@ -64,14 +69,18 @@ pub enum PhaseError { DecodeError(ethabi::Error), } + +/// Utility structure for binding contracts. +/// +/// A bound contract is a combination of a `Client` reference, a `BlockId` and a contract `Address`. +/// These three parts are enough to call a contract's function; return values are automatically +/// decoded. pub struct BoundContract<'a> { client: &'a Client, block_id: BlockId, contract_addr: Address, } -// The debug implementation is helpful when, well, debugging. It allows verifying that the correct -// contract is bound. impl<'a> fmt::Debug for BoundContract<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("BoundContract") @@ -83,6 +92,7 @@ impl<'a> fmt::Debug for BoundContract<'a> { } impl<'a> BoundContract<'a> { + /// Create a new `BoundContract`. #[inline] pub fn bind(client: &Client, block_id: BlockId, contract_addr: Address) -> BoundContract { BoundContract { @@ -92,7 +102,7 @@ impl<'a> BoundContract<'a> { } } - /// Performs a function call to an ethereum machine. + /// Perform a function call to an ethereum machine. /// /// Runs a constant function call on `client`. The `call` value can be serialized by calling any /// api function generated by the `use_contract!` macro. From bdde1833da5b1f28c15ed3b2aec7345282ac0491 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Mon, 31 Dec 2018 11:48:45 +0100 Subject: [PATCH 09/22] Factor out utility functions into `util` module. --- ethcore/src/engines/authority_round/mod.rs | 1 + .../src/engines/authority_round/randomness.rs | 103 +++++------------- ethcore/src/engines/authority_round/util.rs | 75 +++++++++++++ 3 files changed, 101 insertions(+), 78 deletions(-) create mode 100644 ethcore/src/engines/authority_round/util.rs diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 3ff2e11c49b..2d57f0470ce 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -49,6 +49,7 @@ use unexpected::{Mismatch, OutOfBounds}; mod finality; mod randomness; +mod util; /// `AuthorityRound` params. pub struct AuthorityRoundParams { diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 53d73923e6d..1dfc5b01f7b 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -2,11 +2,10 @@ //! //! This module contains the support code for the on-chain randomness generation used by AuRa. -use std::fmt; - use ethereum_types::Address; -use client::{BlockId, CallContract, Client}; +use super::util::{BoundContract, CallError}; +use client::{BlockId, Client}; use_contract!(aura_random, "res/authority_round_random.json"); @@ -52,7 +51,6 @@ pub enum RandomnessPhase { Reveal, } - /// Phase loading error for randomness generation state machine. /// /// This error usually indicates a bug in either the smart contract or the phase loading function. @@ -64,66 +62,7 @@ pub enum PhaseError { /// commit phase. RevealedInCommit, /// Calling a contract to determine the phase failed. - CallError(String), - /// Decoding a called contract's return value failed while determinig the phase. - DecodeError(ethabi::Error), -} - - -/// Utility structure for binding contracts. -/// -/// A bound contract is a combination of a `Client` reference, a `BlockId` and a contract `Address`. -/// These three parts are enough to call a contract's function; return values are automatically -/// decoded. -pub struct BoundContract<'a> { - client: &'a Client, - block_id: BlockId, - contract_addr: Address, -} - -impl<'a> fmt::Debug for BoundContract<'a> { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("BoundContract") - .field("client", &(self.client as *const Client)) - .field("block_id", &self.block_id) - .field("contract_addr", &self.contract_addr) - .finish() - } -} - -impl<'a> BoundContract<'a> { - /// Create a new `BoundContract`. - #[inline] - pub fn bind(client: &Client, block_id: BlockId, contract_addr: Address) -> BoundContract { - BoundContract { - client, - block_id, - contract_addr, - } - } - - /// Perform a function call to an ethereum machine. - /// - /// Runs a constant function call on `client`. The `call` value can be serialized by calling any - /// api function generated by the `use_contract!` macro. - pub fn call_const(&self, call: (ethabi::Bytes, D)) -> Result - where - D: ethabi::FunctionOutputDecoder, - { - let (data, output_decoder) = call; - - let call_return = self - .client - .call_contract(self.block_id, self.contract_addr, data) - .map_err(PhaseError::CallError)?; - - // Decode the result and return it. - // NOTE: We return an already wrapped error here, ideally this function could be generalized by - // removing the `map_err` and sticking it elsewhere. - output_decoder - .decode(call_return.as_slice()) - .map_err(PhaseError::DecodeError) - } + LoadFailed(CallError), } impl RandomnessPhase { @@ -132,7 +71,7 @@ impl RandomnessPhase { /// Calls various constant contract functions to determine the precise state that needs to be /// handled (that is, the phase and whether or not the current validator still needs to send /// commitments or reveal secrets). - fn load( + pub fn load( client: &Client, block_id: BlockId, contract_address: Address, @@ -141,21 +80,29 @@ impl RandomnessPhase { let contract = BoundContract::bind(client, block_id, contract_address); // Determine the current round and which phase we are in. - let round = contract.call_const(aura_random::functions::current_collect_round::call())?; - let is_reveal_phase = - contract.call_const(aura_random::functions::is_reveal_phase::call())?; - let is_commit_phase = - contract.call_const(aura_random::functions::is_commit_phase::call())?; + let round = contract + .call_const(aura_random::functions::current_collect_round::call()) + .map_err(PhaseError::LoadFailed)?; + let is_reveal_phase = contract + .call_const(aura_random::functions::is_reveal_phase::call()) + .map_err(PhaseError::LoadFailed)?; + let is_commit_phase = contract + .call_const(aura_random::functions::is_commit_phase::call()) + .map_err(PhaseError::LoadFailed)?; // Ensure we are not committing or revealing twice. - let committed = contract.call_const(aura_random::functions::is_committed::call( - round, - our_address, - ))?; - let revealed: bool = contract.call_const(aura_random::functions::sent_reveal::call( - round, - our_address, - ))?; + let committed = contract + .call_const(aura_random::functions::is_committed::call( + round, + our_address, + )) + .map_err(PhaseError::LoadFailed)?; + let revealed: bool = contract + .call_const(aura_random::functions::sent_reveal::call( + round, + our_address, + )) + .map_err(PhaseError::LoadFailed)?; // With all the information known, we can determine the actual state we are in. if is_reveal_phase && is_commit_phase { diff --git a/ethcore/src/engines/authority_round/util.rs b/ethcore/src/engines/authority_round/util.rs new file mode 100644 index 00000000000..c6333573882 --- /dev/null +++ b/ethcore/src/engines/authority_round/util.rs @@ -0,0 +1,75 @@ +//! Utility functions. +//! +//! Contains small functions used by the AuRa engine that are not strictly limited to that scope. + +use std::fmt; + +use ethabi; +use ethereum_types::Address; + +use client::{BlockId, CallContract, Client}; + +/// A contract bound to a client and block number. +/// +/// A bound contract is a combination of a `Client` reference, a `BlockId` and a contract `Address`. +/// These three parts are enough to call a contract's function; return values are automatically +/// decoded. +pub struct BoundContract<'a> { + client: &'a Client, + block_id: BlockId, + contract_addr: Address, +} + +/// Contract call failed error. +#[derive(Debug)] +pub enum CallError { + /// The call itself failed. + CallFailed(String), + /// Decoding the return value failed or the decoded value was a failure. + DecodeFailed(ethabi::Error), +} + +impl<'a> fmt::Debug for BoundContract<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("BoundContract") + .field("client", &(self.client as *const Client)) + .field("block_id", &self.block_id) + .field("contract_addr", &self.contract_addr) + .finish() + } +} + +impl<'a> BoundContract<'a> { + /// Create a new `BoundContract`. + #[inline] + pub fn bind(client: &Client, block_id: BlockId, contract_addr: Address) -> BoundContract { + BoundContract { + client, + block_id, + contract_addr, + } + } + + /// Perform a function call to an ethereum machine. + /// + /// Runs a constant function call on `client`. The `call` value can be serialized by calling any + /// api function generated by the `use_contract!` macro. + pub fn call_const(&self, call: (ethabi::Bytes, D)) -> Result + where + D: ethabi::FunctionOutputDecoder, + { + let (data, output_decoder) = call; + + let call_return = self + .client + .call_contract(self.block_id, self.contract_addr, data) + .map_err(CallError::CallFailed)?; + + // Decode the result and return it. + // NOTE: We return an already wrapped error here, ideally this function could be generalized by + // removing the `map_err` and sticking it elsewhere. + output_decoder + .decode(call_return.as_slice()) + .map_err(CallError::DecodeFailed) + } +} From 9afb5099627a3c4bf354caad56c10f8aa0b0ca40 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Mon, 31 Dec 2018 13:54:18 +0100 Subject: [PATCH 10/22] Added capabilities to `BoundContract` to call non-constant functions by scheduling a transaction. --- ethcore/src/engines/authority_round/util.rs | 27 ++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/ethcore/src/engines/authority_round/util.rs b/ethcore/src/engines/authority_round/util.rs index c6333573882..9476a14afbd 100644 --- a/ethcore/src/engines/authority_round/util.rs +++ b/ethcore/src/engines/authority_round/util.rs @@ -7,7 +7,8 @@ use std::fmt; use ethabi; use ethereum_types::Address; -use client::{BlockId, CallContract, Client}; +use client::{BlockId, CallContract, Client, EngineClient}; +use transaction; /// A contract bound to a client and block number. /// @@ -27,6 +28,10 @@ pub enum CallError { CallFailed(String), /// Decoding the return value failed or the decoded value was a failure. DecodeFailed(ethabi::Error), + /// The passed in client reference could not be upgraded to a `BlockchainClient`. + NotFullClient, + /// The transaction required to make a call could not be scheduled. + TransactionFailed(transaction::Error), } impl<'a> fmt::Debug for BoundContract<'a> { @@ -72,4 +77,24 @@ impl<'a> BoundContract<'a> { .decode(call_return.as_slice()) .map_err(CallError::DecodeFailed) } + + /// Schedules a transaction that calls a contract. + /// + /// Causes `client` to schedule a call to the bound contract. The `call` value can be serialized + /// by calling any api function generated by the `use_contract!` macro. + pub fn schedule_call_transaction(&self, call: (ethabi::Bytes, D)) -> Result<(), CallError> { + // NOTE: The second item of `call` is actually meaningless, since the function will only be + // executed later on when the transaction is processed. For this reason, there is no + // `ethabi::FunctionOutputDecoder` trait bound on it, even though the `use_contract` + // macro generates these for constant and non-constant functions the same way. + let (data, _) = call; + + let cl = self + .client + .as_full_client() + .ok_or(CallError::NotFullClient)?; + + cl.transact_contract(self.contract_addr, data) + .map_err(CallError::TransactionFailed) + } } From 35c4f555d84d3779d4b9bd3db695799d53366017 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Mon, 31 Dec 2018 14:53:22 +0100 Subject: [PATCH 11/22] Implemented remainder of high-level logic for `randomness` module. --- .../src/engines/authority_round/randomness.rs | 115 +++++++++++++++--- 1 file changed, 100 insertions(+), 15 deletions(-) diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 1dfc5b01f7b..522424e8fdc 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -2,10 +2,13 @@ //! //! This module contains the support code for the on-chain randomness generation used by AuRa. -use ethereum_types::Address; +use ethabi::{Bytes, Hash}; +use ethereum_types::{Address, U256}; +use rand::Rng; use super::util::{BoundContract, CallError}; -use client::{BlockId, Client}; + +pub type Secret = U256; use_contract!(aura_random, "res/authority_round_random.json"); @@ -34,26 +37,38 @@ use_contract!(aura_random, "res/authority_round_random.json"); /// +--------------+ +---------------+ /// /// -/// Phase transitions are performed by the smart contract and simply queried by the engine, while -/// all calls are made directly. -#[derive(Clone, Copy, Debug)] +/// Phase transitions are performed by the smart contract and simply queried by the engine. +/// +/// A typical case of using `RandomnessPhase` is: +/// +/// 1. `RandomnessPhase::load()` the phase from the blockchain data. +/// 2. Load the stored secret. +/// 3. Call `RandomnessPhase::advance()` with the stored secret. +/// 4. Update the stored secret with the return value. +#[derive(Debug)] pub enum RandomnessPhase { + // NOTE: Some states include information already gathered during `load` (e.g. `our_address`, + // `round`) for efficiency reasons. /// Waiting for the next phase. /// /// This state indicates either the successful revelation in this round or having missed the /// window to make a commitment. Waiting, /// Indicates a commitment is possible, but still missing. - BeforeCommit, + BeforeCommit { our_address: Address }, /// Indicates a successful commitment, waiting for the commit phase to end. Committed, /// Indicates revealing is expected as the next step. - Reveal, + Reveal { our_address: Address, round: U256 }, } /// Phase loading error for randomness generation state machine. /// -/// This error usually indicates a bug in either the smart contract or the phase loading function. +/// This error usually indicates a bug in either the smart contract, the phase loading function or +/// some state being lost. +/// +/// The `LostSecret` and `StaleSecret` will usually result in punishment by the contract or the +/// other validators. #[derive(Debug)] pub enum PhaseError { /// The smart contract reported a phase as both commitment and reveal phase. @@ -63,6 +78,12 @@ pub enum PhaseError { RevealedInCommit, /// Calling a contract to determine the phase failed. LoadFailed(CallError), + /// Failed to schedule a transaction to call a contract. + TransactionFailed(CallError), + /// When trying to reveal the secret, no secret was found. + LostSecret, + /// A secret was stored, but it did not match the committed hash. + StaleSecret, } impl RandomnessPhase { @@ -72,13 +93,9 @@ impl RandomnessPhase { /// handled (that is, the phase and whether or not the current validator still needs to send /// commitments or reveal secrets). pub fn load( - client: &Client, - block_id: BlockId, - contract_address: Address, + contract: &BoundContract, our_address: Address, ) -> Result { - let contract = BoundContract::bind(client, block_id, contract_address); - // Determine the current round and which phase we are in. let round = contract .call_const(aura_random::functions::current_collect_round::call()) @@ -115,7 +132,7 @@ impl RandomnessPhase { } if !committed { - Ok(RandomnessPhase::BeforeCommit) + Ok(RandomnessPhase::BeforeCommit { our_address }) } else { Ok(RandomnessPhase::Committed) } @@ -127,10 +144,78 @@ impl RandomnessPhase { } if !revealed { - Ok(RandomnessPhase::Reveal) + Ok(RandomnessPhase::Reveal { our_address, round }) } else { Ok(RandomnessPhase::Waiting) } } } + + /// Advance the randomness state, if necessary. + /// + /// Creates the transaction necessary to advance the randomness contract's state and schedules + /// them to run on the `client` inside `contract`. The `stored_secret` must be managed by the + /// the caller, passed in each time `advance` is called and replaced with the returned value + /// each time the function returns successfully. + /// + /// **Warning**: The `advance()` function should be called only once per block state; otherwise + /// spurious transactions resulting in punishments might be executed. + pub fn advance( + self, + contract: &BoundContract, + stored_secret: Option, + rng: &mut R, + ) -> Result, PhaseError> { + match self { + RandomnessPhase::Waiting | RandomnessPhase::Committed => Ok(stored_secret), + RandomnessPhase::BeforeCommit { our_address } => { + // We generate a new secret to submit each time, this function will only be called + // once per round of randomness generation. + let mut buf = [0u8; 32]; + rng.fill_bytes(&mut buf); + let secret: Secret = buf.into(); + let secret_hash: Hash = unimplemented!(); + + // Currently the PoS contracts are setup in a way that only the system address can + // commit hashes, so we need to sign "manually". + let signature: Bytes = unimplemented!(); + + // Schedule the transaction that commits the hash. + contract + .schedule_call_transaction(aura_random::functions::commit_hash::call( + secret_hash, + signature, + )) + .map_err(PhaseError::TransactionFailed)?; + + // Store the newly generated secret. + Ok(Some(secret)) + } + RandomnessPhase::Reveal { round, our_address } => { + let secret = stored_secret.ok_or(PhaseError::LostSecret)?; + + // We hash our secret again to check against the already committed hash: + let secret_hash: Hash = unimplemented!(); + let committed_hash: Hash = contract + .call_const(aura_random::functions::get_commit::call(round, our_address)) + .map_err(PhaseError::LoadFailed)?; + + if (secret_hash != committed_hash) { + return Err(PhaseError::StaleSecret); + } + + // We are now sure that we have the correct secret and can reveal it. + let signature: Bytes = unimplemented!(); + contract + .schedule_call_transaction(aura_random::functions::reveal_secret::call( + secret, signature, + )) + .map_err(PhaseError::TransactionFailed)?; + + // We still pass back the secret -- if anything fails later down the line, we can + // resume by simply creating another transaction. + Ok(Some(secret)) + } + } + } } From c1a6d61338c3b0ad9cfbe7a0f40d0bf1a99035c8 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Mon, 31 Dec 2018 16:32:29 +0100 Subject: [PATCH 12/22] Actually hash secrets before transferring them. --- ethcore/src/engines/authority_round/randomness.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 522424e8fdc..943be5cdffa 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -4,11 +4,15 @@ use ethabi::{Bytes, Hash}; use ethereum_types::{Address, U256}; +use hash::keccak; use rand::Rng; use super::util::{BoundContract, CallError}; -pub type Secret = U256; +/// Secret type expected by the contract. +// Note: Conversion from `U256` back into `[u8; 32]` is cumbersome (missing implementations), for +// this reason we store the raw buffers. +pub type Secret = [u8; 32]; use_contract!(aura_random, "res/authority_round_random.json"); @@ -173,8 +177,9 @@ impl RandomnessPhase { // once per round of randomness generation. let mut buf = [0u8; 32]; rng.fill_bytes(&mut buf); + let secret: Secret = buf.into(); - let secret_hash: Hash = unimplemented!(); + let secret_hash: Hash = keccak(secret.as_ref()); // Currently the PoS contracts are setup in a way that only the system address can // commit hashes, so we need to sign "manually". @@ -195,7 +200,7 @@ impl RandomnessPhase { let secret = stored_secret.ok_or(PhaseError::LostSecret)?; // We hash our secret again to check against the already committed hash: - let secret_hash: Hash = unimplemented!(); + let secret_hash: Hash = keccak(secret.as_ref()); let committed_hash: Hash = contract .call_const(aura_random::functions::get_commit::call(round, our_address)) .map_err(PhaseError::LoadFailed)?; From f71f1ab8eb58c41b65334ae2f5fa4ee5b8cefa60 Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Mon, 31 Dec 2018 17:13:57 +0100 Subject: [PATCH 13/22] Remove an outdated `NOTE` in `util.rs`. --- ethcore/src/engines/authority_round/util.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ethcore/src/engines/authority_round/util.rs b/ethcore/src/engines/authority_round/util.rs index 9476a14afbd..b48d83e73e2 100644 --- a/ethcore/src/engines/authority_round/util.rs +++ b/ethcore/src/engines/authority_round/util.rs @@ -71,8 +71,6 @@ impl<'a> BoundContract<'a> { .map_err(CallError::CallFailed)?; // Decode the result and return it. - // NOTE: We return an already wrapped error here, ideally this function could be generalized by - // removing the `map_err` and sticking it elsewhere. output_decoder .decode(call_return.as_slice()) .map_err(CallError::DecodeFailed) From 3fbd11d56e42a1474aa1c5988cc6cf14126ac8fd Mon Sep 17 00:00:00 2001 From: Marc Brinkmann Date: Mon, 31 Dec 2018 17:45:10 +0100 Subject: [PATCH 14/22] Improved docs for `randomness.rs` module. --- ethcore/src/engines/authority_round/randomness.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 943be5cdffa..555d65db9bb 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -1,6 +1,11 @@ //! On-chain randomness generation for authority round //! -//! This module contains the support code for the on-chain randomness generation used by AuRa. +//! This module contains the support code for the on-chain randomness generation used by AuRa. Its +//! core is the finite state machine `RandomnessPhase`, which can be loaded from the blockchain +//! state, then asked to perform potentially necessary transaction afterwards using the `advance()` +//! method. +//! +//! No additional state is kept inside the `RandomnessPhase`, it must be passed in each time. use ethabi::{Bytes, Hash}; use ethereum_types::{Address, U256}; From 31ccaf1e4c5806d704f6f701909ab1d62290803d Mon Sep 17 00:00:00 2001 From: Andreas Fackler Date: Mon, 7 Jan 2019 15:15:04 +0100 Subject: [PATCH 15/22] Replace spaces with tabs. --- ethcore/src/engines/authority_round/mod.rs | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 2d57f0470ce..ce13e03abd7 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -87,8 +87,8 @@ pub struct AuthorityRoundParams { pub strict_empty_steps_transition: u64, /// Sets whether Aura will use Proof of Authority (PoA) or Proof of Stake (PoS) consensus. pub consensus_kind: ConsensusKind, - /// If set, enables random number contract integration. - pub randomness_contract: Option
, + /// If set, enables random number contract integration. + pub randomness_contract: Option
, } const U16_MAX: usize = ::std::u16::MAX as usize; @@ -120,7 +120,7 @@ impl From for AuthorityRoundParams { maximum_empty_steps: p.maximum_empty_steps.map_or(0, Into::into), strict_empty_steps_transition: p.strict_empty_steps_transition.map_or(0, Into::into), consensus_kind: p.consensus_kind.unwrap_or(ConsensusKind::Poa), - randomness_contract: None, + randomness_contract: None, } } } @@ -1798,16 +1798,16 @@ mod tests { assert_eq!(aura.maximum_uncle_count(100), 0); } - #[test] - #[should_panic(expected="counter is too high")] - fn test_counter_increment_too_high() { - use super::Step; - let step = Step { - calibrate: false, - inner: AtomicUsize::new(::std::usize::MAX), - duration: 1, - }; - step.increment(); + #[test] + #[should_panic(expected="counter is too high")] + fn test_counter_increment_too_high() { + use super::Step; + let step = Step { + calibrate: false, + inner: AtomicUsize::new(::std::usize::MAX), + duration: 1, + }; + step.increment(); } #[test] From fe3734216e9c839d0c3001face6f8b0a1350f6e3 Mon Sep 17 00:00:00 2001 From: Andreas Fackler Date: Tue, 8 Jan 2019 15:51:12 +0100 Subject: [PATCH 16/22] Implement signing. --- ethcore/src/engines/authority_round/mod.rs | 35 +++++++++++++++++-- .../src/engines/authority_round/randomness.rs | 15 +++++--- ethcore/src/engines/authority_round/util.rs | 10 +++--- ethcore/src/engines/validator_set/test.rs | 2 ++ 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index ce13e03abd7..187e08b933e 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -26,7 +26,7 @@ use std::time::{UNIX_EPOCH, SystemTime, Duration}; use account_provider::AccountProvider; use block::*; -use client::EngineClient; +use client::{BlockId, EngineClient}; use engines::{Engine, Seal, EngineError, ConstructedVerifier}; use engines::block_reward; use engines::block_reward::{BlockRewardContract, RewardKind}; @@ -436,6 +436,11 @@ pub struct AuthorityRound { maximum_empty_steps: usize, consensus_kind: ConsensusKind, machine: EthereumMachine, + /// The stored secret contribution to randomness. + // TODO: Only used in PoS. Maybe make part of `ConsensusKind`? Or tie together with `randomness_contract`? + rand_secret: RwLock>, + /// If set, enables random number contract integration. + randomness_contract: Option
, } // header-chain validator. @@ -690,6 +695,8 @@ impl AuthorityRound { strict_empty_steps_transition: our_params.strict_empty_steps_transition, consensus_kind: our_params.consensus_kind, machine: machine, + rand_secret: Default::default(), + randomness_contract: our_params.randomness_contract, }); // Do not initialize timeouts for tests. @@ -1143,6 +1150,28 @@ impl Engine for AuthorityRound { epoch_begin: bool, _ancestry: &mut Iterator, ) -> Result<(), Error> { + // Random number generation + // TODO: Is this the right place to do this? + if let (Some(contract_addr), Some(our_addr)) = (self.randomness_contract, self.signer.read().address()) { + let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) { + Some(client) => client, + None => { + debug!(target: "engine", "Unable to close block: missing client ref."); + return Err(EngineError::RequiresClient.into()) + }, + }; + let block_id = BlockId::Number(block.header.number()); + let mut contract = util::BoundContract::bind(&*client, block_id, contract_addr); + // TODO: How should these errors be handled? + let phase = randomness::RandomnessPhase::load(&contract, our_addr) + .map_err(|err| EngineError::FailedSystemCall(format!("Randomness error: {:?}", err)))?; + let secret = *self.rand_secret.read(); + let mut rng = ::rand::OsRng::new()?; + // TODO: Add new transaction to the block? + *self.rand_secret.write() = phase.advance(&contract, secret, &*self.signer.read(), &mut rng) + .map_err(|err| EngineError::FailedSystemCall(format!("Randomness error: {:?}", err)))?; + } + // with immediate transitions, we don't use the epoch mechanism anyway. // the genesis is always considered an epoch, but we ignore it intentionally. if self.immediate_transitions || !epoch_begin { return Ok(()) } @@ -1537,7 +1566,6 @@ mod tests { use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use hash::keccak; use ethereum_types::{Address, H520, H256, U256}; - use ethjson::spec::authority_round::ConsensusKind; use ethkey::Signature; use header::Header; use rlp::encode; @@ -1552,7 +1580,7 @@ mod tests { use engines::{Seal, Engine, EngineError, EthEngine}; use engines::validator_set::{TestSet, SimpleList}; use error::{Error, ErrorKind}; - use super::{AuthorityRoundParams, AuthorityRound, EmptyStep, SealedEmptyStep, calculate_score}; + use super::{AuthorityRoundParams, AuthorityRound, EmptyStep, SealedEmptyStep, calculate_score, ConsensusKind}; fn aura(f: F) -> Arc where F: FnOnce(&mut AuthorityRoundParams), @@ -1573,6 +1601,7 @@ mod tests { block_reward_contract: Default::default(), strict_empty_steps_transition: 0, consensus_kind: ConsensusKind::Poa, + randomness_contract: None, }; // mutate aura params diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 555d65db9bb..118d7f2ea53 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -7,6 +7,8 @@ //! //! No additional state is kept inside the `RandomnessPhase`, it must be passed in each time. +use account_provider::SignError; +use engines::signer::EngineSigner; use ethabi::{Bytes, Hash}; use ethereum_types::{Address, U256}; use hash::keccak; @@ -91,6 +93,8 @@ pub enum PhaseError { TransactionFailed(CallError), /// When trying to reveal the secret, no secret was found. LostSecret, + /// Failed to sign our commitment or secret. + Sign(SignError), /// A secret was stored, but it did not match the committed hash. StaleSecret, } @@ -173,11 +177,12 @@ impl RandomnessPhase { self, contract: &BoundContract, stored_secret: Option, + signer: &EngineSigner, rng: &mut R, ) -> Result, PhaseError> { match self { RandomnessPhase::Waiting | RandomnessPhase::Committed => Ok(stored_secret), - RandomnessPhase::BeforeCommit { our_address } => { + RandomnessPhase::BeforeCommit { .. } => { // We generate a new secret to submit each time, this function will only be called // once per round of randomness generation. let mut buf = [0u8; 32]; @@ -188,7 +193,8 @@ impl RandomnessPhase { // Currently the PoS contracts are setup in a way that only the system address can // commit hashes, so we need to sign "manually". - let signature: Bytes = unimplemented!(); + let signature: Bytes = + signer.sign(secret_hash).map_err(PhaseError::Sign)?.as_ref().into(); // Schedule the transaction that commits the hash. contract @@ -210,12 +216,13 @@ impl RandomnessPhase { .call_const(aura_random::functions::get_commit::call(round, our_address)) .map_err(PhaseError::LoadFailed)?; - if (secret_hash != committed_hash) { + if secret_hash != committed_hash { return Err(PhaseError::StaleSecret); } // We are now sure that we have the correct secret and can reveal it. - let signature: Bytes = unimplemented!(); + let signature: Bytes = + signer.sign(secret.into()).map_err(PhaseError::Sign)?.as_ref().into(); contract .schedule_call_transaction(aura_random::functions::reveal_secret::call( secret, signature, diff --git a/ethcore/src/engines/authority_round/util.rs b/ethcore/src/engines/authority_round/util.rs index b48d83e73e2..e60930d4724 100644 --- a/ethcore/src/engines/authority_round/util.rs +++ b/ethcore/src/engines/authority_round/util.rs @@ -7,7 +7,7 @@ use std::fmt; use ethabi; use ethereum_types::Address; -use client::{BlockId, CallContract, Client, EngineClient}; +use client::{BlockId, EngineClient}; use transaction; /// A contract bound to a client and block number. @@ -16,7 +16,7 @@ use transaction; /// These three parts are enough to call a contract's function; return values are automatically /// decoded. pub struct BoundContract<'a> { - client: &'a Client, + client: &'a EngineClient, block_id: BlockId, contract_addr: Address, } @@ -37,7 +37,7 @@ pub enum CallError { impl<'a> fmt::Debug for BoundContract<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("BoundContract") - .field("client", &(self.client as *const Client)) + .field("client", &(self.client as *const EngineClient)) .field("block_id", &self.block_id) .field("contract_addr", &self.contract_addr) .finish() @@ -47,7 +47,7 @@ impl<'a> fmt::Debug for BoundContract<'a> { impl<'a> BoundContract<'a> { /// Create a new `BoundContract`. #[inline] - pub fn bind(client: &Client, block_id: BlockId, contract_addr: Address) -> BoundContract { + pub fn bind(client: &EngineClient, block_id: BlockId, contract_addr: Address) -> BoundContract { BoundContract { client, block_id, @@ -67,6 +67,8 @@ impl<'a> BoundContract<'a> { let call_return = self .client + .as_full_client() + .ok_or(CallError::NotFullClient)? .call_contract(self.block_id, self.contract_addr, data) .map_err(CallError::CallFailed)?; diff --git a/ethcore/src/engines/validator_set/test.rs b/ethcore/src/engines/validator_set/test.rs index 9650526e577..122bc39e4eb 100644 --- a/ethcore/src/engines/validator_set/test.rs +++ b/ethcore/src/engines/validator_set/test.rs @@ -22,7 +22,9 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use heapsize::HeapSizeOf; use ethereum_types::{H256, Address}; use bytes::Bytes; +use ethkey::Signature; +use error::Error; use machine::{AuxiliaryData, Call, EthereumMachine}; use header::{Header, BlockNumber}; use super::{ValidatorSet, SimpleList}; From 59a19cee4d505de60d2eaca98a80acb276fceed7 Mon Sep 17 00:00:00 2001 From: Andreas Fackler Date: Wed, 9 Jan 2019 14:06:17 +0100 Subject: [PATCH 17/22] Make calls service transactions i.e. zero gas price. --- ethcore/src/client/client.rs | 12 +++++++----- ethcore/src/client/test_client.rs | 10 ++++++---- ethcore/src/client/traits.rs | 12 ++++++++++-- ethcore/src/engines/authority_round/randomness.rs | 4 ++-- ethcore/src/engines/authority_round/util.rs | 10 +++++----- 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 1b49d009225..a900fe1424a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -2110,15 +2110,17 @@ impl BlockChainClient for Client { } } - fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { + fn transact(&self, action: Action, data: Bytes, gas: Option, gas_price: Option) + -> Result<(), transaction::Error> + { let authoring_params = self.importer.miner.authoring_params(); let transaction = Transaction { nonce: self.latest_nonce(&authoring_params.author), - action: Action::Call(address), - gas: self.importer.miner.sensible_gas_limit(), - gas_price: self.importer.miner.sensible_gas_price(), + action, + gas: gas.unwrap_or_else(|| self.importer.miner.sensible_gas_limit()), + gas_price: gas_price.unwrap_or_else(|| self.importer.miner.sensible_gas_price()), value: U256::zero(), - data: data, + data, }; let chain_id = self.engine.signing_chain_id(&self.latest_env_info()); let signature = self.engine.sign(transaction.hash(chain_id)) diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 3c422c49a58..f0f42426bc4 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -859,12 +859,14 @@ impl BlockChainClient for TestBlockChainClient { } } - fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { + fn transact(&self, action: Action, data: Bytes, gas: Option, gas_price: Option) + -> Result<(), transaction::Error> + { let transaction = Transaction { nonce: self.latest_nonce(&self.miner.authoring_params().author), - action: Action::Call(address), - gas: self.spec.gas_limit, - gas_price: U256::zero(), + action, + gas: gas.unwrap_or(self.spec.gas_limit), + gas_price: gas_price.unwrap_or(U256::zero()), value: U256::default(), data: data, }; diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 55d527013ec..9bb7a51d491 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -32,7 +32,7 @@ use header::{BlockNumber}; use log_entry::LocalizedLogEntry; use receipt::LocalizedReceipt; use trace::LocalizedTrace; -use transaction::{self, LocalizedTransaction, SignedTransaction}; +use transaction::{self, LocalizedTransaction, SignedTransaction, Action}; use verification::queue::QueueInfo as BlockQueueInfo; use verification::queue::kind::blocks::Unverified; use state::StateInfo; @@ -388,7 +388,15 @@ pub trait BlockChainClient : Sync + Send + AccountData + BlockChain + CallContra fn pruning_info(&self) -> PruningInfo; /// Schedule state-altering transaction to be executed on the next pending block. - fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error>; + fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { + self.transact(Action::Call(address), data, None, None) + } + + /// Schedule state-altering transaction to be executed on the next pending block with the given gas parameters. + /// + /// If they are `None`, sensible values are selected automatically. + fn transact(&self, action: Action, data: Bytes, gas: Option, gas_price: Option) + -> Result<(), transaction::Error>; /// Get the address of the registry itself. fn registrar_address(&self) -> Option
; diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 118d7f2ea53..204b018c5c8 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -198,7 +198,7 @@ impl RandomnessPhase { // Schedule the transaction that commits the hash. contract - .schedule_call_transaction(aura_random::functions::commit_hash::call( + .schedule_service_transaction(aura_random::functions::commit_hash::call( secret_hash, signature, )) @@ -224,7 +224,7 @@ impl RandomnessPhase { let signature: Bytes = signer.sign(secret.into()).map_err(PhaseError::Sign)?.as_ref().into(); contract - .schedule_call_transaction(aura_random::functions::reveal_secret::call( + .schedule_service_transaction(aura_random::functions::reveal_secret::call( secret, signature, )) .map_err(PhaseError::TransactionFailed)?; diff --git a/ethcore/src/engines/authority_round/util.rs b/ethcore/src/engines/authority_round/util.rs index e60930d4724..9d05d9de48b 100644 --- a/ethcore/src/engines/authority_round/util.rs +++ b/ethcore/src/engines/authority_round/util.rs @@ -5,10 +5,10 @@ use std::fmt; use ethabi; -use ethereum_types::Address; +use ethereum_types::{Address, U256}; use client::{BlockId, EngineClient}; -use transaction; +use transaction::{self, Action}; /// A contract bound to a client and block number. /// @@ -78,11 +78,11 @@ impl<'a> BoundContract<'a> { .map_err(CallError::DecodeFailed) } - /// Schedules a transaction that calls a contract. + /// Schedules a service transaction (with gas price zero) that calls a contract. /// /// Causes `client` to schedule a call to the bound contract. The `call` value can be serialized /// by calling any api function generated by the `use_contract!` macro. - pub fn schedule_call_transaction(&self, call: (ethabi::Bytes, D)) -> Result<(), CallError> { + pub fn schedule_service_transaction(&self, call: (ethabi::Bytes, D)) -> Result<(), CallError> { // NOTE: The second item of `call` is actually meaningless, since the function will only be // executed later on when the transaction is processed. For this reason, there is no // `ethabi::FunctionOutputDecoder` trait bound on it, even though the `use_contract` @@ -94,7 +94,7 @@ impl<'a> BoundContract<'a> { .as_full_client() .ok_or(CallError::NotFullClient)?; - cl.transact_contract(self.contract_addr, data) + cl.transact(Action::Call(self.contract_addr), data, None, Some(U256::zero())) .map_err(CallError::TransactionFailed) } } From adcf4a26e0de44a2dbb7bf97f38824188b74862d Mon Sep 17 00:00:00 2001 From: Andreas Fackler Date: Wed, 9 Jan 2019 15:30:48 +0100 Subject: [PATCH 18/22] Update randomness contract. --- ethcore/res/authority_round_random.json | 584 +++++++++--------- ethcore/src/engines/authority_round/mod.rs | 2 +- .../src/engines/authority_round/randomness.rs | 31 +- ethcore/src/engines/validator_set/test.rs | 3 - 4 files changed, 296 insertions(+), 324 deletions(-) diff --git a/ethcore/res/authority_round_random.json b/ethcore/res/authority_round_random.json index 8e5f17a8790..d3d9af92f2b 100644 --- a/ethcore/res/authority_round_random.json +++ b/ethcore/res/authority_round_random.json @@ -1,336 +1,328 @@ [ { - "type" : "function", - "outputs" : [], - "name" : "commitHash", - "stateMutability" : "nonpayable", - "constant" : false, - "payable" : false, - "inputs" : [ - { - "type" : "bytes32", - "name" : "_secretHash" - }, - { - "type" : "bytes", - "name" : "_signature" - } - ] + "constant": true, + "inputs": [], + "name": "COMMIT_PHASE_LENGTH", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "stateMutability" : "view", - "type" : "function", - "name" : "blocksProducers", - "outputs" : [ - { - "type" : "address[]", - "name" : "" - } - ], - "payable" : false, - "inputs" : [ - { - "type" : "uint256", - "name" : "_collectRound" - } - ], - "constant" : true + "constant": true, + "inputs": [], + "name": "currentRandom", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "type" : "function", - "name" : "COMMIT_PHASE_LENGTH", - "outputs" : [ - { - "name" : "", - "type" : "uint256" - } - ], - "stateMutability" : "view", - "constant" : true, - "payable" : false, - "inputs" : [] + "constant": true, + "inputs": [], + "name": "VALIDATOR_SET_CONTRACT", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "constant" : true, - "payable" : false, - "inputs" : [], - "type" : "function", - "name" : "currentRandom", - "outputs" : [ - { - "type" : "uint256[]", - "name" : "" - } - ], - "stateMutability" : "view" + "constant": true, + "inputs": [], + "name": "COLLECT_ROUND_LENGTH", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "stateMutability" : "view", - "name" : "committedValidators", - "type" : "function", - "outputs" : [ - { - "type" : "address[]", - "name" : "" - } - ], - "inputs" : [ - { - "name" : "_collectRound", - "type" : "uint256" - } - ], - "payable" : false, - "constant" : true + "constant": false, + "inputs": [ + { + "name": "_secretHash", + "type": "bytes32" + } + ], + "name": "commitHash", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" }, { - "payable" : false, - "inputs" : [ - { - "type" : "uint256", - "name" : "_collectRound" - }, - { - "type" : "address", - "name" : "_validator" - } - ], - "constant" : true, - "stateMutability" : "view", - "outputs" : [ - { - "name" : "", - "type" : "bool" - } - ], - "type" : "function", - "name" : "sentReveal" + "constant": false, + "inputs": [ + { + "name": "_secret", + "type": "uint256" + } + ], + "name": "revealSecret", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" }, { - "stateMutability" : "view", - "type" : "function", - "outputs" : [ - { - "name" : "", - "type" : "bool" - } - ], - "name" : "isCommitPhase", - "inputs" : [], - "payable" : false, - "constant" : true + "constant": false, + "inputs": [ + { + "name": "_currentValidator", + "type": "address" + } + ], + "name": "onBlockClose", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" }, { - "stateMutability" : "view", - "name" : "VALIDATOR_SET_CONTRACT", - "type" : "function", - "outputs" : [ - { - "name" : "", - "type" : "address" - } - ], - "payable" : false, - "inputs" : [], - "constant" : true + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + } + ], + "name": "blocksProducers", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "name" : "revealSecret", - "type" : "function", - "outputs" : [], - "stateMutability" : "nonpayable", - "constant" : false, - "inputs" : [ - { - "name" : "_secret", - "type" : "uint256" - }, - { - "name" : "_signature", - "type" : "bytes" - } - ], - "payable" : false + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + } + ], + "name": "committedValidators", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "payable" : false, - "inputs" : [], - "constant" : true, - "stateMutability" : "view", - "type" : "function", - "name" : "currentCollectRound", - "outputs" : [ - { - "name" : "", - "type" : "uint256" - } - ] + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "createdBlockOnCommitsPhase", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "type" : "function", - "name" : "createdBlockOnRevealsPhase", - "outputs" : [ - { - "type" : "bool", - "name" : "" - } - ], - "stateMutability" : "view", - "constant" : true, - "payable" : false, - "inputs" : [ - { - "type" : "uint256", - "name" : "_collectRound" - }, - { - "type" : "address", - "name" : "_validator" - } - ] + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "createdBlockOnRevealsPhase", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "constant" : true, - "inputs" : [], - "payable" : false, - "outputs" : [ - { - "type" : "uint256", - "name" : "" - } - ], - "type" : "function", - "name" : "COLLECT_ROUND_LENGTH", - "stateMutability" : "view" + "constant": true, + "inputs": [], + "name": "currentCollectRound", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "name" : "isCommitted", - "type" : "function", - "outputs" : [ - { - "name" : "", - "type" : "bool" - } - ], - "stateMutability" : "view", - "constant" : true, - "inputs" : [ - { - "type" : "uint256", - "name" : "_collectRound" - }, - { - "type" : "address", - "name" : "_validator" - } - ], - "payable" : false + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "getCommit", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "payable" : false, - "inputs" : [ - { - "name" : "_currentValidator", - "type" : "address" - } - ], - "constant" : false, - "stateMutability" : "nonpayable", - "type" : "function", - "name" : "onBlockClose", - "outputs" : [] + "constant": true, + "inputs": [], + "name": "getCurrentSecret", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "stateMutability" : "view", - "type" : "function", - "outputs" : [ - { - "type" : "bool", - "name" : "" - } - ], - "name" : "isRevealPhase", - "payable" : false, - "inputs" : [], - "constant" : true + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "isCommitted", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "payable" : false, - "inputs" : [], - "constant" : true, - "stateMutability" : "view", - "type" : "function", - "name" : "getCurrentSecret", - "outputs" : [ - { - "type" : "uint256", - "name" : "" - } - ] + "constant": true, + "inputs": [], + "name": "isCommitPhase", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "payable" : false, - "inputs" : [ - { - "name" : "_collectRound", - "type" : "uint256" - }, - { - "name" : "_validator", - "type" : "address" - } - ], - "constant" : true, - "stateMutability" : "view", - "outputs" : [ - { - "type" : "bool", - "name" : "" - } - ], - "type" : "function", - "name" : "createdBlockOnCommitsPhase" + "constant": true, + "inputs": [], + "name": "isRevealPhase", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "stateMutability" : "view", - "name" : "revealsCount", - "type" : "function", - "outputs" : [ - { - "type" : "uint256", - "name" : "" - } - ], - "payable" : false, - "inputs" : [ - { - "type" : "uint256", - "name" : "_collectRound" - } - ], - "constant" : true + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + } + ], + "name": "revealsCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "inputs" : [ - { - "name" : "_collectRound", - "type" : "uint256" - }, - { - "name" : "_validator", - "type" : "address" - } - ], - "payable" : false, - "constant" : true, - "stateMutability" : "view", - "type" : "function", - "outputs" : [ - { - "type" : "bytes32", - "name" : "" - } - ], - "name" : "getCommit" + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "sentReveal", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" } ] diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 187e08b933e..7a70a5c52e1 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -1168,7 +1168,7 @@ impl Engine for AuthorityRound { let secret = *self.rand_secret.read(); let mut rng = ::rand::OsRng::new()?; // TODO: Add new transaction to the block? - *self.rand_secret.write() = phase.advance(&contract, secret, &*self.signer.read(), &mut rng) + *self.rand_secret.write() = phase.advance(&contract, secret, &mut rng) .map_err(|err| EngineError::FailedSystemCall(format!("Randomness error: {:?}", err)))?; } diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 204b018c5c8..84e2139e22e 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -7,9 +7,7 @@ //! //! No additional state is kept inside the `RandomnessPhase`, it must be passed in each time. -use account_provider::SignError; -use engines::signer::EngineSigner; -use ethabi::{Bytes, Hash}; +use ethabi::Hash; use ethereum_types::{Address, U256}; use hash::keccak; use rand::Rng; @@ -27,6 +25,7 @@ use_contract!(aura_random, "res/authority_round_random.json"); /// /// The process of generating random numbers is a simple finite state machine: /// +/// ```ignore /// + /// | /// | @@ -46,6 +45,7 @@ use_contract!(aura_random, "res/authority_round_random.json"); /// | Committed +------------------------------> Reveal | /// | | enter reveal phase | | /// +--------------+ +---------------+ +/// ``` /// /// /// Phase transitions are performed by the smart contract and simply queried by the engine. @@ -93,8 +93,6 @@ pub enum PhaseError { TransactionFailed(CallError), /// When trying to reveal the secret, no secret was found. LostSecret, - /// Failed to sign our commitment or secret. - Sign(SignError), /// A secret was stored, but it did not match the committed hash. StaleSecret, } @@ -177,7 +175,6 @@ impl RandomnessPhase { self, contract: &BoundContract, stored_secret: Option, - signer: &EngineSigner, rng: &mut R, ) -> Result, PhaseError> { match self { @@ -191,18 +188,9 @@ impl RandomnessPhase { let secret: Secret = buf.into(); let secret_hash: Hash = keccak(secret.as_ref()); - // Currently the PoS contracts are setup in a way that only the system address can - // commit hashes, so we need to sign "manually". - let signature: Bytes = - signer.sign(secret_hash).map_err(PhaseError::Sign)?.as_ref().into(); - // Schedule the transaction that commits the hash. - contract - .schedule_service_transaction(aura_random::functions::commit_hash::call( - secret_hash, - signature, - )) - .map_err(PhaseError::TransactionFailed)?; + let data = aura_random::functions::commit_hash::call(secret_hash); + contract.schedule_service_transaction(data).map_err(PhaseError::TransactionFailed)?; // Store the newly generated secret. Ok(Some(secret)) @@ -221,13 +209,8 @@ impl RandomnessPhase { } // We are now sure that we have the correct secret and can reveal it. - let signature: Bytes = - signer.sign(secret.into()).map_err(PhaseError::Sign)?.as_ref().into(); - contract - .schedule_service_transaction(aura_random::functions::reveal_secret::call( - secret, signature, - )) - .map_err(PhaseError::TransactionFailed)?; + let data = aura_random::functions::reveal_secret::call(secret); + contract.schedule_service_transaction(data).map_err(PhaseError::TransactionFailed)?; // We still pass back the secret -- if anything fails later down the line, we can // resume by simply creating another transaction. diff --git a/ethcore/src/engines/validator_set/test.rs b/ethcore/src/engines/validator_set/test.rs index 122bc39e4eb..2cbce1b708d 100644 --- a/ethcore/src/engines/validator_set/test.rs +++ b/ethcore/src/engines/validator_set/test.rs @@ -22,9 +22,6 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use heapsize::HeapSizeOf; use ethereum_types::{H256, Address}; use bytes::Bytes; -use ethkey::Signature; - -use error::Error; use machine::{AuxiliaryData, Call, EthereumMachine}; use header::{Header, BlockNumber}; use super::{ValidatorSet, SimpleList}; From d6c0e95f176de99868b2cde592cf794eb0456a08 Mon Sep 17 00:00:00 2001 From: Andreas Fackler Date: Wed, 9 Jan 2019 17:28:50 +0100 Subject: [PATCH 19/22] Address review comments: Extend docs. --- ethcore/src/engines/authority_round/randomness.rs | 2 +- ethcore/src/engines/authority_round/util.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs index 84e2139e22e..59a8b48e982 100644 --- a/ethcore/src/engines/authority_round/randomness.rs +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -25,7 +25,7 @@ use_contract!(aura_random, "res/authority_round_random.json"); /// /// The process of generating random numbers is a simple finite state machine: /// -/// ```ignore +/// ```text /// + /// | /// | diff --git a/ethcore/src/engines/authority_round/util.rs b/ethcore/src/engines/authority_round/util.rs index 9d05d9de48b..246ea0967e9 100644 --- a/ethcore/src/engines/authority_round/util.rs +++ b/ethcore/src/engines/authority_round/util.rs @@ -55,10 +55,11 @@ impl<'a> BoundContract<'a> { } } - /// Perform a function call to an ethereum machine. + /// Perform a function call to an ethereum machine that doesn't create a transaction or change the state. /// /// Runs a constant function call on `client`. The `call` value can be serialized by calling any - /// api function generated by the `use_contract!` macro. + /// api function generated by the `use_contract!` macro. This does not create any transactions, it only produces a + /// result based on the state at the current block. pub fn call_const(&self, call: (ethabi::Bytes, D)) -> Result where D: ethabi::FunctionOutputDecoder, From 3b677d47b9c589a7897a40d296c24de3dcb6fa6d Mon Sep 17 00:00:00 2001 From: Andreas Fackler Date: Thu, 10 Jan 2019 11:32:55 +0100 Subject: [PATCH 20/22] Add randomness option to spec params. --- ethcore/src/engines/authority_round/mod.rs | 14 +++++++------- json/src/spec/authority_round.rs | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 7a70a5c52e1..058118a66c4 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -88,7 +88,7 @@ pub struct AuthorityRoundParams { /// Sets whether Aura will use Proof of Authority (PoA) or Proof of Stake (PoS) consensus. pub consensus_kind: ConsensusKind, /// If set, enables random number contract integration. - pub randomness_contract: Option
, + pub randomness_contract_address: Option
, } const U16_MAX: usize = ::std::u16::MAX as usize; @@ -120,7 +120,7 @@ impl From for AuthorityRoundParams { maximum_empty_steps: p.maximum_empty_steps.map_or(0, Into::into), strict_empty_steps_transition: p.strict_empty_steps_transition.map_or(0, Into::into), consensus_kind: p.consensus_kind.unwrap_or(ConsensusKind::Poa), - randomness_contract: None, + randomness_contract_address: p.randomness_contract_address.map(Into::into), } } } @@ -437,10 +437,10 @@ pub struct AuthorityRound { consensus_kind: ConsensusKind, machine: EthereumMachine, /// The stored secret contribution to randomness. - // TODO: Only used in PoS. Maybe make part of `ConsensusKind`? Or tie together with `randomness_contract`? + // TODO: Only used in PoS. Maybe make part of `ConsensusKind`? Or tie together with `randomness_contract_address`? rand_secret: RwLock>, /// If set, enables random number contract integration. - randomness_contract: Option
, + randomness_contract_address: Option
, } // header-chain validator. @@ -696,7 +696,7 @@ impl AuthorityRound { consensus_kind: our_params.consensus_kind, machine: machine, rand_secret: Default::default(), - randomness_contract: our_params.randomness_contract, + randomness_contract_address: our_params.randomness_contract_address, }); // Do not initialize timeouts for tests. @@ -1152,7 +1152,7 @@ impl Engine for AuthorityRound { ) -> Result<(), Error> { // Random number generation // TODO: Is this the right place to do this? - if let (Some(contract_addr), Some(our_addr)) = (self.randomness_contract, self.signer.read().address()) { + if let (Some(contract_addr), Some(our_addr)) = (self.randomness_contract_address, self.signer.read().address()) { let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) { Some(client) => client, None => { @@ -1601,7 +1601,7 @@ mod tests { block_reward_contract: Default::default(), strict_empty_steps_transition: 0, consensus_kind: ConsensusKind::Poa, - randomness_contract: None, + randomness_contract_address: None, }; // mutate aura params diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index 541fbf5fea0..a91fa0535c5 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -67,6 +67,8 @@ pub struct AuthorityRoundParams { pub strict_empty_steps_transition: Option, /// Sets whether Aura will use Proof of Authority (PoA) or Proof of Stake (PoS) consensus. pub consensus_kind: Option, + /// If set, enables random number contract integration. + pub randomness_contract_address: Option
, } /// Authority engine deserialization. From 8ce962c9f70015cbebf6a381c14ceb7449fb07f9 Mon Sep 17 00:00:00 2001 From: Andreas Fackler Date: Thu, 10 Jan 2019 12:27:03 +0100 Subject: [PATCH 21/22] Reformat ABI file according to editorconfig. --- ethcore/res/authority_round_random.json | 604 +++++++++++------------- 1 file changed, 277 insertions(+), 327 deletions(-) diff --git a/ethcore/res/authority_round_random.json b/ethcore/res/authority_round_random.json index d3d9af92f2b..eede5beb6cb 100644 --- a/ethcore/res/authority_round_random.json +++ b/ethcore/res/authority_round_random.json @@ -1,328 +1,278 @@ -[ - { - "constant": true, - "inputs": [], - "name": "COMMIT_PHASE_LENGTH", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "currentRandom", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "VALIDATOR_SET_CONTRACT", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "COLLECT_ROUND_LENGTH", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_secretHash", - "type": "bytes32" - } - ], - "name": "commitHash", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_secret", - "type": "uint256" - } - ], - "name": "revealSecret", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_currentValidator", - "type": "address" - } - ], - "name": "onBlockClose", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_collectRound", - "type": "uint256" - } - ], - "name": "blocksProducers", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_collectRound", - "type": "uint256" - } - ], - "name": "committedValidators", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_collectRound", - "type": "uint256" - }, - { - "name": "_validator", - "type": "address" - } - ], - "name": "createdBlockOnCommitsPhase", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_collectRound", - "type": "uint256" - }, - { - "name": "_validator", - "type": "address" - } - ], - "name": "createdBlockOnRevealsPhase", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "currentCollectRound", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_collectRound", - "type": "uint256" - }, - { - "name": "_validator", - "type": "address" - } - ], - "name": "getCommit", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getCurrentSecret", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_collectRound", - "type": "uint256" - }, - { - "name": "_validator", - "type": "address" - } - ], - "name": "isCommitted", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isCommitPhase", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isRevealPhase", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_collectRound", - "type": "uint256" - } - ], - "name": "revealsCount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_collectRound", - "type": "uint256" - }, - { - "name": "_validator", - "type": "address" - } - ], - "name": "sentReveal", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } +[{ + "constant": true, + "inputs": [], + "name": "COMMIT_PHASE_LENGTH", + "outputs": [{ + "name": "", + "type": "uint256" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "currentRandom", + "outputs": [{ + "name": "", + "type": "uint256[]" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "VALIDATOR_SET_CONTRACT", + "outputs": [{ + "name": "", + "type": "address" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "COLLECT_ROUND_LENGTH", + "outputs": [{ + "name": "", + "type": "uint256" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ + "name": "_secretHash", + "type": "bytes32" + }], + "name": "commitHash", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [{ + "name": "_secret", + "type": "uint256" + }], + "name": "revealSecret", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [{ + "name": "_currentValidator", + "type": "address" + }], + "name": "onBlockClose", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [{ + "name": "_collectRound", + "type": "uint256" + }], + "name": "blocksProducers", + "outputs": [{ + "name": "", + "type": "address[]" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ + "name": "_collectRound", + "type": "uint256" + }], + "name": "committedValidators", + "outputs": [{ + "name": "", + "type": "address[]" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "createdBlockOnCommitsPhase", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "createdBlockOnRevealsPhase", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "currentCollectRound", + "outputs": [{ + "name": "", + "type": "uint256" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "getCommit", + "outputs": [{ + "name": "", + "type": "bytes32" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getCurrentSecret", + "outputs": [{ + "name": "", + "type": "uint256" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "isCommitted", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isCommitPhase", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isRevealPhase", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ + "name": "_collectRound", + "type": "uint256" + }], + "name": "revealsCount", + "outputs": [{ + "name": "", + "type": "uint256" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "sentReveal", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "stateMutability": "view", + "type": "function" + } ] From 9f0afa70af26cf97df04a7b4a5998fcce243a93c Mon Sep 17 00:00:00 2001 From: Andreas Fackler Date: Thu, 10 Jan 2019 16:11:56 +0100 Subject: [PATCH 22/22] Add comments about adding txns to current block. --- ethcore/src/engines/authority_round/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 058118a66c4..4aa04234759 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -1151,7 +1151,9 @@ impl Engine for AuthorityRound { _ancestry: &mut Iterator, ) -> Result<(), Error> { // Random number generation - // TODO: Is this the right place to do this? + // This will add local service transactions to the queue. Since `on_new_block` is called before the transactions + // are selected from the queue and local transactions are prioritized, they should end up in this block. + // TODO: Verify this! if let (Some(contract_addr), Some(our_addr)) = (self.randomness_contract_address, self.signer.read().address()) { let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) { Some(client) => client, @@ -1162,12 +1164,11 @@ impl Engine for AuthorityRound { }; let block_id = BlockId::Number(block.header.number()); let mut contract = util::BoundContract::bind(&*client, block_id, contract_addr); - // TODO: How should these errors be handled? + // TODO: How should these errors be handled? let phase = randomness::RandomnessPhase::load(&contract, our_addr) .map_err(|err| EngineError::FailedSystemCall(format!("Randomness error: {:?}", err)))?; let secret = *self.rand_secret.read(); let mut rng = ::rand::OsRng::new()?; - // TODO: Add new transaction to the block? *self.rand_secret.write() = phase.advance(&contract, secret, &mut rng) .map_err(|err| EngineError::FailedSystemCall(format!("Randomness error: {:?}", err)))?; }