diff --git a/ethcore/res/authority_round_random.json b/ethcore/res/authority_round_random.json new file mode 100644 index 00000000000..eede5beb6cb --- /dev/null +++ b/ethcore/res/authority_round_random.json @@ -0,0 +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" + } +] 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/mod.rs b/ethcore/src/engines/authority_round/mod.rs index d2dd261d4be..4aa04234759 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}; @@ -48,6 +48,8 @@ use types::ancestry_action::AncestryAction; use unexpected::{Mismatch, OutOfBounds}; mod finality; +mod randomness; +mod util; /// `AuthorityRound` params. pub struct AuthorityRoundParams { @@ -85,6 +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_address: Option
, } const U16_MAX: usize = ::std::u16::MAX as usize; @@ -116,6 +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_address: p.randomness_contract_address.map(Into::into), } } } @@ -431,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_address`? + rand_secret: RwLock>, + /// If set, enables random number contract integration. + randomness_contract_address: Option
, } // header-chain validator. @@ -685,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_address: our_params.randomness_contract_address, }); // Do not initialize timeouts for tests. @@ -1138,6 +1150,29 @@ impl Engine for AuthorityRound { epoch_begin: bool, _ancestry: &mut Iterator, ) -> Result<(), Error> { + // Random number generation + // 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, + 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()?; + *self.rand_secret.write() = phase.advance(&contract, secret, &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(()) } @@ -1532,7 +1567,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; @@ -1547,7 +1581,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), @@ -1568,6 +1602,7 @@ mod tests { block_reward_contract: Default::default(), strict_empty_steps_transition: 0, consensus_kind: ConsensusKind::Poa, + randomness_contract_address: None, }; // mutate aura params @@ -1793,16 +1828,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] diff --git a/ethcore/src/engines/authority_round/randomness.rs b/ethcore/src/engines/authority_round/randomness.rs new file mode 100644 index 00000000000..59a8b48e982 --- /dev/null +++ b/ethcore/src/engines/authority_round/randomness.rs @@ -0,0 +1,221 @@ +//! On-chain randomness generation for authority round +//! +//! 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::Hash; +use ethereum_types::{Address, U256}; +use hash::keccak; +use rand::Rng; + +use super::util::{BoundContract, CallError}; + +/// 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"); + +/// Validated randomness phase state. +/// +/// The process of generating random numbers is a simple finite state machine: +/// +/// ```text +/// + +/// | +/// | +/// | +/// +--------------+ +-------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. +/// +/// 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 { our_address: Address }, + /// Indicates a successful commitment, waiting for the commit phase to end. + Committed, + /// Indicates revealing is expected as the next step. + 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, 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. + 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. + 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 { + /// 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). + pub fn load( + contract: &BoundContract, + our_address: Address, + ) -> Result { + // Determine the current round and which phase we are in. + 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, + )) + .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 { + return Err(PhaseError::PhaseConflict); + } + + if is_commit_phase { + if revealed { + return Err(PhaseError::RevealedInCommit); + } + + if !committed { + Ok(RandomnessPhase::BeforeCommit { our_address }) + } 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 { 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 { .. } => { + // 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 = keccak(secret.as_ref()); + + // Schedule the transaction that commits the hash. + 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)) + } + 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 = keccak(secret.as_ref()); + 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 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. + Ok(Some(secret)) + } + } + } +} diff --git a/ethcore/src/engines/authority_round/util.rs b/ethcore/src/engines/authority_round/util.rs new file mode 100644 index 00000000000..246ea0967e9 --- /dev/null +++ b/ethcore/src/engines/authority_round/util.rs @@ -0,0 +1,101 @@ +//! 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, U256}; + +use client::{BlockId, EngineClient}; +use transaction::{self, Action}; + +/// 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 EngineClient, + 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), + /// 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> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("BoundContract") + .field("client", &(self.client as *const EngineClient)) + .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: &EngineClient, block_id: BlockId, contract_addr: Address) -> BoundContract { + BoundContract { + client, + block_id, + contract_addr, + } + } + + /// 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. 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, + { + let (data, output_decoder) = call; + + 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)?; + + // Decode the result and return it. + output_decoder + .decode(call_return.as_slice()) + .map_err(CallError::DecodeFailed) + } + + /// 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_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` + // 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(Action::Call(self.contract_addr), data, None, Some(U256::zero())) + .map_err(CallError::TransactionFailed) + } +} diff --git a/ethcore/src/engines/validator_set/test.rs b/ethcore/src/engines/validator_set/test.rs index 9650526e577..2cbce1b708d 100644 --- a/ethcore/src/engines/validator_set/test.rs +++ b/ethcore/src/engines/validator_set/test.rs @@ -22,7 +22,6 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use heapsize::HeapSizeOf; use ethereum_types::{H256, Address}; use bytes::Bytes; - use machine::{AuxiliaryData, Call, EthereumMachine}; use header::{Header, BlockNumber}; use super::{ValidatorSet, SimpleList}; 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.