Basic Authority (#991)

* Firt commit.

* First non-functional but correct implementation of BasicAuthority.

Still needs:
- Sealing infrastructure.

* Punch a hole to give miner access to key store.

* Fix test built.

* Basic version of synchronous mining.

This will seal a block whenever a new transaction comes through.
To be made better we need a timer which will wait for one second after the
last block before sealing a new one - better still would be to cooperatively
interleave blocks with other sealing nodes.

* Add tests.

* Fix minor issues from repotting.

* Address grumbles.
This commit is contained in:
Gav Wood 2016-05-03 17:23:53 +02:00
parent 1583f7d434
commit ac73b2628a
34 changed files with 506 additions and 88 deletions

View File

@ -3,19 +3,18 @@
"engine": {
"Ethash": {
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit": "0x118c30"
}
}
},
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0x118c30",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x1"

View File

@ -3,19 +3,18 @@
"engine": {
"Ethash": {
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit": "0x118c30"
}
}
},
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0x118c30",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x1"

View File

@ -3,19 +3,18 @@
"engine": {
"Ethash": {
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit": "0xffffffffffffffff"
}
}
},
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0xffffffffffffffff",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x1"

View File

@ -3,19 +3,18 @@
"engine": {
"Ethash": {
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit": 0
}
}
},
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": 0,
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x1"

View File

@ -3,19 +3,18 @@
"engine": {
"Ethash": {
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar": ""
"registrar": "",
"frontierCompatibilityModeLimit": "0x789b0"
}
}
},
"params": {
"accountStartNonce": "0x0100000",
"frontierCompatibilityModeLimit": "0x789b0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2"

View File

@ -3,19 +3,18 @@
"engine": {
"Ethash": {
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x08",
"blockReward": "0x14D1120D7B160000",
"registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050"
"registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050",
"frontierCompatibilityModeLimit": "0xffffffffffffffff"
}
}
},
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0xffffffffffffffff",
"maximumExtraDataSize": "0x0400",
"minGasLimit": "125000",
"networkID" : "0x0"

View File

@ -5,7 +5,6 @@
},
"params": {
"accountStartNonce": "0x0100000",
"frontierCompatibilityModeLimit": "0x789b0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2"

View File

@ -1,20 +1,25 @@
{
"name": "Morden",
"name": "TestAuthority",
"engine": {
"Null": null
"BasicAuthority": {
"params": {
"gasLimitBoundDivisor": "0x0400",
"durationLimit": "0x0d",
"authorities" : ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"]
}
}
},
"params": {
"accountStartNonce": "0x0100000",
"frontierCompatibilityModeLimit": "0x0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2"
"networkID" : "0x69"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x00006d6f7264656e",
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578"
"generic": {
"fields": 1,
"rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"
}
},
"difficulty": "0x20000",
@ -29,6 +34,6 @@
"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 } } } },
"102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" }
"9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" }
}
}

View File

@ -0,0 +1,297 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity 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 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. If not, see <http://www.gnu.org/licenses/>.
//! A blockchain engine that supports a basic, non-BFT proof-of-authority.
use common::*;
use util::keys::store::AccountProvider;
use block::*;
use spec::{CommonParams, Spec};
use engine::*;
use evm::{Schedule, Factory};
use ethjson;
/// BasicAuthority params.
#[derive(Debug, PartialEq)]
pub struct BasicAuthorityParams {
/// Gas limit divisor.
pub gas_limit_bound_divisor: U256,
/// Block duration.
pub duration_limit: u64,
/// Valid signatories.
pub authorities: HashSet<Address>,
}
impl From<ethjson::spec::BasicAuthorityParams> for BasicAuthorityParams {
fn from(p: ethjson::spec::BasicAuthorityParams) -> Self {
BasicAuthorityParams {
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
duration_limit: p.duration_limit.into(),
authorities: p.authorities.into_iter().map(Into::into).collect::<HashSet<_>>(),
}
}
}
/// Engine using BasicAuthority proof-of-work consensus algorithm, suitable for Ethereum
/// mainnet chains in the Olympic, Frontier and Homestead eras.
pub struct BasicAuthority {
params: CommonParams,
our_params: BasicAuthorityParams,
builtins: BTreeMap<Address, Builtin>,
factory: Factory,
}
impl BasicAuthority {
/// Create a new instance of BasicAuthority engine
pub fn new(params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap<Address, Builtin>) -> Self {
BasicAuthority {
params: params,
our_params: our_params,
builtins: builtins,
factory: Factory::default(),
}
}
}
impl Engine for BasicAuthority {
fn name(&self) -> &str { "BasicAuthority" }
fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) }
// One field - the signature
fn seal_fields(&self) -> usize { 1 }
fn params(&self) -> &CommonParams { &self.params }
fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins }
/// Additional engine-specific information for the user/developer concerning `header`.
fn extra_info(&self, _header: &Header) -> HashMap<String, String> { hash_map!["signature".to_owned() => "TODO".to_owned()] }
fn vm_factory(&self) -> &Factory { &self.factory }
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
Schedule::new_homestead()
}
fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256) {
header.difficulty = parent.difficulty;
header.gas_limit = {
let gas_limit = parent.gas_limit;
let bound_divisor = self.our_params.gas_limit_bound_divisor;
if gas_limit < gas_floor_target {
min(gas_floor_target, gas_limit + gas_limit / bound_divisor - x!(1))
} else {
max(gas_floor_target, gas_limit - gas_limit / bound_divisor + x!(1))
}
};
header.note_dirty();
// info!("ethash: populate_from_parent #{}: difficulty={} and gas_limit={}", header.number, header.difficulty, header.gas_limit);
}
/// Apply the block reward on finalisation of the block.
/// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current).
fn on_close_block(&self, _block: &mut ExecutedBlock) {}
/// Attempt to seal the block internally.
///
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
/// be returned.
fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> {
if let Some(ap) = accounts {
// check to see if author is contained in self.our_params.authorities
if self.our_params.authorities.contains(block.header().author()) {
if let Ok(secret) = ap.account_secret(block.header().author()) {
return Some(block.header().author_seal(&secret));
} else {
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
}
} else {
trace!(target: "basicauthority", "generate_seal: FAIL: block author {} isn't one of the authorized accounts {:?}", block.header().author(), self.our_params.authorities);
}
} else {
trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided");
}
None
}
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
// check the seal fields.
// TODO: pull this out into common code.
if header.seal.len() != self.seal_fields() {
return Err(From::from(BlockError::InvalidSealArity(
Mismatch { expected: self.seal_fields(), found: header.seal.len() }
)));
}
Ok(())
}
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
// check the signature is legit.
let sig = try!(UntrustedRlp::new(&header.seal[0]).as_val::<H520>());
let signer = Address::from(try!(ec::recover(&sig, &header.bare_hash())).sha3());
if !self.our_params.authorities.contains(&signer) {
return try!(Err(BlockError::InvalidSeal));
}
Ok(())
}
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
// we should not calculate difficulty for genesis blocks
if header.number() == 0 {
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
}
// Check difficulty is correct given the two timestamps.
if header.difficulty() != parent.difficulty() {
return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: *parent.difficulty(), found: *header.difficulty() })))
}
let gas_limit_divisor = self.our_params.gas_limit_bound_divisor;
let min_gas = parent.gas_limit - parent.gas_limit / gas_limit_divisor;
let max_gas = parent.gas_limit + parent.gas_limit / gas_limit_divisor;
if header.gas_limit <= min_gas || header.gas_limit >= max_gas {
return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit })));
}
Ok(())
}
fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> result::Result<(), Error> {
try!(t.check_low_s());
Ok(())
}
fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> {
t.sender().map(|_|()) // Perform EC recovery and cache sender
}
}
impl Header {
/// Get the none field of the header.
pub fn signature(&self) -> H520 {
decode(&self.seal()[0])
}
/// Generate a seal for the block with the given `secret`.
pub fn author_seal(&self, secret: &Secret) -> Vec<Bytes> {
vec![encode(&ec::sign(secret, &self.bare_hash()).unwrap_or(Signature::new())).to_vec()]
}
/// Set the nonce and mix hash fields of the header.
pub fn sign(&mut self, secret: &Secret) {
self.seal = self.author_seal(secret);
}
}
/// Create a new test chain spec with BasicAuthority consensus engine.
pub fn new_test_authority() -> Spec { Spec::load(include_bytes!("../res/test_authority.json")) }
#[cfg(test)]
mod tests {
use super::*;
use common::*;
use block::*;
use engine::*;
use tests::helpers::*;
use util::keys::{TestAccountProvider, TestAccount};
#[test]
fn has_valid_metadata() {
let engine = new_test_authority().engine;
assert!(!engine.name().is_empty());
assert!(engine.version().major >= 1);
}
#[test]
fn can_return_factory() {
let engine = new_test_authority().engine;
engine.vm_factory();
}
#[test]
fn can_return_schedule() {
let engine = new_test_authority().engine;
let schedule = engine.schedule(&EnvInfo {
number: 10000000,
author: x!(0),
timestamp: 0,
difficulty: x!(0),
last_hashes: vec![],
gas_used: x!(0),
gas_limit: x!(0)
});
assert!(schedule.stack_limit > 0);
}
#[test]
fn can_do_seal_verification_fail() {
let engine = new_test_authority().engine;
let header: Header = Header::default();
let verify_result = engine.verify_block_basic(&header, None);
match verify_result {
Err(Error::Block(BlockError::InvalidSealArity(_))) => {},
Err(_) => { panic!("should be block seal-arity mismatch error (got {:?})", verify_result); },
_ => { panic!("Should be error, got Ok"); },
}
}
#[test]
fn can_do_signature_verification_fail() {
let engine = new_test_authority().engine;
let mut header: Header = Header::default();
header.set_seal(vec![rlp::encode(&Signature::zero()).to_vec()]);
let verify_result = engine.verify_block_unordered(&header, None);
match verify_result {
Err(Error::Util(UtilError::Crypto(CryptoError::InvalidSignature))) => {},
Err(_) => { panic!("should be block difficulty error (got {:?})", verify_result); },
_ => { panic!("Should be error, got Ok"); },
}
}
#[test]
fn can_do_signature_verification() {
let secret = "".sha3();
let addr = KeyPair::from_secret("".sha3()).unwrap().address();
let engine = new_test_authority().engine;
let mut header: Header = Header::default();
header.set_author(addr);
header.sign(&secret);
assert!(engine.verify_block_unordered(&header, None).is_ok());
}
#[test]
fn can_generate_seal() {
let addr = KeyPair::from_secret("".sha3()).unwrap().address();
let accounts = hash_map![addr => TestAccount{unlocked: true, password: Default::default(), secret: "".sha3()}];
let tap = TestAccountProvider::new(accounts);
let spec = new_test_authority();
let engine = &spec.engine;
let genesis_header = spec.genesis_header();
let mut db_result = get_temp_journal_db();
let mut db = db_result.take();
spec.ensure_db_good(db.as_hashdb_mut());
let last_hashes = vec![genesis_header.hash()];
let b = OpenBlock::new(engine.deref(), false, db, &genesis_header, last_hashes, addr.clone(), x!(3141562), vec![]);
let b = b.close_and_lock();
let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap();
assert!(b.try_seal(engine.deref(), seal).is_ok());
}
}

View File

@ -89,7 +89,7 @@ pub struct ExecutedBlock {
/// A set of references to `ExecutedBlock` fields that are publicly accessible.
pub struct BlockRefMut<'a> {
/// Block header.
pub header: &'a Header,
pub header: &'a mut Header,
/// Block transactions.
pub transactions: &'a Vec<SignedTransaction>,
/// Block uncles.
@ -133,7 +133,7 @@ impl ExecutedBlock {
/// Get a structure containing individual references to all public fields.
pub fn fields_mut(&mut self) -> BlockRefMut {
BlockRefMut {
header: &self.base.header,
header: &mut self.base.header,
transactions: &self.base.transactions,
uncles: &self.base.uncles,
state: &mut self.state,

View File

@ -42,8 +42,7 @@ use env_info::EnvInfo;
use executive::{Executive, Executed, TransactOptions, contract_address};
use receipt::LocalizedReceipt;
pub use blockchain::CacheSize as BlockChainCacheSize;
use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase, Filter as
TracedbFilter};
use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase, Filter as TracedbFilter};
use trace;
/// General block status

View File

@ -34,9 +34,10 @@ use std::collections::HashSet;
use util::bytes::Bytes;
use util::hash::{Address, H256, H2048};
use util::numbers::U256;
use util::keys::store::AccountProvider;
use blockchain::TreeRoute;
use block_queue::BlockQueueInfo;
use block::{ClosedBlock, LockedBlock, SealedBlock};
use block::{ExecutedBlock, ClosedBlock, LockedBlock, SealedBlock};
use header::{BlockNumber, Header};
use transaction::{LocalizedTransaction, SignedTransaction};
use log_entry::LocalizedLogEntry;
@ -134,6 +135,9 @@ pub trait BlockChainClient : Sync + Send {
/// Makes a non-persistent transaction call.
fn call(&self, t: &SignedTransaction) -> Result<Executed, Error>;
/// Attempt to seal the block internally. See `Engine`.
fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { self.engine().generate_seal(block, accounts) }
/// Executes a function providing it with a reference to an engine.
fn engine(&self) -> &Engine;

View File

@ -15,10 +15,10 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use common::*;
use util::keys::store::AccountProvider;
use block::ExecutedBlock;
use spec::CommonParams;
use evm::Schedule;
use evm::Factory;
use evm::{Schedule, Factory};
/// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based.
/// Provides hooks into each of the major parts of block import.
@ -61,6 +61,14 @@ pub trait Engine : Sync + Send {
/// Block transformation functions, after the transactions.
fn on_close_block(&self, _block: &mut ExecutedBlock) {}
/// Attempt to seal the block internally.
///
/// If `Some` is returned, then you get a valid seal.
///
/// This operation is synchronous and may (quite reasonably) not be available, in which None will
/// be returned.
fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { None }
/// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block)
/// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import.
fn verify_block_basic(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) }

View File

@ -137,6 +137,8 @@ pub enum BlockError {
MismatchedH256SealElement(Mismatch<H256>),
/// Proof-of-work aspect of seal, which we assume is a 256-bit value, is invalid.
InvalidProofOfWork(OutOfBounds<U256>),
/// Some low-level aspect of the seal in incorrect.
InvalidSeal,
/// Gas limit header field is invalid.
InvalidGasLimit(OutOfBounds<U256>),
/// Receipts trie root header field is invalid.

View File

@ -27,8 +27,6 @@ use ethjson;
/// Ethash params.
#[derive(Debug, PartialEq)]
pub struct EthashParams {
/// Tie breaking gas.
pub tie_breaking_gas: bool,
/// Gas limit divisor.
pub gas_limit_bound_divisor: U256,
/// Minimum difficulty.
@ -41,18 +39,20 @@ pub struct EthashParams {
pub block_reward: U256,
/// Namereg contract address.
pub registrar: Address,
/// Homestead transition block number.
pub frontier_compatibility_mode_limit: u64,
}
impl From<ethjson::spec::EthashParams> for EthashParams {
fn from(p: ethjson::spec::EthashParams) -> Self {
EthashParams {
tie_breaking_gas: p.tie_breaking_gas,
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
minimum_difficulty: p.minimum_difficulty.into(),
difficulty_bound_divisor: p.difficulty_bound_divisor.into(),
duration_limit: p.duration_limit.into(),
block_reward: p.block_reward.into(),
registrar: p.registrar.into(),
frontier_compatibility_mode_limit: p.frontier_compatibility_mode_limit.into(),
}
}
}
@ -100,9 +100,9 @@ impl Engine for Ethash {
}
fn schedule(&self, env_info: &EnvInfo) -> Schedule {
trace!(target: "client", "Creating schedule. fCML={}", self.params.frontier_compatibility_mode_limit);
trace!(target: "client", "Creating schedule. fCML={}", self.ethash_params.frontier_compatibility_mode_limit);
if env_info.number < self.params.frontier_compatibility_mode_limit {
if env_info.number < self.ethash_params.frontier_compatibility_mode_limit {
Schedule::new_frontier()
} else {
Schedule::new_homestead()
@ -207,7 +207,7 @@ impl Engine for Ethash {
}
fn verify_transaction_basic(&self, t: &SignedTransaction, header: &Header) -> result::Result<(), Error> {
if header.number() >= self.params.frontier_compatibility_mode_limit {
if header.number() >= self.ethash_params.frontier_compatibility_mode_limit {
try!(t.check_low_s());
}
Ok(())
@ -229,7 +229,7 @@ impl Ethash {
let min_difficulty = self.ethash_params.minimum_difficulty;
let difficulty_bound_divisor = self.ethash_params.difficulty_bound_divisor;
let duration_limit = self.ethash_params.duration_limit;
let frontier_limit = self.params.frontier_compatibility_mode_limit;
let frontier_limit = self.ethash_params.frontier_compatibility_mode_limit;
let mut target = if header.number < frontier_limit {
if header.timestamp >= parent.timestamp + duration_limit {

View File

@ -89,6 +89,7 @@ extern crate bloomchain;
#[cfg(test)] extern crate ethcore_devtools as devtools;
#[cfg(feature = "jit" )] extern crate evmjit;
pub mod basic_authority;
pub mod block;
pub mod block_queue;
pub mod client;

View File

@ -57,11 +57,7 @@ impl Engine for NullEngine {
&self.builtins
}
fn schedule(&self, env_info: &EnvInfo) -> Schedule {
if env_info.number < self.params.frontier_compatibility_mode_limit {
Schedule::new_frontier()
} else {
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
Schedule::new_homestead()
}
}
}

View File

@ -24,6 +24,7 @@ use account_db::*;
use super::genesis::Genesis;
use super::seal::Generic as GenericSeal;
use ethereum;
use basic_authority::BasicAuthority;
use ethjson;
/// Parameters common to all engines.
@ -31,8 +32,6 @@ use ethjson;
pub struct CommonParams {
/// Account start nonce.
pub account_start_nonce: U256,
/// Frontier compatibility mode limit.
pub frontier_compatibility_mode_limit: u64,
/// Maximum size of extra data.
pub maximum_extra_data_size: usize,
/// Network id.
@ -45,7 +44,6 @@ impl From<ethjson::spec::Params> for CommonParams {
fn from(p: ethjson::spec::Params) -> Self {
CommonParams {
account_start_nonce: p.account_start_nonce.into(),
frontier_compatibility_mode_limit: p.frontier_compatibility_mode_limit.into(),
maximum_extra_data_size: p.maximum_extra_data_size.into(),
network_id: p.network_id.into(),
min_gas_limit: p.min_gas_limit.into(),
@ -131,7 +129,8 @@ impl Spec {
fn engine(engine_spec: ethjson::spec::Engine, params: CommonParams, builtins: BTreeMap<Address, Builtin>) -> Box<Engine> {
match engine_spec {
ethjson::spec::Engine::Null => Box::new(NullEngine::new(params, builtins)),
ethjson::spec::Engine::Ethash(ethash) => Box::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins))
ethjson::spec::Engine::Ethash(ethash) => Box::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)),
ethjson::spec::Engine::BasicAuthority(basic_authority) => Box::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)),
}
}
@ -241,15 +240,10 @@ impl Spec {
From::from(ethjson::spec::Spec::load(reader).expect("invalid json file"))
}
/// Create a new Spec which conforms to the Morden chain except that it's a NullEngine consensus.
/// Create a new Spec which conforms to the Frontier-era Morden chain except that it's a NullEngine consensus.
pub fn new_test() -> Spec {
Spec::load(include_bytes!("../../res/null_morden.json"))
}
/// Create a new Spec which conforms to the Morden chain except that it's a NullEngine consensus.
pub fn new_homestead_test() -> Spec {
Spec::load(include_bytes!("../../res/null_homestead_morden.json"))
}
}
#[cfg(test)]

View File

@ -0,0 +1,59 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity 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 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. If not, see <http://www.gnu.org/licenses/>.
//! Ethash params deserialization.
use uint::Uint;
use hash::Address;
/// Ethash params deserialization.
#[derive(Debug, PartialEq, Deserialize)]
pub struct BasicAuthorityParams {
/// Gas limit divisor.
#[serde(rename="gasLimitBoundDivisor")]
pub gas_limit_bound_divisor: Uint,
/// Block duration.
#[serde(rename="durationLimit")]
pub duration_limit: Uint,
/// Valid authorities
pub authorities: Vec<Address>,
}
/// Ethash engine deserialization.
#[derive(Debug, PartialEq, Deserialize)]
pub struct BasicAuthority {
/// Ethash params.
pub params: BasicAuthorityParams,
}
#[cfg(test)]
mod tests {
use serde_json;
use spec::basic_authority::BasicAuthority;
#[test]
fn basic_authority_deserialization() {
let s = r#"{
"params": {
"gasLimitBoundDivisor": "0x0400",
"durationLimit": "0x0d",
"authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"]
}
}"#;
let _deserialized: BasicAuthority = serde_json::from_str(s).unwrap();
}
}

View File

@ -19,6 +19,7 @@
use serde::Deserializer;
use serde::de::Visitor;
use spec::Ethash;
use spec::BasicAuthority;
/// Engine deserialization.
#[derive(Debug, PartialEq, Deserialize)]
@ -27,6 +28,8 @@ pub enum Engine {
Null,
/// Ethash engine.
Ethash(Ethash),
/// BasicAuthority engine.
BasicAuthority(BasicAuthority),
}
#[cfg(test)]
@ -46,13 +49,13 @@ mod tests {
let s = r#"{
"Ethash": {
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit" : "0x"
}
}
}"#;

View File

@ -22,9 +22,6 @@ use hash::Address;
/// Ethash params deserialization.
#[derive(Debug, PartialEq, Deserialize)]
pub struct EthashParams {
/// Tie breaking gas.
#[serde(rename="tieBreakingGas")]
pub tie_breaking_gas: bool,
/// Gas limit divisor.
#[serde(rename="gasLimitBoundDivisor")]
pub gas_limit_bound_divisor: Uint,
@ -42,6 +39,9 @@ pub struct EthashParams {
pub block_reward: Uint,
/// Namereg contract address.
pub registrar: Address,
/// Homestead transition block number.
#[serde(rename="frontierCompatibilityModeLimit")]
pub frontier_compatibility_mode_limit: Uint,
}
/// Ethash engine deserialization.
@ -60,13 +60,13 @@ mod tests {
fn ethash_deserialization() {
let s = r#"{
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit" : "0x42"
}
}"#;

View File

@ -25,6 +25,7 @@ pub mod seal;
pub mod engine;
pub mod state;
pub mod ethash;
pub mod basic_authority;
pub use self::account::Account;
pub use self::builtin::{Builtin, Pricing, Linear};
@ -35,3 +36,4 @@ pub use self::seal::{Seal, Ethereum, Generic};
pub use self::engine::Engine;
pub use self::state::State;
pub use self::ethash::{Ethash, EthashParams};
pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams};

View File

@ -24,9 +24,6 @@ pub struct Params {
/// Account start nonce.
#[serde(rename="accountStartNonce")]
pub account_start_nonce: Uint,
/// Homestead transition block number.
#[serde(rename="frontierCompatibilityModeLimit")]
pub frontier_compatibility_mode_limit: Uint,
/// Maximum size of extra data.
#[serde(rename="maximumExtraDataSize")]
pub maximum_extra_data_size: Uint,

View File

@ -57,13 +57,13 @@ mod tests {
"engine": {
"Ethash": {
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"frontierCompatibilityModeLimit" : "0x"
}
}
},

View File

@ -18,6 +18,7 @@ use rayon::prelude::*;
use std::sync::atomic::AtomicBool;
use util::*;
use util::keys::store::{AccountService, AccountProvider};
use ethcore::views::{BlockView, HeaderView};
use ethcore::client::{BlockChainClient, BlockId};
use ethcore::block::{ClosedBlock, IsBlock};
@ -38,6 +39,8 @@ pub struct Miner {
gas_floor_target: RwLock<U256>,
author: RwLock<Address>,
extra_data: RwLock<Bytes>,
accounts: RwLock<Option<Arc<AccountService>>>, // TODO: this is horrible since AccountService already contains a single RwLock field. refactor.
}
impl Default for Miner {
@ -51,6 +54,7 @@ impl Default for Miner {
gas_floor_target: RwLock::new(U256::zero()),
author: RwLock::new(Address::default()),
extra_data: RwLock::new(Vec::new()),
accounts: RwLock::new(None),
}
}
}
@ -67,6 +71,22 @@ impl Miner {
gas_floor_target: RwLock::new(U256::zero()),
author: RwLock::new(Address::default()),
extra_data: RwLock::new(Vec::new()),
accounts: RwLock::new(None),
})
}
/// Creates new instance of miner
pub fn with_accounts(force_sealing: bool, accounts: Arc<AccountService>) -> Arc<Miner> {
Arc::new(Miner {
transaction_queue: Mutex::new(TransactionQueue::new()),
force_sealing: force_sealing,
sealing_enabled: AtomicBool::new(force_sealing),
sealing_block_last_request: Mutex::new(0),
sealing_work: Mutex::new(UsingQueue::new(5)),
gas_floor_target: RwLock::new(U256::zero()),
author: RwLock::new(Address::default()),
extra_data: RwLock::new(Vec::new()),
accounts: RwLock::new(Some(accounts)),
})
}
@ -142,6 +162,30 @@ impl Miner {
queue.remove_invalid(&hash, &fetch_account);
}
if let Some(block) = b {
if !block.transactions().is_empty() {
trace!(target: "miner", "prepare_sealing: block has transaction - attempting internal seal.");
// block with transactions - see if we can seal immediately.
let a = self.accounts.read().unwrap();
let s = chain.generate_seal(block.block(), match a.deref() {
&Some(ref x) => Some(x.deref() as &AccountProvider),
&None => None,
});
if let Some(seal) = s {
trace!(target: "miner", "prepare_sealing: managed internal seal. importing...");
if let Ok(sealed) = chain.try_seal(block.lock(), seal) {
if let Ok(_) = chain.import_block(sealed.rlp_bytes()) {
trace!(target: "miner", "prepare_sealing: sealed internally and imported. leaving.");
} else {
warn!("prepare_sealing: ERROR: could not import internally sealed block. WTF?");
}
} else {
warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal. WTF?");
}
return;
} else {
trace!(target: "miner", "prepare_sealing: unable to generate seal internally");
}
}
if sealing_work.peek_last_ref().map_or(true, |pb| pb.block().fields().header.hash() != block.block().fields().header.hash()) {
trace!(target: "miner", "Pushing a new, refreshed or borrowed pending {}...", block.block().fields().header.hash());
sealing_work.push(block);

View File

@ -146,7 +146,7 @@ fn execute_client(conf: Configuration) {
let client = service.client();
// Miner
let miner = Miner::new(conf.args.flag_force_sealing);
let miner = Miner::with_accounts(conf.args.flag_force_sealing, account_service.clone());
miner.set_author(conf.author());
miner.set_gas_floor_target(conf.gas_floor_target());
miner.set_extra_data(conf.extra_data());

View File

@ -20,13 +20,14 @@ use std::sync::{Arc, RwLock};
use jsonrpc_core::IoHandler;
use util::hash::{Address, H256, FixedHash};
use util::numbers::{Uint, U256};
use util::keys::{TestAccount, TestAccountProvider};
use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionId};
use ethcore::log_entry::{LocalizedLogEntry, LogEntry};
use ethcore::receipt::LocalizedReceipt;
use ethcore::transaction::{Transaction, Action};
use ethminer::ExternalMiner;
use v1::{Eth, EthClient};
use v1::tests::helpers::{TestAccount, TestAccountProvider, TestSyncProvider, Config, TestMinerService};
use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService};
fn blockchain_client() -> Arc<TestBlockChainClient> {
let client = TestBlockChainClient::new();

View File

@ -16,10 +16,8 @@
//! Test rpc services.
mod account_provider;
mod sync_provider;
mod miner_service;
pub use self::account_provider::{TestAccount, TestAccountProvider};
pub use self::sync_provider::{Config, TestSyncProvider};
pub use self::miner_service::{TestMinerService};

View File

@ -16,9 +16,9 @@
use std::sync::Arc;
use jsonrpc_core::IoHandler;
use v1::tests::helpers::{TestAccount, TestAccountProvider};
use v1::{PersonalClient, Personal};
use util::numbers::*;
use util::keys::{TestAccount, TestAccountProvider};
use v1::{PersonalClient, Personal};
use std::collections::*;
fn accounts_provider() -> Arc<TestAccountProvider> {
@ -49,7 +49,6 @@ fn accounts() {
assert_eq!(io.handle_request(request), Some(response.to_owned()));
}
#[test]
fn new_account() {
let (test_provider, io) = setup();

View File

@ -1484,7 +1484,7 @@ mod tests {
}
fn dummy_sync_with_peer(peer_latest_hash: H256) -> ChainSync {
let mut sync = ChainSync::new(SyncConfig::default(), Miner::new(false));
let mut sync = ChainSync::new(SyncConfig::default(), Arc::new(Miner::default()));
sync.peers.insert(0,
PeerInfo {
protocol_version: 0,

View File

@ -24,6 +24,20 @@ pub use vector::*;
pub use numbers::*;
pub use sha3::*;
#[macro_export]
macro_rules! hash_map {
( $( $x:expr => $y:expr ),* ) => {
vec![ $( ($x, $y) ),* ].into_iter().collect::<HashMap<_, _>>()
}
}
#[macro_export]
macro_rules! hash_mapx {
( $( $x:expr => $y:expr ),* ) => {
vec![ $( ( From::from($x), From::from($y) ) ),* ].into_iter().collect::<HashMap<_, _>>()
}
}
#[macro_export]
macro_rules! map {
( $( $x:expr => $y:expr ),* ) => {

View File

@ -19,3 +19,6 @@
pub mod directory;
pub mod store;
mod geth_import;
mod test_account_provider;
pub use self::test_account_provider::{TestAccount, TestAccountProvider};

View File

@ -31,6 +31,8 @@ const KEY_LENGTH_AES: u32 = KEY_LENGTH/2;
const KEY_LENGTH_USIZE: usize = KEY_LENGTH as usize;
const KEY_LENGTH_AES_USIZE: usize = KEY_LENGTH_AES as usize;
// TODO: this file needs repotting into several separate files.
/// Encrypted hash-map, each request should contain password
pub trait EncryptedHashMap<Key: Hash + Eq> {
/// Returns existing value for the key, if any
@ -87,10 +89,12 @@ pub trait AccountProvider : Send + Sync {
fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError>;
/// Creates account
fn new_account(&self, pass: &str) -> Result<Address, ::std::io::Error>;
/// Returns secret for unlocked account
/// Returns secret for unlocked `account`.
fn account_secret(&self, account: &Address) -> Result<crypto::Secret, SigningError>;
/// Returns secret for unlocked account
fn sign(&self, account: &Address, message: &H256) -> Result<crypto::Signature, SigningError>;
/// Returns signature when unlocked `account` signs `message`.
fn sign(&self, account: &Address, message: &H256) -> Result<crypto::Signature, SigningError> {
self.account_secret(account).and_then(|s| crypto::ec::sign(&s, message).map_err(|_| SigningError::InvalidSecret))
}
}
/// Thread-safe accounts management

View File

@ -19,9 +19,9 @@
use std::sync::RwLock;
use std::collections::HashMap;
use std::io;
use util::hash::{Address, H256, FixedHash};
use util::crypto::{Secret, Signature, KeyPair};
use util::keys::store::{AccountProvider, SigningError, EncryptedHashMapError};
use hash::{Address, FixedHash};
use crypto::{Secret, KeyPair};
use super::store::{AccountProvider, SigningError, EncryptedHashMapError};
/// Account mock.
#[derive(Clone)]
@ -98,10 +98,5 @@ impl AccountProvider for TestAccountProvider {
.ok_or(SigningError::NoAccount)
.map(|acc| acc.secret.clone())
}
fn sign(&self, _account: &Address, _message: &H256) -> Result<Signature, SigningError> {
unimplemented!()
}
}