EIP-168, 169: Dust protection (#4757)
* Dust protection * Track touched accounts in the substate * Minor alterations
This commit is contained in:
parent
6b16fe3f14
commit
57626b60e7
@ -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())?);
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
@ -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(),
|
||||||
|
@ -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> {
|
||||||
|
@ -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))
|
||||||
|
@ -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());
|
||||||
|
@ -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;
|
||||||
|
@ -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,10 +181,11 @@ 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);
|
||||||
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(¶ms.address)?;
|
let prev_bal = self.state.balance(¶ms.address)?;
|
||||||
if let ActionValue::Transfer(val) = params.value {
|
if let ActionValue::Transfer(val) = params.value {
|
||||||
self.state.sub_balance(¶ms.sender, &val)?;
|
self.state.sub_balance(¶ms.sender, &val, &mut substate.to_cleanup_mode(&schedule))?;
|
||||||
self.state.new_contract(¶ms.address, val + prev_bal, nonce_offset);
|
self.state.new_contract(¶ms.address, val + prev_bal, nonce_offset);
|
||||||
} else {
|
} else {
|
||||||
self.state.new_contract(¶ms.address, prev_bal, nonce_offset);
|
self.state.new_contract(¶ms.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)),
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))?;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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} }
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
};
|
};
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)]
|
||||||
|
@ -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(),
|
||||||
|
@ -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 || {
|
||||||
|
@ -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());
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user