diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index f0f3d8ed02..9989e6b50e 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -91,3 +91,9 @@ func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash com log.Crit("Failed to delete storage snapshot", "err", err) } } + +// IterateStorageSnapshots returns an iterator for walking the entire storage +// space of a specific account. +func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator { + return db.NewIteratorWithPrefix(storageSnapshotsKey(accountHash)) +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 2e0e12a755..8e611246a1 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -159,6 +159,11 @@ func storageSnapshotKey(accountHash, storageHash common.Hash) []byte { return append(append(StateSnapshotPrefix, accountHash.Bytes()...), storageHash.Bytes()...) } +// storageSnapshotsKey = StateSnapshotPrefix + account hash + storage hash +func storageSnapshotsKey(accountHash common.Hash) []byte { + return append(StateSnapshotPrefix, accountHash.Bytes()...) +} + // bloomBitsKey = bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash func bloomBitsKey(bit uint, section uint64, hash common.Hash) []byte { key := append(append(bloomBitsPrefix, make([]byte, 10)...), hash.Bytes()...) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 52e67d7f89..9c8558bd9a 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -247,27 +247,51 @@ func (dl *diffLayer) Cap(layers int, memory uint64) (uint64, uint64) { base = parent.parent.(*diskLayer) batch = base.db.NewBatch() ) + parent.lock.RLock() + defer parent.lock.RUnlock() + // Start by temporarilly deleting the current snapshot block marker. This // ensures that in the case of a crash, the entire snapshot is invalidated. rawdb.DeleteSnapshotBlock(batch) // Push all the accounts into the database for hash, data := range parent.accountData { - rawdb.WriteAccountSnapshot(batch, hash, data) - base.cache.Set(string(hash[:]), data) + if len(data) > 0 { + // Account was updated, push to disk + rawdb.WriteAccountSnapshot(batch, hash, data) + base.cache.Set(string(hash[:]), data) - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - log.Crit("Failed to write account snapshot", "err", err) + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write account snapshot", "err", err) + } + batch.Reset() } - batch.Reset() + } else { + // Account was deleted, remove all storage slots too + rawdb.DeleteAccountSnapshot(batch, hash) + base.cache.Set(string(hash[:]), nil) + + it := rawdb.IterateStorageSnapshots(base.db, hash) + for it.Next() { + if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator + batch.Delete(key) + base.cache.Delete(string(key[1:])) + } + } + it.Release() } } // Push all the storage slots into the database for accountHash, storage := range parent.storageData { for storageHash, data := range storage { - rawdb.WriteStorageSnapshot(batch, accountHash, storageHash, data) - base.cache.Set(string(append(accountHash[:], storageHash[:]...)), data) + if len(data) > 0 { + rawdb.WriteStorageSnapshot(batch, accountHash, storageHash, data) + base.cache.Set(string(append(accountHash[:], storageHash[:]...)), data) + } else { + rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash) + base.cache.Set(string(append(accountHash[:], storageHash[:]...)), nil) + } } if batch.ValueSize() > ethdb.IdealBatchSize { if err := batch.Write(); err != nil { diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index ca9c4e18c9..0d451fe50d 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -119,6 +119,7 @@ func generateSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, storageNodes int accountSize common.StorageSize storageSize common.StorageSize + logged time.Time ) batch := db.NewBatch() triedb := trie.NewDatabase(db) @@ -176,8 +177,13 @@ func generateSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, storageSize += curStorageSize storageNodes += curStorageNodes - fmt.Printf("%#x: %9s + %9s (%6d slots, %6d nodes), total %9s (%d accs, %d nodes) + %9s (%d slots, %d nodes)\n", accIt.Key, curAccountSize.TerminalString(), curStorageSize.TerminalString(), curStorageCount, curStorageNodes, accountSize.TerminalString(), accountCount, accIt.Nodes, storageSize.TerminalString(), storageCount, storageNodes) + if time.Since(logged) > 8*time.Second { + fmt.Printf("%#x: %9s + %9s (%6d slots, %6d nodes), total %9s (%d accs, %d nodes) + %9s (%d slots, %d nodes)\n", accIt.Key, curAccountSize.TerminalString(), curStorageSize.TerminalString(), curStorageCount, curStorageNodes, accountSize.TerminalString(), accountCount, accIt.Nodes, storageSize.TerminalString(), storageCount, storageNodes) + logged = time.Now() + } } + fmt.Printf("Totals: %9s (%d accs, %d nodes) + %9s (%d slots, %d nodes)\n", accountSize.TerminalString(), accountCount, accIt.Nodes, storageSize.TerminalString(), storageCount, storageNodes) + // Update the snapshot block marker and write any remainder data rawdb.WriteSnapshotBlock(batch, headNumber, headRoot) batch.Write() diff --git a/core/state/state_object.go b/core/state/state_object.go index 01b66d777e..f2ca7fae42 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -287,6 +287,23 @@ func (s *stateObject) updateTrie(db Database) Trie { // Make sure all dirty slots are finalized into the pending storage area s.finalise() + // Retrieve the snapshot storage map for the object + var storage map[common.Hash][]byte + if s.db.snap != nil { + // Retrieve the old storage map, if available + s.db.snapLock.RLock() + storage = s.db.snapStorage[s.addrHash] + s.db.snapLock.RUnlock() + + // If no old storage map was available, create a new one + if storage == nil { + storage = make(map[common.Hash][]byte) + + s.db.snapLock.Lock() + s.db.snapStorage[s.addrHash] = storage + s.db.snapLock.Unlock() + } + } // Insert all the pending updates into the trie tr := s.getTrie(db) for key, value := range s.pendingStorage { @@ -305,20 +322,7 @@ func (s *stateObject) updateTrie(db Database) Trie { s.setError(tr.TryUpdate(key[:], v)) } // If state snapshotting is active, cache the data til commit - if s.db.snap != nil { - // Retrieve an old storage map, if available - s.db.snapLock.RLock() - storage := s.db.snapStorage[s.addrHash] - s.db.snapLock.RUnlock() - - if storage == nil { - // No old storage available, create a new one - storage = make(map[common.Hash][]byte) - - s.db.snapLock.Lock() - s.db.snapStorage[s.addrHash] = storage - s.db.snapLock.Unlock() - } + if storage != nil { storage[crypto.Keccak256Hash(key[:])] = v // v will be nil if value is 0x00 } } diff --git a/core/state/statedb.go b/core/state/statedb.go index ff83fa028e..fa4ee5c591 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -484,7 +484,10 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { // If state snapshotting is active, cache the data til commit if s.snap != nil { - s.snapAccounts[obj.addrHash] = nil // Yes, nil means deleted + s.snapLock.Lock() + s.snapAccounts[obj.addrHash] = nil // We need to maintain account deletions explicitly + s.snapStorage[obj.addrHash] = nil // We need to maintain storage deletions explicitly + s.snapLock.Unlock() } }