forked from cerc-io/plugeth
core, accounts, eth, trie: handle genesis state missing (#28171)
* core, accounts, eth, trie: handle genesis state missing * core, eth, trie: polish * core: manage txpool subscription in mainpool * eth/backend: fix test * cmd, eth: fix test * core/rawdb, trie/triedb/pathdb: address comments * eth, trie: address comments * eth: inline the function * eth: use synced flag * core/txpool: revert changes in txpool * core, eth, trie: rename functions
This commit is contained in:
+16
-4
@@ -273,15 +273,27 @@ func (db *Database) Recoverable(root common.Hash) (bool, error) {
|
||||
return pdb.Recoverable(root), nil
|
||||
}
|
||||
|
||||
// Reset wipes all available journal from the persistent database and discard
|
||||
// all caches and diff layers. Using the given root to create a new disk layer.
|
||||
// Disable deactivates the database and invalidates all available state layers
|
||||
// as stale to prevent access to the persistent state, which is in the syncing
|
||||
// stage.
|
||||
//
|
||||
// It's only supported by path-based database and will return an error for others.
|
||||
func (db *Database) Reset(root common.Hash) error {
|
||||
func (db *Database) Disable() error {
|
||||
pdb, ok := db.backend.(*pathdb.Database)
|
||||
if !ok {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
return pdb.Reset(root)
|
||||
return pdb.Disable()
|
||||
}
|
||||
|
||||
// Enable activates database and resets the state tree with the provided persistent
|
||||
// state root once the state sync is finished.
|
||||
func (db *Database) Enable(root common.Hash) error {
|
||||
pdb, ok := db.backend.(*pathdb.Database)
|
||||
if !ok {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
return pdb.Enable(root)
|
||||
}
|
||||
|
||||
// Journal commits an entire diff hierarchy to disk into a single journal entry.
|
||||
|
||||
@@ -128,7 +128,8 @@ type Database struct {
|
||||
// readOnly is the flag whether the mutation is allowed to be applied.
|
||||
// It will be set automatically when the database is journaled during
|
||||
// the shutdown to reject all following unexpected mutations.
|
||||
readOnly bool // Indicator if database is opened in read only mode
|
||||
readOnly bool // Flag if database is opened in read only mode
|
||||
waitSync bool // Flag if database is deactivated due to initial state sync
|
||||
bufferSize int // Memory allowance (in bytes) for caching dirty nodes
|
||||
config *Config // Configuration for database
|
||||
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
||||
@@ -179,6 +180,12 @@ func New(diskdb ethdb.Database, config *Config) *Database {
|
||||
log.Warn("Truncated extra state histories", "number", pruned)
|
||||
}
|
||||
}
|
||||
// Disable database in case node is still in the initial state sync stage.
|
||||
if rawdb.ReadSnapSyncStatusFlag(diskdb) == rawdb.StateSyncRunning && !db.readOnly {
|
||||
if err := db.Disable(); err != nil {
|
||||
log.Crit("Failed to disable database", "err", err) // impossible to happen
|
||||
}
|
||||
}
|
||||
log.Warn("Path-based state scheme is an experimental feature")
|
||||
return db
|
||||
}
|
||||
@@ -204,9 +211,9 @@ func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint6
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
// Short circuit if the database is in read only mode.
|
||||
if db.readOnly {
|
||||
return errSnapshotReadOnly
|
||||
// Short circuit if the mutation is not allowed.
|
||||
if err := db.modifyAllowed(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.tree.add(root, parentRoot, block, nodes, states); err != nil {
|
||||
return err
|
||||
@@ -227,45 +234,59 @@ func (db *Database) Commit(root common.Hash, report bool) error {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
// Short circuit if the database is in read only mode.
|
||||
if db.readOnly {
|
||||
return errSnapshotReadOnly
|
||||
// Short circuit if the mutation is not allowed.
|
||||
if err := db.modifyAllowed(); err != nil {
|
||||
return err
|
||||
}
|
||||
return db.tree.cap(root, 0)
|
||||
}
|
||||
|
||||
// Reset rebuilds the database with the specified state as the base.
|
||||
//
|
||||
// - if target state is empty, clear the stored state and all layers on top
|
||||
// - if target state is non-empty, ensure the stored state matches with it
|
||||
// and clear all other layers on top.
|
||||
func (db *Database) Reset(root common.Hash) error {
|
||||
// Disable deactivates the database and invalidates all available state layers
|
||||
// as stale to prevent access to the persistent state, which is in the syncing
|
||||
// stage.
|
||||
func (db *Database) Disable() error {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
// Short circuit if the database is in read only mode.
|
||||
if db.readOnly {
|
||||
return errSnapshotReadOnly
|
||||
return errDatabaseReadOnly
|
||||
}
|
||||
batch := db.diskdb.NewBatch()
|
||||
root = types.TrieRootHash(root)
|
||||
if root == types.EmptyRootHash {
|
||||
// Empty state is requested as the target, nuke out
|
||||
// the root node and leave all others as dangling.
|
||||
rawdb.DeleteAccountTrieNode(batch, nil)
|
||||
} else {
|
||||
// Ensure the requested state is existent before any
|
||||
// action is applied.
|
||||
_, hash := rawdb.ReadAccountTrieNode(db.diskdb, nil)
|
||||
if hash != root {
|
||||
return fmt.Errorf("state is mismatched, local: %x, target: %x", hash, root)
|
||||
}
|
||||
// Prevent duplicated disable operation.
|
||||
if db.waitSync {
|
||||
log.Error("Reject duplicated disable operation")
|
||||
return nil
|
||||
}
|
||||
// Mark the disk layer as stale before applying any mutation.
|
||||
db.waitSync = true
|
||||
|
||||
// Mark the disk layer as stale to prevent access to persistent state.
|
||||
db.tree.bottom().markStale()
|
||||
|
||||
// Write the initial sync flag to persist it across restarts.
|
||||
rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncRunning)
|
||||
log.Info("Disabled trie database due to state sync")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enable activates database and resets the state tree with the provided persistent
|
||||
// state root once the state sync is finished.
|
||||
func (db *Database) Enable(root common.Hash) error {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
// Short circuit if the database is in read only mode.
|
||||
if db.readOnly {
|
||||
return errDatabaseReadOnly
|
||||
}
|
||||
// Ensure the provided state root matches the stored one.
|
||||
root = types.TrieRootHash(root)
|
||||
_, stored := rawdb.ReadAccountTrieNode(db.diskdb, nil)
|
||||
if stored != root {
|
||||
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
|
||||
}
|
||||
// Drop the stale state journal in persistent database and
|
||||
// reset the persistent state id back to zero.
|
||||
batch := db.diskdb.NewBatch()
|
||||
rawdb.DeleteTrieJournal(batch)
|
||||
rawdb.WritePersistentStateID(batch, 0)
|
||||
if err := batch.Write(); err != nil {
|
||||
@@ -282,8 +303,11 @@ func (db *Database) Reset(root common.Hash) error {
|
||||
}
|
||||
// Re-construct a new disk layer backed by persistent state
|
||||
// with **empty clean cache and node buffer**.
|
||||
dl := newDiskLayer(root, 0, db, nil, newNodeBuffer(db.bufferSize, nil, 0))
|
||||
db.tree.reset(dl)
|
||||
db.tree.reset(newDiskLayer(root, 0, db, nil, newNodeBuffer(db.bufferSize, nil, 0)))
|
||||
|
||||
// Re-enable the database as the final step.
|
||||
db.waitSync = false
|
||||
rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncFinished)
|
||||
log.Info("Rebuilt trie database", "root", root)
|
||||
return nil
|
||||
}
|
||||
@@ -296,7 +320,10 @@ func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error
|
||||
defer db.lock.Unlock()
|
||||
|
||||
// Short circuit if rollback operation is not supported.
|
||||
if db.readOnly || db.freezer == nil {
|
||||
if err := db.modifyAllowed(); err != nil {
|
||||
return err
|
||||
}
|
||||
if db.freezer == nil {
|
||||
return errors.New("state rollback is non-supported")
|
||||
}
|
||||
// Short circuit if the target state is not recoverable.
|
||||
@@ -424,3 +451,15 @@ func (db *Database) SetBufferSize(size int) error {
|
||||
func (db *Database) Scheme() string {
|
||||
return rawdb.PathScheme
|
||||
}
|
||||
|
||||
// modifyAllowed returns the indicator if mutation is allowed. This function
|
||||
// assumes the db.lock is already held.
|
||||
func (db *Database) modifyAllowed() error {
|
||||
if db.readOnly {
|
||||
return errDatabaseReadOnly
|
||||
}
|
||||
if db.waitSync {
|
||||
return errDatabaseWaitSync
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -439,38 +439,39 @@ func TestDatabaseRecoverable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReset(t *testing.T) {
|
||||
var (
|
||||
tester = newTester(t)
|
||||
index = tester.bottomIndex()
|
||||
)
|
||||
func TestDisable(t *testing.T) {
|
||||
tester := newTester(t)
|
||||
defer tester.release()
|
||||
|
||||
// Reset database to unknown target, should reject it
|
||||
if err := tester.db.Reset(testutil.RandomHash()); err == nil {
|
||||
t.Fatal("Failed to reject invalid reset")
|
||||
_, stored := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)
|
||||
if err := tester.db.Disable(); err != nil {
|
||||
t.Fatal("Failed to deactivate database")
|
||||
}
|
||||
// Reset database to state persisted in the disk
|
||||
if err := tester.db.Reset(types.EmptyRootHash); err != nil {
|
||||
t.Fatalf("Failed to reset database %v", err)
|
||||
if err := tester.db.Enable(types.EmptyRootHash); err == nil {
|
||||
t.Fatalf("Invalid activation should be rejected")
|
||||
}
|
||||
if err := tester.db.Enable(stored); err != nil {
|
||||
t.Fatal("Failed to activate database")
|
||||
}
|
||||
|
||||
// Ensure journal is deleted from disk
|
||||
if blob := rawdb.ReadTrieJournal(tester.db.diskdb); len(blob) != 0 {
|
||||
t.Fatal("Failed to clean journal")
|
||||
}
|
||||
// Ensure all trie histories are removed
|
||||
for i := 0; i <= index; i++ {
|
||||
_, err := readHistory(tester.db.freezer, uint64(i+1))
|
||||
if err == nil {
|
||||
t.Fatalf("Failed to clean state history, index %d", i+1)
|
||||
}
|
||||
n, err := tester.db.freezer.Ancients()
|
||||
if err != nil {
|
||||
t.Fatal("Failed to clean state history")
|
||||
}
|
||||
if n != 0 {
|
||||
t.Fatal("Failed to clean state history")
|
||||
}
|
||||
// Verify layer tree structure, single disk layer is expected
|
||||
if tester.db.tree.len() != 1 {
|
||||
t.Fatalf("Extra layer kept %d", tester.db.tree.len())
|
||||
}
|
||||
if tester.db.tree.bottom().rootHash() != types.EmptyRootHash {
|
||||
t.Fatalf("Root hash is not matched exp %x got %x", types.EmptyRootHash, tester.db.tree.bottom().rootHash())
|
||||
if tester.db.tree.bottom().rootHash() != stored {
|
||||
t.Fatalf("Root hash is not matched exp %x got %x", stored, tester.db.tree.bottom().rootHash())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// errSnapshotReadOnly is returned if the database is opened in read only mode
|
||||
// and mutation is requested.
|
||||
errSnapshotReadOnly = errors.New("read only")
|
||||
// errDatabaseReadOnly is returned if the database is opened in read only mode
|
||||
// to prevent any mutation.
|
||||
errDatabaseReadOnly = errors.New("read only")
|
||||
|
||||
// errDatabaseWaitSync is returned if the initial state sync is not completed
|
||||
// yet and database is disabled to prevent accessing state.
|
||||
errDatabaseWaitSync = errors.New("waiting for sync")
|
||||
|
||||
// errSnapshotStale is returned from data accessors if the underlying layer
|
||||
// layer had been invalidated due to the chain progressing forward far enough
|
||||
|
||||
@@ -356,7 +356,7 @@ func (db *Database) Journal(root common.Hash) error {
|
||||
|
||||
// Short circuit if the database is in read only mode.
|
||||
if db.readOnly {
|
||||
return errSnapshotReadOnly
|
||||
return errDatabaseReadOnly
|
||||
}
|
||||
// Firstly write out the metadata of journal
|
||||
journal := new(bytes.Buffer)
|
||||
|
||||
Reference in New Issue
Block a user