mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 19:30:44 +00:00
solve the issue by adding an atomic value in CachingDB
This commit is contained in:
parent
c61d2d307d
commit
edbce93d07
6 changed files with 293 additions and 22 deletions
|
|
@ -2081,6 +2081,14 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
)
|
||||
defer interrupt.Store(true) // terminate the prefetch at the end
|
||||
|
||||
if bc.chainConfig.IsVerkle(block.Number(), block.Time()) {
|
||||
parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
||||
if parent != nil && !bc.chainConfig.IsVerkle(parent.Number, parent.Time) {
|
||||
bc.SetForkBoundary(parentRoot)
|
||||
defer bc.ClearForkBoundary()
|
||||
}
|
||||
}
|
||||
|
||||
if bc.cfg.NoPrefetch {
|
||||
statedb, err = state.New(parentRoot, bc.statedb)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -416,6 +416,14 @@ func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
|
|||
return state.New(root, bc.statedb)
|
||||
}
|
||||
|
||||
func (bc *BlockChain) SetForkBoundary(root common.Hash) {
|
||||
bc.statedb.SetForkBoundary(root)
|
||||
}
|
||||
|
||||
func (bc *BlockChain) ClearForkBoundary() {
|
||||
bc.statedb.ClearForkBoundary()
|
||||
}
|
||||
|
||||
// HistoricState returns a historic state specified by the given root.
|
||||
// Live states are not available and won't be served, please use `State`
|
||||
// or `StateAt` instead.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package state
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
|
|
@ -158,6 +159,8 @@ type CachingDB struct {
|
|||
|
||||
// Transition-specific fields
|
||||
TransitionStatePerRoot *lru.Cache[common.Hash, *overlay.TransitionState]
|
||||
|
||||
forkBoundaryRoot atomic.Value // stores common.Hash; non-zero means fork boundary active for this root
|
||||
}
|
||||
|
||||
// NewDatabase creates a state database with the provided data sources.
|
||||
|
|
@ -178,6 +181,23 @@ func NewDatabaseForTesting() *CachingDB {
|
|||
return NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil)
|
||||
}
|
||||
|
||||
func (db *CachingDB) SetForkBoundary(root common.Hash) {
|
||||
db.forkBoundaryRoot.Store(root)
|
||||
}
|
||||
|
||||
func (db *CachingDB) ClearForkBoundary() {
|
||||
db.forkBoundaryRoot.Store(common.Hash{})
|
||||
}
|
||||
|
||||
func (db *CachingDB) isForkBoundary(root common.Hash) bool {
|
||||
v := db.forkBoundaryRoot.Load()
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
stored, ok := v.(common.Hash)
|
||||
return ok && stored != (common.Hash{}) && stored == root
|
||||
}
|
||||
|
||||
var (
|
||||
transitionStartedKey = common.Hash{} // slot 0: non-zero if transition started
|
||||
conversionProgressAddressKey = common.Hash{1} // slot 1: current account pointer
|
||||
|
|
@ -269,6 +289,9 @@ func (db *CachingDB) StateReader(stateRoot common.Hash) (StateReader, error) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if ts == nil && db.isForkBoundary(stateRoot) {
|
||||
ts = &overlay.TransitionState{Started: true, BaseRoot: stateRoot}
|
||||
}
|
||||
// Configure the trie reader, which is expected to be available as the
|
||||
// gatekeeper unless the state is corrupted.
|
||||
tr, err := newTrieReader(stateRoot, db.triedb, ts)
|
||||
|
|
@ -307,6 +330,9 @@ func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithSta
|
|||
|
||||
// OpenTrie opens the main account trie at a specific root hash.
|
||||
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
||||
if db.isForkBoundary(root) {
|
||||
return db.openForkBoundaryTrie(root)
|
||||
}
|
||||
reader, err := db.triedb.StateReader(root)
|
||||
if err != nil {
|
||||
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
|
||||
|
|
@ -324,11 +350,10 @@ func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
|||
return nil, fmt.Errorf("could not open the overlay tree: %w", err)
|
||||
}
|
||||
if !ts.InTransition() {
|
||||
// Transition complete, use BinaryTrie only
|
||||
return bt, nil
|
||||
}
|
||||
|
||||
base, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
|
||||
base, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db.triedb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create base trie in OpenTrie: %w", err)
|
||||
}
|
||||
|
|
@ -341,6 +366,18 @@ func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
|||
return tr, nil
|
||||
}
|
||||
|
||||
func (db *CachingDB) openForkBoundaryTrie(root common.Hash) (Trie, error) {
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db.triedb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open empty binary trie at fork boundary: %w", err)
|
||||
}
|
||||
base, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open base MPT at fork boundary: %w", err)
|
||||
}
|
||||
return transitiontrie.NewTransitionTrie(base, bt, false), nil
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) {
|
||||
if self != nil && self.IsVerkle() {
|
||||
|
|
|
|||
226
core/state/fork_boundary_test.go
Normal file
226
core/state/fork_boundary_test.go
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
func TestForkBoundaryOpenTrie(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
tdb := triedb.NewDatabase(db, nil)
|
||||
sdb := NewDatabase(tdb, nil)
|
||||
|
||||
statedb, err := New(types.EmptyRootHash, sdb)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create statedb: %v", err)
|
||||
}
|
||||
|
||||
addr := common.HexToAddress("0x1234")
|
||||
statedb.SetBalance(addr, uint256.NewInt(1000), tracing.BalanceIncreaseGenesisBalance)
|
||||
statedb.SetNonce(addr, 1, tracing.NonceChangeGenesis)
|
||||
|
||||
root, err := statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit: %v", err)
|
||||
}
|
||||
if err := tdb.Commit(root, false); err != nil {
|
||||
t.Fatalf("failed to commit triedb: %v", err)
|
||||
}
|
||||
|
||||
sdb.SetForkBoundary(root)
|
||||
defer sdb.ClearForkBoundary()
|
||||
|
||||
tr, err := sdb.OpenTrie(root)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open trie: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := tr.(*transitiontrie.TransitionTrie); !ok {
|
||||
t.Fatalf("expected TransitionTrie at fork boundary, got %T", tr)
|
||||
}
|
||||
|
||||
acct, err := tr.GetAccount(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get account: %v", err)
|
||||
}
|
||||
if acct == nil {
|
||||
t.Fatal("expected non-nil account from MPT base")
|
||||
}
|
||||
if acct.Balance.Cmp(uint256.NewInt(1000)) != 0 {
|
||||
t.Fatalf("unexpected balance: got %v, want 1000", acct.Balance)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForkBoundaryStateReader(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
tdb := triedb.NewDatabase(db, nil)
|
||||
sdb := NewDatabase(tdb, nil)
|
||||
|
||||
statedb, err := New(types.EmptyRootHash, sdb)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create statedb: %v", err)
|
||||
}
|
||||
|
||||
addr := common.HexToAddress("0x5678")
|
||||
statedb.SetBalance(addr, uint256.NewInt(2000), tracing.BalanceIncreaseGenesisBalance)
|
||||
|
||||
root, err := statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit: %v", err)
|
||||
}
|
||||
if err := tdb.Commit(root, false); err != nil {
|
||||
t.Fatalf("failed to commit triedb: %v", err)
|
||||
}
|
||||
|
||||
sdb.SetForkBoundary(root)
|
||||
defer sdb.ClearForkBoundary()
|
||||
|
||||
reader, err := sdb.StateReader(root)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create state reader: %v", err)
|
||||
}
|
||||
|
||||
acct, err := reader.Account(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get account: %v", err)
|
||||
}
|
||||
if acct == nil {
|
||||
t.Fatal("expected non-nil account")
|
||||
}
|
||||
if acct.Balance.Cmp(uint256.NewInt(2000)) != 0 {
|
||||
t.Fatalf("unexpected balance: got %v, want 2000", acct.Balance)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForkBoundaryWrite(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
tdb := triedb.NewDatabase(db, nil)
|
||||
sdb := NewDatabase(tdb, nil)
|
||||
|
||||
statedb, err := New(types.EmptyRootHash, sdb)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create statedb: %v", err)
|
||||
}
|
||||
|
||||
addr := common.HexToAddress("0xabcd")
|
||||
statedb.SetBalance(addr, uint256.NewInt(500), tracing.BalanceIncreaseGenesisBalance)
|
||||
|
||||
root, err := statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit: %v", err)
|
||||
}
|
||||
if err := tdb.Commit(root, false); err != nil {
|
||||
t.Fatalf("failed to commit triedb: %v", err)
|
||||
}
|
||||
|
||||
sdb.SetForkBoundary(root)
|
||||
defer sdb.ClearForkBoundary()
|
||||
|
||||
tr, err := sdb.OpenTrie(root)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open trie: %v", err)
|
||||
}
|
||||
|
||||
newAddr := common.HexToAddress("0xef01")
|
||||
err = tr.UpdateAccount(newAddr, &types.StateAccount{
|
||||
Nonce: 1,
|
||||
Balance: uint256.NewInt(100),
|
||||
Root: types.EmptyRootHash,
|
||||
CodeHash: types.EmptyCodeHash.Bytes(),
|
||||
}, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to update account: %v", err)
|
||||
}
|
||||
|
||||
acct, err := tr.GetAccount(newAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get new account: %v", err)
|
||||
}
|
||||
if acct == nil {
|
||||
t.Fatal("expected non-nil new account")
|
||||
}
|
||||
if acct.Balance.Cmp(uint256.NewInt(100)) != 0 {
|
||||
t.Fatalf("unexpected balance for new account: got %v, want 100", acct.Balance)
|
||||
}
|
||||
|
||||
origAcct, err := tr.GetAccount(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get original account: %v", err)
|
||||
}
|
||||
if origAcct == nil {
|
||||
t.Fatal("expected non-nil original account from MPT base")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForkBoundaryNotActive(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
tdb := triedb.NewDatabase(db, nil)
|
||||
sdb := NewDatabase(tdb, nil)
|
||||
|
||||
statedb, err := New(types.EmptyRootHash, sdb)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create statedb: %v", err)
|
||||
}
|
||||
|
||||
addr := common.HexToAddress("0x1111")
|
||||
statedb.SetBalance(addr, uint256.NewInt(300), tracing.BalanceIncreaseGenesisBalance)
|
||||
|
||||
root, err := statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit: %v", err)
|
||||
}
|
||||
if err := tdb.Commit(root, false); err != nil {
|
||||
t.Fatalf("failed to commit triedb: %v", err)
|
||||
}
|
||||
|
||||
tr, err := sdb.OpenTrie(root)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open trie: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := tr.(*transitiontrie.TransitionTrie); ok {
|
||||
t.Fatal("should not get TransitionTrie without fork boundary set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForkBoundaryWrongRoot(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
tdb := triedb.NewDatabase(db, nil)
|
||||
sdb := NewDatabase(tdb, nil)
|
||||
|
||||
statedb, err := New(types.EmptyRootHash, sdb)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create statedb: %v", err)
|
||||
}
|
||||
|
||||
addr := common.HexToAddress("0x2222")
|
||||
statedb.SetBalance(addr, uint256.NewInt(400), tracing.BalanceIncreaseGenesisBalance)
|
||||
|
||||
root, err := statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit: %v", err)
|
||||
}
|
||||
if err := tdb.Commit(root, false); err != nil {
|
||||
t.Fatalf("failed to commit triedb: %v", err)
|
||||
}
|
||||
|
||||
otherRoot := common.HexToHash("0xdeadbeef")
|
||||
sdb.SetForkBoundary(otherRoot)
|
||||
defer sdb.ClearForkBoundary()
|
||||
|
||||
tr, err := sdb.OpenTrie(root)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open trie: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := tr.(*transitiontrie.TransitionTrie); ok {
|
||||
t.Fatal("should not get TransitionTrie for different root")
|
||||
}
|
||||
}
|
||||
|
|
@ -318,18 +318,18 @@ func newTrieReader(root common.Hash, db *triedb.Database, ts *overlay.Transition
|
|||
tr Trie
|
||||
err error
|
||||
)
|
||||
if !db.IsVerkle() {
|
||||
if !db.IsVerkle() && !ts.InTransition() && !ts.Transitioned() {
|
||||
tr, err = trie.NewStateTrie(trie.StateTrieID(root), db)
|
||||
} else {
|
||||
// When IsVerkle() is true, create a BinaryTrie wrapped in TransitionTrie
|
||||
binTrie, binErr := bintrie.NewBinaryTrie(root, db)
|
||||
binRoot := root
|
||||
if ts.InTransition() && ts.BaseRoot == root {
|
||||
binRoot = types.EmptyBinaryHash
|
||||
}
|
||||
binTrie, binErr := bintrie.NewBinaryTrie(binRoot, db)
|
||||
if binErr != nil {
|
||||
return nil, binErr
|
||||
}
|
||||
|
||||
// Based on the transition status, determine if the overlay
|
||||
// tree needs to be created, or if a single, target tree is
|
||||
// to be picked.
|
||||
if ts.InTransition() {
|
||||
mpt, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db)
|
||||
if err != nil {
|
||||
|
|
@ -337,19 +337,6 @@ func newTrieReader(root common.Hash, db *triedb.Database, ts *overlay.Transition
|
|||
}
|
||||
tr = transitiontrie.NewTransitionTrie(mpt, binTrie, false)
|
||||
} else {
|
||||
// HACK: Use TransitionTrie with nil base as a wrapper to make BinaryTrie
|
||||
// satisfy the Trie interface. This works around the import cycle between
|
||||
// trie and trie/bintrie packages.
|
||||
//
|
||||
// TODO: In future PRs, refactor the package structure to avoid this hack:
|
||||
// - Option 1: Move common interfaces (Trie, NodeIterator) to a separate
|
||||
// package that both trie and trie/bintrie can import
|
||||
// - Option 2: Create a factory function in the trie package that returns
|
||||
// BinaryTrie as a Trie interface without direct import
|
||||
// - Option 3: Move BinaryTrie to the main trie package
|
||||
//
|
||||
// The current approach works but adds unnecessary overhead and complexity
|
||||
// by using TransitionTrie when there's no actual transition happening.
|
||||
tr = transitiontrie.NewTransitionTrie(nil, binTrie, false)
|
||||
}
|
||||
}
|
||||
|
|
@ -404,7 +391,7 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
|
|||
found bool
|
||||
value common.Hash
|
||||
)
|
||||
if r.db.IsVerkle() {
|
||||
if r.mainTrie.IsVerkle() {
|
||||
tr = r.mainTrie
|
||||
} else {
|
||||
tr, found = r.subTries[addr]
|
||||
|
|
|
|||
|
|
@ -252,6 +252,11 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir
|
|||
header.ExcessBlobGas = &excessBlobGas
|
||||
header.ParentBeaconRoot = genParams.beaconRoot
|
||||
}
|
||||
if miner.chainConfig.IsVerkle(header.Number, header.Time) &&
|
||||
!miner.chainConfig.IsVerkle(parent.Number, parent.Time) {
|
||||
miner.chain.SetForkBoundary(parent.Root)
|
||||
defer miner.chain.ClearForkBoundary()
|
||||
}
|
||||
// Could potentially happen if starting to mine in an odd state.
|
||||
// Note genParams.coinbase can be different with header.Coinbase
|
||||
// since clique algorithm can modify the coinbase field in header.
|
||||
|
|
|
|||
Loading…
Reference in a new issue