merge branch accountdb_migration into pv64
This commit is contained in:
@@ -32,6 +32,10 @@ bloomchain = "0.1"
|
||||
rayon = "0.3.1"
|
||||
ethstore = { path = "../ethstore" }
|
||||
|
||||
[dependencies.hyper]
|
||||
git = "https://github.com/ethcore/hyper"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
jit = ["evmjit"]
|
||||
evm-debug = []
|
||||
|
||||
@@ -108,6 +108,12 @@ impl Account {
|
||||
self.code_cache = code;
|
||||
}
|
||||
|
||||
/// Reset this account's code to the given code.
|
||||
pub fn reset_code(&mut self, code: Bytes) {
|
||||
self.code_hash = None;
|
||||
self.init_code(code);
|
||||
}
|
||||
|
||||
/// Set (and cache) the contents of the trie's storage at `key` to `value`.
|
||||
pub fn set_storage(&mut self, key: H256, value: H256) {
|
||||
self.storage_overlay.borrow_mut().insert(key, (Filth::Dirty, value));
|
||||
@@ -336,6 +342,21 @@ mod tests {
|
||||
assert_eq!(a.code_hash().hex(), "af231e631776a517ca23125370d542873eca1fb4d613ed9b5d5335a46ae5b7eb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_code() {
|
||||
let mut a = Account::new_contract(69.into(), 0.into());
|
||||
let mut db = MemoryDB::new();
|
||||
let mut db = AccountDBMut::new(&mut db, &Address::new());
|
||||
a.init_code(vec![0x55, 0x44, 0xffu8]);
|
||||
assert_eq!(a.code_hash(), SHA3_EMPTY);
|
||||
a.commit_code(&mut db);
|
||||
assert_eq!(a.code_hash().hex(), "af231e631776a517ca23125370d542873eca1fb4d613ed9b5d5335a46ae5b7eb");
|
||||
a.reset_code(vec![0x55]);
|
||||
assert_eq!(a.code_hash(), SHA3_EMPTY);
|
||||
a.commit_code(&mut db);
|
||||
assert_eq!(a.code_hash().hex(), "37bf2238b11b68cdc8382cece82651b59d3c3988873b6e0f33d79694aa45f1be");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rlpio() {
|
||||
let a = Account::new(U256::from(69u8), U256::from(0u8), HashMap::new(), Bytes::new());
|
||||
@@ -348,7 +369,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn new_account() {
|
||||
|
||||
let a = Account::new(U256::from(69u8), U256::from(0u8), HashMap::new(), Bytes::new());
|
||||
assert_eq!(a.rlp().to_hex(), "f8448045a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
|
||||
assert_eq!(a.balance(), &U256::from(69u8));
|
||||
@@ -359,7 +379,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn create_account() {
|
||||
|
||||
let a = Account::new(U256::from(69u8), U256::from(0u8), HashMap::new(), Bytes::new());
|
||||
assert_eq!(a.rlp().to_hex(), "f8448045a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@ use util::*;
|
||||
|
||||
static NULL_RLP_STATIC: [u8; 1] = [0x80; 1];
|
||||
|
||||
#[inline]
|
||||
fn combine_key<'a>(address_hash: &'a H256, key: &'a H256) -> H256 {
|
||||
address_hash ^ key
|
||||
}
|
||||
|
||||
// TODO: introduce HashDBMut?
|
||||
/// DB backend wrapper for Account trie
|
||||
/// Transforms trie node keys for the database
|
||||
@@ -11,23 +16,17 @@ pub struct AccountDB<'db> {
|
||||
address_hash: H256,
|
||||
}
|
||||
|
||||
// used to ensure that account storage keys are unique in the database.
|
||||
#[inline]
|
||||
fn combine_key<'a>(address: &'a H256, key: &'a H256) -> H256 {
|
||||
address ^ key
|
||||
}
|
||||
|
||||
impl<'db> AccountDB<'db> {
|
||||
/// Create an AccountDB from an address.
|
||||
pub fn new(db: &'db HashDB, address: &Address) -> AccountDB<'db> {
|
||||
/// Create a new AccountDB from an address.
|
||||
pub fn new(db: &'db HashDB, address: &Address) -> Self {
|
||||
Self::from_hash(db, address.sha3())
|
||||
}
|
||||
|
||||
/// Create an AccountDB from an address' hash.
|
||||
pub fn from_hash(db: &'db HashDB, address_hash: H256) -> AccountDB<'db> {
|
||||
/// Create a new AcountDB from an address' hash.
|
||||
pub fn from_hash(db: &'db HashDB, address_hash: H256) -> Self {
|
||||
AccountDB {
|
||||
db: db,
|
||||
address_hash: address_hash
|
||||
address_hash: address_hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,13 +70,13 @@ pub struct AccountDBMut<'db> {
|
||||
}
|
||||
|
||||
impl<'db> AccountDBMut<'db> {
|
||||
/// Create an AccountDBMut from an address.
|
||||
pub fn new(db: &'db mut HashDB, address: &Address) -> AccountDBMut<'db> {
|
||||
/// Create a new AccountDB from an address.
|
||||
pub fn new(db: &'db mut HashDB, address: &Address) -> Self {
|
||||
Self::from_hash(db, address.sha3())
|
||||
}
|
||||
|
||||
/// Create an AccountDBMut from an address' hash.
|
||||
pub fn from_hash(db: &'db mut HashDB, address_hash: H256) -> AccountDBMut<'db> {
|
||||
/// Create a new AcountDB from an address' hash.
|
||||
pub fn from_hash(db: &'db mut HashDB, address_hash: H256) -> Self {
|
||||
AccountDBMut {
|
||||
db: db,
|
||||
address_hash: address_hash,
|
||||
|
||||
@@ -164,6 +164,12 @@ pub trait IsBlock {
|
||||
fn uncles(&self) -> &Vec<Header> { &self.block().base.uncles }
|
||||
}
|
||||
|
||||
/// Trait for a object that has a state database.
|
||||
pub trait Drain {
|
||||
/// Drop this object and return the underlieing database.
|
||||
fn drain(self) -> Box<JournalDB>;
|
||||
}
|
||||
|
||||
impl IsBlock for ExecutedBlock {
|
||||
fn block(&self) -> &ExecutedBlock { self }
|
||||
}
|
||||
@@ -436,9 +442,11 @@ impl LockedBlock {
|
||||
_ => Ok(SealedBlock { block: s.block, uncle_bytes: s.uncle_bytes }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drain for LockedBlock {
|
||||
/// Drop this object and return the underlieing database.
|
||||
pub fn drain(self) -> Box<JournalDB> { self.block.state.drop().1 }
|
||||
fn drain(self) -> Box<JournalDB> { self.block.state.drop().1 }
|
||||
}
|
||||
|
||||
impl SealedBlock {
|
||||
@@ -450,9 +458,11 @@ impl SealedBlock {
|
||||
block_rlp.append_raw(&self.uncle_bytes, 1);
|
||||
block_rlp.out()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drain for SealedBlock {
|
||||
/// Drop this object and return the underlieing database.
|
||||
pub fn drain(self) -> Box<JournalDB> { self.block.state.drop().1 }
|
||||
fn drain(self) -> Box<JournalDB> { self.block.state.drop().1 }
|
||||
}
|
||||
|
||||
impl IsBlock for SealedBlock {
|
||||
|
||||
@@ -250,7 +250,7 @@ impl Client {
|
||||
Ok(locked_block)
|
||||
}
|
||||
|
||||
fn calculate_enacted_retracted(&self, import_results: Vec<ImportRoute>) -> (Vec<H256>, Vec<H256>) {
|
||||
fn calculate_enacted_retracted(&self, import_results: &[ImportRoute]) -> (Vec<H256>, Vec<H256>) {
|
||||
fn map_to_vec(map: Vec<(H256, bool)>) -> Vec<H256> {
|
||||
map.into_iter().map(|(k, _v)| k).collect()
|
||||
}
|
||||
@@ -260,12 +260,12 @@ impl Client {
|
||||
// could be retracted in import `k+1`. This is why to understand if after all inserts
|
||||
// the block is enacted or retracted we iterate over all routes and at the end final state
|
||||
// will be in the hashmap
|
||||
let map = import_results.into_iter().fold(HashMap::new(), |mut map, route| {
|
||||
for hash in route.enacted {
|
||||
map.insert(hash, true);
|
||||
let map = import_results.iter().fold(HashMap::new(), |mut map, route| {
|
||||
for hash in &route.enacted {
|
||||
map.insert(hash.clone(), true);
|
||||
}
|
||||
for hash in route.retracted {
|
||||
map.insert(hash, false);
|
||||
for hash in &route.retracted {
|
||||
map.insert(hash.clone(), false);
|
||||
}
|
||||
map
|
||||
});
|
||||
@@ -302,36 +302,10 @@ impl Client {
|
||||
invalid_blocks.insert(header.hash());
|
||||
continue;
|
||||
}
|
||||
let closed_block = closed_block.unwrap();
|
||||
imported_blocks.push(header.hash());
|
||||
|
||||
// Are we committing an era?
|
||||
let ancient = if header.number() >= HISTORY {
|
||||
let n = header.number() - HISTORY;
|
||||
Some((n, self.chain.block_hash(n).unwrap()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Commit results
|
||||
let closed_block = closed_block.unwrap();
|
||||
let receipts = closed_block.block().receipts().clone();
|
||||
let traces = From::from(closed_block.block().traces().clone().unwrap_or_else(Vec::new));
|
||||
|
||||
closed_block.drain()
|
||||
.commit(header.number(), &header.hash(), ancient)
|
||||
.expect("State DB commit failed.");
|
||||
|
||||
// And update the chain after commit to prevent race conditions
|
||||
// (when something is in chain but you are not able to fetch details)
|
||||
let route = self.chain.insert_block(&block.bytes, receipts);
|
||||
self.tracedb.import(TraceImportRequest {
|
||||
traces: traces,
|
||||
block_hash: header.hash(),
|
||||
block_number: header.number(),
|
||||
enacted: route.enacted.clone(),
|
||||
retracted: route.retracted.len()
|
||||
});
|
||||
|
||||
let route = self.commit_block(closed_block, &header.hash(), &block.bytes);
|
||||
import_results.push(route);
|
||||
|
||||
self.report.write().unwrap().accrue_block(&block);
|
||||
@@ -352,7 +326,7 @@ impl Client {
|
||||
|
||||
{
|
||||
if !imported_blocks.is_empty() && self.block_queue.queue_info().is_empty() {
|
||||
let (enacted, retracted) = self.calculate_enacted_retracted(import_results);
|
||||
let (enacted, retracted) = self.calculate_enacted_retracted(&import_results);
|
||||
|
||||
if self.queue_info().is_empty() {
|
||||
self.miner.chain_new_blocks(self, &imported_blocks, &invalid_blocks, &enacted, &retracted);
|
||||
@@ -363,19 +337,50 @@ impl Client {
|
||||
invalid: invalid_blocks,
|
||||
enacted: enacted,
|
||||
retracted: retracted,
|
||||
sealed: Vec::new(),
|
||||
})).unwrap_or_else(|e| warn!("Error sending IO notification: {:?}", e));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if self.chain_info().best_block_hash != original_best {
|
||||
self.miner.update_sealing(self);
|
||||
}
|
||||
if self.chain_info().best_block_hash != original_best {
|
||||
self.miner.update_sealing(self);
|
||||
}
|
||||
|
||||
imported
|
||||
}
|
||||
|
||||
fn commit_block<B>(&self, block: B, hash: &H256, block_data: &Bytes) -> ImportRoute where B: IsBlock + Drain {
|
||||
let number = block.header().number();
|
||||
// Are we committing an era?
|
||||
let ancient = if number >= HISTORY {
|
||||
let n = number - HISTORY;
|
||||
Some((n, self.chain.block_hash(n).unwrap()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Commit results
|
||||
let receipts = block.receipts().clone();
|
||||
let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new));
|
||||
|
||||
// CHECK! I *think* this is fine, even if the state_root is equal to another
|
||||
// already-imported block of the same number.
|
||||
// TODO: Prove it with a test.
|
||||
block.drain().commit(number, hash, ancient).expect("State DB commit failed.");
|
||||
|
||||
// And update the chain after commit to prevent race conditions
|
||||
// (when something is in chain but you are not able to fetch details)
|
||||
let route = self.chain.insert_block(block_data, receipts);
|
||||
self.tracedb.import(TraceImportRequest {
|
||||
traces: traces,
|
||||
block_hash: hash.clone(),
|
||||
block_number: number,
|
||||
enacted: route.enacted.clone(),
|
||||
retracted: route.retracted.len()
|
||||
});
|
||||
route
|
||||
}
|
||||
|
||||
/// Import transactions from the IO queue
|
||||
pub fn import_queued_transactions(&self, transactions: &[Bytes]) -> usize {
|
||||
let _timer = PerfTimer::new("import_queued_transactions");
|
||||
@@ -516,9 +521,6 @@ impl BlockChainClient for Client {
|
||||
ret
|
||||
}
|
||||
|
||||
fn vm_factory(&self) -> &EvmFactory {
|
||||
&self.vm_factory
|
||||
}
|
||||
|
||||
fn block_header(&self, id: BlockID) -> Option<Bytes> {
|
||||
Self::block_hash(&self.chain, id).and_then(|hash| self.chain.block(&hash).map(|bytes| BlockView::new(&bytes).rlp().at(0).as_raw().to_vec()))
|
||||
@@ -868,6 +870,43 @@ impl MiningBlockChainClient for Client {
|
||||
|
||||
open_block
|
||||
}
|
||||
|
||||
fn vm_factory(&self) -> &EvmFactory {
|
||||
&self.vm_factory
|
||||
}
|
||||
|
||||
fn import_sealed_block(&self, block: SealedBlock) -> ImportResult {
|
||||
let _import_lock = self.import_lock.lock();
|
||||
let _timer = PerfTimer::new("import_sealed_block");
|
||||
|
||||
let original_best = self.chain_info().best_block_hash;
|
||||
|
||||
let h = block.header().hash();
|
||||
let number = block.header().number();
|
||||
|
||||
let block_data = block.rlp_bytes();
|
||||
let route = self.commit_block(block, &h, &block_data);
|
||||
trace!(target: "client", "Imported sealed block #{} ({})", number, h);
|
||||
|
||||
{
|
||||
let (enacted, retracted) = self.calculate_enacted_retracted(&[route]);
|
||||
self.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted);
|
||||
|
||||
self.io_channel.send(NetworkIoMessage::User(SyncMessage::NewChainBlocks {
|
||||
imported: vec![h.clone()],
|
||||
invalid: vec![],
|
||||
enacted: enacted,
|
||||
retracted: retracted,
|
||||
sealed: vec![h.clone()],
|
||||
})).unwrap_or_else(|e| warn!("Error sending IO notification: {:?}", e));
|
||||
}
|
||||
|
||||
if self.chain_info().best_block_hash != original_best {
|
||||
self.miner.update_sealing(self);
|
||||
}
|
||||
|
||||
Ok(h)
|
||||
}
|
||||
}
|
||||
|
||||
impl MayPanic for Client {
|
||||
|
||||
@@ -38,7 +38,7 @@ use util::numbers::U256;
|
||||
use util::Itertools;
|
||||
use blockchain::TreeRoute;
|
||||
use block_queue::BlockQueueInfo;
|
||||
use block::OpenBlock;
|
||||
use block::{OpenBlock, SealedBlock};
|
||||
use header::{BlockNumber, Header};
|
||||
use transaction::{LocalizedTransaction, SignedTransaction};
|
||||
use log_entry::LocalizedLogEntry;
|
||||
@@ -173,9 +173,6 @@ pub trait BlockChainClient : Sync + Send {
|
||||
// TODO: should be able to accept blockchain location for call.
|
||||
fn call(&self, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, ExecutionError>;
|
||||
|
||||
/// Returns EvmFactory.
|
||||
fn vm_factory(&self) -> &EvmFactory;
|
||||
|
||||
/// Returns traces matching given filter.
|
||||
fn filter_traces(&self, filter: TraceFilter) -> Option<Vec<LocalizedTrace>>;
|
||||
|
||||
@@ -258,4 +255,10 @@ pub trait MiningBlockChainClient : BlockChainClient {
|
||||
/// Returns OpenBlock prepared for closing.
|
||||
fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes)
|
||||
-> OpenBlock;
|
||||
|
||||
/// Returns EvmFactory.
|
||||
fn vm_factory(&self) -> &EvmFactory;
|
||||
|
||||
/// Import sealed block. Skips all verifications.
|
||||
fn import_sealed_block(&self, block: SealedBlock) -> ImportResult;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ use miner::{Miner, MinerService};
|
||||
use spec::Spec;
|
||||
|
||||
use block_queue::BlockQueueInfo;
|
||||
use block::OpenBlock;
|
||||
use block::{OpenBlock, SealedBlock};
|
||||
use executive::Executed;
|
||||
use error::{ExecutionError};
|
||||
use trace::LocalizedTrace;
|
||||
@@ -244,6 +244,14 @@ impl MiningBlockChainClient for TestBlockChainClient {
|
||||
fn prepare_open_block(&self, _author: Address, _gas_range_target: (U256, U256), _extra_data: Bytes) -> OpenBlock {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn vm_factory(&self) -> &EvmFactory {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn import_sealed_block(&self, _block: SealedBlock) -> ImportResult {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockChainClient for TestBlockChainClient {
|
||||
@@ -463,10 +471,6 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn vm_factory(&self) -> &EvmFactory {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn filter_traces(&self, _filter: TraceFilter) -> Option<Vec<LocalizedTrace>> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate ethash;
|
||||
|
||||
use self::ethash::{quick_get_difficulty, EthashManager, H256 as EH256};
|
||||
use ethash::{quick_get_difficulty, EthashManager, H256 as EH256};
|
||||
use common::*;
|
||||
use block::*;
|
||||
use spec::CommonParams;
|
||||
|
||||
@@ -91,6 +91,8 @@ extern crate ethjson;
|
||||
extern crate bloomchain;
|
||||
#[macro_use] extern crate ethcore_ipc as ipc;
|
||||
extern crate rayon;
|
||||
extern crate hyper;
|
||||
extern crate ethash;
|
||||
pub extern crate ethstore;
|
||||
|
||||
#[cfg(test)] extern crate ethcore_devtools as devtools;
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
mod v6;
|
||||
|
||||
pub use self::v6::ToV6;
|
||||
pub use self::v6::ToV6;
|
||||
@@ -1,3 +1,4 @@
|
||||
//! Database migrations.
|
||||
|
||||
pub mod extras;
|
||||
pub mod state;
|
||||
|
||||
5
ethcore/src/migrations/state/mod.rs
Normal file
5
ethcore/src/migrations/state/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
//! State database migrations.
|
||||
|
||||
mod v7;
|
||||
|
||||
pub use self::v7::ToV7;
|
||||
31
ethcore/src/migrations/state/v7.rs
Normal file
31
ethcore/src/migrations/state/v7.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use util::hash::{FixedHash, H256};
|
||||
use util::migration::Migration;
|
||||
use util::sha3::Hashable;
|
||||
|
||||
/// This migration migrates the state db to use an accountdb which ensures uniqueness
|
||||
/// using an address' hash as opposed to the address itself.
|
||||
pub struct ToV7;
|
||||
|
||||
impl Migration for ToV7 {
|
||||
fn version(&self) -> u32 {
|
||||
7
|
||||
}
|
||||
|
||||
fn simple_migrate(&self, mut key: Vec<u8>, value: Vec<u8>) -> Option<(Vec<u8>, Vec<u8>)> {
|
||||
let val_hash = value.sha3();
|
||||
assert!(key.len() == 32); // all keys in the state db are hashes.
|
||||
let key_h = H256::from_slice(&key[..]);
|
||||
if key_h != val_hash {
|
||||
// this is a key which has been xor'd with an address.
|
||||
// recover the address
|
||||
let address = key_h ^ val_hash;
|
||||
let address_hash = address.sha3();
|
||||
|
||||
let new_key = address_hash ^ val_hash;
|
||||
key.copy_from_slice(&new_key[..]);
|
||||
}
|
||||
// nothing to do here
|
||||
Some((key, value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
use rayon::prelude::*;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
use util::*;
|
||||
use util::Colour::White;
|
||||
use account_provider::AccountProvider;
|
||||
use views::{BlockView, HeaderView};
|
||||
use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics};
|
||||
@@ -28,6 +30,7 @@ use receipt::{Receipt};
|
||||
use spec::Spec;
|
||||
use engine::Engine;
|
||||
use miner::{MinerService, MinerStatus, TransactionQueue, AccountDetails, TransactionImportResult, TransactionOrigin};
|
||||
use miner::work_notify::WorkPoster;
|
||||
|
||||
/// Different possible definitions for pending transaction set.
|
||||
#[derive(Debug)]
|
||||
@@ -44,29 +47,41 @@ pub enum PendingSet {
|
||||
/// Configures the behaviour of the miner.
|
||||
#[derive(Debug)]
|
||||
pub struct MinerOptions {
|
||||
/// URLs to notify when there is new work.
|
||||
pub new_work_notify: Vec<String>,
|
||||
/// Force the miner to reseal, even when nobody has asked for work.
|
||||
pub force_sealing: bool,
|
||||
/// Reseal on receipt of new external transactions.
|
||||
pub reseal_on_external_tx: bool,
|
||||
/// Reseal on receipt of new local transactions.
|
||||
pub reseal_on_own_tx: bool,
|
||||
/// Minimum period between transaction-inspired reseals.
|
||||
pub reseal_min_period: Duration,
|
||||
/// Maximum amount of gas to bother considering for block insertion.
|
||||
pub tx_gas_limit: U256,
|
||||
/// Maximum size of the transaction queue.
|
||||
pub tx_queue_size: usize,
|
||||
/// Whether we should fallback to providing all the queue's transactions or just pending.
|
||||
pub pending_set: PendingSet,
|
||||
/// How many historical work packages can we store before running out?
|
||||
pub work_queue_size: usize,
|
||||
/// Can we submit two different solutions for the same block and expect both to result in an import?
|
||||
pub enable_resubmission: bool,
|
||||
}
|
||||
|
||||
impl Default for MinerOptions {
|
||||
fn default() -> Self {
|
||||
MinerOptions {
|
||||
new_work_notify: vec![],
|
||||
force_sealing: false,
|
||||
reseal_on_external_tx: true,
|
||||
reseal_on_own_tx: true,
|
||||
tx_gas_limit: !U256::zero(),
|
||||
tx_queue_size: 1024,
|
||||
pending_set: PendingSet::AlwaysQueue,
|
||||
reseal_min_period: Duration::from_secs(0),
|
||||
work_queue_size: 20,
|
||||
enable_resubmission: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,6 +95,7 @@ pub struct Miner {
|
||||
// for sealing...
|
||||
options: MinerOptions,
|
||||
sealing_enabled: AtomicBool,
|
||||
next_allowed_reseal: Mutex<Instant>,
|
||||
sealing_block_last_request: Mutex<u64>,
|
||||
gas_range_target: RwLock<(U256, U256)>,
|
||||
author: RwLock<Address>,
|
||||
@@ -87,6 +103,7 @@ pub struct Miner {
|
||||
spec: Spec,
|
||||
|
||||
accounts: Option<Arc<AccountProvider>>,
|
||||
work_poster: Option<WorkPoster>,
|
||||
}
|
||||
|
||||
impl Miner {
|
||||
@@ -96,29 +113,34 @@ impl Miner {
|
||||
transaction_queue: Mutex::new(TransactionQueue::new()),
|
||||
options: Default::default(),
|
||||
sealing_enabled: AtomicBool::new(false),
|
||||
next_allowed_reseal: Mutex::new(Instant::now()),
|
||||
sealing_block_last_request: Mutex::new(0),
|
||||
sealing_work: Mutex::new(UsingQueue::new(5)),
|
||||
sealing_work: Mutex::new(UsingQueue::new(20)),
|
||||
gas_range_target: RwLock::new((U256::zero(), U256::zero())),
|
||||
author: RwLock::new(Address::default()),
|
||||
extra_data: RwLock::new(Vec::new()),
|
||||
accounts: None,
|
||||
spec: spec,
|
||||
work_poster: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new instance of miner
|
||||
pub fn new(options: MinerOptions, spec: Spec, accounts: Option<Arc<AccountProvider>>) -> Arc<Miner> {
|
||||
let work_poster = if !options.new_work_notify.is_empty() { Some(WorkPoster::new(&options.new_work_notify)) } else { None };
|
||||
Arc::new(Miner {
|
||||
transaction_queue: Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, options.tx_gas_limit)),
|
||||
sealing_enabled: AtomicBool::new(options.force_sealing),
|
||||
options: options,
|
||||
sealing_enabled: AtomicBool::new(options.force_sealing || !options.new_work_notify.is_empty()),
|
||||
next_allowed_reseal: Mutex::new(Instant::now()),
|
||||
sealing_block_last_request: Mutex::new(0),
|
||||
sealing_work: Mutex::new(UsingQueue::new(5)),
|
||||
sealing_work: Mutex::new(UsingQueue::new(options.work_queue_size)),
|
||||
gas_range_target: RwLock::new((U256::zero(), U256::zero())),
|
||||
author: RwLock::new(Address::default()),
|
||||
extra_data: RwLock::new(Vec::new()),
|
||||
options: options,
|
||||
accounts: accounts,
|
||||
spec: spec,
|
||||
work_poster: work_poster,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -126,15 +148,20 @@ impl Miner {
|
||||
self.spec.engine.deref()
|
||||
}
|
||||
|
||||
fn forced_sealing(&self) -> bool {
|
||||
self.options.force_sealing || !self.options.new_work_notify.is_empty()
|
||||
}
|
||||
|
||||
/// Prepares new block for sealing including top transactions from queue.
|
||||
#[cfg_attr(feature="dev", allow(match_same_arms))]
|
||||
#[cfg_attr(feature="dev", allow(cyclomatic_complexity))]
|
||||
fn prepare_sealing(&self, chain: &MiningBlockChainClient) {
|
||||
trace!(target: "miner", "prepare_sealing: entering");
|
||||
|
||||
let (transactions, mut open_block) = {
|
||||
let (transactions, mut open_block, original_work_hash) = {
|
||||
let transactions = {self.transaction_queue.lock().unwrap().top_transactions()};
|
||||
let mut sealing_work = self.sealing_work.lock().unwrap();
|
||||
let last_work_hash = sealing_work.peek_last_ref().map(|pb| pb.block().fields().header.hash());
|
||||
let best_hash = chain.best_block_header().sha3();
|
||||
/*
|
||||
// check to see if last ClosedBlock in would_seals is actually same parent block.
|
||||
@@ -161,7 +188,7 @@ impl Miner {
|
||||
)
|
||||
}
|
||||
};
|
||||
(transactions, open_block)
|
||||
(transactions, open_block, last_work_hash)
|
||||
};
|
||||
|
||||
let mut invalid_transactions = HashSet::new();
|
||||
@@ -227,13 +254,27 @@ impl Miner {
|
||||
}
|
||||
}
|
||||
|
||||
let mut sealing_work = self.sealing_work.lock().unwrap();
|
||||
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);
|
||||
let (work, is_new) = {
|
||||
let mut sealing_work = self.sealing_work.lock().unwrap();
|
||||
let last_work_hash = sealing_work.peek_last_ref().map(|pb| pb.block().fields().header.hash());
|
||||
trace!(target: "miner", "Checking whether we need to reseal: orig={:?} last={:?}, this={:?}", original_work_hash, last_work_hash, block.block().fields().header.hash());
|
||||
let (work, is_new) = if last_work_hash.map_or(true, |h| h != block.block().fields().header.hash()) {
|
||||
trace!(target: "miner", "Pushing a new, refreshed or borrowed pending {}...", block.block().fields().header.hash());
|
||||
let pow_hash = block.block().fields().header.hash();
|
||||
let number = block.block().fields().header.number();
|
||||
let difficulty = *block.block().fields().header.difficulty();
|
||||
let is_new = original_work_hash.map_or(true, |h| block.block().fields().header.hash() != h);
|
||||
sealing_work.push(block);
|
||||
(Some((pow_hash, difficulty, number)), is_new)
|
||||
} else {
|
||||
(None, false)
|
||||
};
|
||||
trace!(target: "miner", "prepare_sealing: leaving (last={:?})", sealing_work.peek_last_ref().map(|b| b.block().fields().header.hash()));
|
||||
(work, is_new)
|
||||
};
|
||||
if is_new {
|
||||
work.map(|(pow_hash, difficulty, number)| self.work_poster.as_ref().map(|ref p| p.notify(pow_hash, difficulty, number)));
|
||||
}
|
||||
|
||||
trace!(target: "miner", "prepare_sealing: leaving (last={:?})", sealing_work.peek_last_ref().map(|b| b.block().fields().header.hash()));
|
||||
}
|
||||
|
||||
fn update_gas_limit(&self, chain: &MiningBlockChainClient) {
|
||||
@@ -261,6 +302,9 @@ impl Miner {
|
||||
// Return if
|
||||
!have_work
|
||||
}
|
||||
|
||||
/// Are we allowed to do a non-mandatory reseal?
|
||||
fn tx_reseal_allowed(&self) -> bool { Instant::now() > *self.next_allowed_reseal.lock().unwrap() }
|
||||
}
|
||||
|
||||
const SEALING_TIMEOUT_IN_BLOCKS : u64 = 5;
|
||||
@@ -431,7 +475,7 @@ impl MinerService for Miner {
|
||||
.map(|tx| transaction_queue.add(tx, &fetch_account, TransactionOrigin::External))
|
||||
.collect()
|
||||
};
|
||||
if !results.is_empty() && self.options.reseal_on_external_tx {
|
||||
if !results.is_empty() && self.options.reseal_on_external_tx && self.tx_reseal_allowed() {
|
||||
self.update_sealing(chain);
|
||||
}
|
||||
results
|
||||
@@ -466,7 +510,7 @@ impl MinerService for Miner {
|
||||
import
|
||||
};
|
||||
|
||||
if imported.is_ok() && self.options.reseal_on_own_tx {
|
||||
if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() {
|
||||
// Make sure to do it after transaction is imported and lock is droped.
|
||||
// We need to create pending block and enable sealing
|
||||
let prepared = self.enable_and_prepare_sealing(chain);
|
||||
@@ -549,7 +593,7 @@ impl MinerService for Miner {
|
||||
let current_no = chain.chain_info().best_block_number;
|
||||
let has_local_transactions = self.transaction_queue.lock().unwrap().has_local_pending_transactions();
|
||||
let last_request = *self.sealing_block_last_request.lock().unwrap();
|
||||
let should_disable_sealing = !self.options.force_sealing
|
||||
let should_disable_sealing = !self.forced_sealing()
|
||||
&& !has_local_transactions
|
||||
&& current_no > last_request
|
||||
&& current_no - last_request > SEALING_TIMEOUT_IN_BLOCKS;
|
||||
@@ -559,6 +603,7 @@ impl MinerService for Miner {
|
||||
self.sealing_enabled.store(false, atomic::Ordering::Relaxed);
|
||||
self.sealing_work.lock().unwrap().reset();
|
||||
} else {
|
||||
*self.next_allowed_reseal.lock().unwrap() = Instant::now() + self.options.reseal_min_period;
|
||||
self.prepare_sealing(chain);
|
||||
}
|
||||
}
|
||||
@@ -575,26 +620,22 @@ impl MinerService for Miner {
|
||||
}
|
||||
|
||||
fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec<Bytes>) -> Result<(), Error> {
|
||||
if let Some(b) = self.sealing_work.lock().unwrap().take_used_if(|b| &b.hash() == &pow_hash) {
|
||||
match b.lock().try_seal(self.engine(), seal) {
|
||||
Err(_) => {
|
||||
info!(target: "miner", "Mined block rejected, PoW was invalid.");
|
||||
Err(Error::PowInvalid)
|
||||
}
|
||||
Ok(sealed) => {
|
||||
info!(target: "miner", "New block mined, hash: {}", sealed.header().hash());
|
||||
// TODO: commit DB from `sealed.drain` and make a VerifiedBlock to skip running the transactions twice.
|
||||
let b = sealed.rlp_bytes();
|
||||
let h = b.sha3();
|
||||
try!(chain.import_block(b));
|
||||
info!("Block {} submitted and imported.", h);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
let result = if let Some(b) = self.sealing_work.lock().unwrap().get_used_if(if self.options.enable_resubmission { GetAction::Clone } else { GetAction::Take }, |b| &b.hash() == &pow_hash) {
|
||||
b.lock().try_seal(self.engine(), seal).or_else(|_| {
|
||||
warn!(target: "miner", "Mined solution rejected: Invalid.");
|
||||
Err(Error::PowInvalid)
|
||||
})
|
||||
} else {
|
||||
info!(target: "miner", "Mined block rejected, PoW hash invalid or out of date.");
|
||||
warn!(target: "miner", "Mined solution rejected: Block unknown or out of date.");
|
||||
Err(Error::PowHashInvalid)
|
||||
}
|
||||
};
|
||||
result.and_then(|sealed| {
|
||||
let n = sealed.header().number();
|
||||
let h = sealed.header().hash();
|
||||
try!(chain.import_sealed_block(sealed));
|
||||
info!(target: "miner", "Mined block imported OK. #{}: {}", paint(White.bold(), format!("{}", n)), paint(White.bold(), h.hex()));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn chain_new_blocks(&self, chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) {
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
mod miner;
|
||||
mod external;
|
||||
mod transaction_queue;
|
||||
mod work_notify;
|
||||
|
||||
pub use self::transaction_queue::{TransactionQueue, AccountDetails, TransactionImportResult, TransactionOrigin};
|
||||
pub use self::miner::{Miner, MinerOptions, PendingSet};
|
||||
|
||||
@@ -441,7 +441,7 @@ impl TransactionQueue {
|
||||
|
||||
trace!(target: "miner", "Importing: {:?}", tx.hash());
|
||||
|
||||
if tx.gas_price < self.minimal_gas_price {
|
||||
if tx.gas_price < self.minimal_gas_price && origin != TransactionOrigin::Local {
|
||||
trace!(target: "miner",
|
||||
"Dropping transaction below minimal gas price threshold: {:?} (gp: {} < {})",
|
||||
tx.hash(),
|
||||
@@ -1055,7 +1055,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_import_transaction_below_min_gas_price_threshold() {
|
||||
fn should_not_import_transaction_below_min_gas_price_threshold_if_external() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let tx = new_tx();
|
||||
@@ -1074,6 +1074,23 @@ mod test {
|
||||
assert_eq!(stats.future, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_import_transaction_below_min_gas_price_threshold_if_local() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let tx = new_tx();
|
||||
txq.set_minimal_gas_price(tx.gas_price + U256::one());
|
||||
|
||||
// when
|
||||
let res = txq.add(tx, &default_nonce, TransactionOrigin::Local);
|
||||
|
||||
// then
|
||||
assert_eq!(res.unwrap(), TransactionImportResult::Current);
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.pending, 1);
|
||||
assert_eq!(stats.future, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_reject_incorectly_signed_transaction() {
|
||||
// given
|
||||
|
||||
115
ethcore/src/miner/work_notify.rs
Normal file
115
ethcore/src/miner/work_notify.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
// 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/>.
|
||||
|
||||
extern crate hyper;
|
||||
|
||||
use hyper::header::ContentType;
|
||||
use hyper::method::Method;
|
||||
use hyper::client::{Request, Response, Client};
|
||||
use hyper::{Next};
|
||||
use hyper::net::HttpStream;
|
||||
use ethash::SeedHashCompute;
|
||||
use hyper::Url;
|
||||
use util::*;
|
||||
use ethereum::ethash::Ethash;
|
||||
|
||||
pub struct WorkPoster {
|
||||
urls: Vec<Url>,
|
||||
client: Mutex<Client<PostHandler>>,
|
||||
seed_compute: Mutex<SeedHashCompute>,
|
||||
}
|
||||
|
||||
impl WorkPoster {
|
||||
pub fn new(urls: &[String]) -> Self {
|
||||
let urls = urls.into_iter().filter_map(|u| {
|
||||
match Url::parse(&u) {
|
||||
Ok(url) => Some(url),
|
||||
Err(e) => {
|
||||
warn!("Error parsing URL {} : {}", u, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}).collect();
|
||||
let client = WorkPoster::create_client();
|
||||
WorkPoster {
|
||||
client: Mutex::new(client),
|
||||
urls: urls,
|
||||
seed_compute: Mutex::new(SeedHashCompute::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_client() -> Client<PostHandler> {
|
||||
let client = Client::<PostHandler>::configure()
|
||||
.keep_alive(true)
|
||||
.build().expect("Error creating HTTP client") as Client<PostHandler>;
|
||||
client
|
||||
}
|
||||
|
||||
pub fn notify(&self, pow_hash: H256, difficulty: U256, number: u64) {
|
||||
// TODO: move this to engine
|
||||
let target = Ethash::difficulty_to_boundary(&difficulty);
|
||||
let seed_hash = &self.seed_compute.lock().unwrap().get_seedhash(number);
|
||||
let seed_hash = H256::from_slice(&seed_hash[..]);
|
||||
let body = format!(r#"{{ "result": ["0x{}","0x{}","0x{}","0x{:x}"] }}"#,
|
||||
pow_hash.hex(), seed_hash.hex(), target.hex(), number);
|
||||
let mut client = self.client.lock().unwrap();
|
||||
for u in &self.urls {
|
||||
if let Err(e) = client.request(u.clone(), PostHandler { body: body.clone() }) {
|
||||
warn!("Error sending HTTP notification to {} : {}, retrying", u, e);
|
||||
// TODO: remove this once https://github.com/hyperium/hyper/issues/848 is fixed
|
||||
*client = WorkPoster::create_client();
|
||||
if let Err(e) = client.request(u.clone(), PostHandler { body: body.clone() }) {
|
||||
warn!("Error sending HTTP notification to {} : {}", u, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PostHandler {
|
||||
body: String,
|
||||
}
|
||||
|
||||
impl hyper::client::Handler<HttpStream> for PostHandler {
|
||||
fn on_request(&mut self, request: &mut Request) -> Next {
|
||||
request.set_method(Method::Post);
|
||||
request.headers_mut().set(ContentType::json());
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_request_writable(&mut self, encoder: &mut hyper::Encoder<HttpStream>) -> Next {
|
||||
if let Err(e) = encoder.write_all(self.body.as_bytes()) {
|
||||
trace!("Error posting work data: {}", e);
|
||||
}
|
||||
encoder.close();
|
||||
Next::read()
|
||||
|
||||
}
|
||||
|
||||
fn on_response(&mut self, _response: Response) -> Next {
|
||||
Next::end()
|
||||
}
|
||||
|
||||
fn on_response_readable(&mut self, _decoder: &mut hyper::Decoder<HttpStream>) -> Next {
|
||||
Next::end()
|
||||
}
|
||||
|
||||
fn on_error(&mut self, err: hyper::Error) -> Next {
|
||||
trace!("Error posting work data: {}", err);
|
||||
Next::end()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
//! Creates and registers client and network services.
|
||||
|
||||
use util::*;
|
||||
use util::Colour::{Yellow, White};
|
||||
use util::panics::*;
|
||||
use spec::Spec;
|
||||
use error::*;
|
||||
@@ -36,6 +37,8 @@ pub enum SyncMessage {
|
||||
retracted: Vec<H256>,
|
||||
/// Hashes of blocks that are now included in cannonical chain
|
||||
enacted: Vec<H256>,
|
||||
/// Hashes of blocks that are sealed by this node
|
||||
sealed: Vec<H256>,
|
||||
},
|
||||
/// Best Block Hash in chain has been changed
|
||||
NewChainHead,
|
||||
@@ -69,8 +72,7 @@ impl ClientService {
|
||||
try!(net_service.start());
|
||||
}
|
||||
|
||||
info!("Starting {}", net_service.host_info());
|
||||
info!("Configured for {} using {:?} engine", spec.name, spec.engine.name());
|
||||
info!("Configured for {} using {} engine", paint(White.bold(), spec.name.clone()), paint(Yellow.bold(), spec.engine.name().to_owned()));
|
||||
let client = try!(Client::new(config, spec, db_path, miner, net_service.io().channel()));
|
||||
panic_handler.forward_from(client.deref());
|
||||
let client_io = Arc::new(ClientIoHandler {
|
||||
|
||||
@@ -208,12 +208,17 @@ impl State {
|
||||
self.require(a, false).set_storage(key, value)
|
||||
}
|
||||
|
||||
/// Initialise the code of account `a` so that it is `value` for `key`.
|
||||
/// Initialise the code of account `a` so that it is `code`.
|
||||
/// NOTE: Account should have been created with `new_contract`.
|
||||
pub fn init_code(&mut self, a: &Address, code: Bytes) {
|
||||
self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{}).init_code(code);
|
||||
}
|
||||
|
||||
/// Reset the code of account `a` so that it is `code`.
|
||||
pub fn reset_code(&mut self, a: &Address, code: Bytes) {
|
||||
self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{}).reset_code(code);
|
||||
}
|
||||
|
||||
/// Execute a given transaction.
|
||||
/// This will change the state accordingly.
|
||||
pub fn apply(&mut self, env_info: &EnvInfo, engine: &Engine, vm_factory: &EvmFactory, t: &SignedTransaction, tracing: bool) -> ApplyResult {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
use client::{BlockChainClient, Client, ClientConfig};
|
||||
use common::*;
|
||||
use spec::*;
|
||||
use block::{OpenBlock};
|
||||
use block::{OpenBlock, Drain};
|
||||
use blockchain::{BlockChain, Config as BlockChainConfig};
|
||||
use state::*;
|
||||
use evm::Schedule;
|
||||
|
||||
Reference in New Issue
Block a user