-
Notifications
You must be signed in to change notification settings - Fork 1.7k
EIP-168, 169: Dust protection #4757
Conversation
added a variant C to 168, which might be worth considering |
169.c.c.2 is definitely not redundant. |
will need resolving |
resolved |
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't think this will work for newly created accounts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's newly created account that does not have code it is a basic account, right? It stops being basic only after it gets the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess it's fine - this particular function should work at least (though at one point new_contract
actually gave a non-basic account which just happened to have no code yet, but looks like it's now just an uncached basic account).
ethcore/src/state/mod.rs
Outdated
@@ -95,6 +97,7 @@ enum AccountState { | |||
/// account (`None`) | |||
struct AccountEntry { | |||
account: Option<Account>, | |||
clean_balance: Option<U256>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably worth documenting these members now - clean_balance
isn't immediately obvious in meaning. would probably rename to old_balance
or something similar to emphasise its underlying semantics.
ethcore/src/state/mod.rs
Outdated
@@ -121,12 +128,14 @@ impl AccountEntry { | |||
AccountEntry { | |||
account: self.account.as_ref().map(Account::clone_dirty), | |||
state: self.state, | |||
clean_balance: self.clean_balance, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
appears after other fields (others constructors appear prior)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is in line with the declaration order. Other constructors are exceptions because they use account
before it is moved into self.account
. Will fix here for consistency.
ethcore/src/state/mod.rs
Outdated
_ => {} | ||
} | ||
} else if cleanup_mode == CleanupMode::KillEmpty && self.exists(a)? { | ||
self.require(a, false)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider introducing a function fn touch(&self, a: ...) -> ... { self.require(a, false) }
and using that in order to make clear the intent here.
this doesn't make use of is_value_transfer
; i am skeptical that precise semantics have been preserved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This branch only executes when is_value_transfer == false
ethcore/src/state/mod.rs
Outdated
pub fn kill_garbage(&mut self, 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 (entry.is_dirty() || address == &RIPEMD_BUILTIN) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RIPEMD
is very ugly - can we avoid having it here?
also, not entirely sure what these conditions are supposed to achieve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added this because this new implementation revealed a subtle consensus flaw in the previous one. If a built-in runs out of gas it's account still gets deleted. This is now in sync with the go-ethereum and other clients which also have a special case for this particular built-in account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that said, this special-casing is only for the mainnet; our code should reflect that.
ethcore/src/state/mod.rs
Outdated
self.cache.borrow().iter().filter_map(|(address, ref entry)| | ||
if (entry.is_dirty() || address == &RIPEMD_BUILTIN) && | ||
((remove_empty_touched && entry.is_null()) | ||
|| min_balance.map_or(false, |ref balance| entry.account.as_ref().map_or(false, |account| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indentation off by one (still have an open paren here from previous line)
ethcore/src/state/mod.rs
Outdated
((remove_empty_touched && entry.is_null()) | ||
|| min_balance.map_or(false, |ref balance| entry.account.as_ref().map_or(false, |account| | ||
(account.is_basic() || kill_contracts) | ||
&& account.balance() < balance && entry.clean_balance.as_ref().map_or(false, |b| account.balance() < b)))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could do with some commentary.
@@ -28,9 +28,6 @@ pub struct Substate { | |||
/// Any accounts that have suicided. | |||
pub suicides: HashSet<Address>, | |||
|
|||
/// Any accounts that are tagged for garbage collection. | |||
pub garbage: HashSet<Address>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm a bit concerned that the alteration of semantics (from explicit collection of gardbage-collected accounts to the implicit reliance on touching the cache) here will lead to subtle consensus flaws. i don't remember exactly, but when i coded this originally i chose this means of doing it for a reason.
@@ -392,6 +400,12 @@ mod tests { | |||
verify_block_family(&header, bytes, engine, bc) | |||
} | |||
|
|||
fn unordered_test(bytes: &[u8], engine: &Engine) -> Result<(), Error> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mark with #[test]
if only used in the test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is part of the tests
mod already, but not a test itself.
not at all keen on the removal of |
Current implementation does not handle state reverts properly. This is what caused the original RIPEMD built-in bug, for which there's going to be an exception added to the YP. |
Well I guess the point is that while the notion of "in the cache" may be similar to the definition of "touched" within the strict context of EIP/YP, because it was not explicitly implemented or specified to be exactly that concept, we run the risk that what might reasonably be considered an internal change or optimisation to our state-caching code will accidentally and subtly break consensus. |
i now remember the reason we don't want to do it this way: reversion of an EVM call-path should also revert the touchedness of an account, in much the same way it reverts other aspects of substate. |
This has been updated to track touched accounts in the substate |
6249981
to
52c4732
Compare
Could use a merge |
Variant C of 168 purposefully doesn't mention "touched" accounts:
Yet this PR seems to operate solely on them. |
@rphmeier I'll merge it once @gavofyork is happy with the implementation. |
@gavofyork Quoting EIP-161:
Clearly this includes all EIP-168C accounts as well. A check for "Created or whose balance is decreased by the execution of that transaction which is now dust" is performed "at the end of the transaction" in Could you point out what's exactly wrong with that? I don't think there's any sense in maintaining yet another additional list of accounts that were "Created or whose balance is decreased by any non-zero value" as this is, again, just wasting memory. |
ethcore/src/state/mod.rs
Outdated
trace!(target: "state", "sub_balance({}, {}): {}", a, decr, self.balance(a)?); | ||
if !decr.is_zero() || !self.exists(a)? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reason for removal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is never called for non-existing accounts. However it is indeed incorrect to assume it here. I'll restore it.
the concern was with the prefilter of "touched" being applied to to EIP168's "any account created or whose balance is decreased". just need to prove that EIP168's condition strictly implies the property of being "touched". i suspect it does, but it does need a reasonably formal proof before we can be sure this properly implements the EIP. |
/// 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could just multiply in u64
ethcore/src/state/mod.rs
Outdated
@@ -106,6 +110,10 @@ impl AccountEntry { | |||
self.state == AccountState::Dirty | |||
} | |||
|
|||
fn is_null(&self) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better name: exists_and_is_null
This implements
ethereum/EIPs#168 Variant C
ethereum/EIPs#169 Variant C