Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

don't delete account and storage entries on commit #126

Merged

Conversation

mattsse
Copy link
Collaborator

@mattsse mattsse commented Jun 10, 2022

As discovered here foundry-rs/foundry#1894

deleting storage or account entries does not play nice with fork mode which basically does:

if !db.contains(acc) {
    // fetch from remote 
}

So deleting accounts/slots (when empty) would result in the fork's account/slot being served and not 0 or the empty account.

Solution

  • on CacheDb::commit don't delete AccountInfo or storage entries in the CacheDb's maps

  • also bumps to v1.6.0

@mattsse
Copy link
Collaborator Author

mattsse commented Jun 10, 2022

hmm, perhaps keeping destroyed accounts has some side-effects?

Copy link
Member

@rakita rakita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selfdestruct, what else to say. :)
I am merkelizing everything in the state when running tests. I think i am skipping empty/zero values but I am not sure tbh, will check later.

@@ -87,26 +87,12 @@ impl<ExtDB: DatabaseRef> CacheDB<ExtDB> {
impl<ExtDB: DatabaseRef> DatabaseCommit for CacheDB<ExtDB> {
fn commit(&mut self, changes: Map<H160, Account>) {
for (add, acc) in changes {
if acc.is_empty() || matches!(acc.filth, Filth::Destroyed) {
self.cache.remove(&add);
self.storage.remove(&add);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, you need to set cache to Account::default and iterate over storage.get(add).iter() and set them all to zero

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rakita you said set "cache to Account::default" but we're currently setting the changes? is that right, or a mistake?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gakonst changes are added after, because of this: #126 (comment)

self.insert_cache(add, acc.info);
let storage = self.storage.entry(add).or_default();
if acc.filth.abandon_old_storage() {
storage.clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here iterate over all storage and set all values to zero

Comment on lines 99 to 108
for (index, value) in acc.storage {
if value.is_zero() {
storage.remove(&index);
} else {
storage.insert(index, value);
}
}
if storage.is_empty() {
self.storage.remove(&add);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for this for and if use extend

@rakita
Copy link
Member

rakita commented Jun 10, 2022

@mattsse in state_merkle_trie_root skip AccountInfos that are is_empty

@mattsse
Copy link
Collaborator Author

mattsse commented Jun 10, 2022

I guess we'd also need to skip 0 in storage now when computing sec_trie_root, right?

@rakita
Copy link
Member

rakita commented Jun 10, 2022

I guess we'd also need to skip 0 in storage now when computing sec_trie_root, right?

zero storage slots are already skipped. I am not sure why it is not working. I vaguely remember there are some funky things with merkelization in tests where some empty accounts get merkelized I will need to manually run it and compare it.

@rakita
Copy link
Member

rakita commented Jun 10, 2022

@mattsse
The problem was with state cleanup that was introduced way back in eth: https://eips.ethereum.org/EIPS/eip-161
The state can contain "empty" accounts, with this EIP, only accounts that are accessed are going to be removed from state.

For tests that means that there are preloaded empty accounts and not all of them are touched (called) and those empty accounts need to appear in Merkle trie.

I added changes field to CacheDB and I am open to modifying this to a more flexible form needed, but at least we know what is going on.

@@ -86,26 +100,25 @@ impl<ExtDB: DatabaseRef> CacheDB<ExtDB> {
// TODO It is currently only committing to cached in-memory DB
impl<ExtDB: DatabaseRef> DatabaseCommit for CacheDB<ExtDB> {
fn commit(&mut self, changes: Map<H160, Account>) {
// clear storage by setting all values to `0`
fn clear_storage(storage: &mut HashMap<U256, U256>) {
storage.values_mut().for_each(|val| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something to consider in the future, maybe we want to convert these to rayon parallel iters? maybe not worth it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this particular thing, it would be best to introduce storage incarnation: https://github.com/ledgerwatch/erigon/blob/devel/docs/programmers_guide/db_walkthrough.MD#table-plainstate
In general, for introducing rayon, we need to be careful that we have a good amount of data and processing that we want to iterate/Parallelize on.

@@ -87,26 +87,12 @@ impl<ExtDB: DatabaseRef> CacheDB<ExtDB> {
impl<ExtDB: DatabaseRef> DatabaseCommit for CacheDB<ExtDB> {
fn commit(&mut self, changes: Map<H160, Account>) {
for (add, acc) in changes {
if acc.is_empty() || matches!(acc.filth, Filth::Destroyed) {
self.cache.remove(&add);
self.storage.remove(&add);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rakita you said set "cache to Account::default" but we're currently setting the changes? is that right, or a mistake?

} else {
self.insert_cache(add, acc.info);
self.insert_change(add, acc.info);
Copy link
Collaborator

@gakonst gakonst Jun 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same q here, what's the diff between cache / change? should we add a note that this is a temp fix?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is because of this: #126 (comment)
the cache is loaded from the external DB.
the changes is what is applied from commit.

@gakonst
Copy link
Collaborator

gakonst commented Jun 11, 2022

LGTM - couple comments above, should we try to release this ASAP to fix the downstream bugs in Foundry?

@gakonst
Copy link
Collaborator

gakonst commented Jun 14, 2022

@mattsse @rakita do we intend to merge this or are there any blockers?

@mattsse
Copy link
Collaborator Author

mattsse commented Jun 14, 2022

I think this is good to merge, don't think this has any downside

@rakita
Copy link
Member

rakita commented Jun 14, 2022

I think this is good to merge, don't think this has any downside

Hey i will merge it as it is, but check in forge if it breaks something as in using cache to check data but it now need changes

@rakita rakita merged commit a95390f into bluealloy:main Jun 14, 2022
@gakonst
Copy link
Collaborator

gakonst commented Jun 14, 2022

Good point - will keep it in mind before we update^ thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants