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",
|
"block-reward 0.1.0",
|
||||||
"client-traits 0.1.0",
|
"client-traits 0.1.0",
|
||||||
"common-types 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",
|
"engine 0.1.0",
|
||||||
"env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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 1.12.0",
|
||||||
"ethcore-accounts 0.1.0",
|
"ethcore-accounts 0.1.0",
|
||||||
"ethcore-io 1.12.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)",
|
"lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"machine 0.1.0",
|
"machine 0.1.0",
|
||||||
"macros 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)",
|
"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)",
|
"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)",
|
"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)",
|
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"spec 0.1.0",
|
"spec 0.1.0",
|
||||||
|
@ -73,7 +73,6 @@ blooms-db = { path = "../util/blooms-db" }
|
|||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
engine = { path = "./engine", features = ["test-helpers"] }
|
engine = { path = "./engine", features = ["test-helpers"] }
|
||||||
env_logger = "0.5"
|
env_logger = "0.5"
|
||||||
ethash = { path = "../ethash" }
|
|
||||||
ethcore-accounts = { path = "../accounts" }
|
ethcore-accounts = { path = "../accounts" }
|
||||||
ethcore-builtin = { path = "./builtin" }
|
ethcore-builtin = { path = "./builtin" }
|
||||||
ethjson = { path = "../json", features = ["test-helpers"] }
|
ethjson = { path = "../json", features = ["test-helpers"] }
|
||||||
|
@ -42,7 +42,7 @@ use common_types::{
|
|||||||
pruning_info::PruningInfo,
|
pruning_info::PruningInfo,
|
||||||
receipt::LocalizedReceipt,
|
receipt::LocalizedReceipt,
|
||||||
trace_filter::Filter as TraceFilter,
|
trace_filter::Filter as TraceFilter,
|
||||||
transaction::{self, LocalizedTransaction, CallError, SignedTransaction, UnverifiedTransaction},
|
transaction::{self, Action, LocalizedTransaction, CallError, SignedTransaction, UnverifiedTransaction},
|
||||||
tree_route::TreeRoute,
|
tree_route::TreeRoute,
|
||||||
verification::{VerificationQueueInfo, Unverified},
|
verification::{VerificationQueueInfo, Unverified},
|
||||||
};
|
};
|
||||||
@ -395,8 +395,66 @@ pub trait BlockChainClient:
|
|||||||
/// Returns information about pruning/data availability.
|
/// Returns information about pruning/data availability.
|
||||||
fn pruning_info(&self) -> PruningInfo;
|
fn pruning_info(&self) -> PruningInfo;
|
||||||
|
|
||||||
/// Schedule state-altering transaction to be executed on the next pending block.
|
/// Returns a transaction signed with the key configured in the engine signer.
|
||||||
fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error>;
|
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
|
/// resets the blockchain
|
||||||
|
@ -32,7 +32,7 @@ use common_types::{
|
|||||||
},
|
},
|
||||||
errors::{EthcoreError as Error, EngineError},
|
errors::{EthcoreError as Error, EngineError},
|
||||||
snapshot::Snapshotting,
|
snapshot::Snapshotting,
|
||||||
transaction::{self, UnverifiedTransaction},
|
transaction::{self, SignedTransaction, UnverifiedTransaction},
|
||||||
};
|
};
|
||||||
use client_traits::EngineClient;
|
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.
|
/// Allow mutating the header during seal generation. Currently only used by Clique.
|
||||||
fn on_seal_block(&self, _block: &mut ExecutedBlock) -> Result<(), Error> { Ok(()) }
|
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.
|
/// Returns the engine's current sealing state.
|
||||||
fn sealing_state(&self) -> SealingState { SealingState::External }
|
fn sealing_state(&self) -> SealingState { SealingState::External }
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
//! A signer used by Engines which need to sign messages.
|
//! A signer used by Engines which need to sign messages.
|
||||||
|
|
||||||
use ethereum_types::{H256, Address};
|
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.
|
/// Everything that an Engine needs to sign messages.
|
||||||
pub trait EngineSigner: Send + Sync {
|
pub trait EngineSigner: Send + Sync {
|
||||||
@ -26,6 +26,12 @@ pub trait EngineSigner: Send + Sync {
|
|||||||
|
|
||||||
/// Signing address
|
/// Signing address
|
||||||
fn address(&self) -> 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.
|
/// Creates a new `EngineSigner` from given key pair.
|
||||||
@ -40,7 +46,15 @@ impl EngineSigner for Signer {
|
|||||||
parity_crypto::publickey::sign(self.0.secret(), &hash)
|
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 {
|
fn address(&self) -> Address {
|
||||||
self.0.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 ethereum_types::{Address, H256};
|
||||||
use ethkey::Password;
|
use ethkey::Password;
|
||||||
use parity_crypto::publickey::{Signature, Error};
|
use parity_crypto::publickey::{Public, Signature, Error};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use accounts::{self, AccountProvider, SignError};
|
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 {
|
fn address(&self) -> Address {
|
||||||
self.1
|
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" }
|
block-reward = { path = "../../block-reward" }
|
||||||
client-traits = { path = "../../client-traits" }
|
client-traits = { path = "../../client-traits" }
|
||||||
common-types = { path = "../../types" }
|
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"
|
ethereum-types = "0.8.0"
|
||||||
ethjson = { path = "../../../json" }
|
ethjson = { path = "../../../json" }
|
||||||
parity-crypto = { version = "0.4.2", features = ["publickey"] }
|
parity-crypto = { version = "0.4.2", features = ["publickey"] }
|
||||||
@ -22,7 +26,9 @@ log = "0.4"
|
|||||||
lru-cache = "0.1"
|
lru-cache = "0.1"
|
||||||
machine = { path = "../../machine" }
|
machine = { path = "../../machine" }
|
||||||
macros = { path = "../../../util/macros" }
|
macros = { path = "../../../util/macros" }
|
||||||
|
parity-bytes = "0.1"
|
||||||
parking_lot = "0.9"
|
parking_lot = "0.9"
|
||||||
|
rand = "0.7"
|
||||||
rlp = "0.4.0"
|
rlp = "0.4.0"
|
||||||
time-utils = { path = "../../../util/time-utils" }
|
time-utils = { path = "../../../util/time-utils" }
|
||||||
unexpected = { path = "../../../util/unexpected" }
|
unexpected = { path = "../../../util/unexpected" }
|
||||||
|
@ -40,7 +40,7 @@ use std::sync::{Weak, Arc};
|
|||||||
use std::time::{UNIX_EPOCH, Duration};
|
use std::time::{UNIX_EPOCH, Duration};
|
||||||
use std::u64;
|
use std::u64;
|
||||||
|
|
||||||
use client_traits::{EngineClient, ForceUpdateSealing};
|
use client_traits::{EngineClient, ForceUpdateSealing, TransactionRequest};
|
||||||
use engine::{Engine, ConstructedVerifier};
|
use engine::{Engine, ConstructedVerifier};
|
||||||
use block_reward::{self, BlockRewardContract, RewardKind};
|
use block_reward::{self, BlockRewardContract, RewardKind};
|
||||||
use ethjson;
|
use ethjson;
|
||||||
@ -55,6 +55,7 @@ use engine::signer::EngineSigner;
|
|||||||
use parity_crypto::publickey::Signature;
|
use parity_crypto::publickey::Signature;
|
||||||
use io::{IoContext, IoHandler, TimerToken, IoService};
|
use io::{IoContext, IoHandler, TimerToken, IoService};
|
||||||
use itertools::{self, Itertools};
|
use itertools::{self, Itertools};
|
||||||
|
use rand::rngs::OsRng;
|
||||||
use rlp::{encode, Decodable, DecoderError, Encodable, RlpStream, Rlp};
|
use rlp::{encode, Decodable, DecoderError, Encodable, RlpStream, Rlp};
|
||||||
use ethereum_types::{H256, H520, Address, U128, U256};
|
use ethereum_types::{H256, H520, Address, U128, U256};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
@ -72,13 +73,17 @@ use common_types::{
|
|||||||
machine::{Call, AuxiliaryData},
|
machine::{Call, AuxiliaryData},
|
||||||
},
|
},
|
||||||
errors::{BlockError, EthcoreError as Error, EngineError},
|
errors::{BlockError, EthcoreError as Error, EngineError},
|
||||||
|
ids::BlockId,
|
||||||
snapshot::Snapshotting,
|
snapshot::Snapshotting,
|
||||||
|
transaction::SignedTransaction,
|
||||||
};
|
};
|
||||||
use unexpected::{Mismatch, OutOfBounds};
|
use unexpected::{Mismatch, OutOfBounds};
|
||||||
|
|
||||||
use validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
use validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
||||||
|
|
||||||
mod finality;
|
mod finality;
|
||||||
|
mod randomness;
|
||||||
|
pub(crate) mod util;
|
||||||
|
|
||||||
use self::finality::RollingFinality;
|
use self::finality::RollingFinality;
|
||||||
|
|
||||||
@ -117,6 +122,8 @@ pub struct AuthorityRoundParams {
|
|||||||
pub maximum_empty_steps: usize,
|
pub maximum_empty_steps: usize,
|
||||||
/// Transition block to strict empty steps validation.
|
/// Transition block to strict empty steps validation.
|
||||||
pub strict_empty_steps_transition: u64,
|
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;
|
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())
|
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 {
|
AuthorityRoundParams {
|
||||||
step_durations,
|
step_durations,
|
||||||
validators: new_validator_set(p.validators),
|
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),
|
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),
|
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),
|
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,
|
machine: Machine,
|
||||||
/// History of step hashes recently received from peers.
|
/// History of step hashes recently received from peers.
|
||||||
received_step_hashes: RwLock<BTreeMap<(u64, Address), H256>>,
|
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.
|
// header-chain validator.
|
||||||
@ -851,6 +866,7 @@ impl AuthorityRound {
|
|||||||
strict_empty_steps_transition: our_params.strict_empty_steps_transition,
|
strict_empty_steps_transition: our_params.strict_empty_steps_transition,
|
||||||
machine,
|
machine,
|
||||||
received_step_hashes: RwLock::new(Default::default()),
|
received_step_hashes: RwLock::new(Default::default()),
|
||||||
|
randomness_contract_address: our_params.randomness_contract_address,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Do not initialize timeouts for tests.
|
// Do not initialize timeouts for tests.
|
||||||
@ -1045,6 +1061,41 @@ impl AuthorityRound {
|
|||||||
fn address(&self) -> Option<Address> {
|
fn address(&self) -> Option<Address> {
|
||||||
self.signer.read().as_ref().map(|s| s.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 {
|
fn unix_now() -> Duration {
|
||||||
@ -1326,7 +1377,6 @@ impl Engine for AuthorityRound {
|
|||||||
// report any skipped primaries between the parent block and
|
// report any skipped primaries between the parent block and
|
||||||
// the block we're sealing, unless we have empty steps enabled
|
// the block we're sealing, unless we have empty steps enabled
|
||||||
if header.number() < self.empty_steps_transition {
|
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);
|
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)
|
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.
|
/// Check the number of seal fields.
|
||||||
fn verify_block_basic(&self, header: &Header) -> Result<(), Error> {
|
fn verify_block_basic(&self, header: &Header) -> Result<(), Error> {
|
||||||
if header.number() >= self.validate_score_transition && *header.difficulty() >= U256::from(U128::max_value()) {
|
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 std::time::Duration;
|
||||||
use keccak_hash::keccak;
|
use keccak_hash::keccak;
|
||||||
use accounts::AccountProvider;
|
use accounts::AccountProvider;
|
||||||
|
use ethabi_contract::use_contract;
|
||||||
use ethereum_types::{Address, H520, H256, U256};
|
use ethereum_types::{Address, H520, H256, U256};
|
||||||
use parity_crypto::publickey::Signature;
|
use parity_crypto::publickey::Signature;
|
||||||
use common_types::{
|
use common_types::{
|
||||||
header::Header,
|
header::Header,
|
||||||
engines::{Seal, params::CommonParams},
|
engines::{Seal, params::CommonParams},
|
||||||
|
ids::BlockId,
|
||||||
errors::{EthcoreError as Error, EngineError},
|
errors::{EthcoreError as Error, EngineError},
|
||||||
transaction::{Action, Transaction},
|
transaction::{Action, Transaction},
|
||||||
};
|
};
|
||||||
use rlp::encode;
|
use rlp::encode;
|
||||||
use ethcore::{
|
use ethcore::{
|
||||||
block::*,
|
block::*,
|
||||||
|
miner::{Author, MinerService},
|
||||||
test_helpers::{
|
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
|
TestNotify
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -1831,7 +1888,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
AuthorityRoundParams, AuthorityRound, EmptyStep, SealedEmptyStep, StepDurationInfo,
|
AuthorityRoundParams, AuthorityRound, EmptyStep, SealedEmptyStep, StepDurationInfo,
|
||||||
calculate_score,
|
calculate_score, util::BoundContract,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn build_aura<F>(f: F) -> Arc<AuthorityRound> where
|
fn build_aura<F>(f: F) -> Arc<AuthorityRound> where
|
||||||
@ -1852,6 +1909,7 @@ mod tests {
|
|||||||
block_reward_contract_transitions: Default::default(),
|
block_reward_contract_transitions: Default::default(),
|
||||||
strict_empty_steps_transition: 0,
|
strict_empty_steps_transition: 0,
|
||||||
two_thirds_majority_transition: 0,
|
two_thirds_majority_transition: 0,
|
||||||
|
randomness_contract_address: BTreeMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// mutate aura params
|
// 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]
|
#[test]
|
||||||
fn extra_info_from_seal() {
|
fn extra_info_from_seal() {
|
||||||
let (spec, tap, accounts) = setup_empty_steps();
|
let (spec, tap, accounts) = setup_empty_steps();
|
||||||
|
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},
|
engines::machine::{Call, AuxiliaryData},
|
||||||
};
|
};
|
||||||
|
|
||||||
use client_traits::EngineClient;
|
use client_traits::{EngineClient, TransactionRequest};
|
||||||
use engine::SystemCall;
|
use engine::SystemCall;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -68,7 +68,7 @@ impl ValidatorContract {
|
|||||||
|
|
||||||
match client.as_full_client() {
|
match client.as_full_client() {
|
||||||
Some(c) => {
|
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))?;
|
.map_err(|e| format!("Transaction import error: {}", e))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
@ -149,7 +149,7 @@ mod tests {
|
|||||||
use accounts::AccountProvider;
|
use accounts::AccountProvider;
|
||||||
use call_contract::CallContract;
|
use call_contract::CallContract;
|
||||||
use common_types::{header::Header, ids::BlockId};
|
use common_types::{header::Header, ids::BlockId};
|
||||||
use client_traits::{BlockChainClient, ChainInfo, BlockInfo};
|
use client_traits::{BlockChainClient, ChainInfo, BlockInfo, TransactionRequest};
|
||||||
use ethcore::{
|
use ethcore::{
|
||||||
miner::{self, MinerService},
|
miner::{self, MinerService},
|
||||||
test_helpers::generate_dummy_client_with_spec,
|
test_helpers::generate_dummy_client_with_spec,
|
||||||
@ -225,7 +225,7 @@ mod tests {
|
|||||||
assert_eq!(client.chain_info().best_block_number, 2);
|
assert_eq!(client.chain_info().best_block_number, 2);
|
||||||
|
|
||||||
// Check if misbehaving validator was removed.
|
// 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();
|
||||||
client.engine().step();
|
client.engine().step();
|
||||||
assert_eq!(client.chain_info().best_block_number, 2);
|
assert_eq!(client.chain_info().best_block_number, 2);
|
||||||
|
@ -161,11 +161,13 @@ mod tests {
|
|||||||
ids::BlockId,
|
ids::BlockId,
|
||||||
verification::Unverified,
|
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 engine::EpochChange;
|
||||||
use ethcore::{
|
use ethcore::{
|
||||||
miner::{self, MinerService},
|
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 ethereum_types::Address;
|
||||||
use parity_crypto::publickey::Secret;
|
use parity_crypto::publickey::Secret;
|
||||||
@ -190,7 +192,7 @@ mod tests {
|
|||||||
// Wrong signer for the first block.
|
// Wrong signer for the first block.
|
||||||
let signer = Box::new((tap.clone(), v1, "".into()));
|
let signer = Box::new((tap.clone(), v1, "".into()));
|
||||||
client.miner().set_author(miner::Author::Sealer(signer));
|
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);
|
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
||||||
assert_eq!(client.chain_info().best_block_number, 0);
|
assert_eq!(client.chain_info().best_block_number, 0);
|
||||||
// Right signer for the first block.
|
// Right signer for the first block.
|
||||||
@ -199,7 +201,7 @@ mod tests {
|
|||||||
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
||||||
assert_eq!(client.chain_info().best_block_number, 1);
|
assert_eq!(client.chain_info().best_block_number, 1);
|
||||||
// This time v0 is wrong.
|
// 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);
|
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
||||||
assert_eq!(client.chain_info().best_block_number, 1);
|
assert_eq!(client.chain_info().best_block_number, 1);
|
||||||
let signer = Box::new((tap.clone(), v1, "".into()));
|
let signer = Box::new((tap.clone(), v1, "".into()));
|
||||||
@ -207,12 +209,12 @@ mod tests {
|
|||||||
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
||||||
assert_eq!(client.chain_info().best_block_number, 2);
|
assert_eq!(client.chain_info().best_block_number, 2);
|
||||||
// v1 is still good.
|
// 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);
|
EngineClient::update_sealing(&*client, ForceUpdateSealing::No);
|
||||||
assert_eq!(client.chain_info().best_block_number, 3);
|
assert_eq!(client.chain_info().best_block_number, 3);
|
||||||
|
|
||||||
// Check syncing.
|
// 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 _);
|
sync_client.engine().register_client(Arc::downgrade(&sync_client) as _);
|
||||||
for i in 1..4 {
|
for i in 1..4 {
|
||||||
sync_client.import_block(Unverified::from_rlp(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap()).unwrap();
|
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 engine::{EpochChange, Proof};
|
||||||
use ethcore::{
|
use ethcore::{
|
||||||
miner::{self, MinerService},
|
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 parity_crypto::publickey::Secret;
|
||||||
use ethereum_types::Address;
|
use ethereum_types::Address;
|
||||||
@ -551,7 +551,7 @@ mod tests {
|
|||||||
assert_eq!(client.chain_info().best_block_number, 3);
|
assert_eq!(client.chain_info().best_block_number, 3);
|
||||||
|
|
||||||
// Check syncing.
|
// 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 _);
|
sync_client.engine().register_client(Arc::downgrade(&sync_client) as _);
|
||||||
for i in 1..4 {
|
for i in 1..4 {
|
||||||
sync_client.import_block(Unverified::from_rlp(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap()).unwrap();
|
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]
|
#[test]
|
||||||
fn sends_async_messages() {
|
fn sends_async_messages() {
|
||||||
let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()];
|
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 service = IoService::<ClientIoMessage<Client>>::start().unwrap();
|
||||||
let spec = spec::new_test();
|
let spec = spec::new_test();
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ fn restored_is_equivalent() {
|
|||||||
const TX_PER: usize = 5;
|
const TX_PER: usize = 5;
|
||||||
|
|
||||||
let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()];
|
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 tempdir = TempDir::new("").unwrap();
|
||||||
let client_db = tempdir.path().join("client_db");
|
let client_db = tempdir.path().join("client_db");
|
||||||
@ -207,7 +207,7 @@ fn restored_is_equivalent() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn guards_delete_folders() {
|
fn guards_delete_folders() {
|
||||||
let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()];
|
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 spec = spec::new_null();
|
||||||
let tempdir = TempDir::new("").unwrap();
|
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 gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()];
|
||||||
let spec_f = spec::new_null;
|
let spec_f = spec::new_null;
|
||||||
let spec = spec_f();
|
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();
|
let bc = client.chain();
|
||||||
|
|
||||||
@ -374,7 +374,7 @@ fn recover_aborted_recovery() {
|
|||||||
|
|
||||||
const NUM_BLOCKS: u32 = 400;
|
const NUM_BLOCKS: u32 = 400;
|
||||||
let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()];
|
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 spec = spec::new_null();
|
||||||
let tempdir = TempDir::new("").unwrap();
|
let tempdir = TempDir::new("").unwrap();
|
||||||
|
@ -86,6 +86,7 @@ bundle_test_spec! {
|
|||||||
"authority_round" => new_test_round,
|
"authority_round" => new_test_round,
|
||||||
"authority_round_block_reward_contract" => new_test_round_block_reward_contract,
|
"authority_round_block_reward_contract" => new_test_round_block_reward_contract,
|
||||||
"authority_round_empty_steps" => new_test_round_empty_steps,
|
"authority_round_empty_steps" => new_test_round_empty_steps,
|
||||||
|
"authority_round_randomness_contract" => new_test_round_randomness_contract,
|
||||||
"constructor" => new_test_constructor,
|
"constructor" => new_test_constructor,
|
||||||
"ethereum/byzantium_test" => new_byzantium_test,
|
"ethereum/byzantium_test" => new_byzantium_test,
|
||||||
"ethereum/constantinople_test" => new_constantinople_test,
|
"ethereum/constantinople_test" => new_constantinople_test,
|
||||||
|
@ -79,6 +79,7 @@ use client_traits::{
|
|||||||
StateOrBlock,
|
StateOrBlock,
|
||||||
Tick,
|
Tick,
|
||||||
TransactionInfo,
|
TransactionInfo,
|
||||||
|
TransactionRequest,
|
||||||
ForceUpdateSealing
|
ForceUpdateSealing
|
||||||
};
|
};
|
||||||
use db::{keys::BlockDetails, Readable, Writable};
|
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 authoring_params = self.importer.miner.authoring_params();
|
||||||
let service_transaction_checker = self.importer.miner.service_transaction_checker();
|
let service_transaction_checker = self.importer.miner.service_transaction_checker();
|
||||||
let gas_price = if let Some(checker) = service_transaction_checker {
|
let gas_price = if let Some(checker) = service_transaction_checker {
|
||||||
match checker.check_address(self, authoring_params.author) {
|
match checker.check_address(self, authoring_params.author) {
|
||||||
Ok(true) => U256::zero(),
|
Ok(true) => U256::zero(),
|
||||||
_ => self.importer.miner.sensible_gas_price(),
|
_ => gas_price.unwrap_or_else(|| self.importer.miner.sensible_gas_price()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.importer.miner.sensible_gas_price()
|
self.importer.miner.sensible_gas_price()
|
||||||
};
|
};
|
||||||
let transaction = transaction::Transaction {
|
let transaction = transaction::Transaction {
|
||||||
nonce: self.latest_nonce(&authoring_params.author),
|
nonce: nonce.unwrap_or_else(|| self.latest_nonce(&authoring_params.author)),
|
||||||
action: Action::Call(address),
|
action,
|
||||||
gas: self.importer.miner.sensible_gas_limit(),
|
gas: gas.unwrap_or_else(|| self.importer.miner.sensible_gas_limit()),
|
||||||
gas_price,
|
gas_price,
|
||||||
value: U256::zero(),
|
value: U256::zero(),
|
||||||
data: data,
|
data,
|
||||||
};
|
};
|
||||||
let chain_id = self.engine.signing_chain_id(&self.latest_env_info());
|
let chain_id = self.engine.signing_chain_id(&self.latest_env_info());
|
||||||
let signature = self.engine.sign(transaction.hash(chain_id))
|
let signature = self.engine.sign(transaction.hash(chain_id))
|
||||||
.map_err(|e| transaction::Error::InvalidSignature(e.to_string()))?;
|
.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())
|
self.importer.miner.import_own_transaction(self, signed.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2978,7 +2985,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_mark_finalization_correctly_for_parent() {
|
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 chain = client.chain();
|
||||||
|
|
||||||
let block1_details = chain.block_hash(1).and_then(|h| chain.block_details(&h));
|
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.
|
/// NOTE This should be only used for tests.
|
||||||
pub fn new_for_tests(spec: &Spec, accounts: Option<HashSet<Address>>) -> Miner {
|
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();
|
let minimal_gas_price = 0.into();
|
||||||
Miner::new(MinerOptions {
|
Miner::new(MinerOptions {
|
||||||
pool_verification_options: pool::verifier::Options {
|
pool_verification_options: pool::verifier::Options {
|
||||||
@ -336,6 +343,7 @@ impl Miner {
|
|||||||
no_early_reject: false,
|
no_early_reject: false,
|
||||||
},
|
},
|
||||||
reseal_min_period: Duration::from_secs(0),
|
reseal_min_period: Duration::from_secs(0),
|
||||||
|
force_sealing,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}, GasPricer::new_fixed(minimal_gas_price), spec, accounts.unwrap_or_default())
|
}, GasPricer::new_fixed(minimal_gas_price), spec, accounts.unwrap_or_default())
|
||||||
}
|
}
|
||||||
@ -422,7 +430,8 @@ impl Miner {
|
|||||||
let chain_info = chain.chain_info();
|
let chain_info = chain.chain_info();
|
||||||
|
|
||||||
// Open block
|
// 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 mut sealing = self.sealing.lock();
|
||||||
let last_work_hash = sealing.queue.peek_last_ref().map(|pb| pb.header.hash());
|
let last_work_hash = sealing.queue.peek_last_ref().map(|pb| pb.header.hash());
|
||||||
let best_hash = chain_info.best_block_hash;
|
let best_hash = chain_info.best_block_hash;
|
||||||
@ -433,27 +442,40 @@ impl Miner {
|
|||||||
// if at least one was pushed successfully, close and enqueue new ClosedBlock;
|
// if at least one was pushed successfully, close and enqueue new ClosedBlock;
|
||||||
// otherwise, leave everything alone.
|
// otherwise, leave everything alone.
|
||||||
// otherwise, author a fresh block.
|
// 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) => {
|
Some(old_block) => {
|
||||||
trace!(target: "miner", "prepare_block: Already have previous work; updating and returning");
|
trace!(target: "miner", "prepare_block: Already have previous work; updating and returning");
|
||||||
// add transactions to old_block
|
// add transactions to old_block
|
||||||
chain.reopen_block(old_block)
|
(chain.reopen_block(old_block), last_work_hash, Vec::new())
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// block not found - create it.
|
// block not found - create it.
|
||||||
trace!(target: "miner", "prepare_block: No existing work - making new block");
|
trace!(target: "miner", "prepare_block: No existing work - making new block");
|
||||||
let params = self.params.read().clone();
|
let params = self.params.read().clone();
|
||||||
|
|
||||||
match chain.prepare_open_block(
|
let block = match chain.prepare_open_block(
|
||||||
params.author,
|
params.author,
|
||||||
params.gas_range_target,
|
params.gas_range_target,
|
||||||
params.extra_data,
|
params.extra_data,
|
||||||
) {
|
) {
|
||||||
Ok(block) => block,
|
Ok(block) => block,
|
||||||
Err(err) => {
|
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;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -462,9 +484,6 @@ impl Miner {
|
|||||||
open_block.remove_gas_limit();
|
open_block.remove_gas_limit();
|
||||||
}
|
}
|
||||||
|
|
||||||
(open_block, last_work_hash)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut invalid_transactions = HashSet::new();
|
let mut invalid_transactions = HashSet::new();
|
||||||
let mut not_allowed_transactions = HashSet::new();
|
let mut not_allowed_transactions = HashSet::new();
|
||||||
let mut senders_to_penalize = 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)
|
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(),
|
client.clone(),
|
||||||
pool::PendingSettings {
|
pool::PendingSettings {
|
||||||
block_number: chain_info.best_block_number,
|
block_number: chain_info.best_block_number,
|
||||||
current_timestamp: chain_info.best_block_timestamp,
|
current_timestamp: chain_info.best_block_timestamp,
|
||||||
nonce_cap,
|
nonce_cap,
|
||||||
max_len: max_transactions,
|
max_len: max_transactions.saturating_sub(engine_txs.len()),
|
||||||
ordering: miner::PendingOrdering::Priority,
|
ordering: miner::PendingOrdering::Priority,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -504,12 +523,11 @@ impl Miner {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let block_start = Instant::now();
|
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 start = Instant::now();
|
||||||
|
|
||||||
let transaction = tx.signed().clone();
|
|
||||||
let hash = transaction.hash();
|
let hash = transaction.hash();
|
||||||
let sender = transaction.sender();
|
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
|
/// Generates dummy client (not test client) with corresponding amount of blocks
|
||||||
pub fn generate_dummy_client(block_number: u32) -> Arc<Client> {
|
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
|
/// 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> {
|
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
|
/// 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 {
|
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
|
/// 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
|
F: Fn() -> Spec
|
||||||
{
|
{
|
||||||
let test_spec = test_spec();
|
let test_spec = test_spec();
|
||||||
let client_db = new_db();
|
let client_db = new_db();
|
||||||
|
|
||||||
|
let miner = Miner::new_for_tests_force_sealing(&test_spec, None, force_sealing);
|
||||||
|
|
||||||
let client = Client::new(
|
let client = Client::new(
|
||||||
ClientConfig::default(),
|
ClientConfig::default(),
|
||||||
&test_spec,
|
&test_spec,
|
||||||
client_db,
|
client_db,
|
||||||
Arc::new(Miner::new_for_tests(&test_spec, None)),
|
Arc::new(miner),
|
||||||
IoChannel::disconnected(),
|
IoChannel::disconnected(),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let test_engine = &*test_spec.engine;
|
let test_engine = &*test_spec.engine;
|
||||||
|
@ -72,7 +72,7 @@ use client::{
|
|||||||
use client_traits::{
|
use client_traits::{
|
||||||
BlockInfo, Nonce, Balance, ChainInfo, TransactionInfo, BlockChainClient, ImportBlock,
|
BlockInfo, Nonce, Balance, ChainInfo, TransactionInfo, BlockChainClient, ImportBlock,
|
||||||
AccountData, BlockChain, IoClient, BadBlocks, ScheduleInfo, StateClient, ProvingBlockChainClient,
|
AccountData, BlockChain, IoClient, BadBlocks, ScheduleInfo, StateClient, ProvingBlockChainClient,
|
||||||
StateOrBlock, ForceUpdateSealing
|
StateOrBlock, ForceUpdateSealing, TransactionRequest
|
||||||
};
|
};
|
||||||
use engine::Engine;
|
use engine::Engine;
|
||||||
use machine::executed::Executed;
|
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 {
|
let transaction = Transaction {
|
||||||
nonce: self.latest_nonce(&self.miner.authoring_params().author),
|
nonce: nonce.unwrap_or_else(|| self.latest_nonce(&self.miner.authoring_params().author)),
|
||||||
action: Action::Call(address),
|
action,
|
||||||
gas: self.spec.gas_limit,
|
gas: gas.unwrap_or(self.spec.gas_limit),
|
||||||
gas_price: U256::zero(),
|
gas_price: gas_price.unwrap_or_else(U256::zero),
|
||||||
value: U256::default(),
|
value: U256::default(),
|
||||||
data: data,
|
data: data,
|
||||||
};
|
};
|
||||||
let chain_id = Some(self.spec.chain_id());
|
let chain_id = Some(self.spec.chain_id());
|
||||||
let sig = self.spec.engine.sign(transaction.hash(chain_id)).unwrap();
|
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())
|
self.miner.import_own_transaction(self, signed.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,10 @@ pub enum EngineError {
|
|||||||
InsufficientProof(String),
|
InsufficientProof(String),
|
||||||
/// Failed system call.
|
/// Failed system call.
|
||||||
FailedSystemCall(String),
|
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.
|
/// Malformed consensus message.
|
||||||
MalformedMessage(String),
|
MalformedMessage(String),
|
||||||
/// Requires client ref, but none registered.
|
/// 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),
|
BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob),
|
||||||
InsufficientProof(ref msg) => format!("Insufficient validation proof: {}", msg),
|
InsufficientProof(ref msg) => format!("Insufficient validation proof: {}", msg),
|
||||||
FailedSystemCall(ref msg) => format!("Failed to make system call: {}", 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),
|
MalformedMessage(ref msg) => format!("Received malformed consensus message: {}", msg),
|
||||||
RequiresClient => format!("Call requires client but none registered"),
|
RequiresClient => format!("Call requires client but none registered"),
|
||||||
RequiresSigner => format!("Call requires signer 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>,
|
pub strict_empty_steps_transition: Option<Uint>,
|
||||||
/// First block for which a 2/3 quorum (instead of 1/2) is required.
|
/// First block for which a 2/3 quorum (instead of 1/2) is required.
|
||||||
pub two_thirds_majority_transition: Option<Uint>,
|
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.
|
/// Authority engine deserialization.
|
||||||
@ -124,7 +126,11 @@ mod tests {
|
|||||||
"validateStepTransition": 150,
|
"validateStepTransition": 150,
|
||||||
"blockReward": 5000000,
|
"blockReward": 5000000,
|
||||||
"maximumUncleCountTransition": 10000000,
|
"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.immediate_transitions, None);
|
||||||
assert_eq!(deserialized.params.maximum_uncle_count_transition, Some(Uint(10_000_000.into())));
|
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.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 accounts::AccountProvider;
|
||||||
use ethkey::Password;
|
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.
|
/// An implementation of EngineSigner using internal account management.
|
||||||
pub struct EngineSigner {
|
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 {
|
fn address(&self) -> 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