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),
|
||||
extra_data: Bytes,
|
||||
) -> 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 {
|
||||
block: ExecutedBlock::new(state, tracing),
|
||||
engine: engine,
|
||||
@ -269,7 +270,7 @@ impl<'x> OpenBlock<'x> {
|
||||
};
|
||||
|
||||
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_timestamp_now(parent.timestamp());
|
||||
r.block.header.set_extra_data(extra_data);
|
||||
@ -556,7 +557,7 @@ pub fn enact(
|
||||
) -> Result<LockedBlock, Error> {
|
||||
{
|
||||
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",
|
||||
header.number(), s.root(), header.author(), s.balance(&header.author())?);
|
||||
}
|
||||
|
@ -810,7 +810,7 @@ impl Client {
|
||||
}
|
||||
|
||||
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(
|
||||
self.state_db.lock().boxed_clone_canon(&header.hash()),
|
||||
header.state_root(),
|
||||
self.engine.account_start_nonce(),
|
||||
self.engine.account_start_nonce(header.number()),
|
||||
self.factories.clone())
|
||||
.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 {
|
||||
let from = Address::default();
|
||||
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),
|
||||
gas: U256::from(50_000_000),
|
||||
gas_price: U256::default(),
|
||||
|
@ -89,7 +89,7 @@ impl EvmTestClient {
|
||||
-> Result<(U256, Vec<u8>), EvmTestError>
|
||||
{
|
||||
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)?;
|
||||
let info = client::EnvInfo {
|
||||
number: genesis.number(),
|
||||
|
@ -161,8 +161,14 @@ pub trait Engine : Sync + Send {
|
||||
fn maximum_uncle_count(&self) -> usize { 2 }
|
||||
/// The number of generations back that uncles can be.
|
||||
fn maximum_uncle_age(&self) -> usize { 6 }
|
||||
/// The nonce with which accounts begin.
|
||||
fn account_start_nonce(&self) -> U256 { self.params().account_start_nonce }
|
||||
/// The nonce with which accounts begin at given block.
|
||||
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.
|
||||
fn on_new_block(&self, block: &mut ExecutedBlock, last_hashes: Arc<LastHashes>) -> Result<(), Error> {
|
||||
|
@ -165,6 +165,8 @@ pub enum BlockError {
|
||||
InvalidNumber(Mismatch<BlockNumber>),
|
||||
/// Block number isn't sensible.
|
||||
RidiculousNumber(OutOfBounds<BlockNumber>),
|
||||
/// Too many transactions from a particular address.
|
||||
TooManyTransactions(Address),
|
||||
/// Parent given is unknown.
|
||||
UnknownParent(H256),
|
||||
/// Uncle parent given is unknown.
|
||||
@ -205,6 +207,7 @@ impl fmt::Display for BlockError {
|
||||
UnknownParent(ref hash) => format!("Unknown parent: {}", hash),
|
||||
UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash),
|
||||
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))
|
||||
|
@ -98,8 +98,7 @@ mod tests {
|
||||
let engine = &spec.engine;
|
||||
let genesis_header = spec.genesis_header();
|
||||
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(), Default::default()).unwrap();
|
||||
let s = State::from_existing(db, genesis_header.state_root().clone(), engine.account_start_nonce(0), Default::default()).unwrap();
|
||||
assert_eq!(s.balance(&"0000000000000000000000000000000000000001".into()).unwrap(), 1u64.into());
|
||||
assert_eq!(s.balance(&"0000000000000000000000000000000000000002".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::vmtype::VMType;
|
||||
pub use self::factory::Factory;
|
||||
pub use self::schedule::Schedule;
|
||||
pub use self::schedule::{Schedule, CleanDustMode};
|
||||
pub use types::executed::CallType;
|
||||
|
@ -108,6 +108,19 @@ pub struct Schedule {
|
||||
pub blockhash_gas: usize,
|
||||
/// Static Call opcode enabled.
|
||||
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 {
|
||||
@ -168,15 +181,16 @@ impl Schedule {
|
||||
kill_empty: kill_empty,
|
||||
blockhash_gas: 20,
|
||||
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 {
|
||||
let mut schedule = Schedule::new_post_eip150(usize::max_value(), true, true, true);
|
||||
schedule.apply_params(block_number, params);
|
||||
schedule
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply common spec config parameters to the schedule.
|
||||
pub fn apply_params(&mut self, block_number: u64, params: &CommonParams) {
|
||||
@ -186,6 +200,9 @@ impl Schedule {
|
||||
if block_number >= params.eip210_transition {
|
||||
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.
|
||||
@ -244,6 +261,7 @@ impl Schedule {
|
||||
kill_empty: false,
|
||||
blockhash_gas: 20,
|
||||
have_static_call: false,
|
||||
kill_dust: CleanDustMode::Off,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ use engines::Engine;
|
||||
use types::executed::CallType;
|
||||
use env_info::EnvInfo;
|
||||
use error::ExecutionError;
|
||||
use evm::{self, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData};
|
||||
use evm::{self, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData, CleanDustMode};
|
||||
use externalities::*;
|
||||
use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer};
|
||||
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 }));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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 }));
|
||||
}
|
||||
|
||||
let mut substate = Substate::new();
|
||||
|
||||
// NOTE: there can be no invalid transactions from this point.
|
||||
if !t.is_unsigned() {
|
||||
self.state.inc_nonce(&sender)?;
|
||||
}
|
||||
self.state.sub_balance(&sender, &U256::from(gas_cost))?;
|
||||
|
||||
let mut substate = Substate::new();
|
||||
self.state.sub_balance(&sender, &U256::from(gas_cost), &mut substate.to_cleanup_mode(&schedule))?;
|
||||
|
||||
let (result, output) = match t.action {
|
||||
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 prev_bal = self.state.balance(¶ms.address)?;
|
||||
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);
|
||||
} else {
|
||||
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
|
||||
for address in &substate.garbage {
|
||||
if self.state.exists(address)? && !self.state.exists_and_not_null(address)? {
|
||||
self.state.kill_account(address);
|
||||
}
|
||||
}
|
||||
let min_balance = if schedule.kill_dust != CleanDustMode::Off { Some(U256::from(schedule.tx_gas) * t.gas_price) } else { None };
|
||||
self.state.kill_garbage(&substate.touched, schedule.kill_empty, &min_balance, schedule.kill_dust == CleanDustMode::WithCodeAndStorage)?;
|
||||
|
||||
match result {
|
||||
Err(evm::Error::Internal(msg)) => Err(ExecutionError::Internal(msg)),
|
||||
|
@ -17,7 +17,7 @@
|
||||
//! Transaction Execution environment.
|
||||
use util::*;
|
||||
use action_params::{ActionParams, ActionValue};
|
||||
use state::{Backend as StateBackend, State, Substate};
|
||||
use state::{Backend as StateBackend, State, Substate, CleanupMode};
|
||||
use engines::Engine;
|
||||
use env_info::EnvInfo;
|
||||
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)?;
|
||||
if &address == refund_address {
|
||||
// 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 {
|
||||
trace!(target: "ext", "Suiciding {} -> {} (xfer: {})", address, refund_address, balance);
|
||||
self.state.transfer_balance(
|
||||
|
@ -328,7 +328,10 @@ impl Miner {
|
||||
let _timer = PerfTimer::new("prepare_block");
|
||||
let chain_info = chain.chain_info();
|
||||
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 last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash());
|
||||
let best_hash = chain_info.best_block_hash;
|
||||
|
@ -1084,11 +1084,11 @@ impl TransactionQueue {
|
||||
|
||||
/// Returns top transactions from the queue ordered by priority.
|
||||
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) {
|
||||
|
||||
let mut delayed = HashSet::new();
|
||||
@ -1098,6 +1098,11 @@ impl TransactionQueue {
|
||||
if delayed.contains(&sender) {
|
||||
continue;
|
||||
}
|
||||
if let Some(max_nonce) = nonce_cap {
|
||||
if tx.nonce() >= max_nonce {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let delay = match tx.condition {
|
||||
Some(Condition::Number(n)) => n > best_block,
|
||||
Some(Condition::Timestamp(t)) => t > best_timestamp,
|
||||
@ -1112,16 +1117,16 @@ impl TransactionQueue {
|
||||
}
|
||||
|
||||
/// 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();
|
||||
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
|
||||
}
|
||||
|
||||
/// Return all ready transactions.
|
||||
pub fn pending_transactions(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<PendingTransaction> {
|
||||
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
|
||||
}
|
||||
|
||||
@ -2205,9 +2210,9 @@ pub mod test {
|
||||
// then
|
||||
assert_eq!(res1, 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);
|
||||
let top = txq.top_transactions_at(1, 0);
|
||||
let top = txq.top_transactions_at(1, 0, None);
|
||||
assert_eq!(top.len(), 2);
|
||||
}
|
||||
|
||||
@ -2809,4 +2814,19 @@ pub mod test {
|
||||
// then
|
||||
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};
|
||||
|
||||
let transaction = Transaction {
|
||||
nonce: engine.account_start_nonce(),
|
||||
nonce: engine.account_start_nonce(header.number()),
|
||||
action: Action::Call(addr),
|
||||
gas: 50_000_000.into(),
|
||||
gas_price: 0.into(),
|
||||
@ -469,7 +469,7 @@ impl Rebuilder for ChunkRebuilder {
|
||||
let mut state = State::from_existing(
|
||||
db.boxed_clone(),
|
||||
self.manifest.state_root.clone(),
|
||||
engine.account_start_nonce(),
|
||||
engine.account_start_nonce(target_header.number()),
|
||||
factories,
|
||||
).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.
|
||||
let nonce = RefCell::new(client.engine().account_start_nonce());
|
||||
let nonce = RefCell::new(client.engine().account_start_nonce(0));
|
||||
let exec = |addr, data| {
|
||||
let mut nonce = nonce.borrow_mut();
|
||||
let transaction = Transaction {
|
||||
|
@ -76,6 +76,12 @@ pub struct CommonParams {
|
||||
pub eip211_transition: BlockNumber,
|
||||
/// Number of first block where EIP-214 rules begin.
|
||||
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 {
|
||||
@ -100,6 +106,9 @@ impl From<ethjson::spec::Params> for CommonParams {
|
||||
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),
|
||||
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 mut state = State::from_existing(
|
||||
@ -460,7 +469,7 @@ mod tests {
|
||||
::ethcore_logger::init_log();
|
||||
let spec = Spec::new_test_constructor();
|
||||
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();
|
||||
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
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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 (`None`)
|
||||
struct AccountEntry {
|
||||
/// Account entry. `None` if account known to be non-existant.
|
||||
account: Option<Account>,
|
||||
/// Unmodified account balance.
|
||||
old_balance: Option<U256>,
|
||||
/// Entry state.
|
||||
state: AccountState,
|
||||
}
|
||||
|
||||
@ -107,6 +111,10 @@ impl AccountEntry {
|
||||
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
|
||||
/// basic account data and modified storage keys.
|
||||
/// Returns None if clean.
|
||||
@ -121,6 +129,7 @@ impl AccountEntry {
|
||||
/// basic account data and modified storage keys.
|
||||
fn clone_dirty(&self) -> AccountEntry {
|
||||
AccountEntry {
|
||||
old_balance: self.old_balance,
|
||||
account: self.account.as_ref().map(Account::clone_dirty),
|
||||
state: self.state,
|
||||
}
|
||||
@ -129,6 +138,7 @@ impl AccountEntry {
|
||||
// Create a new account entry and mark it as dirty.
|
||||
fn new_dirty(account: Option<Account>) -> AccountEntry {
|
||||
AccountEntry {
|
||||
old_balance: account.as_ref().map(|a| a.balance().clone()),
|
||||
account: account,
|
||||
state: AccountState::Dirty,
|
||||
}
|
||||
@ -137,6 +147,7 @@ impl AccountEntry {
|
||||
// Create a new account entry and mark it as clean.
|
||||
fn new_clean(account: Option<Account>) -> AccountEntry {
|
||||
AccountEntry {
|
||||
old_balance: account.as_ref().map(|a| a.balance().clone()),
|
||||
account: account,
|
||||
state: AccountState::CleanFresh,
|
||||
}
|
||||
@ -145,6 +156,7 @@ impl AccountEntry {
|
||||
// Create a new account entry and mark it as clean and cached.
|
||||
fn new_clean_cached(account: Option<Account>) -> AccountEntry {
|
||||
AccountEntry {
|
||||
old_balance: account.as_ref().map(|a| a.balance().clone()),
|
||||
account: account,
|
||||
state: AccountState::CleanCached,
|
||||
}
|
||||
@ -181,7 +193,7 @@ pub fn check_proof(
|
||||
let res = State::from_existing(
|
||||
backend,
|
||||
root,
|
||||
engine.account_start_nonce(),
|
||||
engine.account_start_nonce(env_info.number),
|
||||
factories
|
||||
);
|
||||
|
||||
@ -266,8 +278,8 @@ pub enum CleanupMode<'a> {
|
||||
ForceCreate,
|
||||
/// Don't delete null accounts upon touching, but also don't create them.
|
||||
NoEmpty,
|
||||
/// Add encountered null accounts to the provided kill-set, to be deleted later.
|
||||
KillEmpty(&'a mut HashSet<Address>),
|
||||
/// Mark all touched accounts.
|
||||
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. \
|
||||
@ -549,31 +561,30 @@ impl<B: Backend> State<B> {
|
||||
let is_value_transfer = !incr.is_zero();
|
||||
if is_value_transfer || (cleanup_mode == CleanupMode::ForceCreate && !self.exists(a)?) {
|
||||
self.require(a, false)?.add_balance(incr);
|
||||
} else {
|
||||
match cleanup_mode {
|
||||
CleanupMode::KillEmpty(set) => if !is_value_transfer && self.exists(a)? && !self.exists_and_not_null(a)? {
|
||||
set.insert(a.clone());
|
||||
},
|
||||
_ => {}
|
||||
} else if let CleanupMode::TrackTouched(set) = cleanup_mode {
|
||||
if self.exists(a)? {
|
||||
set.insert(*a);
|
||||
self.touch(a)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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)?);
|
||||
if !decr.is_zero() || !self.exists(a)? {
|
||||
self.require(a, false)?.sub_balance(decr);
|
||||
}
|
||||
|
||||
if let CleanupMode::TrackTouched(ref mut set) = *cleanup_mode {
|
||||
set.insert(*a);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
self.sub_balance(from, by)?;
|
||||
pub fn transfer_balance(&mut self, from: &Address, to: &Address, by: &U256, mut cleanup_mode: CleanupMode) -> trie::Result<()> {
|
||||
self.sub_balance(from, by, &mut cleanup_mode)?;
|
||||
self.add_balance(to, by, cleanup_mode)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -640,33 +651,33 @@ impl<B: Backend> State<B> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit accounts to SecTrieDBMut. This is similar to cpp-ethereum's dev::eth::commit.
|
||||
/// `accounts` is mutable because we may need to commit the code or storage and record that.
|
||||
fn touch(&mut self, a: &Address) -> trie::Result<()> {
|
||||
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(needless_borrow))]
|
||||
fn commit_into(
|
||||
factories: &Factories,
|
||||
db: &mut B,
|
||||
root: &mut H256,
|
||||
accounts: &mut HashMap<Address, AccountEntry>
|
||||
) -> Result<(), Error> {
|
||||
pub fn commit(&mut self) -> Result<(), Error> {
|
||||
// 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()) {
|
||||
if let Some(ref mut account) = a.account {
|
||||
let addr_hash = account.address_hash(address);
|
||||
{
|
||||
let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash);
|
||||
account.commit_storage(&factories.trie, account_db.as_hashdb_mut())?;
|
||||
let mut account_db = self.factories.accountdb.create(self.db.as_hashdb_mut(), addr_hash);
|
||||
account.commit_storage(&self.factories.trie, account_db.as_hashdb_mut())?;
|
||||
account.commit_code(account_db.as_hashdb_mut());
|
||||
}
|
||||
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()) {
|
||||
a.state = AccountState::Committed;
|
||||
match a.account {
|
||||
@ -676,7 +687,7 @@ impl<B: Backend> State<B> {
|
||||
None => {
|
||||
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
|
||||
pub fn clear(&mut self) {
|
||||
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(feature = "json-tests")]
|
||||
/// Populate the state from `accounts`.
|
||||
@ -1926,7 +1950,7 @@ mod tests {
|
||||
assert_eq!(state.balance(&a).unwrap(), U256::from(69u64));
|
||||
state.commit().unwrap();
|
||||
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));
|
||||
state.commit().unwrap();
|
||||
assert_eq!(state.balance(&a).unwrap(), U256::from(27u64));
|
||||
@ -2026,4 +2050,44 @@ mod tests {
|
||||
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 util::{Address, U256};
|
||||
use log_entry::LogEntry;
|
||||
use evm::Schedule;
|
||||
use evm::{Schedule, CleanDustMode};
|
||||
use super::CleanupMode;
|
||||
|
||||
/// State changes which should be applied in finalize,
|
||||
@ -28,8 +28,8 @@ pub struct Substate {
|
||||
/// Any accounts that have suicided.
|
||||
pub suicides: HashSet<Address>,
|
||||
|
||||
/// Any accounts that are tagged for garbage collection.
|
||||
pub garbage: HashSet<Address>,
|
||||
/// Any accounts that are touched.
|
||||
pub touched: HashSet<Address>,
|
||||
|
||||
/// Any logs.
|
||||
pub logs: Vec<LogEntry>,
|
||||
@ -50,7 +50,7 @@ impl Substate {
|
||||
/// Merge secondary substate `s` into self, accruing each element correspondingly.
|
||||
pub fn accrue(&mut self, s: Substate) {
|
||||
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.sstore_clears_count = self.sstore_clears_count + s.sstore_clears_count;
|
||||
self.contracts_created.extend(s.contracts_created.into_iter());
|
||||
@ -59,10 +59,10 @@ impl Substate {
|
||||
/// Get the cleanup mode object from this.
|
||||
#[cfg_attr(feature="dev", allow(wrong_self_convention))]
|
||||
pub fn to_cleanup_mode(&mut self, schedule: &Schedule) -> CleanupMode {
|
||||
match (schedule.no_empty, schedule.kill_empty) {
|
||||
(false, _) => CleanupMode::ForceCreate,
|
||||
(true, false) => CleanupMode::NoEmpty,
|
||||
(true, true) => CleanupMode::KillEmpty(&mut self.garbage),
|
||||
match (schedule.kill_dust != CleanDustMode::Off, schedule.no_empty, schedule.kill_empty) {
|
||||
(false, false, _) => CleanupMode::ForceCreate,
|
||||
(false, true, false) => CleanupMode::NoEmpty,
|
||||
(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
|
||||
MutableCallInStaticContext,
|
||||
/// Returned when transacting from a non-existing account with dust protection enabled.
|
||||
SenderMustExist,
|
||||
/// Returned when internal evm error occurs.
|
||||
Internal(String),
|
||||
/// Returned when generic transaction occurs
|
||||
@ -179,6 +181,7 @@ impl fmt::Display for ExecutionError {
|
||||
format!("Cost of transaction exceeds sender balance. {} is required \
|
||||
but the sender only has {}", required, got),
|
||||
MutableCallInStaticContext => "Mutable Call in static context".to_owned(),
|
||||
SenderMustExist => "Transacting from an empty account".to_owned(),
|
||||
Internal(ref msg) => msg.clone(),
|
||||
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.
|
||||
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);
|
||||
for t in v.transactions() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -391,6 +399,12 @@ mod tests {
|
||||
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]
|
||||
#[cfg_attr(feature="dev", allow(similar_names))]
|
||||
fn test_verify_block() {
|
||||
@ -556,4 +570,34 @@ mod tests {
|
||||
|
||||
// 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.
|
||||
#[serde(rename="eip214Transition")]
|
||||
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)]
|
||||
|
@ -107,7 +107,7 @@ impl ContractClient for LightRegistrar {
|
||||
self.on_demand
|
||||
.request(ctx, on_demand::request::TransactionProof {
|
||||
tx: Transaction {
|
||||
nonce: self.client.engine().account_start_nonce(),
|
||||
nonce: self.client.engine().account_start_nonce(header.number()),
|
||||
action: Action::Call(address),
|
||||
gas: 50_000_000.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 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());
|
||||
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.
|
||||
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.
|
||||
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 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 {
|
||||
header: best_header.into(),
|
||||
address: addr,
|
||||
|
Loading…
Reference in New Issue
Block a user