EIP-168, 169: Dust protection (#4757)

* Dust protection

* Track touched accounts in the substate

* Minor alterations
This commit is contained in:
Arkadiy Paronyan 2017-06-28 09:10:57 +02:00 committed by GitHub
parent 6b16fe3f14
commit 57626b60e7
25 changed files with 270 additions and 85 deletions

View File

@ -261,7 +261,8 @@ impl<'x> OpenBlock<'x> {
gas_range_target: (U256, U256), gas_range_target: (U256, U256),
extra_data: Bytes, extra_data: Bytes,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let state = State::from_existing(db, parent.state_root().clone(), engine.account_start_nonce(), factories)?; let number = parent.number() + 1;
let state = State::from_existing(db, parent.state_root().clone(), engine.account_start_nonce(number), factories)?;
let mut r = OpenBlock { let mut r = OpenBlock {
block: ExecutedBlock::new(state, tracing), block: ExecutedBlock::new(state, tracing),
engine: engine, engine: engine,
@ -269,7 +270,7 @@ impl<'x> OpenBlock<'x> {
}; };
r.block.header.set_parent_hash(parent.hash()); r.block.header.set_parent_hash(parent.hash());
r.block.header.set_number(parent.number() + 1); r.block.header.set_number(number);
r.block.header.set_author(author); r.block.header.set_author(author);
r.block.header.set_timestamp_now(parent.timestamp()); r.block.header.set_timestamp_now(parent.timestamp());
r.block.header.set_extra_data(extra_data); r.block.header.set_extra_data(extra_data);
@ -556,7 +557,7 @@ pub fn enact(
) -> Result<LockedBlock, Error> { ) -> Result<LockedBlock, Error> {
{ {
if ::log::max_log_level() >= ::log::LogLevel::Trace { if ::log::max_log_level() >= ::log::LogLevel::Trace {
let s = State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(), factories.clone())?; let s = State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(parent.number() + 1), factories.clone())?;
trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n", trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n",
header.number(), s.root(), header.author(), s.balance(&header.author())?); header.number(), s.root(), header.author(), s.balance(&header.author())?);
} }

View File

@ -810,7 +810,7 @@ impl Client {
} }
let root = header.state_root(); let root = header.state_root();
State::from_existing(db, root, self.engine.account_start_nonce(), self.factories.clone()).ok() State::from_existing(db, root, self.engine.account_start_nonce(block_number), self.factories.clone()).ok()
}) })
} }
@ -835,7 +835,7 @@ impl Client {
State::from_existing( State::from_existing(
self.state_db.lock().boxed_clone_canon(&header.hash()), self.state_db.lock().boxed_clone_canon(&header.hash()),
header.state_root(), header.state_root(),
self.engine.account_start_nonce(), self.engine.account_start_nonce(header.number()),
self.factories.clone()) self.factories.clone())
.expect("State root of best block header always valid.") .expect("State root of best block header always valid.")
} }
@ -987,7 +987,7 @@ impl Client {
fn contract_call_tx(&self, block_id: BlockId, address: Address, data: Bytes) -> SignedTransaction { fn contract_call_tx(&self, block_id: BlockId, address: Address, data: Bytes) -> SignedTransaction {
let from = Address::default(); let from = Address::default();
Transaction { Transaction {
nonce: self.nonce(&from, block_id).unwrap_or_else(|| self.engine.account_start_nonce()), nonce: self.nonce(&from, block_id).unwrap_or_else(|| self.engine.account_start_nonce(0)),
action: Action::Call(address), action: Action::Call(address),
gas: U256::from(50_000_000), gas: U256::from(50_000_000),
gas_price: U256::default(), gas_price: U256::default(),

View File

@ -89,7 +89,7 @@ impl EvmTestClient {
-> Result<(U256, Vec<u8>), EvmTestError> -> Result<(U256, Vec<u8>), EvmTestError>
{ {
let genesis = self.spec.genesis_header(); let genesis = self.spec.genesis_header();
let mut state = state::State::from_existing(self.state_db.boxed_clone(), *genesis.state_root(), self.spec.engine.account_start_nonce(), self.factories.clone()) let mut state = state::State::from_existing(self.state_db.boxed_clone(), *genesis.state_root(), self.spec.engine.account_start_nonce(0), self.factories.clone())
.map_err(EvmTestError::Trie)?; .map_err(EvmTestError::Trie)?;
let info = client::EnvInfo { let info = client::EnvInfo {
number: genesis.number(), number: genesis.number(),

View File

@ -161,8 +161,14 @@ pub trait Engine : Sync + Send {
fn maximum_uncle_count(&self) -> usize { 2 } fn maximum_uncle_count(&self) -> usize { 2 }
/// The number of generations back that uncles can be. /// The number of generations back that uncles can be.
fn maximum_uncle_age(&self) -> usize { 6 } fn maximum_uncle_age(&self) -> usize { 6 }
/// The nonce with which accounts begin. /// The nonce with which accounts begin at given block.
fn account_start_nonce(&self) -> U256 { self.params().account_start_nonce } fn account_start_nonce(&self, block: u64) -> U256 {
if block >= self.params().dust_protection_transition {
U256::from(self.params().nonce_cap_increment) * U256::from(block)
} else {
self.params().account_start_nonce
}
}
/// Block transformation functions, before the transactions. /// Block transformation functions, before the transactions.
fn on_new_block(&self, block: &mut ExecutedBlock, last_hashes: Arc<LastHashes>) -> Result<(), Error> { fn on_new_block(&self, block: &mut ExecutedBlock, last_hashes: Arc<LastHashes>) -> Result<(), Error> {

View File

@ -165,6 +165,8 @@ pub enum BlockError {
InvalidNumber(Mismatch<BlockNumber>), InvalidNumber(Mismatch<BlockNumber>),
/// Block number isn't sensible. /// Block number isn't sensible.
RidiculousNumber(OutOfBounds<BlockNumber>), RidiculousNumber(OutOfBounds<BlockNumber>),
/// Too many transactions from a particular address.
TooManyTransactions(Address),
/// Parent given is unknown. /// Parent given is unknown.
UnknownParent(H256), UnknownParent(H256),
/// Uncle parent given is unknown. /// Uncle parent given is unknown.
@ -205,6 +207,7 @@ impl fmt::Display for BlockError {
UnknownParent(ref hash) => format!("Unknown parent: {}", hash), UnknownParent(ref hash) => format!("Unknown parent: {}", hash),
UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash), UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash),
UnknownEpochTransition(ref num) => format!("Unknown transition to epoch number: {}", num), UnknownEpochTransition(ref num) => format!("Unknown transition to epoch number: {}", num),
TooManyTransactions(ref address) => format!("Too many transactions from: {}", address),
}; };
f.write_fmt(format_args!("Block error ({})", msg)) f.write_fmt(format_args!("Block error ({})", msg))

View File

@ -98,8 +98,7 @@ mod tests {
let engine = &spec.engine; let engine = &spec.engine;
let genesis_header = spec.genesis_header(); let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap(); let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let s = State::from_existing(db, genesis_header.state_root().clone(), engine.account_start_nonce(0), Default::default()).unwrap();
let s = State::from_existing(db, genesis_header.state_root().clone(), engine.account_start_nonce(), Default::default()).unwrap();
assert_eq!(s.balance(&"0000000000000000000000000000000000000001".into()).unwrap(), 1u64.into()); assert_eq!(s.balance(&"0000000000000000000000000000000000000001".into()).unwrap(), 1u64.into());
assert_eq!(s.balance(&"0000000000000000000000000000000000000002".into()).unwrap(), 1u64.into()); assert_eq!(s.balance(&"0000000000000000000000000000000000000002".into()).unwrap(), 1u64.into());
assert_eq!(s.balance(&"0000000000000000000000000000000000000003".into()).unwrap(), 1u64.into()); assert_eq!(s.balance(&"0000000000000000000000000000000000000003".into()).unwrap(), 1u64.into());

View File

@ -38,5 +38,5 @@ pub use self::ext::{Ext, ContractCreateResult, MessageCallResult, CreateContract
pub use self::instructions::{InstructionInfo, INSTRUCTIONS, push_bytes}; pub use self::instructions::{InstructionInfo, INSTRUCTIONS, push_bytes};
pub use self::vmtype::VMType; pub use self::vmtype::VMType;
pub use self::factory::Factory; pub use self::factory::Factory;
pub use self::schedule::Schedule; pub use self::schedule::{Schedule, CleanDustMode};
pub use types::executed::CallType; pub use types::executed::CallType;

View File

@ -108,6 +108,19 @@ pub struct Schedule {
pub blockhash_gas: usize, pub blockhash_gas: usize,
/// Static Call opcode enabled. /// Static Call opcode enabled.
pub have_static_call: bool, pub have_static_call: bool,
/// Kill basic accounts below this balance if touched.
pub kill_dust: CleanDustMode,
}
/// Dust accounts cleanup mode.
#[derive(PartialEq, Eq)]
pub enum CleanDustMode {
/// Dust cleanup is disabled.
Off,
/// Basic dust accounts will be removed.
BasicOnly,
/// Basic and contract dust accounts will be removed.
WithCodeAndStorage,
} }
impl Schedule { impl Schedule {
@ -168,15 +181,16 @@ impl Schedule {
kill_empty: kill_empty, kill_empty: kill_empty,
blockhash_gas: 20, blockhash_gas: 20,
have_static_call: false, have_static_call: false,
kill_dust: CleanDustMode::Off,
} }
} }
/// Schedule for the Metropolis era from common spec params. /// Schedule for the post-EIP-150-era of the Ethereum main net.
pub fn from_params(block_number: u64, params: &CommonParams) -> Schedule { pub fn from_params(block_number: u64, params: &CommonParams) -> Schedule {
let mut schedule = Schedule::new_post_eip150(usize::max_value(), true, true, true); let mut schedule = Schedule::new_post_eip150(usize::max_value(), true, true, true);
schedule.apply_params(block_number, params); schedule.apply_params(block_number, params);
schedule schedule
} }
/// Apply common spec config parameters to the schedule. /// Apply common spec config parameters to the schedule.
pub fn apply_params(&mut self, block_number: u64, params: &CommonParams) { pub fn apply_params(&mut self, block_number: u64, params: &CommonParams) {
@ -186,6 +200,9 @@ impl Schedule {
if block_number >= params.eip210_transition { if block_number >= params.eip210_transition {
self.blockhash_gas = 350; self.blockhash_gas = 350;
} }
if block_number >= params.dust_protection_transition {
self.kill_dust = if params.remove_dust_contracts { CleanDustMode::WithCodeAndStorage } else { CleanDustMode::BasicOnly };
}
} }
/// Schedule for the Metropolis of the Ethereum main net. /// Schedule for the Metropolis of the Ethereum main net.
@ -244,6 +261,7 @@ impl Schedule {
kill_empty: false, kill_empty: false,
blockhash_gas: 20, blockhash_gas: 20,
have_static_call: false, have_static_call: false,
kill_dust: CleanDustMode::Off,
} }
} }
} }

View File

@ -22,7 +22,7 @@ use engines::Engine;
use types::executed::CallType; use types::executed::CallType;
use env_info::EnvInfo; use env_info::EnvInfo;
use error::ExecutionError; use error::ExecutionError;
use evm::{self, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData}; use evm::{self, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData, CleanDustMode};
use externalities::*; use externalities::*;
use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer}; use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer};
use transaction::{Action, SignedTransaction}; use transaction::{Action, SignedTransaction};
@ -164,6 +164,10 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
return Err(From::from(ExecutionError::NotEnoughBaseGas { required: base_gas_required, got: t.gas })); return Err(From::from(ExecutionError::NotEnoughBaseGas { required: base_gas_required, got: t.gas }));
} }
if !t.is_unsigned() && check_nonce && schedule.kill_dust != CleanDustMode::Off && !self.state.exists(&sender)? {
return Err(From::from(ExecutionError::SenderMustExist));
}
let init_gas = t.gas - base_gas_required; let init_gas = t.gas - base_gas_required;
// validate transaction nonce // validate transaction nonce
@ -191,13 +195,13 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
return Err(From::from(ExecutionError::NotEnoughCash { required: total_cost, got: balance512 })); return Err(From::from(ExecutionError::NotEnoughCash { required: total_cost, got: balance512 }));
} }
let mut substate = Substate::new();
// NOTE: there can be no invalid transactions from this point. // NOTE: there can be no invalid transactions from this point.
if !t.is_unsigned() { if !t.is_unsigned() {
self.state.inc_nonce(&sender)?; self.state.inc_nonce(&sender)?;
} }
self.state.sub_balance(&sender, &U256::from(gas_cost))?; self.state.sub_balance(&sender, &U256::from(gas_cost), &mut substate.to_cleanup_mode(&schedule))?;
let mut substate = Substate::new();
let (result, output) = match t.action { let (result, output) = match t.action {
Action::Create => { Action::Create => {
@ -434,7 +438,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
let nonce_offset = if schedule.no_empty {1} else {0}.into(); let nonce_offset = if schedule.no_empty {1} else {0}.into();
let prev_bal = self.state.balance(&params.address)?; let prev_bal = self.state.balance(&params.address)?;
if let ActionValue::Transfer(val) = params.value { if let ActionValue::Transfer(val) = params.value {
self.state.sub_balance(&params.sender, &val)?; self.state.sub_balance(&params.sender, &val, &mut substate.to_cleanup_mode(&schedule))?;
self.state.new_contract(&params.address, val + prev_bal, nonce_offset); self.state.new_contract(&params.address, val + prev_bal, nonce_offset);
} else { } else {
self.state.new_contract(&params.address, prev_bal, nonce_offset); self.state.new_contract(&params.address, prev_bal, nonce_offset);
@ -512,11 +516,8 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
} }
// perform garbage-collection // perform garbage-collection
for address in &substate.garbage { let min_balance = if schedule.kill_dust != CleanDustMode::Off { Some(U256::from(schedule.tx_gas) * t.gas_price) } else { None };
if self.state.exists(address)? && !self.state.exists_and_not_null(address)? { self.state.kill_garbage(&substate.touched, schedule.kill_empty, &min_balance, schedule.kill_dust == CleanDustMode::WithCodeAndStorage)?;
self.state.kill_account(address);
}
}
match result { match result {
Err(evm::Error::Internal(msg)) => Err(ExecutionError::Internal(msg)), Err(evm::Error::Internal(msg)) => Err(ExecutionError::Internal(msg)),

View File

@ -17,7 +17,7 @@
//! Transaction Execution environment. //! Transaction Execution environment.
use util::*; use util::*;
use action_params::{ActionParams, ActionValue}; use action_params::{ActionParams, ActionValue};
use state::{Backend as StateBackend, State, Substate}; use state::{Backend as StateBackend, State, Substate, CleanupMode};
use engines::Engine; use engines::Engine;
use env_info::EnvInfo; use env_info::EnvInfo;
use executive::*; use executive::*;
@ -347,7 +347,7 @@ impl<'a, T: 'a, V: 'a, B: 'a, E: 'a> Ext for Externalities<'a, T, V, B, E>
let balance = self.balance(&address)?; let balance = self.balance(&address)?;
if &address == refund_address { if &address == refund_address {
// TODO [todr] To be consistent with CPP client we set balance to 0 in that case. // TODO [todr] To be consistent with CPP client we set balance to 0 in that case.
self.state.sub_balance(&address, &balance)?; self.state.sub_balance(&address, &balance, &mut CleanupMode::NoEmpty)?;
} else { } else {
trace!(target: "ext", "Suiciding {} -> {} (xfer: {})", address, refund_address, balance); trace!(target: "ext", "Suiciding {} -> {} (xfer: {})", address, refund_address, balance);
self.state.transfer_balance( self.state.transfer_balance(

View File

@ -328,7 +328,10 @@ impl Miner {
let _timer = PerfTimer::new("prepare_block"); let _timer = PerfTimer::new("prepare_block");
let chain_info = chain.chain_info(); let chain_info = chain.chain_info();
let (transactions, mut open_block, original_work_hash) = { let (transactions, mut open_block, original_work_hash) = {
let transactions = {self.transaction_queue.read().top_transactions_at(chain_info.best_block_number, chain_info.best_block_timestamp)}; let nonce_cap = if chain_info.best_block_number + 1 >= self.engine.params().dust_protection_transition {
Some((self.engine.params().nonce_cap_increment * (chain_info.best_block_number + 1)).into())
} else { None };
let transactions = {self.transaction_queue.read().top_transactions_at(chain_info.best_block_number, chain_info.best_block_timestamp, nonce_cap)};
let mut sealing_work = self.sealing_work.lock(); let mut sealing_work = self.sealing_work.lock();
let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash());
let best_hash = chain_info.best_block_hash; let best_hash = chain_info.best_block_hash;

View File

@ -1084,11 +1084,11 @@ impl TransactionQueue {
/// Returns top transactions from the queue ordered by priority. /// Returns top transactions from the queue ordered by priority.
pub fn top_transactions(&self) -> Vec<SignedTransaction> { pub fn top_transactions(&self) -> Vec<SignedTransaction> {
self.top_transactions_at(BlockNumber::max_value(), u64::max_value()) self.top_transactions_at(BlockNumber::max_value(), u64::max_value(), None)
} }
fn filter_pending_transaction<F>(&self, best_block: BlockNumber, best_timestamp: u64, mut f: F) fn filter_pending_transaction<F>(&self, best_block: BlockNumber, best_timestamp: u64, nonce_cap: Option<U256>, mut f: F)
where F: FnMut(&VerifiedTransaction) { where F: FnMut(&VerifiedTransaction) {
let mut delayed = HashSet::new(); let mut delayed = HashSet::new();
@ -1098,6 +1098,11 @@ impl TransactionQueue {
if delayed.contains(&sender) { if delayed.contains(&sender) {
continue; continue;
} }
if let Some(max_nonce) = nonce_cap {
if tx.nonce() >= max_nonce {
continue;
}
}
let delay = match tx.condition { let delay = match tx.condition {
Some(Condition::Number(n)) => n > best_block, Some(Condition::Number(n)) => n > best_block,
Some(Condition::Timestamp(t)) => t > best_timestamp, Some(Condition::Timestamp(t)) => t > best_timestamp,
@ -1112,16 +1117,16 @@ impl TransactionQueue {
} }
/// Returns top transactions from the queue ordered by priority. /// Returns top transactions from the queue ordered by priority.
pub fn top_transactions_at(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<SignedTransaction> { pub fn top_transactions_at(&self, best_block: BlockNumber, best_timestamp: u64, nonce_cap: Option<U256>) -> Vec<SignedTransaction> {
let mut r = Vec::new(); let mut r = Vec::new();
self.filter_pending_transaction(best_block, best_timestamp, |tx| r.push(tx.transaction.clone())); self.filter_pending_transaction(best_block, best_timestamp, nonce_cap, |tx| r.push(tx.transaction.clone()));
r r
} }
/// Return all ready transactions. /// Return all ready transactions.
pub fn pending_transactions(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<PendingTransaction> { pub fn pending_transactions(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<PendingTransaction> {
let mut r = Vec::new(); let mut r = Vec::new();
self.filter_pending_transaction(best_block, best_timestamp, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.condition.clone()))); self.filter_pending_transaction(best_block, best_timestamp, None, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.condition.clone())));
r r
} }
@ -2205,9 +2210,9 @@ pub mod test {
// then // then
assert_eq!(res1, TransactionImportResult::Current); assert_eq!(res1, TransactionImportResult::Current);
assert_eq!(res2, TransactionImportResult::Current); assert_eq!(res2, TransactionImportResult::Current);
let top = txq.top_transactions_at(0, 0); let top = txq.top_transactions_at(0, 0, None);
assert_eq!(top.len(), 0); assert_eq!(top.len(), 0);
let top = txq.top_transactions_at(1, 0); let top = txq.top_transactions_at(1, 0, None);
assert_eq!(top.len(), 2); assert_eq!(top.len(), 2);
} }
@ -2809,4 +2814,19 @@ pub mod test {
// then // then
assert_eq!(txq.top_transactions().len(), 1); assert_eq!(txq.top_transactions().len(), 1);
} }
#[test]
fn should_not_return_transactions_over_nonce_cap() {
// given
let keypair = Random.generate().unwrap();
let mut txq = TransactionQueue::default();
// when
for nonce in 123..130 {
let tx = new_unsigned_tx(nonce.into(), default_gas_val(), default_gas_price()).sign(keypair.secret(), None);
txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap();
}
// then
assert_eq!(txq.top_transactions_at(BlockNumber::max_value(), u64::max_value(), Some(127.into())).len(), 4);
}
} }

View File

@ -211,7 +211,7 @@ fn make_tx_and_env(
use transaction::{Action, Transaction}; use transaction::{Action, Transaction};
let transaction = Transaction { let transaction = Transaction {
nonce: engine.account_start_nonce(), nonce: engine.account_start_nonce(header.number()),
action: Action::Call(addr), action: Action::Call(addr),
gas: 50_000_000.into(), gas: 50_000_000.into(),
gas_price: 0.into(), gas_price: 0.into(),
@ -469,7 +469,7 @@ impl Rebuilder for ChunkRebuilder {
let mut state = State::from_existing( let mut state = State::from_existing(
db.boxed_clone(), db.boxed_clone(),
self.manifest.state_root.clone(), self.manifest.state_root.clone(),
engine.account_start_nonce(), engine.account_start_nonce(target_header.number()),
factories, factories,
).map_err(|e| format!("State root mismatch: {}", e))?; ).map_err(|e| format!("State root mismatch: {}", e))?;

View File

@ -124,7 +124,7 @@ fn make_chain(accounts: Arc<AccountProvider>, blocks_beyond: usize, transitions:
}; };
// execution callback for native contract: push transaction to be sealed. // execution callback for native contract: push transaction to be sealed.
let nonce = RefCell::new(client.engine().account_start_nonce()); let nonce = RefCell::new(client.engine().account_start_nonce(0));
let exec = |addr, data| { let exec = |addr, data| {
let mut nonce = nonce.borrow_mut(); let mut nonce = nonce.borrow_mut();
let transaction = Transaction { let transaction = Transaction {

View File

@ -76,6 +76,12 @@ pub struct CommonParams {
pub eip211_transition: BlockNumber, pub eip211_transition: BlockNumber,
/// Number of first block where EIP-214 rules begin. /// Number of first block where EIP-214 rules begin.
pub eip214_transition: BlockNumber, pub eip214_transition: BlockNumber,
/// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin.
pub dust_protection_transition: BlockNumber,
/// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled.
pub nonce_cap_increment : u64,
/// Enable dust cleanup for contracts.
pub remove_dust_contracts : bool,
} }
impl From<ethjson::spec::Params> for CommonParams { impl From<ethjson::spec::Params> for CommonParams {
@ -100,6 +106,9 @@ impl From<ethjson::spec::Params> for CommonParams {
eip210_contract_gas: p.eip210_contract_gas.map_or(1000000.into(), Into::into), eip210_contract_gas: p.eip210_contract_gas.map_or(1000000.into(), Into::into),
eip211_transition: p.eip211_transition.map_or(BlockNumber::max_value(), Into::into), eip211_transition: p.eip211_transition.map_or(BlockNumber::max_value(), Into::into),
eip214_transition: p.eip214_transition.map_or(BlockNumber::max_value(), Into::into), eip214_transition: p.eip214_transition.map_or(BlockNumber::max_value(), Into::into),
dust_protection_transition: p.dust_protection_transition.map_or(BlockNumber::max_value(), Into::into),
nonce_cap_increment: p.nonce_cap_increment.map_or(64, Into::into),
remove_dust_contracts: p.remove_dust_contracts.unwrap_or(false),
} }
} }
} }
@ -224,7 +233,7 @@ impl Spec {
); );
} }
let start_nonce = self.engine.account_start_nonce(); let start_nonce = self.engine.account_start_nonce(0);
let (root, db) = { let (root, db) = {
let mut state = State::from_existing( let mut state = State::from_existing(
@ -460,7 +469,7 @@ mod tests {
::ethcore_logger::init_log(); ::ethcore_logger::init_log();
let spec = Spec::new_test_constructor(); let spec = Spec::new_test_constructor();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap(); let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let state = State::from_existing(db.boxed_clone(), spec.state_root(), spec.engine.account_start_nonce(), Default::default()).unwrap(); let state = State::from_existing(db.boxed_clone(), spec.state_root(), spec.engine.account_start_nonce(0), Default::default()).unwrap();
let expected = H256::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap(); let expected = H256::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap();
assert_eq!(state.storage_at(&Address::from_str("0000000000000000000000000000000000000005").unwrap(), &H256::zero()).unwrap(), expected); assert_eq!(state.storage_at(&Address::from_str("0000000000000000000000000000000000000005").unwrap(), &H256::zero()).unwrap(), expected);
} }

View File

@ -315,6 +315,11 @@ impl Account {
self.code_hash == SHA3_EMPTY self.code_hash == SHA3_EMPTY
} }
/// Check if account is basic (Has no code).
pub fn is_basic(&self) -> bool {
self.code_hash == SHA3_EMPTY
}
/// Return the storage root associated with this account or None if it has been altered via the overlay. /// Return the storage root associated with this account or None if it has been altered via the overlay.
pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} } pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} }

View File

@ -96,7 +96,11 @@ enum AccountState {
/// Account entry can contain existing (`Some`) or non-existing /// Account entry can contain existing (`Some`) or non-existing
/// account (`None`) /// account (`None`)
struct AccountEntry { struct AccountEntry {
/// Account entry. `None` if account known to be non-existant.
account: Option<Account>, account: Option<Account>,
/// Unmodified account balance.
old_balance: Option<U256>,
/// Entry state.
state: AccountState, state: AccountState,
} }
@ -107,6 +111,10 @@ impl AccountEntry {
self.state == AccountState::Dirty self.state == AccountState::Dirty
} }
fn exists_and_is_null(&self) -> bool {
self.account.as_ref().map_or(false, |a| a.is_null())
}
/// Clone dirty data into new `AccountEntry`. This includes /// Clone dirty data into new `AccountEntry`. This includes
/// basic account data and modified storage keys. /// basic account data and modified storage keys.
/// Returns None if clean. /// Returns None if clean.
@ -121,6 +129,7 @@ impl AccountEntry {
/// basic account data and modified storage keys. /// basic account data and modified storage keys.
fn clone_dirty(&self) -> AccountEntry { fn clone_dirty(&self) -> AccountEntry {
AccountEntry { AccountEntry {
old_balance: self.old_balance,
account: self.account.as_ref().map(Account::clone_dirty), account: self.account.as_ref().map(Account::clone_dirty),
state: self.state, state: self.state,
} }
@ -129,6 +138,7 @@ impl AccountEntry {
// Create a new account entry and mark it as dirty. // Create a new account entry and mark it as dirty.
fn new_dirty(account: Option<Account>) -> AccountEntry { fn new_dirty(account: Option<Account>) -> AccountEntry {
AccountEntry { AccountEntry {
old_balance: account.as_ref().map(|a| a.balance().clone()),
account: account, account: account,
state: AccountState::Dirty, state: AccountState::Dirty,
} }
@ -137,6 +147,7 @@ impl AccountEntry {
// Create a new account entry and mark it as clean. // Create a new account entry and mark it as clean.
fn new_clean(account: Option<Account>) -> AccountEntry { fn new_clean(account: Option<Account>) -> AccountEntry {
AccountEntry { AccountEntry {
old_balance: account.as_ref().map(|a| a.balance().clone()),
account: account, account: account,
state: AccountState::CleanFresh, state: AccountState::CleanFresh,
} }
@ -145,6 +156,7 @@ impl AccountEntry {
// Create a new account entry and mark it as clean and cached. // Create a new account entry and mark it as clean and cached.
fn new_clean_cached(account: Option<Account>) -> AccountEntry { fn new_clean_cached(account: Option<Account>) -> AccountEntry {
AccountEntry { AccountEntry {
old_balance: account.as_ref().map(|a| a.balance().clone()),
account: account, account: account,
state: AccountState::CleanCached, state: AccountState::CleanCached,
} }
@ -181,7 +193,7 @@ pub fn check_proof(
let res = State::from_existing( let res = State::from_existing(
backend, backend,
root, root,
engine.account_start_nonce(), engine.account_start_nonce(env_info.number),
factories factories
); );
@ -266,8 +278,8 @@ pub enum CleanupMode<'a> {
ForceCreate, ForceCreate,
/// Don't delete null accounts upon touching, but also don't create them. /// Don't delete null accounts upon touching, but also don't create them.
NoEmpty, NoEmpty,
/// Add encountered null accounts to the provided kill-set, to be deleted later. /// Mark all touched accounts.
KillEmpty(&'a mut HashSet<Address>), TrackTouched(&'a mut HashSet<Address>),
} }
const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with valid root. Creating a SecTrieDB with a valid root will not fail. \ const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with valid root. Creating a SecTrieDB with a valid root will not fail. \
@ -549,31 +561,30 @@ impl<B: Backend> State<B> {
let is_value_transfer = !incr.is_zero(); let is_value_transfer = !incr.is_zero();
if is_value_transfer || (cleanup_mode == CleanupMode::ForceCreate && !self.exists(a)?) { if is_value_transfer || (cleanup_mode == CleanupMode::ForceCreate && !self.exists(a)?) {
self.require(a, false)?.add_balance(incr); self.require(a, false)?.add_balance(incr);
} else { } else if let CleanupMode::TrackTouched(set) = cleanup_mode {
match cleanup_mode { if self.exists(a)? {
CleanupMode::KillEmpty(set) => if !is_value_transfer && self.exists(a)? && !self.exists_and_not_null(a)? { set.insert(*a);
set.insert(a.clone()); self.touch(a)?;
},
_ => {}
} }
} }
Ok(()) Ok(())
} }
/// Subtract `decr` from the balance of account `a`. /// Subtract `decr` from the balance of account `a`.
pub fn sub_balance(&mut self, a: &Address, decr: &U256) -> trie::Result<()> { pub fn sub_balance(&mut self, a: &Address, decr: &U256, cleanup_mode: &mut CleanupMode) -> trie::Result<()> {
trace!(target: "state", "sub_balance({}, {}): {}", a, decr, self.balance(a)?); trace!(target: "state", "sub_balance({}, {}): {}", a, decr, self.balance(a)?);
if !decr.is_zero() || !self.exists(a)? { if !decr.is_zero() || !self.exists(a)? {
self.require(a, false)?.sub_balance(decr); self.require(a, false)?.sub_balance(decr);
} }
if let CleanupMode::TrackTouched(ref mut set) = *cleanup_mode {
set.insert(*a);
}
Ok(()) Ok(())
} }
/// Subtracts `by` from the balance of `from` and adds it to that of `to`. /// Subtracts `by` from the balance of `from` and adds it to that of `to`.
pub fn transfer_balance(&mut self, from: &Address, to: &Address, by: &U256, cleanup_mode: CleanupMode) -> trie::Result<()> { pub fn transfer_balance(&mut self, from: &Address, to: &Address, by: &U256, mut cleanup_mode: CleanupMode) -> trie::Result<()> {
self.sub_balance(from, by)?; self.sub_balance(from, by, &mut cleanup_mode)?;
self.add_balance(to, by, cleanup_mode)?; self.add_balance(to, by, cleanup_mode)?;
Ok(()) Ok(())
} }
@ -640,33 +651,33 @@ impl<B: Backend> State<B> {
} }
} }
/// Commit accounts to SecTrieDBMut. This is similar to cpp-ethereum's dev::eth::commit. fn touch(&mut self, a: &Address) -> trie::Result<()> {
/// `accounts` is mutable because we may need to commit the code or storage and record that. self.require(a, false)?;
Ok(())
}
/// Commits our cached account changes into the trie.
#[cfg_attr(feature="dev", allow(match_ref_pats))] #[cfg_attr(feature="dev", allow(match_ref_pats))]
#[cfg_attr(feature="dev", allow(needless_borrow))] #[cfg_attr(feature="dev", allow(needless_borrow))]
fn commit_into( pub fn commit(&mut self) -> Result<(), Error> {
factories: &Factories,
db: &mut B,
root: &mut H256,
accounts: &mut HashMap<Address, AccountEntry>
) -> Result<(), Error> {
// first, commit the sub trees. // first, commit the sub trees.
let mut accounts = self.cache.borrow_mut();
for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) {
if let Some(ref mut account) = a.account { if let Some(ref mut account) = a.account {
let addr_hash = account.address_hash(address); let addr_hash = account.address_hash(address);
{ {
let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash); let mut account_db = self.factories.accountdb.create(self.db.as_hashdb_mut(), addr_hash);
account.commit_storage(&factories.trie, account_db.as_hashdb_mut())?; account.commit_storage(&self.factories.trie, account_db.as_hashdb_mut())?;
account.commit_code(account_db.as_hashdb_mut()); account.commit_code(account_db.as_hashdb_mut());
} }
if !account.is_empty() { if !account.is_empty() {
db.note_non_null_account(address); self.db.note_non_null_account(address);
} }
} }
} }
{ {
let mut trie = factories.trie.from_existing(db.as_hashdb_mut(), root)?; let mut trie = self.factories.trie.from_existing(self.db.as_hashdb_mut(), &mut self.root)?;
for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) {
a.state = AccountState::Committed; a.state = AccountState::Committed;
match a.account { match a.account {
@ -676,7 +687,7 @@ impl<B: Backend> State<B> {
None => { None => {
trie.remove(address)?; trie.remove(address)?;
}, },
} };
} }
} }
@ -692,17 +703,30 @@ impl<B: Backend> State<B> {
} }
} }
/// Commits our cached account changes into the trie.
pub fn commit(&mut self) -> Result<(), Error> {
assert!(self.checkpoints.borrow().is_empty());
Self::commit_into(&self.factories, &mut self.db, &mut self.root, &mut *self.cache.borrow_mut())
}
/// Clear state cache /// Clear state cache
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.cache.borrow_mut().clear(); self.cache.borrow_mut().clear();
} }
/// Remove any touched empty or dust accounts.
pub fn kill_garbage(&mut self, touched: &HashSet<Address>, remove_empty_touched: bool, min_balance: &Option<U256>, kill_contracts: bool) -> trie::Result<()> {
let to_kill: HashSet<_> = {
self.cache.borrow().iter().filter_map(|(address, ref entry)|
if touched.contains(address) && // Check all touched accounts
((remove_empty_touched && entry.exists_and_is_null()) // Remove all empty touched accounts.
|| min_balance.map_or(false, |ref balance| entry.account.as_ref().map_or(false, |account|
(account.is_basic() || kill_contracts) // Remove all basic and optionally contract accounts where balance has been decreased.
&& account.balance() < balance && entry.old_balance.as_ref().map_or(false, |b| account.balance() < b)))) {
Some(address.clone())
} else { None }).collect()
};
for address in to_kill {
self.kill_account(&address);
}
Ok(())
}
#[cfg(test)] #[cfg(test)]
#[cfg(feature = "json-tests")] #[cfg(feature = "json-tests")]
/// Populate the state from `accounts`. /// Populate the state from `accounts`.
@ -1926,7 +1950,7 @@ mod tests {
assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); assert_eq!(state.balance(&a).unwrap(), U256::from(69u64));
state.commit().unwrap(); state.commit().unwrap();
assert_eq!(state.balance(&a).unwrap(), U256::from(69u64)); assert_eq!(state.balance(&a).unwrap(), U256::from(69u64));
state.sub_balance(&a, &U256::from(42u64)).unwrap(); state.sub_balance(&a, &U256::from(42u64), &mut CleanupMode::NoEmpty).unwrap();
assert_eq!(state.balance(&a).unwrap(), U256::from(27u64)); assert_eq!(state.balance(&a).unwrap(), U256::from(27u64));
state.commit().unwrap(); state.commit().unwrap();
assert_eq!(state.balance(&a).unwrap(), U256::from(27u64)); assert_eq!(state.balance(&a).unwrap(), U256::from(27u64));
@ -2026,4 +2050,44 @@ mod tests {
new_state.diff_from(state).unwrap(); new_state.diff_from(state).unwrap();
} }
#[test]
fn should_kill_garbage() {
let a = 10.into();
let b = 20.into();
let c = 30.into();
let d = 40.into();
let e = 50.into();
let x = 0.into();
let db = get_temp_state_db();
let (root, db) = {
let mut state = State::new(db, U256::from(0), Default::default());
state.add_balance(&a, &U256::default(), CleanupMode::ForceCreate).unwrap(); // create an empty account
state.add_balance(&b, &100.into(), CleanupMode::ForceCreate).unwrap(); // create a dust account
state.add_balance(&c, &101.into(), CleanupMode::ForceCreate).unwrap(); // create a normal account
state.add_balance(&d, &99.into(), CleanupMode::ForceCreate).unwrap(); // create another dust account
state.new_contract(&e, 100.into(), 1.into()); // create a contract account
state.init_code(&e, vec![0x00]).unwrap();
state.commit().unwrap();
state.drop()
};
let mut state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap();
let mut touched = HashSet::new();
state.add_balance(&a, &U256::default(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account
state.transfer_balance(&b, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance
state.transfer_balance(&c, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance
state.transfer_balance(&e, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance
state.kill_garbage(&touched, true, &None, false).unwrap();
assert!(!state.exists(&a).unwrap());
assert!(state.exists(&b).unwrap());
state.kill_garbage(&touched, true, &Some(100.into()), false).unwrap();
assert!(!state.exists(&b).unwrap());
assert!(state.exists(&c).unwrap());
assert!(state.exists(&d).unwrap());
assert!(state.exists(&e).unwrap());
state.kill_garbage(&touched, true, &Some(100.into()), true).unwrap();
assert!(state.exists(&c).unwrap());
assert!(state.exists(&d).unwrap());
assert!(!state.exists(&e).unwrap());
}
} }

View File

@ -18,7 +18,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use util::{Address, U256}; use util::{Address, U256};
use log_entry::LogEntry; use log_entry::LogEntry;
use evm::Schedule; use evm::{Schedule, CleanDustMode};
use super::CleanupMode; use super::CleanupMode;
/// State changes which should be applied in finalize, /// State changes which should be applied in finalize,
@ -28,8 +28,8 @@ pub struct Substate {
/// Any accounts that have suicided. /// Any accounts that have suicided.
pub suicides: HashSet<Address>, pub suicides: HashSet<Address>,
/// Any accounts that are tagged for garbage collection. /// Any accounts that are touched.
pub garbage: HashSet<Address>, pub touched: HashSet<Address>,
/// Any logs. /// Any logs.
pub logs: Vec<LogEntry>, pub logs: Vec<LogEntry>,
@ -50,7 +50,7 @@ impl Substate {
/// Merge secondary substate `s` into self, accruing each element correspondingly. /// Merge secondary substate `s` into self, accruing each element correspondingly.
pub fn accrue(&mut self, s: Substate) { pub fn accrue(&mut self, s: Substate) {
self.suicides.extend(s.suicides.into_iter()); self.suicides.extend(s.suicides.into_iter());
self.garbage.extend(s.garbage.into_iter()); self.touched.extend(s.touched.into_iter());
self.logs.extend(s.logs.into_iter()); self.logs.extend(s.logs.into_iter());
self.sstore_clears_count = self.sstore_clears_count + s.sstore_clears_count; self.sstore_clears_count = self.sstore_clears_count + s.sstore_clears_count;
self.contracts_created.extend(s.contracts_created.into_iter()); self.contracts_created.extend(s.contracts_created.into_iter());
@ -59,10 +59,10 @@ impl Substate {
/// Get the cleanup mode object from this. /// Get the cleanup mode object from this.
#[cfg_attr(feature="dev", allow(wrong_self_convention))] #[cfg_attr(feature="dev", allow(wrong_self_convention))]
pub fn to_cleanup_mode(&mut self, schedule: &Schedule) -> CleanupMode { pub fn to_cleanup_mode(&mut self, schedule: &Schedule) -> CleanupMode {
match (schedule.no_empty, schedule.kill_empty) { match (schedule.kill_dust != CleanDustMode::Off, schedule.no_empty, schedule.kill_empty) {
(false, _) => CleanupMode::ForceCreate, (false, false, _) => CleanupMode::ForceCreate,
(true, false) => CleanupMode::NoEmpty, (false, true, false) => CleanupMode::NoEmpty,
(true, true) => CleanupMode::KillEmpty(&mut self.garbage), (false, true, true) | (true, _, _,) => CleanupMode::TrackTouched(&mut self.touched),
} }
} }
} }

View File

@ -151,6 +151,8 @@ pub enum ExecutionError {
}, },
/// When execution tries to modify the state in static context /// When execution tries to modify the state in static context
MutableCallInStaticContext, MutableCallInStaticContext,
/// Returned when transacting from a non-existing account with dust protection enabled.
SenderMustExist,
/// Returned when internal evm error occurs. /// Returned when internal evm error occurs.
Internal(String), Internal(String),
/// Returned when generic transaction occurs /// Returned when generic transaction occurs
@ -179,6 +181,7 @@ impl fmt::Display for ExecutionError {
format!("Cost of transaction exceeds sender balance. {} is required \ format!("Cost of transaction exceeds sender balance. {} is required \
but the sender only has {}", required, got), but the sender only has {}", required, got),
MutableCallInStaticContext => "Mutable Call in static context".to_owned(), MutableCallInStaticContext => "Mutable Call in static context".to_owned(),
SenderMustExist => "Transacting from an empty account".to_owned(),
Internal(ref msg) => msg.clone(), Internal(ref msg) => msg.clone(),
TransactionMalformed(ref err) => format!("Malformed transaction: {}", err), TransactionMalformed(ref err) => format!("Malformed transaction: {}", err),
}; };

View File

@ -80,10 +80,18 @@ pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine, che
} }
// Verify transactions. // Verify transactions.
let mut transactions = Vec::new(); let mut transactions = Vec::new();
let nonce_cap = if header.number() >= engine.params().dust_protection_transition {
Some((engine.params().nonce_cap_increment * header.number()).into())
} else { None };
{ {
let v = BlockView::new(&bytes); let v = BlockView::new(&bytes);
for t in v.transactions() { for t in v.transactions() {
let t = engine.verify_transaction(t, &header)?; let t = engine.verify_transaction(t, &header)?;
if let Some(max_nonce) = nonce_cap {
if t.nonce >= max_nonce {
return Err(BlockError::TooManyTransactions(t.sender()).into());
}
}
transactions.push(t); transactions.push(t);
} }
} }
@ -391,6 +399,12 @@ mod tests {
verify_block_family(&header, bytes, engine, bc) verify_block_family(&header, bytes, engine, bc)
} }
fn unordered_test(bytes: &[u8], engine: &Engine) -> Result<(), Error> {
let header = BlockView::new(bytes).header();
verify_block_unordered(header, bytes.to_vec(), engine, false)?;
Ok(())
}
#[test] #[test]
#[cfg_attr(feature="dev", allow(similar_names))] #[cfg_attr(feature="dev", allow(similar_names))]
fn test_verify_block() { fn test_verify_block() {
@ -556,4 +570,34 @@ mod tests {
// TODO: some additional uncle checks // TODO: some additional uncle checks
} }
#[test]
fn dust_protection() {
use ethkey::{Generator, Random};
use types::transaction::{Transaction, Action};
use engines::NullEngine;
let mut params = CommonParams::default();
params.dust_protection_transition = 0;
params.nonce_cap_increment = 2;
let mut header = Header::default();
header.set_number(1);
let keypair = Random.generate().unwrap();
let bad_transactions: Vec<_> = (0..3).map(|i| Transaction {
action: Action::Create,
value: U256::zero(),
data: Vec::new(),
gas: 0.into(),
gas_price: U256::zero(),
nonce: i.into(),
}.sign(keypair.secret(), None)).collect();
let good_transactions = [bad_transactions[0].clone(), bad_transactions[1].clone()];
let engine = NullEngine::new(params, BTreeMap::new());
check_fail(unordered_test(&create_test_block_with_data(&header, &bad_transactions, &[]), &engine), TooManyTransactions(keypair.address()));
unordered_test(&create_test_block_with_data(&header, &good_transactions, &[]), &engine).unwrap();
}
} }

View File

@ -81,6 +81,14 @@ pub struct Params {
/// See `CommonParams` docs. /// See `CommonParams` docs.
#[serde(rename="eip214Transition")] #[serde(rename="eip214Transition")]
pub eip214_transition: Option<Uint>, pub eip214_transition: Option<Uint>,
/// See `CommonParams` docs.
#[serde(rename="dustProtectionTransition")]
pub dust_protection_transition: Option<Uint>,
/// See `CommonParams` docs.
#[serde(rename="nonceCapIncrement")]
pub nonce_cap_increment: Option<Uint>,
/// See `CommonParams` docs.
pub remove_dust_contracts : Option<bool>,
} }
#[cfg(test)] #[cfg(test)]

View File

@ -107,7 +107,7 @@ impl ContractClient for LightRegistrar {
self.on_demand self.on_demand
.request(ctx, on_demand::request::TransactionProof { .request(ctx, on_demand::request::TransactionProof {
tx: Transaction { tx: Transaction {
nonce: self.client.engine().account_start_nonce(), nonce: self.client.engine().account_start_nonce(header.number()),
action: Action::Call(address), action: Action::Call(address),
gas: 50_000_000.into(), gas: 50_000_000.into(),
gas_price: 0.into(), gas_price: 0.into(),

View File

@ -67,7 +67,7 @@ impl IoHandler<ClientIoMessage> for QueueCull {
let (sync, on_demand, txq) = (self.sync.clone(), self.on_demand.clone(), self.txq.clone()); let (sync, on_demand, txq) = (self.sync.clone(), self.on_demand.clone(), self.txq.clone());
let best_header = self.client.best_block_header(); let best_header = self.client.best_block_header();
let start_nonce = self.client.engine().account_start_nonce(); let start_nonce = self.client.engine().account_start_nonce(best_header.number());
info!(target: "cull", "Attempting to cull queued transactions from {} senders.", senders.len()); info!(target: "cull", "Attempting to cull queued transactions from {} senders.", senders.len());
self.remote.spawn_with_timeout(move || { self.remote.spawn_with_timeout(move || {

View File

@ -227,7 +227,8 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
} }
// start on_demand service. // start on_demand service.
let on_demand = Arc::new(::light::on_demand::OnDemand::new(cache.clone())); let account_start_nonce = service.client().engine().account_start_nonce(0);
let on_demand = Arc::new(::light::on_demand::OnDemand::new(cache.clone(), account_start_nonce));
// set network path. // set network path.
net_conf.net_config_path = Some(db_dirs.network_path().to_string_lossy().into_owned()); net_conf.net_config_path = Some(db_dirs.network_path().to_string_lossy().into_owned());

View File

@ -284,7 +284,7 @@ impl LightDispatcher {
} }
let best_header = self.client.best_block_header(); let best_header = self.client.best_block_header();
let account_start_nonce = self.client.engine().account_start_nonce(); let account_start_nonce = self.client.engine().account_start_nonce(best_header.number());
let nonce_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account { let nonce_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account {
header: best_header.into(), header: best_header.into(),
address: addr, address: addr,