Preserve cache on reverting the snapshot (#2488)
* Preserve cache on reverting the snapshot * Renamed merge_with into replace_with * Renamed and documented snapshotting methods
This commit is contained in:
parent
e380955c34
commit
6c1b2fbed5
@ -265,7 +265,7 @@ impl<'a> Executive<'a> {
|
|||||||
let cost = self.engine.cost_of_builtin(¶ms.code_address, data);
|
let cost = self.engine.cost_of_builtin(¶ms.code_address, data);
|
||||||
if cost <= params.gas {
|
if cost <= params.gas {
|
||||||
self.engine.execute_builtin(¶ms.code_address, data, &mut output);
|
self.engine.execute_builtin(¶ms.code_address, data, &mut output);
|
||||||
self.state.clear_snapshot();
|
self.state.discard_snapshot();
|
||||||
|
|
||||||
// trace only top level calls to builtins to avoid DDoS attacks
|
// trace only top level calls to builtins to avoid DDoS attacks
|
||||||
if self.depth == 0 {
|
if self.depth == 0 {
|
||||||
@ -285,7 +285,7 @@ impl<'a> Executive<'a> {
|
|||||||
Ok(params.gas - cost)
|
Ok(params.gas - cost)
|
||||||
} else {
|
} else {
|
||||||
// just drain the whole gas
|
// just drain the whole gas
|
||||||
self.state.revert_snapshot();
|
self.state.revert_to_snapshot();
|
||||||
|
|
||||||
tracer.trace_failed_call(trace_info, vec![], evm::Error::OutOfGas.into());
|
tracer.trace_failed_call(trace_info, vec![], evm::Error::OutOfGas.into());
|
||||||
|
|
||||||
@ -331,7 +331,7 @@ impl<'a> Executive<'a> {
|
|||||||
res
|
res
|
||||||
} else {
|
} else {
|
||||||
// otherwise it's just a basic transaction, only do tracing, if necessary.
|
// otherwise it's just a basic transaction, only do tracing, if necessary.
|
||||||
self.state.clear_snapshot();
|
self.state.discard_snapshot();
|
||||||
|
|
||||||
tracer.trace_call(trace_info, U256::zero(), trace_output, vec![]);
|
tracer.trace_call(trace_info, U256::zero(), trace_output, vec![]);
|
||||||
Ok(params.gas)
|
Ok(params.gas)
|
||||||
@ -473,10 +473,10 @@ impl<'a> Executive<'a> {
|
|||||||
| Err(evm::Error::BadInstruction {.. })
|
| Err(evm::Error::BadInstruction {.. })
|
||||||
| Err(evm::Error::StackUnderflow {..})
|
| Err(evm::Error::StackUnderflow {..})
|
||||||
| Err(evm::Error::OutOfStack {..}) => {
|
| Err(evm::Error::OutOfStack {..}) => {
|
||||||
self.state.revert_snapshot();
|
self.state.revert_to_snapshot();
|
||||||
},
|
},
|
||||||
Ok(_) | Err(evm::Error::Internal) => {
|
Ok(_) | Err(evm::Error::Internal) => {
|
||||||
self.state.clear_snapshot();
|
self.state.discard_snapshot();
|
||||||
substate.accrue(un_substate);
|
substate.accrue(un_substate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -394,7 +394,7 @@ impl Account {
|
|||||||
/// Replace self with the data from other account merging storage cache.
|
/// Replace self with the data from other account merging storage cache.
|
||||||
/// Basic account data and all modifications are overwritten
|
/// Basic account data and all modifications are overwritten
|
||||||
/// with new values.
|
/// with new values.
|
||||||
pub fn merge_with(&mut self, other: Account) {
|
pub fn overwrite_with(&mut self, other: Account) {
|
||||||
self.balance = other.balance;
|
self.balance = other.balance;
|
||||||
self.nonce = other.nonce;
|
self.nonce = other.nonce;
|
||||||
self.storage_root = other.storage_root;
|
self.storage_root = other.storage_root;
|
||||||
|
@ -93,7 +93,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 {
|
||||||
account: account,
|
account: account,
|
||||||
@ -101,13 +101,27 @@ 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 {
|
||||||
account: account,
|
account: account,
|
||||||
state: AccountState::Clean,
|
state: AccountState::Clean,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace data with another entry but preserve storage cache.
|
||||||
|
fn overwrite_with(&mut self, other: AccountEntry) {
|
||||||
|
self.state = other.state;
|
||||||
|
match other.account {
|
||||||
|
Some(acc) => match self.account {
|
||||||
|
Some(ref mut ours) => {
|
||||||
|
ours.overwrite_with(acc);
|
||||||
|
},
|
||||||
|
None => {},
|
||||||
|
},
|
||||||
|
None => self.account = None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of the entire state of all accounts in the system.
|
/// Representation of the entire state of all accounts in the system.
|
||||||
@ -142,10 +156,24 @@ impl AccountEntry {
|
|||||||
///
|
///
|
||||||
/// Upon destruction all the local cache data merged into the global cache.
|
/// Upon destruction all the local cache data merged into the global cache.
|
||||||
/// The merge might be rejected if current state is non-canonical.
|
/// The merge might be rejected if current state is non-canonical.
|
||||||
|
///
|
||||||
|
/// State snapshotting.
|
||||||
|
///
|
||||||
|
/// A new snapshot can be created with `snapshot()`. Snapshots can be
|
||||||
|
/// created in a hierarchy.
|
||||||
|
/// When a snapshot is active all changes are applied directly into
|
||||||
|
/// `cache` and the original value is copied into an active snapshot.
|
||||||
|
/// Reverting a snapshot with `revert_to_snapshot` involves copying
|
||||||
|
/// original values from the latest snapshot back into `cache`. The code
|
||||||
|
/// takes care not to overwrite cached storage while doing that.
|
||||||
|
/// Snapshot can be discateded with `discard_snapshot`. All of the orignal
|
||||||
|
/// backed-up values are moved into a parent snapshot (if any).
|
||||||
|
///
|
||||||
pub struct State {
|
pub struct State {
|
||||||
db: StateDB,
|
db: StateDB,
|
||||||
root: H256,
|
root: H256,
|
||||||
cache: RefCell<HashMap<Address, AccountEntry>>,
|
cache: RefCell<HashMap<Address, AccountEntry>>,
|
||||||
|
// The original account is preserved in
|
||||||
snapshots: RefCell<Vec<HashMap<Address, Option<AccountEntry>>>>,
|
snapshots: RefCell<Vec<HashMap<Address, Option<AccountEntry>>>>,
|
||||||
account_start_nonce: U256,
|
account_start_nonce: U256,
|
||||||
factories: Factories,
|
factories: Factories,
|
||||||
@ -199,31 +227,44 @@ impl State {
|
|||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a recoverable snaphot of this state
|
/// Create a recoverable snaphot of this state.
|
||||||
pub fn snapshot(&mut self) {
|
pub fn snapshot(&mut self) {
|
||||||
self.snapshots.borrow_mut().push(HashMap::new());
|
self.snapshots.borrow_mut().push(HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Merge last snapshot with previous
|
/// Merge last snapshot with previous.
|
||||||
pub fn clear_snapshot(&mut self) {
|
pub fn discard_snapshot(&mut self) {
|
||||||
// merge with previous snapshot
|
// merge with previous snapshot
|
||||||
let last = self.snapshots.borrow_mut().pop();
|
let last = self.snapshots.borrow_mut().pop();
|
||||||
if let Some(mut snapshot) = last {
|
if let Some(mut snapshot) = last {
|
||||||
if let Some(ref mut prev) = self.snapshots.borrow_mut().last_mut() {
|
if let Some(ref mut prev) = self.snapshots.borrow_mut().last_mut() {
|
||||||
for (k, v) in snapshot.drain() {
|
if prev.is_empty() {
|
||||||
prev.entry(k).or_insert(v);
|
**prev = snapshot;
|
||||||
|
} else {
|
||||||
|
for (k, v) in snapshot.drain() {
|
||||||
|
prev.entry(k).or_insert(v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Revert to snapshot
|
/// Revert to the last snapshot and discard it.
|
||||||
pub fn revert_snapshot(&mut self) {
|
pub fn revert_to_snapshot(&mut self) {
|
||||||
if let Some(mut snapshot) = self.snapshots.borrow_mut().pop() {
|
if let Some(mut snapshot) = self.snapshots.borrow_mut().pop() {
|
||||||
for (k, v) in snapshot.drain() {
|
for (k, v) in snapshot.drain() {
|
||||||
match v {
|
match v {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
self.cache.borrow_mut().insert(k, v);
|
match self.cache.borrow_mut().entry(k) {
|
||||||
|
Entry::Occupied(mut e) => {
|
||||||
|
// Merge snapshotted changes back into the main account
|
||||||
|
// storage preserving the cache.
|
||||||
|
e.get_mut().overwrite_with(v);
|
||||||
|
},
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
e.insert(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
match self.cache.borrow_mut().entry(k) {
|
match self.cache.borrow_mut().entry(k) {
|
||||||
@ -1717,12 +1758,12 @@ fn snapshot_basic() {
|
|||||||
state.snapshot();
|
state.snapshot();
|
||||||
state.add_balance(&a, &U256::from(69u64));
|
state.add_balance(&a, &U256::from(69u64));
|
||||||
assert_eq!(state.balance(&a), U256::from(69u64));
|
assert_eq!(state.balance(&a), U256::from(69u64));
|
||||||
state.clear_snapshot();
|
state.discard_snapshot();
|
||||||
assert_eq!(state.balance(&a), U256::from(69u64));
|
assert_eq!(state.balance(&a), U256::from(69u64));
|
||||||
state.snapshot();
|
state.snapshot();
|
||||||
state.add_balance(&a, &U256::from(1u64));
|
state.add_balance(&a, &U256::from(1u64));
|
||||||
assert_eq!(state.balance(&a), U256::from(70u64));
|
assert_eq!(state.balance(&a), U256::from(70u64));
|
||||||
state.revert_snapshot();
|
state.revert_to_snapshot();
|
||||||
assert_eq!(state.balance(&a), U256::from(69u64));
|
assert_eq!(state.balance(&a), U256::from(69u64));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1735,9 +1776,9 @@ fn snapshot_nested() {
|
|||||||
state.snapshot();
|
state.snapshot();
|
||||||
state.add_balance(&a, &U256::from(69u64));
|
state.add_balance(&a, &U256::from(69u64));
|
||||||
assert_eq!(state.balance(&a), U256::from(69u64));
|
assert_eq!(state.balance(&a), U256::from(69u64));
|
||||||
state.clear_snapshot();
|
state.discard_snapshot();
|
||||||
assert_eq!(state.balance(&a), U256::from(69u64));
|
assert_eq!(state.balance(&a), U256::from(69u64));
|
||||||
state.revert_snapshot();
|
state.revert_to_snapshot();
|
||||||
assert_eq!(state.balance(&a), U256::from(0));
|
assert_eq!(state.balance(&a), U256::from(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ impl StateDB {
|
|||||||
for (address, account) in self.cache_overlay.drain(..) {
|
for (address, account) in self.cache_overlay.drain(..) {
|
||||||
if let Some(&mut Some(ref mut existing)) = cache.accounts.get_mut(&address) {
|
if let Some(&mut Some(ref mut existing)) = cache.accounts.get_mut(&address) {
|
||||||
if let Some(new) = account {
|
if let Some(new) = account {
|
||||||
existing.merge_with(new);
|
existing.overwrite_with(new);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user