Add randomness contract support to AuthorityRound. (#10946)
* Add randomness contract support to Authority Round. Changes have been cherry-picked from poanetwork's aura-pos branch. Most of the work has been done by @mbr. * Address review comments for randomness contract. Co-Authored-By: David <dvdplm@gmail.com> * Rename revealSecret to revealNumber * Update Randomness contract bytecode * Use H256, rename secret to random number. * Use get_commit_and_cipher * Clean up Miner::prepare_block. * Remove is_reveal_phase call. * Add more comments, require randomness contract map. * Simplify run_randomness_phase * Address review comments. * Remove Client::transact_contract.
This commit is contained in:
parent
f6909d8243
commit
2b1d148ceb
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -172,8 +172,12 @@ dependencies = [
|
||||
"block-reward 0.1.0",
|
||||
"client-traits 0.1.0",
|
||||
"common-types 0.1.0",
|
||||
"derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"engine 0.1.0",
|
||||
"env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethabi 9.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethabi-contract 9.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethabi-derive 9.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethcore 1.12.0",
|
||||
"ethcore-accounts 0.1.0",
|
||||
"ethcore-io 1.12.0",
|
||||
@ -186,8 +190,10 @@ dependencies = [
|
||||
"lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"machine 0.1.0",
|
||||
"macros 0.1.0",
|
||||
"parity-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-crypto 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rlp 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"spec 0.1.0",
|
||||
|
@ -73,7 +73,6 @@ blooms-db = { path = "../util/blooms-db" }
|
||||
criterion = "0.3"
|
||||
engine = { path = "./engine", features = ["test-helpers"] }
|
||||
env_logger = "0.5"
|
||||
ethash = { path = "../ethash" }
|
||||
ethcore-accounts = { path = "../accounts" }
|
||||
ethcore-builtin = { path = "./builtin" }
|
||||
ethjson = { path = "../json", features = ["test-helpers"] }
|
||||
|
@ -42,7 +42,7 @@ use common_types::{
|
||||
pruning_info::PruningInfo,
|
||||
receipt::LocalizedReceipt,
|
||||
trace_filter::Filter as TraceFilter,
|
||||
transaction::{self, LocalizedTransaction, CallError, SignedTransaction, UnverifiedTransaction},
|
||||
transaction::{self, Action, LocalizedTransaction, CallError, SignedTransaction, UnverifiedTransaction},
|
||||
tree_route::TreeRoute,
|
||||
verification::{VerificationQueueInfo, Unverified},
|
||||
};
|
||||
@ -395,8 +395,66 @@ pub trait BlockChainClient:
|
||||
/// Returns information about pruning/data availability.
|
||||
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>;
|
||||
/// Returns a transaction signed with the key configured in the engine signer.
|
||||
fn create_transaction(&self, tx_request: TransactionRequest) -> Result<SignedTransaction, transaction::Error>;
|
||||
|
||||
/// Schedule state-altering transaction to be executed on the next pending
|
||||
/// block with the given gas and nonce parameters.
|
||||
fn transact(&self, tx_request: TransactionRequest) -> Result<(), transaction::Error>;
|
||||
}
|
||||
|
||||
/// The data required for a `Client` to create a transaction.
|
||||
///
|
||||
/// Gas limit, gas price, or nonce can be set explicitly, e.g. to create service
|
||||
/// transactions with zero gas price, or sequences of transactions with consecutive nonces.
|
||||
pub struct TransactionRequest {
|
||||
pub action: Action,
|
||||
pub data: Bytes,
|
||||
pub gas: Option<U256>,
|
||||
pub gas_price: Option<U256>,
|
||||
pub nonce: Option<U256>,
|
||||
}
|
||||
|
||||
impl TransactionRequest {
|
||||
/// Creates a request to call a contract at `address` with the specified call data.
|
||||
pub fn call(address: Address, data: Bytes) -> TransactionRequest {
|
||||
TransactionRequest {
|
||||
action: Action::Call(address),
|
||||
data,
|
||||
gas: None,
|
||||
gas_price: None,
|
||||
nonce: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a request to create a new contract, with the specified bytecode.
|
||||
pub fn create(data: Bytes) -> TransactionRequest {
|
||||
TransactionRequest {
|
||||
action: Action::Create,
|
||||
data,
|
||||
gas: None,
|
||||
gas_price: None,
|
||||
nonce: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a gas limit. If this is not specified, a sensible default is used.
|
||||
pub fn gas(mut self, gas: U256) -> TransactionRequest {
|
||||
self.gas = Some(gas);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a gas price. If this is not specified, a sensible default is used.
|
||||
pub fn gas_price(mut self, gas_price: U256) -> TransactionRequest {
|
||||
self.gas_price = Some(gas_price);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a nonce. If this is not specified, the appropriate latest nonce for the author is used.
|
||||
pub fn nonce(mut self, nonce: U256) -> TransactionRequest {
|
||||
self.nonce = Some(nonce);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// resets the blockchain
|
||||
|
@ -32,7 +32,7 @@ use common_types::{
|
||||
},
|
||||
errors::{EthcoreError as Error, EngineError},
|
||||
snapshot::Snapshotting,
|
||||
transaction::{self, UnverifiedTransaction},
|
||||
transaction::{self, SignedTransaction, UnverifiedTransaction},
|
||||
};
|
||||
use client_traits::EngineClient;
|
||||
|
||||
@ -185,6 +185,14 @@ pub trait Engine: Sync + Send {
|
||||
/// Allow mutating the header during seal generation. Currently only used by Clique.
|
||||
fn on_seal_block(&self, _block: &mut ExecutedBlock) -> Result<(), Error> { Ok(()) }
|
||||
|
||||
/// Returns a list of transactions for a new block if we are the author.
|
||||
///
|
||||
/// This is called when the miner prepares a new block that this node will author and seal. It returns a list of
|
||||
/// transactions that will be added to the block before any other transactions from the queue.
|
||||
fn generate_engine_transactions(&self, _block: &ExecutedBlock) -> Result<Vec<SignedTransaction>, Error> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Returns the engine's current sealing state.
|
||||
fn sealing_state(&self) -> SealingState { SealingState::External }
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
//! A signer used by Engines which need to sign messages.
|
||||
|
||||
use ethereum_types::{H256, Address};
|
||||
use parity_crypto::publickey::{Signature, KeyPair, Error};
|
||||
use parity_crypto::publickey::{ecies, Public, Signature, KeyPair, Error};
|
||||
|
||||
/// Everything that an Engine needs to sign messages.
|
||||
pub trait EngineSigner: Send + Sync {
|
||||
@ -26,6 +26,12 @@ pub trait EngineSigner: Send + Sync {
|
||||
|
||||
/// Signing address
|
||||
fn address(&self) -> Address;
|
||||
|
||||
/// Decrypt a message that was encrypted to this signer's key.
|
||||
fn decrypt(&self, auth_data: &[u8], cipher: &[u8]) -> Result<Vec<u8>, Error>;
|
||||
|
||||
/// The signer's public key, if available.
|
||||
fn public(&self) -> Option<Public>;
|
||||
}
|
||||
|
||||
/// Creates a new `EngineSigner` from given key pair.
|
||||
@ -40,7 +46,15 @@ impl EngineSigner for Signer {
|
||||
parity_crypto::publickey::sign(self.0.secret(), &hash)
|
||||
}
|
||||
|
||||
fn decrypt(&self, auth_data: &[u8], cipher: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
ecies::decrypt(self.0.secret(), auth_data, cipher)
|
||||
}
|
||||
|
||||
fn address(&self) -> Address {
|
||||
self.0.address()
|
||||
}
|
||||
|
||||
fn public(&self) -> Option<Public> {
|
||||
Some(*self.0.public())
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ use std::sync::Arc;
|
||||
|
||||
use ethereum_types::{Address, H256};
|
||||
use ethkey::Password;
|
||||
use parity_crypto::publickey::{Signature, Error};
|
||||
use parity_crypto::publickey::{Public, Signature, Error};
|
||||
use log::warn;
|
||||
use accounts::{self, AccountProvider, SignError};
|
||||
|
||||
@ -44,7 +44,18 @@ impl EngineSigner for (Arc<AccountProvider>, Address, Password) {
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt(&self, auth_data: &[u8], cipher: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
self.0.decrypt(self.1, None, auth_data, cipher).map_err(|e| {
|
||||
warn!("Unable to decrypt message: {:?}", e);
|
||||
Error::InvalidMessage
|
||||
})
|
||||
}
|
||||
|
||||
fn address(&self) -> Address {
|
||||
self.1
|
||||
}
|
||||
|
||||
fn public(&self) -> Option<Public> {
|
||||
self.0.account_public(self.1, &self.2).ok()
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,10 @@ license = "GPL-3.0"
|
||||
block-reward = { path = "../../block-reward" }
|
||||
client-traits = { path = "../../client-traits" }
|
||||
common-types = { path = "../../types" }
|
||||
derive_more = "0.15.0"
|
||||
ethabi = "9.0.1"
|
||||
ethabi-contract = "9.0.0"
|
||||
ethabi-derive = "9.0.1"
|
||||
ethereum-types = "0.8.0"
|
||||
ethjson = { path = "../../../json" }
|
||||
parity-crypto = { version = "0.4.2", features = ["publickey"] }
|
||||
@ -22,7 +26,9 @@ log = "0.4"
|
||||
lru-cache = "0.1"
|
||||
machine = { path = "../../machine" }
|
||||
macros = { path = "../../../util/macros" }
|
||||
parity-bytes = "0.1"
|
||||
parking_lot = "0.9"
|
||||
rand = "0.7"
|
||||
rlp = "0.4.0"
|
||||
time-utils = { path = "../../../util/time-utils" }
|
||||
unexpected = { path = "../../../util/unexpected" }
|
||||
|
@ -40,7 +40,7 @@ use std::sync::{Weak, Arc};
|
||||
use std::time::{UNIX_EPOCH, Duration};
|
||||
use std::u64;
|
||||
|
||||
use client_traits::{EngineClient, ForceUpdateSealing};
|
||||
use client_traits::{EngineClient, ForceUpdateSealing, TransactionRequest};
|
||||
use engine::{Engine, ConstructedVerifier};
|
||||
use block_reward::{self, BlockRewardContract, RewardKind};
|
||||
use ethjson;
|
||||
@ -55,6 +55,7 @@ use engine::signer::EngineSigner;
|
||||
use parity_crypto::publickey::Signature;
|
||||
use io::{IoContext, IoHandler, TimerToken, IoService};
|
||||
use itertools::{self, Itertools};
|
||||
use rand::rngs::OsRng;
|
||||
use rlp::{encode, Decodable, DecoderError, Encodable, RlpStream, Rlp};
|
||||
use ethereum_types::{H256, H520, Address, U128, U256};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
@ -72,13 +73,17 @@ use common_types::{
|
||||
machine::{Call, AuxiliaryData},
|
||||
},
|
||||
errors::{BlockError, EthcoreError as Error, EngineError},
|
||||
ids::BlockId,
|
||||
snapshot::Snapshotting,
|
||||
transaction::SignedTransaction,
|
||||
};
|
||||
use unexpected::{Mismatch, OutOfBounds};
|
||||
|
||||
use validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
||||
|
||||
mod finality;
|
||||
mod randomness;
|
||||
pub(crate) mod util;
|
||||
|
||||
use self::finality::RollingFinality;
|
||||
|
||||
@ -117,6 +122,8 @@ pub struct AuthorityRoundParams {
|
||||
pub maximum_empty_steps: usize,
|
||||
/// Transition block to strict empty steps validation.
|
||||
pub strict_empty_steps_transition: u64,
|
||||
/// If set, enables random number contract integration. It maps the transition block to the contract address.
|
||||
pub randomness_contract_address: BTreeMap<u64, Address>,
|
||||
}
|
||||
|
||||
const U16_MAX: usize = ::std::u16::MAX as usize;
|
||||
@ -168,6 +175,11 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
|
||||
BlockRewardContract::new_from_address(address.into())
|
||||
);
|
||||
}
|
||||
let randomness_contract_address = p.randomness_contract_address.map_or_else(BTreeMap::new, |transitions| {
|
||||
transitions.into_iter().map(|(ethjson::uint::Uint(block), addr)| {
|
||||
(block.as_u64(), addr.into())
|
||||
}).collect()
|
||||
});
|
||||
AuthorityRoundParams {
|
||||
step_durations,
|
||||
validators: new_validator_set(p.validators),
|
||||
@ -183,6 +195,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
|
||||
maximum_empty_steps: p.maximum_empty_steps.map_or(0, Into::into),
|
||||
two_thirds_majority_transition: p.two_thirds_majority_transition.map_or_else(BlockNumber::max_value, Into::into),
|
||||
strict_empty_steps_transition: p.strict_empty_steps_transition.map_or(0, Into::into),
|
||||
randomness_contract_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -550,6 +563,8 @@ pub struct AuthorityRound {
|
||||
machine: Machine,
|
||||
/// History of step hashes recently received from peers.
|
||||
received_step_hashes: RwLock<BTreeMap<(u64, Address), H256>>,
|
||||
/// If set, enables random number contract integration. It maps the transition block to the contract address.
|
||||
randomness_contract_address: BTreeMap<u64, Address>,
|
||||
}
|
||||
|
||||
// header-chain validator.
|
||||
@ -851,6 +866,7 @@ impl AuthorityRound {
|
||||
strict_empty_steps_transition: our_params.strict_empty_steps_transition,
|
||||
machine,
|
||||
received_step_hashes: RwLock::new(Default::default()),
|
||||
randomness_contract_address: our_params.randomness_contract_address,
|
||||
});
|
||||
|
||||
// Do not initialize timeouts for tests.
|
||||
@ -1045,6 +1061,41 @@ impl AuthorityRound {
|
||||
fn address(&self) -> Option<Address> {
|
||||
self.signer.read().as_ref().map(|s| s.address() )
|
||||
}
|
||||
|
||||
/// Make calls to the randomness contract.
|
||||
fn run_randomness_phase(&self, block: &ExecutedBlock) -> Result<Vec<SignedTransaction>, Error> {
|
||||
let contract_addr = match self.randomness_contract_address.range(..=block.header.number()).last() {
|
||||
Some((_, &contract_addr)) => contract_addr,
|
||||
None => return Ok(Vec::new()), // No randomness contract.
|
||||
};
|
||||
|
||||
let opt_signer = self.signer.read();
|
||||
let signer = match opt_signer.as_ref() {
|
||||
Some(signer) => signer,
|
||||
None => return Ok(Vec::new()), // We are not a validator, so we shouldn't call the contracts.
|
||||
};
|
||||
let our_addr = signer.address();
|
||||
let client = self.client.read().as_ref().and_then(|weak| weak.upgrade()).ok_or_else(|| {
|
||||
debug!(target: "engine", "Unable to prepare block: missing client ref.");
|
||||
EngineError::RequiresClient
|
||||
})?;
|
||||
let full_client = client.as_full_client()
|
||||
.ok_or_else(|| EngineError::FailedSystemCall("Failed to upgrade to BlockchainClient.".to_string()))?;
|
||||
|
||||
// Random number generation
|
||||
let contract = util::BoundContract::new(&*client, BlockId::Latest, contract_addr);
|
||||
let phase = randomness::RandomnessPhase::load(&contract, our_addr)
|
||||
.map_err(|err| EngineError::Custom(format!("Randomness error in load(): {:?}", err)))?;
|
||||
let data = match phase.advance(&contract, &mut OsRng, signer.as_ref())
|
||||
.map_err(|err| EngineError::Custom(format!("Randomness error in advance(): {:?}", err)))? {
|
||||
Some(data) => data,
|
||||
None => return Ok(Vec::new()), // Nothing to commit or reveal at the moment.
|
||||
};
|
||||
|
||||
let nonce = block.state.nonce(&our_addr)?;
|
||||
let tx_request = TransactionRequest::call(contract_addr, data).gas_price(U256::zero()).nonce(nonce);
|
||||
Ok(vec![full_client.create_transaction(tx_request)?])
|
||||
}
|
||||
}
|
||||
|
||||
fn unix_now() -> Duration {
|
||||
@ -1326,7 +1377,6 @@ impl Engine for AuthorityRound {
|
||||
// report any skipped primaries between the parent block and
|
||||
// the block we're sealing, unless we have empty steps enabled
|
||||
if header.number() < self.empty_steps_transition {
|
||||
trace!(target: "engine", "generate_seal: reporting misbehaviour for step={}, block=#{}", step, header.number());
|
||||
self.report_skipped(header, step, parent_step, &*validators, epoch_transition_number);
|
||||
}
|
||||
|
||||
@ -1430,6 +1480,10 @@ impl Engine for AuthorityRound {
|
||||
block_reward::apply_block_rewards(&rewards, block, &self.machine)
|
||||
}
|
||||
|
||||
fn generate_engine_transactions(&self, block: &ExecutedBlock) -> Result<Vec<SignedTransaction>, Error> {
|
||||
self.run_randomness_phase(block)
|
||||
}
|
||||
|
||||
/// Check the number of seal fields.
|
||||
fn verify_block_basic(&self, header: &Header) -> Result<(), Error> {
|
||||
if header.number() >= self.validate_score_transition && *header.difficulty() >= U256::from(U128::max_value()) {
|
||||
@ -1805,19 +1859,22 @@ mod tests {
|
||||
use std::time::Duration;
|
||||
use keccak_hash::keccak;
|
||||
use accounts::AccountProvider;
|
||||
use ethabi_contract::use_contract;
|
||||
use ethereum_types::{Address, H520, H256, U256};
|
||||
use parity_crypto::publickey::Signature;
|
||||
use common_types::{
|
||||
header::Header,
|
||||
engines::{Seal, params::CommonParams},
|
||||
ids::BlockId,
|
||||
errors::{EthcoreError as Error, EngineError},
|
||||
transaction::{Action, Transaction},
|
||||
};
|
||||
use rlp::encode;
|
||||
use ethcore::{
|
||||
block::*,
|
||||
miner::{Author, MinerService},
|
||||
test_helpers::{
|
||||
generate_dummy_client_with_spec, get_temp_state_db,
|
||||
generate_dummy_client_with_spec, generate_dummy_client_with_spec_and_data, get_temp_state_db,
|
||||
TestNotify
|
||||
},
|
||||
};
|
||||
@ -1831,7 +1888,7 @@ mod tests {
|
||||
|
||||
use super::{
|
||||
AuthorityRoundParams, AuthorityRound, EmptyStep, SealedEmptyStep, StepDurationInfo,
|
||||
calculate_score,
|
||||
calculate_score, util::BoundContract,
|
||||
};
|
||||
|
||||
fn build_aura<F>(f: F) -> Arc<AuthorityRound> where
|
||||
@ -1852,6 +1909,7 @@ mod tests {
|
||||
block_reward_contract_transitions: Default::default(),
|
||||
strict_empty_steps_transition: 0,
|
||||
two_thirds_majority_transition: 0,
|
||||
randomness_contract_address: BTreeMap::new(),
|
||||
};
|
||||
|
||||
// mutate aura params
|
||||
@ -2578,6 +2636,54 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn randomness_contract() -> Result<(), super::util::CallError> {
|
||||
use_contract!(rand_contract, "../../res/contracts/test_authority_round_random.json");
|
||||
|
||||
env_logger::init();
|
||||
|
||||
let contract_addr = Address::from_str("0000000000000000000000000000000000000042").unwrap();
|
||||
let client = generate_dummy_client_with_spec_and_data(
|
||||
spec::new_test_round_randomness_contract, 0, 0, &[], true
|
||||
);
|
||||
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
|
||||
let addr1 = tap.insert_account(keccak("1").into(), &"1".into()).unwrap();
|
||||
// Unlock account so that the engine can decrypt the secret.
|
||||
tap.unlock_account_permanently(addr1, "1".into()).expect("unlock");
|
||||
|
||||
let signer = Box::new((tap.clone(), addr1, "1".into()));
|
||||
client.miner().set_author(Author::Sealer(signer.clone()));
|
||||
client.miner().set_gas_range_target((U256::from(1000000), U256::from(1000000)));
|
||||
|
||||
let engine = client.engine();
|
||||
engine.set_signer(Some(signer));
|
||||
engine.register_client(Arc::downgrade(&client) as _);
|
||||
let bc = BoundContract::new(&*client, BlockId::Latest, contract_addr);
|
||||
|
||||
// First the contract is in the commit phase, and we haven't committed yet.
|
||||
assert!(bc.call_const(rand_contract::functions::is_commit_phase::call())?);
|
||||
assert!(!bc.call_const(rand_contract::functions::is_committed::call(0, addr1))?);
|
||||
|
||||
// We produce a block and commit.
|
||||
engine.step();
|
||||
assert!(bc.call_const(rand_contract::functions::is_committed::call(0, addr1))?);
|
||||
|
||||
// After two more blocks we are in the reveal phase...
|
||||
engine.step();
|
||||
engine.step();
|
||||
assert!(bc.call_const(rand_contract::functions::is_reveal_phase::call())?);
|
||||
assert!(!bc.call_const(rand_contract::functions::sent_reveal::call(0, addr1))?);
|
||||
assert!(bc.call_const(rand_contract::functions::get_value::call())?.is_zero());
|
||||
|
||||
// ...so in the next step, we reveal our random value, and the contract's random value is not zero anymore.
|
||||
engine.step();
|
||||
assert!(bc.call_const(rand_contract::functions::sent_reveal::call(0, addr1))?);
|
||||
assert!(!bc.call_const(rand_contract::functions::get_value::call())?.is_zero());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_info_from_seal() {
|
||||
let (spec, tap, accounts) = setup_empty_steps();
|
||||
@ -2734,7 +2840,7 @@ mod tests {
|
||||
"validators": {
|
||||
"list" : ["0x1000000000000000000000000000000000000001"]
|
||||
},
|
||||
"blockRewardContractTransition": "0",
|
||||
"blockRewardContractTransition": "0",
|
||||
"blockRewardContractAddress": "0x2000000000000000000000000000000000000002",
|
||||
"blockRewardContractTransitions": {
|
||||
"7": "0x3000000000000000000000000000000000000003",
|
||||
@ -2765,7 +2871,7 @@ mod tests {
|
||||
"validators": {
|
||||
"list" : ["0x1000000000000000000000000000000000000001"]
|
||||
},
|
||||
"blockRewardContractTransition": "7",
|
||||
"blockRewardContractTransition": "7",
|
||||
"blockRewardContractAddress": "0x2000000000000000000000000000000000000002",
|
||||
"blockRewardContractTransitions": {
|
||||
"0": "0x3000000000000000000000000000000000000003",
|
||||
|
257
ethcore/engines/authority-round/src/randomness.rs
Normal file
257
ethcore/engines/authority-round/src/randomness.rs
Normal file
@ -0,0 +1,257 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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.
|
||||
//!
|
||||
//! The process of generating random numbers is a simple finite state machine:
|
||||
//!
|
||||
//! ```text
|
||||
//! +
|
||||
//! |
|
||||
//! |
|
||||
//! |
|
||||
//! +--------------+ +-------v-------+
|
||||
//! | | | |
|
||||
//! | BeforeCommit <------------------------------+ Waiting |
|
||||
//! | | enter commit phase | |
|
||||
//! +------+-------+ +-------^-------+
|
||||
//! | |
|
||||
//! | call |
|
||||
//! | `commitHash()` | call
|
||||
//! | | `revealNumber()`
|
||||
//! | |
|
||||
//! +------v-------+ +-------+-------+
|
||||
//! | | | |
|
||||
//! | Committed +------------------------------> Reveal |
|
||||
//! | | enter reveal phase | |
|
||||
//! +--------------+ +---------------+
|
||||
//! ```
|
||||
//!
|
||||
//! Phase transitions are performed by the smart contract and simply queried by the engine.
|
||||
//!
|
||||
//! Randomness generation works as follows:
|
||||
//! * During the commit phase, all validators locally generate a random number, and commit that number's hash to the
|
||||
//! contract.
|
||||
//! * During the reveal phase, all validators reveal their local random number to the contract. The contract should
|
||||
//! verify that it matches the committed hash.
|
||||
//! * Finally, the XOR of all revealed numbers is used as an on-chain random number.
|
||||
//!
|
||||
//! An adversary can only influence that number by either controlling _all_ validators who committed, or, to a lesser
|
||||
//! extent, by not revealing committed numbers.
|
||||
//! The length of the commit and reveal phases, as well as any penalties for failure to reveal, are defined by the
|
||||
//! contract.
|
||||
//!
|
||||
//! A typical case of using `RandomnessPhase` is:
|
||||
//!
|
||||
//! 1. `RandomnessPhase::load()` the phase from the blockchain data.
|
||||
//! 2. Call `RandomnessPhase::advance()`.
|
||||
//!
|
||||
//! A production implementation of a randomness contract can be found here:
|
||||
//! https://github.com/poanetwork/posdao-contracts/blob/4fddb108993d4962951717b49222327f3d94275b/contracts/RandomAuRa.sol
|
||||
|
||||
use derive_more::Display;
|
||||
use ethabi::Hash;
|
||||
use ethabi_contract::use_contract;
|
||||
use ethereum_types::{Address, H256, U256};
|
||||
use keccak_hash::keccak;
|
||||
use log::{debug, error};
|
||||
use parity_crypto::publickey::{ecies, Error as CryptoError};
|
||||
use parity_bytes::Bytes;
|
||||
use rand::Rng;
|
||||
use engine::signer::EngineSigner;
|
||||
|
||||
use crate::util::{BoundContract, CallError};
|
||||
|
||||
/// Random number type expected by the contract: This is generated locally, kept secret during the commit phase, and
|
||||
/// published in the reveal phase.
|
||||
pub type RandNumber = H256;
|
||||
|
||||
use_contract!(aura_random, "../../res/contracts/authority_round_random.json");
|
||||
|
||||
/// Validated randomness phase state.
|
||||
#[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, i.e. having failed to commit during the commit phase.
|
||||
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 { 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.
|
||||
///
|
||||
/// `BadRandNumber` will usually result in punishment by the contract or the other validators.
|
||||
#[derive(Debug, Display)]
|
||||
pub enum PhaseError {
|
||||
/// The smart contract reported that we already revealed something while still being in the
|
||||
/// commit phase.
|
||||
#[display(fmt = "Revealed during commit phase")]
|
||||
RevealedInCommit,
|
||||
/// Failed to load contract information.
|
||||
#[display(fmt = "Error loading randomness contract information: {:?}", _0)]
|
||||
LoadFailed(CallError),
|
||||
/// Failed to load the stored encrypted random number.
|
||||
#[display(fmt = "Failed to load random number from the randomness contract")]
|
||||
BadRandNumber,
|
||||
/// Failed to encrypt random number.
|
||||
#[display(fmt = "Failed to encrypt random number: {}", _0)]
|
||||
Crypto(CryptoError),
|
||||
/// Failed to get the engine signer's public key.
|
||||
#[display(fmt = "Failed to get the engine signer's public key")]
|
||||
MissingPublicKey,
|
||||
}
|
||||
|
||||
impl From<CryptoError> for PhaseError {
|
||||
fn from(err: CryptoError) -> PhaseError {
|
||||
PhaseError::Crypto(err)
|
||||
}
|
||||
}
|
||||
|
||||
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 random numbers).
|
||||
pub fn load(
|
||||
contract: &BoundContract,
|
||||
our_address: Address,
|
||||
) -> Result<RandomnessPhase, PhaseError> {
|
||||
// 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_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_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 commitment, 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 random seed construction process as far as possible.
|
||||
///
|
||||
/// Returns the encoded contract call necessary to advance the randomness contract's state.
|
||||
///
|
||||
/// **Warning**: After calling the `advance()` function, wait until the returned transaction has been included in
|
||||
/// a block before calling it again; otherwise spurious transactions resulting in punishments might be executed.
|
||||
pub fn advance<R: Rng>(
|
||||
self,
|
||||
contract: &BoundContract,
|
||||
rng: &mut R,
|
||||
signer: &dyn EngineSigner,
|
||||
) -> Result<Option<Bytes>, PhaseError> {
|
||||
match self {
|
||||
RandomnessPhase::Waiting | RandomnessPhase::Committed => Ok(None),
|
||||
RandomnessPhase::BeforeCommit => {
|
||||
// Generate a new random number, but don't reveal it yet. Instead, we publish its hash to the
|
||||
// randomness contract, together with the number encrypted to ourselves. That way we will later be
|
||||
// able to decrypt and reveal it, and other parties are able to verify it against the hash.
|
||||
let number: RandNumber = rng.gen();
|
||||
let number_hash: Hash = keccak(number.as_bytes());
|
||||
let public = signer.public().ok_or(PhaseError::MissingPublicKey)?;
|
||||
let cipher = ecies::encrypt(&public, &number_hash.0, number.as_bytes())?;
|
||||
|
||||
debug!(target: "engine", "Randomness contract: committing {}.", number_hash);
|
||||
// Return the call data for the transaction that commits the hash and the encrypted number.
|
||||
let (data, _decoder) = aura_random::functions::commit_hash::call(number_hash, cipher);
|
||||
Ok(Some(data))
|
||||
}
|
||||
RandomnessPhase::Reveal { round, our_address } => {
|
||||
// Load the hash and encrypted number that we stored in the commit phase.
|
||||
let call = aura_random::functions::get_commit_and_cipher::call(round, our_address);
|
||||
let (committed_hash, cipher) = contract
|
||||
.call_const(call)
|
||||
.map_err(PhaseError::LoadFailed)?;
|
||||
|
||||
// Decrypt the number and check against the hash.
|
||||
let number_bytes = signer.decrypt(&committed_hash.0, &cipher)?;
|
||||
let number = if number_bytes.len() == 32 {
|
||||
RandNumber::from_slice(&number_bytes)
|
||||
} else {
|
||||
// This can only happen if there is a bug in the smart contract,
|
||||
// or if the entire network goes awry.
|
||||
error!(target: "engine", "Decrypted random number has the wrong length.");
|
||||
return Err(PhaseError::BadRandNumber);
|
||||
};
|
||||
let number_hash: Hash = keccak(number.as_bytes());
|
||||
if number_hash != committed_hash {
|
||||
error!(target: "engine", "Decrypted random number doesn't agree with the hash.");
|
||||
return Err(PhaseError::BadRandNumber);
|
||||
}
|
||||
|
||||
debug!(target: "engine", "Randomness contract: scheduling tx to reveal our random number {} (round={}, our_address={}).", number_hash, round, our_address);
|
||||
// We are now sure that we have the correct secret and can reveal it. So we return the call data for the
|
||||
// transaction that stores the revealed random bytes on the contract.
|
||||
let (data, _decoder) = aura_random::functions::reveal_number::call(number.as_bytes());
|
||||
Ok(Some(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
94
ethcore/engines/authority-round/src/util.rs
Normal file
94
ethcore/engines/authority-round/src/util.rs
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utility functions.
|
||||
//!
|
||||
//! Contains small functions used by the AuRa engine that are not strictly limited to that scope.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use client_traits::EngineClient;
|
||||
use common_types::ids::BlockId;
|
||||
use ethabi;
|
||||
use ethereum_types::Address;
|
||||
|
||||
/// 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 dyn 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,
|
||||
}
|
||||
|
||||
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 dyn EngineClient))
|
||||
.field("block_id", &self.block_id)
|
||||
.field("contract_addr", &self.contract_addr)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BoundContract<'a> {
|
||||
/// Create a new `BoundContract`.
|
||||
pub fn new(client: &dyn 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: It is constant in the sense that it does not alter the EVM
|
||||
/// state.
|
||||
pub fn call_const<D>(&self, call: (ethabi::Bytes, D)) -> Result<D::Output, CallError>
|
||||
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)
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ use common_types::{
|
||||
engines::machine::{Call, AuxiliaryData},
|
||||
};
|
||||
|
||||
use client_traits::EngineClient;
|
||||
use client_traits::{EngineClient, TransactionRequest};
|
||||
use engine::SystemCall;
|
||||
|
||||
use crate::{
|
||||
@ -68,7 +68,7 @@ impl ValidatorContract {
|
||||
|
||||
match client.as_full_client() {
|
||||
Some(c) => {
|
||||
c.transact_contract(self.contract_address, data)
|
||||
c.transact(TransactionRequest::call(self.contract_address, data))
|
||||
.map_err(|e| format!("Transaction import error: {}", e))?;
|
||||
Ok(())
|
||||
},
|
||||
@ -149,7 +149,7 @@ mod tests {
|
||||
use accounts::AccountProvider;
|
||||
use call_contract::CallContract;
|
||||
use common_types::{header::Header, ids::BlockId};
|
||||
use client_traits::{BlockChainClient, ChainInfo, BlockInfo};
|
||||
use client_traits::{BlockChainClient, ChainInfo, BlockInfo, TransactionRequest};
|
||||
use ethcore::{
|
||||
miner::{self, MinerService},
|
||||
test_helpers::generate_dummy_client_with_spec,
|
||||
@ -225,7 +225,7 @@ mod tests {
|
||||
assert_eq!(client.chain_info().best_block_number, 2);
|
||||
|
||||
// Check if misbehaving validator was removed.
|
||||
client.transact_contract(Default::default(), Default::default()).unwrap();
|
||||
client.transact(TransactionRequest::call(Default::default(), Default::default())).unwrap();
|
||||
client.engine().step();
|
||||
client.engine().step();
|
||||
assert_eq!(client.chain_info().best_block_number, 2);
|
||||
|
@ -161,11 +161,13 @@ mod tests {
|
||||
ids::BlockId,
|
||||
verification::Unverified,
|
||||
};
|
||||
use client_traits::{BlockChainClient, BlockInfo, ChainInfo, ImportBlock, EngineClient, ForceUpdateSealing};
|
||||
use client_traits::{
|
||||
BlockChainClient, BlockInfo, ChainInfo, ImportBlock, EngineClient, ForceUpdateSealing, TransactionRequest
|
||||
};
|
||||
use engine::EpochChange;
|
||||
use ethcore::{
|
||||
miner::{self, MinerService},
|
||||
test_helpers::{generate_dummy_client_with_spec, generate_dummy_client_with_spec_and_data},
|
||||
test_helpers::generate_dummy_client_with_spec,
|
||||
};
|
||||
use ethereum_types::Address;
|
||||
use parity_crypto::publickey::Secret;
|
||||
@ -190,7 +192,7 @@ mod tests {
|
||||
// Wrong signer for the first block.
|
||||
let signer = Box::new((tap.clone(), v1, "".into()));
|
||||
client.miner().set_author(miner::Author::Sealer(signer));
|
||||
client.transact_contract(Default::default(), Default::default()).unwrap();
|
||||
client.transact(TransactionRequest::call(Default::default(), Default::default())).unwrap();
|
||||
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
||||
assert_eq!(client.chain_info().best_block_number, 0);
|
||||
// Right signer for the first block.
|
||||
@ -199,7 +201,7 @@ mod tests {
|
||||
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
// This time v0 is wrong.
|
||||
client.transact_contract(Default::default(), Default::default()).unwrap();
|
||||
client.transact(TransactionRequest::call(Default::default(), Default::default())).unwrap();
|
||||
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
let signer = Box::new((tap.clone(), v1, "".into()));
|
||||
@ -207,12 +209,12 @@ mod tests {
|
||||
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
||||
assert_eq!(client.chain_info().best_block_number, 2);
|
||||
// v1 is still good.
|
||||
client.transact_contract(Default::default(), Default::default()).unwrap();
|
||||
client.transact(TransactionRequest::call(Default::default(), Default::default())).unwrap();
|
||||
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
||||
assert_eq!(client.chain_info().best_block_number, 3);
|
||||
|
||||
// Check syncing.
|
||||
let sync_client = generate_dummy_client_with_spec_and_data(spec::new_validator_multi, 0, 0, &[]);
|
||||
let sync_client = generate_dummy_client_with_spec(spec::new_validator_multi);
|
||||
sync_client.engine().register_client(Arc::downgrade(&sync_client) as _);
|
||||
for i in 1..4 {
|
||||
sync_client.import_block(Unverified::from_rlp(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap()).unwrap();
|
||||
|
@ -468,7 +468,7 @@ mod tests {
|
||||
use engine::{EpochChange, Proof};
|
||||
use ethcore::{
|
||||
miner::{self, MinerService},
|
||||
test_helpers::{generate_dummy_client_with_spec, generate_dummy_client_with_spec_and_data}
|
||||
test_helpers::generate_dummy_client_with_spec
|
||||
};
|
||||
use parity_crypto::publickey::Secret;
|
||||
use ethereum_types::Address;
|
||||
@ -551,7 +551,7 @@ mod tests {
|
||||
assert_eq!(client.chain_info().best_block_number, 3);
|
||||
|
||||
// Check syncing.
|
||||
let sync_client = generate_dummy_client_with_spec_and_data(spec::new_validator_safe_contract, 0, 0, &[]);
|
||||
let sync_client = generate_dummy_client_with_spec(spec::new_validator_safe_contract);
|
||||
sync_client.engine().register_client(Arc::downgrade(&sync_client) as _);
|
||||
for i in 1..4 {
|
||||
sync_client.import_block(Unverified::from_rlp(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap()).unwrap();
|
||||
|
100
ethcore/res/authority_round_randomness_contract.json
Normal file
100
ethcore/res/authority_round_randomness_contract.json
Normal file
@ -0,0 +1,100 @@
|
||||
{
|
||||
"name": "TestAuthorityRoundRandomnessContract",
|
||||
"engine": {
|
||||
"authorityRound": {
|
||||
"params": {
|
||||
"stepDuration": 1,
|
||||
"startStep": 2,
|
||||
"validators": {
|
||||
"list": [
|
||||
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
|
||||
]
|
||||
},
|
||||
"immediateTransitions": true,
|
||||
"maximumEmptySteps": "2",
|
||||
"randomnessContractAddress": {
|
||||
"0": "0x0000000000000000000000000000000000000042"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"accountStartNonce": "0x0",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x69",
|
||||
"eip140Transition": "0x0",
|
||||
"eip211Transition": "0x0",
|
||||
"eip214Transition": "0x0",
|
||||
"eip658Transition": "0x0"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"authorityRound": {
|
||||
"step": "0x0",
|
||||
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"difficulty": "0x20000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0x222222"
|
||||
},
|
||||
"accounts": {
|
||||
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e": { "balance": "100000000000" },
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
"0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
||||
"0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
||||
"0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
|
||||
"0000000000000000000000000000000000000005": { "balance": "1", "builtin": { "name": "modexp", "activate_at": 0, "pricing": { "modexp": { "divisor": 20 } } } },
|
||||
"0000000000000000000000000000000000000006": {
|
||||
"balance": "1",
|
||||
"builtin": {
|
||||
"name": "alt_bn128_add",
|
||||
"pricing": {
|
||||
"0x0": {
|
||||
"price": { "linear": { "base": 500, "word": 0 }}
|
||||
},
|
||||
"0x7fffffffffffff": {
|
||||
"price": { "linear": { "base": 150, "word": 0 }}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"0000000000000000000000000000000000000007": {
|
||||
"balance": "1",
|
||||
"builtin": {
|
||||
"name": "alt_bn128_mul",
|
||||
"pricing": {
|
||||
"0x0": {
|
||||
"price": { "linear": { "base": 40000, "word": 0 }}
|
||||
},
|
||||
"0x7fffffffffffff": {
|
||||
"price": { "linear": { "base": 6000, "word": 0 }}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"0000000000000000000000000000000000000008": {
|
||||
"balance": "1",
|
||||
"builtin": {
|
||||
"name": "alt_bn128_pairing",
|
||||
"pricing": {
|
||||
"0x0": {
|
||||
"price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }}
|
||||
},
|
||||
"0x7fffffffffffff": {
|
||||
"price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"0000000000000000000000000000000000000042": {
|
||||
"balance": "1",
|
||||
"constructor": "608060405234801561001057600080fd5b50610820806100206000396000f3fe608060405234801561001057600080fd5b50600436106100ec576000357c01000000000000000000000000000000000000000000000000000000009004806363f160e6116100a95780637a3e286b116100835780637a3e286b14610378578063baf11cab14610380578063c358ced0146103ac578063fe7d567d146103b4576100ec565b806363f160e614610285578063695e89f6146102c557806374ce906714610370576100ec565b806304fdb016146100f15780630b61ba8514610192578063209652551461020b5780632e8a8dd5146102255780633fa4f245146102515780635580e58b14610259575b600080fd5b61011d6004803603604081101561010757600080fd5b5080359060200135600160a060020a03166103d1565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561015757818101518382015260200161013f565b50505050905090810190601f1680156101845780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610209600480360360408110156101a857600080fd5b813591908101906040810160208201356401000000008111156101ca57600080fd5b8201836020820111156101dc57600080fd5b803590602001918460018302840111640100000000831117156101fe57600080fd5b509092509050610475565b005b6102136104fa565b60408051918252519081900360200190f35b6102136004803603604081101561023b57600080fd5b5080359060200135600160a060020a0316610501565b61021361051b565b6102136004803603604081101561026f57600080fd5b5080359060200135600160a060020a0316610521565b6102b16004803603604081101561029b57600080fd5b5080359060200135600160a060020a031661053e565b604080519115158252519081900360200190f35b6102f1600480360360408110156102db57600080fd5b5080359060200135600160a060020a0316610568565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561033457818101518382015260200161031c565b50505050905090810190601f1680156103615780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b6102b1610639565b610213610649565b6102b16004803603604081101561039657600080fd5b5080359060200135600160a060020a0316610654565b6102b161067c565b610209600480360360208110156103ca57600080fd5b5035610687565b600160208181526000938452604080852082529284529282902080548351600293821615610100026000190190911692909204601f8101859004850283018501909352828252909290919083018282801561046d5780601f106104425761010080835404028352916020019161046d565b820191906000526020600020905b81548152906001019060200180831161045057829003601f168201915b505050505081565b41331461048157600080fd5b61048d60014303610735565b61049657600080fd5b60006104a460014303610740565b90506104b08133610654565b156104ba57600080fd5b600081815260208181526040808320338085529083528184208890558484526001835281842090845290915290206104f3908484610753565b5050505050565b6003545b90565b600060208181529281526040808220909352908152205481565b60035481565b600260209081526000928352604080842090915290825290205481565b6000918252600260209081526040808420600160a060020a03939093168452919052902054151590565b600082815260208181526040808320600160a060020a03851680855290835281842054868552600180855283862092865291845282852080548451600294821615610100026000190190911693909304601f810186900486028401860190945283835260609491939092918391908301828280156106275780601f106105fc57610100808354040283529160200191610627565b820191906000526020600020905b81548152906001019060200180831161060a57829003601f168201915b50505050509050915091509250929050565b600061064443610735565b905090565b600061064443610740565b600091825260208281526040808420600160a060020a03939093168452919052902054151590565b600061064443610747565b41331461069357600080fd5b61069f60014303610747565b6106a857600080fd5b60006106b660014303610740565b90506106c2813361053e565b156106cc57600080fd5b60408051602080820185905282518083038201815291830183528151918101919091206000848152808352838120338252909252919020541461070e57600080fd5b60009081526002602090815260408083203384529091529020819055600380549091189055565b600360069091061090565b6006900490565b60036006909106101590565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106107945782800160ff198235161785556107c1565b828001600101855582156107c1579182015b828111156107c15782358255916020019190600101906107a6565b506107cd9291506107d1565b5090565b6104fe91905b808211156107cd57600081556001016107d756fea265627a7a7230582008bb7311af9026bd70ddb998741333d414a366275b9b433a2943bbd6bedc27ae64736f6c634300050a0032"
|
||||
}
|
||||
}
|
||||
}
|
133
ethcore/res/contracts/authority_round_random.json
Normal file
133
ethcore/res/contracts/authority_round_random.json
Normal file
@ -0,0 +1,133 @@
|
||||
[{
|
||||
"constant": false,
|
||||
"inputs": [{
|
||||
"name": "_secretHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "_cipher",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "commitHash",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [{
|
||||
"name": "_number",
|
||||
"type": "uint256"
|
||||
}],
|
||||
"name": "revealNumber",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"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": "_miningAddress",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getCommitAndCipher",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"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": "_validator",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "sentReveal",
|
||||
"outputs": [{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
265
ethcore/res/contracts/test_authority_round_random.json
Normal file
265
ethcore/res/contracts/test_authority_round_random.json
Normal file
@ -0,0 +1,265 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ciphers",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_collectRound",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_miningAddress",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getCipher",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_secretHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "_cipher",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "commitHash",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "getValue",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "hashes",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "value",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "secrets",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_collectRound",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_miningAddress",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "sentReveal",
|
||||
"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": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_number",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "revealNumber",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "getRound",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_collectRound",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_miningAddress",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "isCommitted",
|
||||
"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": "_miningAddress",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getCommit",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
102
ethcore/res/contracts/test_authority_round_random.sol
Normal file
102
ethcore/res/contracts/test_authority_round_random.sol
Normal file
@ -0,0 +1,102 @@
|
||||
pragma solidity 0.5.10;
|
||||
|
||||
/// @dev Randomness test contract based on https://github.com/poanetwork/posdao-contracts.
|
||||
/// Generates and stores random numbers in a RANDAO manner and accumulates a random seed.
|
||||
contract Random {
|
||||
mapping(uint256 => mapping(address => bytes32)) public hashes;
|
||||
mapping(uint256 => mapping(address => bytes)) public ciphers;
|
||||
mapping(uint256 => mapping(address => uint256)) public secrets;
|
||||
uint256 public value;
|
||||
|
||||
/// @dev Called by the validator's node to store a hash and a cipher of the validator's secret on each collection
|
||||
/// round. The validator's node must use its mining address to call this function.
|
||||
/// This function can only be called once per collection round (during the `commits phase`).
|
||||
/// @param _secretHash The Keccak-256 hash of the validator's secret.
|
||||
/// @param _cipher The cipher of the validator's secret. Can be used by the node to decrypt and reveal.
|
||||
function commitHash(bytes32 _secretHash, bytes calldata _cipher) external {
|
||||
require(block.coinbase == msg.sender);
|
||||
require(_isCommitPhase(block.number - 1));
|
||||
uint256 round = _collectRound(block.number - 1);
|
||||
require(!isCommitted(round, msg.sender));
|
||||
hashes[round][msg.sender] = _secretHash;
|
||||
ciphers[round][msg.sender] = _cipher;
|
||||
}
|
||||
|
||||
/// @dev Called by the validator's node to XOR its secret with the current random seed.
|
||||
/// The validator's node must use its mining address to call this function.
|
||||
/// This function can only be called once per collection round (during the `reveals phase`).
|
||||
/// @param _number The validator's secret.
|
||||
function revealNumber(uint256 _number) external {
|
||||
require(block.coinbase == msg.sender);
|
||||
require(_isRevealPhase(block.number - 1));
|
||||
uint256 round = _collectRound(block.number - 1);
|
||||
require(!sentReveal(round, msg.sender));
|
||||
require(hashes[round][msg.sender] == keccak256(abi.encodePacked(_number)));
|
||||
secrets[round][msg.sender] = _number;
|
||||
value ^= _number;
|
||||
}
|
||||
|
||||
/// @dev Returns the Keccak-256 hash and cipher of the validator's secret for the specified collection round
|
||||
/// and the specified validator stored by the validator through the `commitHash` function.
|
||||
/// @param _collectRound The serial number of the collection round for which hash and cipher should be retrieved.
|
||||
/// @param _miningAddress The mining address of validator.
|
||||
function getCommitAndCipher(
|
||||
uint256 _collectRound,
|
||||
address _miningAddress
|
||||
) public view returns(bytes32, bytes memory) {
|
||||
return (hashes[_collectRound][_miningAddress], ciphers[_collectRound][_miningAddress]);
|
||||
}
|
||||
|
||||
/// @dev Returns a boolean flag indicating whether the specified validator has committed their secret's hash for the
|
||||
/// specified collection round.
|
||||
/// @param _collectRound The serial number of the collection round for which the checkup should be done.
|
||||
/// @param _miningAddress The mining address of the validator.
|
||||
function isCommitted(uint256 _collectRound, address _miningAddress) public view returns(bool) {
|
||||
return hashes[_collectRound][_miningAddress] != bytes32(0);
|
||||
}
|
||||
|
||||
/// @dev Returns a boolean flag indicating whether the current phase of the current collection round
|
||||
/// is a `commits phase`. Used by the validator's node to determine if it should commit the hash of
|
||||
/// the secret during the current collection round.
|
||||
function isCommitPhase() public view returns(bool) {
|
||||
return _isCommitPhase(block.number);
|
||||
}
|
||||
|
||||
/// @dev Returns a boolean flag indicating whether the current phase of the current collection round
|
||||
/// is a `reveals phase`. Used by the validator's node to determine if it should reveal the secret during
|
||||
/// the current collection round.
|
||||
function isRevealPhase() public view returns(bool) {
|
||||
return _isRevealPhase(block.number);
|
||||
}
|
||||
|
||||
/// @dev Returns a boolean flag of whether the specified validator has revealed their secret for the
|
||||
/// specified collection round.
|
||||
/// @param _collectRound The serial number of the collection round for which the checkup should be done.
|
||||
/// @param _miningAddress The mining address of the validator.
|
||||
function sentReveal(uint256 _collectRound, address _miningAddress) public view returns(bool) {
|
||||
return secrets[_collectRound][_miningAddress] != uint256(0);
|
||||
}
|
||||
|
||||
/// @dev Returns the current collect round number.
|
||||
function currentCollectRound() public view returns(uint256) {
|
||||
return _collectRound(block.number);
|
||||
}
|
||||
|
||||
/// @dev Returns the current random value.
|
||||
function getValue() public view returns(uint256) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function _collectRound(uint256 blockNumber) private pure returns(uint256) {
|
||||
return blockNumber / 6;
|
||||
}
|
||||
|
||||
function _isCommitPhase(uint256 blockNumber) private pure returns(bool) {
|
||||
return blockNumber % 6 < 3;
|
||||
}
|
||||
|
||||
function _isRevealPhase(uint256 blockNumber) private pure returns(bool) {
|
||||
return blockNumber % 6 >= 3;
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ use journaldb::Algorithm;
|
||||
#[test]
|
||||
fn sends_async_messages() {
|
||||
let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()];
|
||||
let client = generate_dummy_client_with_spec_and_data(spec::new_null, 400, 5, &gas_prices);
|
||||
let client = generate_dummy_client_with_spec_and_data(spec::new_null, 400, 5, &gas_prices, false);
|
||||
let service = IoService::<ClientIoMessage<Client>>::start().unwrap();
|
||||
let spec = spec::new_test();
|
||||
|
||||
@ -143,7 +143,7 @@ fn restored_is_equivalent() {
|
||||
const TX_PER: usize = 5;
|
||||
|
||||
let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()];
|
||||
let client = generate_dummy_client_with_spec_and_data(spec::new_null, NUM_BLOCKS, TX_PER, &gas_prices);
|
||||
let client = generate_dummy_client_with_spec_and_data(spec::new_null, NUM_BLOCKS, TX_PER, &gas_prices, false);
|
||||
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let client_db = tempdir.path().join("client_db");
|
||||
@ -207,7 +207,7 @@ fn restored_is_equivalent() {
|
||||
#[test]
|
||||
fn guards_delete_folders() {
|
||||
let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()];
|
||||
let client = generate_dummy_client_with_spec_and_data(spec::new_null, 400, 5, &gas_prices);
|
||||
let client = generate_dummy_client_with_spec_and_data(spec::new_null, 400, 5, &gas_prices, false);
|
||||
|
||||
let spec = spec::new_null();
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
@ -266,7 +266,7 @@ fn keep_ancient_blocks() {
|
||||
let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()];
|
||||
let spec_f = spec::new_null;
|
||||
let spec = spec_f();
|
||||
let client = generate_dummy_client_with_spec_and_data(spec_f, NUM_BLOCKS as u32, 5, &gas_prices);
|
||||
let client = generate_dummy_client_with_spec_and_data(spec_f, NUM_BLOCKS as u32, 5, &gas_prices, false);
|
||||
|
||||
let bc = client.chain();
|
||||
|
||||
@ -374,7 +374,7 @@ fn recover_aborted_recovery() {
|
||||
|
||||
const NUM_BLOCKS: u32 = 400;
|
||||
let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()];
|
||||
let client = generate_dummy_client_with_spec_and_data(spec::new_null, NUM_BLOCKS, 5, &gas_prices);
|
||||
let client = generate_dummy_client_with_spec_and_data(spec::new_null, NUM_BLOCKS, 5, &gas_prices, false);
|
||||
|
||||
let spec = spec::new_null();
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
|
@ -86,6 +86,7 @@ bundle_test_spec! {
|
||||
"authority_round" => new_test_round,
|
||||
"authority_round_block_reward_contract" => new_test_round_block_reward_contract,
|
||||
"authority_round_empty_steps" => new_test_round_empty_steps,
|
||||
"authority_round_randomness_contract" => new_test_round_randomness_contract,
|
||||
"constructor" => new_test_constructor,
|
||||
"ethereum/byzantium_test" => new_byzantium_test,
|
||||
"ethereum/constantinople_test" => new_constantinople_test,
|
||||
|
@ -79,6 +79,7 @@ use client_traits::{
|
||||
StateOrBlock,
|
||||
Tick,
|
||||
TransactionInfo,
|
||||
TransactionRequest,
|
||||
ForceUpdateSealing
|
||||
};
|
||||
use db::{keys::BlockDetails, Readable, Writable};
|
||||
@ -2154,29 +2155,35 @@ impl BlockChainClient for Client {
|
||||
}
|
||||
}
|
||||
|
||||
fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> {
|
||||
fn create_transaction(&self, TransactionRequest { action, data, gas, gas_price, nonce }: TransactionRequest)
|
||||
-> Result<SignedTransaction, transaction::Error>
|
||||
{
|
||||
let authoring_params = self.importer.miner.authoring_params();
|
||||
let service_transaction_checker = self.importer.miner.service_transaction_checker();
|
||||
let gas_price = if let Some(checker) = service_transaction_checker {
|
||||
match checker.check_address(self, authoring_params.author) {
|
||||
Ok(true) => U256::zero(),
|
||||
_ => self.importer.miner.sensible_gas_price(),
|
||||
_ => gas_price.unwrap_or_else(|| self.importer.miner.sensible_gas_price()),
|
||||
}
|
||||
} else {
|
||||
self.importer.miner.sensible_gas_price()
|
||||
};
|
||||
let transaction = transaction::Transaction {
|
||||
nonce: self.latest_nonce(&authoring_params.author),
|
||||
action: Action::Call(address),
|
||||
gas: self.importer.miner.sensible_gas_limit(),
|
||||
nonce: nonce.unwrap_or_else(|| self.latest_nonce(&authoring_params.author)),
|
||||
action,
|
||||
gas: gas.unwrap_or_else(|| self.importer.miner.sensible_gas_limit()),
|
||||
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))
|
||||
.map_err(|e| transaction::Error::InvalidSignature(e.to_string()))?;
|
||||
let signed = SignedTransaction::new(transaction.with_signature(signature, chain_id))?;
|
||||
Ok(SignedTransaction::new(transaction.with_signature(signature, chain_id))?)
|
||||
}
|
||||
|
||||
fn transact(&self, tx_request: TransactionRequest) -> Result<(), transaction::Error> {
|
||||
let signed = self.create_transaction(tx_request)?;
|
||||
self.importer.miner.import_own_transaction(self, signed.into())
|
||||
}
|
||||
}
|
||||
@ -2978,7 +2985,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn should_mark_finalization_correctly_for_parent() {
|
||||
let client = generate_dummy_client_with_spec_and_data(spec::new_test_with_finality, 2, 0, &[]);
|
||||
let client = generate_dummy_client_with_spec_and_data(spec::new_test_with_finality, 2, 0, &[], false);
|
||||
let chain = client.chain();
|
||||
|
||||
let block1_details = chain.block_hash(1).and_then(|h| chain.block_details(&h));
|
||||
|
@ -327,6 +327,13 @@ impl Miner {
|
||||
///
|
||||
/// NOTE This should be only used for tests.
|
||||
pub fn new_for_tests(spec: &Spec, accounts: Option<HashSet<Address>>) -> Miner {
|
||||
Miner::new_for_tests_force_sealing(spec, accounts, false)
|
||||
}
|
||||
|
||||
/// Creates new instance of miner with given spec and accounts.
|
||||
///
|
||||
/// NOTE This should be only used for tests.
|
||||
pub fn new_for_tests_force_sealing(spec: &Spec, accounts: Option<HashSet<Address>>, force_sealing: bool) -> Miner {
|
||||
let minimal_gas_price = 0.into();
|
||||
Miner::new(MinerOptions {
|
||||
pool_verification_options: pool::verifier::Options {
|
||||
@ -336,6 +343,7 @@ impl Miner {
|
||||
no_early_reject: false,
|
||||
},
|
||||
reseal_min_period: Duration::from_secs(0),
|
||||
force_sealing,
|
||||
..Default::default()
|
||||
}, GasPricer::new_fixed(minimal_gas_price), spec, accounts.unwrap_or_default())
|
||||
}
|
||||
@ -422,7 +430,8 @@ impl Miner {
|
||||
let chain_info = chain.chain_info();
|
||||
|
||||
// Open block
|
||||
let (mut open_block, original_work_hash) = {
|
||||
// Some engines add transactions to the block for their own purposes, e.g. AuthorityRound RANDAO.
|
||||
let (mut open_block, original_work_hash, engine_txs) = {
|
||||
let mut sealing = self.sealing.lock();
|
||||
let last_work_hash = sealing.queue.peek_last_ref().map(|pb| pb.header.hash());
|
||||
let best_hash = chain_info.best_block_hash;
|
||||
@ -433,38 +442,48 @@ impl Miner {
|
||||
// if at least one was pushed successfully, close and enqueue new ClosedBlock;
|
||||
// otherwise, leave everything alone.
|
||||
// otherwise, author a fresh block.
|
||||
let mut open_block = match sealing.queue.get_pending_if(|b| b.header.parent_hash() == &best_hash) {
|
||||
match sealing.queue.get_pending_if(|b| b.header.parent_hash() == &best_hash) {
|
||||
Some(old_block) => {
|
||||
trace!(target: "miner", "prepare_block: Already have previous work; updating and returning");
|
||||
// add transactions to old_block
|
||||
chain.reopen_block(old_block)
|
||||
(chain.reopen_block(old_block), last_work_hash, Vec::new())
|
||||
}
|
||||
None => {
|
||||
// block not found - create it.
|
||||
trace!(target: "miner", "prepare_block: No existing work - making new block");
|
||||
let params = self.params.read().clone();
|
||||
|
||||
match chain.prepare_open_block(
|
||||
let block = match chain.prepare_open_block(
|
||||
params.author,
|
||||
params.gas_range_target,
|
||||
params.extra_data,
|
||||
) {
|
||||
Ok(block) => block,
|
||||
Err(err) => {
|
||||
warn!(target: "miner", "Open new block failed with error {:?}. This is likely an error in chain specificiations or on-chain consensus smart contracts.", err);
|
||||
warn!(target: "miner", "Open new block failed with error {:?}. This is likely an error in \
|
||||
chain specification or on-chain consensus smart contracts.", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Before adding from the queue to the new block, give the engine a chance to add transactions.
|
||||
match self.engine.generate_engine_transactions(&block) {
|
||||
Ok(transactions) => (block, last_work_hash, transactions),
|
||||
Err(err) => {
|
||||
error!(target: "miner", "Failed to prepare engine transactions for new block: {:?}. \
|
||||
This is likely an error in chain specification or on-chain consensus smart \
|
||||
contracts.", err);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if self.options.infinite_pending_block {
|
||||
open_block.remove_gas_limit();
|
||||
}
|
||||
|
||||
(open_block, last_work_hash)
|
||||
};
|
||||
|
||||
if self.options.infinite_pending_block {
|
||||
open_block.remove_gas_limit();
|
||||
}
|
||||
|
||||
let mut invalid_transactions = HashSet::new();
|
||||
let mut not_allowed_transactions = HashSet::new();
|
||||
let mut senders_to_penalize = HashSet::new();
|
||||
@ -488,13 +507,13 @@ impl Miner {
|
||||
MAX_SKIPPED_TRANSACTIONS.saturating_add(cmp::min(*open_block.header.gas_limit() / min_tx_gas, u64::max_value().into()).as_u64() as usize)
|
||||
};
|
||||
|
||||
let pending: Vec<Arc<_>> = self.transaction_queue.pending(
|
||||
let queue_txs: Vec<Arc<_>> = self.transaction_queue.pending(
|
||||
client.clone(),
|
||||
pool::PendingSettings {
|
||||
block_number: chain_info.best_block_number,
|
||||
current_timestamp: chain_info.best_block_timestamp,
|
||||
nonce_cap,
|
||||
max_len: max_transactions,
|
||||
max_len: max_transactions.saturating_sub(engine_txs.len()),
|
||||
ordering: miner::PendingOrdering::Priority,
|
||||
}
|
||||
);
|
||||
@ -504,12 +523,11 @@ impl Miner {
|
||||
};
|
||||
|
||||
let block_start = Instant::now();
|
||||
debug!(target: "miner", "Attempting to push {} transactions.", pending.len());
|
||||
debug!(target: "miner", "Attempting to push {} transactions.", engine_txs.len() + queue_txs.len());
|
||||
|
||||
for tx in pending {
|
||||
for transaction in engine_txs.into_iter().chain(queue_txs.into_iter().map(|tx| tx.signed().clone())) {
|
||||
let start = Instant::now();
|
||||
|
||||
let transaction = tx.signed().clone();
|
||||
let hash = transaction.hash();
|
||||
let sender = transaction.sender();
|
||||
|
||||
|
@ -118,31 +118,35 @@ pub fn create_test_block_with_data(header: &Header, transactions: &[SignedTransa
|
||||
|
||||
/// Generates dummy client (not test client) with corresponding amount of blocks
|
||||
pub fn generate_dummy_client(block_number: u32) -> Arc<Client> {
|
||||
generate_dummy_client_with_spec_and_data(spec::new_test, block_number, 0, &[])
|
||||
generate_dummy_client_with_spec_and_data(spec::new_test, block_number, 0, &[], false)
|
||||
}
|
||||
|
||||
/// Generates dummy client (not test client) with corresponding amount of blocks and txs per every block
|
||||
pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> {
|
||||
generate_dummy_client_with_spec_and_data(spec::new_null, block_number, txs_per_block, tx_gas_prices)
|
||||
generate_dummy_client_with_spec_and_data(spec::new_null, block_number, txs_per_block, tx_gas_prices, false)
|
||||
}
|
||||
|
||||
/// Generates dummy client (not test client) with corresponding spec and accounts
|
||||
pub fn generate_dummy_client_with_spec<F>(test_spec: F) -> Arc<Client> where F: Fn() -> Spec {
|
||||
generate_dummy_client_with_spec_and_data(test_spec, 0, 0, &[])
|
||||
generate_dummy_client_with_spec_and_data(test_spec, 0, 0, &[], false)
|
||||
}
|
||||
|
||||
/// Generates dummy client (not test client) with corresponding amount of blocks, txs per block and spec
|
||||
pub fn generate_dummy_client_with_spec_and_data<F>(test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> where
|
||||
pub fn generate_dummy_client_with_spec_and_data<F>(
|
||||
test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256], force_sealing: bool,
|
||||
) -> Arc<Client> where
|
||||
F: Fn() -> Spec
|
||||
{
|
||||
let test_spec = test_spec();
|
||||
let client_db = new_db();
|
||||
|
||||
let miner = Miner::new_for_tests_force_sealing(&test_spec, None, force_sealing);
|
||||
|
||||
let client = Client::new(
|
||||
ClientConfig::default(),
|
||||
&test_spec,
|
||||
client_db,
|
||||
Arc::new(Miner::new_for_tests(&test_spec, None)),
|
||||
Arc::new(miner),
|
||||
IoChannel::disconnected(),
|
||||
).unwrap();
|
||||
let test_engine = &*test_spec.engine;
|
||||
|
@ -72,7 +72,7 @@ use client::{
|
||||
use client_traits::{
|
||||
BlockInfo, Nonce, Balance, ChainInfo, TransactionInfo, BlockChainClient, ImportBlock,
|
||||
AccountData, BlockChain, IoClient, BadBlocks, ScheduleInfo, StateClient, ProvingBlockChainClient,
|
||||
StateOrBlock, ForceUpdateSealing
|
||||
StateOrBlock, ForceUpdateSealing, TransactionRequest
|
||||
};
|
||||
use engine::Engine;
|
||||
use machine::executed::Executed;
|
||||
@ -912,18 +912,24 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> {
|
||||
fn create_transaction(&self, TransactionRequest { action, data, gas, gas_price, nonce }: TransactionRequest)
|
||||
-> Result<SignedTransaction, 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(),
|
||||
nonce: nonce.unwrap_or_else(|| self.latest_nonce(&self.miner.authoring_params().author)),
|
||||
action,
|
||||
gas: gas.unwrap_or(self.spec.gas_limit),
|
||||
gas_price: gas_price.unwrap_or_else(U256::zero),
|
||||
value: U256::default(),
|
||||
data: data,
|
||||
};
|
||||
let chain_id = Some(self.spec.chain_id());
|
||||
let sig = self.spec.engine.sign(transaction.hash(chain_id)).unwrap();
|
||||
let signed = SignedTransaction::new(transaction.with_signature(sig, chain_id)).unwrap();
|
||||
Ok(SignedTransaction::new(transaction.with_signature(sig, chain_id)).unwrap())
|
||||
}
|
||||
|
||||
fn transact(&self, tx_request: TransactionRequest) -> Result<(), transaction::Error> {
|
||||
let signed = self.create_transaction(tx_request)?;
|
||||
self.miner.import_own_transaction(self, signed.into())
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,10 @@ pub enum EngineError {
|
||||
InsufficientProof(String),
|
||||
/// Failed system call.
|
||||
FailedSystemCall(String),
|
||||
/// Failed to decode the result of a system call.
|
||||
SystemCallResultDecoding(String),
|
||||
/// The result of a system call is invalid.
|
||||
SystemCallResultInvalid(String),
|
||||
/// Malformed consensus message.
|
||||
MalformedMessage(String),
|
||||
/// Requires client ref, but none registered.
|
||||
@ -91,6 +95,8 @@ impl fmt::Display for EngineError {
|
||||
BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob),
|
||||
InsufficientProof(ref msg) => format!("Insufficient validation proof: {}", msg),
|
||||
FailedSystemCall(ref msg) => format!("Failed to make system call: {}", msg),
|
||||
SystemCallResultDecoding(ref msg) => format!("Failed to decode the result of a system call: {}", msg),
|
||||
SystemCallResultInvalid(ref msg) => format!("The result of a system call is invalid: {}", msg),
|
||||
MalformedMessage(ref msg) => format!("Received malformed consensus message: {}", msg),
|
||||
RequiresClient => format!("Call requires client but none registered"),
|
||||
RequiresSigner => format!("Call requires signer but none registered"),
|
||||
|
@ -95,6 +95,8 @@ pub struct AuthorityRoundParams {
|
||||
pub strict_empty_steps_transition: Option<Uint>,
|
||||
/// First block for which a 2/3 quorum (instead of 1/2) is required.
|
||||
pub two_thirds_majority_transition: Option<Uint>,
|
||||
/// The random number contract's address, or a map of contract transitions.
|
||||
pub randomness_contract_address: Option<BTreeMap<Uint, Address>>,
|
||||
}
|
||||
|
||||
/// Authority engine deserialization.
|
||||
@ -124,7 +126,11 @@ mod tests {
|
||||
"validateStepTransition": 150,
|
||||
"blockReward": 5000000,
|
||||
"maximumUncleCountTransition": 10000000,
|
||||
"maximumUncleCount": 5
|
||||
"maximumUncleCount": 5,
|
||||
"randomnessContractAddress": {
|
||||
"10": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"20": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
@ -138,6 +144,10 @@ mod tests {
|
||||
assert_eq!(deserialized.params.immediate_transitions, None);
|
||||
assert_eq!(deserialized.params.maximum_uncle_count_transition, Some(Uint(10_000_000.into())));
|
||||
assert_eq!(deserialized.params.maximum_uncle_count, Some(Uint(5.into())));
|
||||
|
||||
assert_eq!(deserialized.params.randomness_contract_address.unwrap(),
|
||||
vec![
|
||||
(Uint(10.into()), Address(H160::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap())),
|
||||
(Uint(20.into()), Address(H160::from_str("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").unwrap())),
|
||||
].into_iter().collect());
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ use std::sync::Arc;
|
||||
|
||||
use accounts::AccountProvider;
|
||||
use ethkey::Password;
|
||||
use crypto::publickey::{Address, Message, Signature, Error};
|
||||
use crypto::publickey::{Address, Message, Public, Signature, Error};
|
||||
|
||||
/// An implementation of EngineSigner using internal account management.
|
||||
pub struct EngineSigner {
|
||||
@ -42,8 +42,19 @@ impl engine::signer::EngineSigner for EngineSigner {
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt(&self, auth_data: &[u8], cipher: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
self.accounts.decrypt(self.address, None, auth_data, cipher).map_err(|e| {
|
||||
warn!("Unable to decrypt message: {:?}", e);
|
||||
Error::InvalidMessage
|
||||
})
|
||||
}
|
||||
|
||||
fn address(&self) -> Address {
|
||||
self.address
|
||||
}
|
||||
|
||||
fn public(&self) -> Option<Public> {
|
||||
self.accounts.account_public(self.address, &self.password).ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user