From 4e7e8385b42bab96fd768aeffb2fd2aba9754d28 Mon Sep 17 00:00:00 2001 From: Andreas Fackler Date: Tue, 8 Jan 2019 15:51:12 +0100 Subject: [PATCH] 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 | 7 ++-- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 1ef08cfed83..4ee63aec9af 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(()) } @@ -1551,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), @@ -1571,6 +1600,8 @@ mod tests { block_reward_contract_transition: 0, 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 a6df5ff77a8..d54e6f683df 100644 --- a/ethcore/src/engines/validator_set/test.rs +++ b/ethcore/src/engines/validator_set/test.rs @@ -21,7 +21,9 @@ use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use heapsize::HeapSizeOf; use ethereum_types::{H256, Address}; +use ethkey::Signature; +use error::Error; use machine::{AuxiliaryData, Call, EthereumMachine}; use header::{Header, BlockNumber}; use super::{ValidatorSet, SimpleList}; @@ -84,8 +86,9 @@ impl ValidatorSet for TestSet { 1 } - fn report_malicious(&self, _validator: &Address, _set_block: BlockNumber, block: BlockNumber, signer: &mut dyn FnMut(H256) -> Signature) { - self.last_malicious.store(block as usize, AtomicOrdering::SeqCst) + fn report_malicious(&self, _validator: &Address, _set_block: BlockNumber, block: BlockNumber, _signer: &dyn Fn(H256) -> Result) -> Result<(), Error> { + self.last_malicious.store(block as usize, AtomicOrdering::SeqCst); + Ok(Default::default()) // TODO: Return signature? } fn report_benign(&self, _validator: &Address, _set_block: BlockNumber, block: BlockNumber) {