From fe247d464dec96c0a11800de09e16f7bd26eecd1 Mon Sep 17 00:00:00 2001 From: Austin Larson Date: Tue, 6 Jan 2026 10:52:48 -0500 Subject: [PATCH 1/5] refactor: Config helper --- graft/coreth/core/blockchain.go | 12 ++--- graft/coreth/core/genesis_test.go | 4 +- graft/coreth/tests/state_test_util.go | 3 +- graft/evm/firewood/hash_test.go | 3 +- graft/evm/firewood/triedb.go | 64 ++++++++++++++--------- graft/subnet-evm/core/blockchain.go | 12 ++--- graft/subnet-evm/core/genesis_test.go | 4 +- graft/subnet-evm/tests/state_test_util.go | 3 +- 8 files changed, 56 insertions(+), 49 deletions(-) diff --git a/graft/coreth/core/blockchain.go b/graft/coreth/core/blockchain.go index 875e5ca35b05..e34805ee681c 100644 --- a/graft/coreth/core/blockchain.go +++ b/graft/coreth/core/blockchain.go @@ -231,12 +231,12 @@ func (c *CacheConfig) triedbConfig() *triedb.Config { } config.DBOverride = firewood.Config{ - ChainDataDir: c.ChainDataDir, - CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, - FreeListCacheEntries: firewood.Defaults.FreeListCacheEntries, - Revisions: uint(c.StateHistory), // must be at least 2 - ReadCacheStrategy: ffi.CacheAllReads, - ArchiveMode: !c.Pruning, + DatabasePath: c.ChainDataDir, + CacheSizeBytes: uint(c.TrieCleanLimit * 1024 * 1024), + FreeListCacheEntries: 40_000, // Firewood default + RevisionsInMemory: uint(c.StateHistory), // must be at least 2 + CacheStrategy: ffi.CacheAllReads, + Archive: !c.Pruning, }.BackendConstructor } return config diff --git a/graft/coreth/core/genesis_test.go b/graft/coreth/core/genesis_test.go index 75b112602054..ffaecc301d4a 100644 --- a/graft/coreth/core/genesis_test.go +++ b/graft/coreth/core/genesis_test.go @@ -304,9 +304,7 @@ func newDbConfig(t *testing.T, scheme string) *triedb.Config { case rawdb.PathScheme: return &triedb.Config{DBOverride: pathdb.Defaults.BackendConstructor} case customrawdb.FirewoodScheme: - fwCfg := firewood.Defaults - // Create a unique temporary directory for each test - fwCfg.ChainDataDir = t.TempDir() + fwCfg := firewood.DefaultConfig(t.TempDir()) return &triedb.Config{DBOverride: fwCfg.BackendConstructor} default: t.Fatalf("unknown scheme %s", scheme) diff --git a/graft/coreth/tests/state_test_util.go b/graft/coreth/tests/state_test_util.go index 502446f001f4..b54cb4426cd8 100644 --- a/graft/coreth/tests/state_test_util.go +++ b/graft/coreth/tests/state_test_util.go @@ -68,8 +68,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bo case rawdb.PathScheme: tconf.DBOverride = pathdb.Defaults.BackendConstructor case customrawdb.FirewoodScheme: - cfg := firewood.Defaults - cfg.ChainDataDir = tempdir + cfg := firewood.DefaultConfig(tempdir) tconf.DBOverride = cfg.BackendConstructor default: panic("unknown trie database scheme" + scheme) diff --git a/graft/evm/firewood/hash_test.go b/graft/evm/firewood/hash_test.go index 184b307c6b59..5b676a4fdf11 100644 --- a/graft/evm/firewood/hash_test.go +++ b/graft/evm/firewood/hash_test.go @@ -77,8 +77,7 @@ func newFuzzState(t *testing.T) *fuzzState { }) firewoodMemdb := rawdb.NewMemoryDatabase() - fwCfg := Defaults // copy the defaults - fwCfg.ChainDataDir = t.TempDir() // Use a temporary directory for the Firewood + fwCfg := DefaultConfig(t.TempDir()) firewoodState := state.NewDatabaseWithConfig( firewoodMemdb, &triedb.Config{ diff --git a/graft/evm/firewood/triedb.go b/graft/evm/firewood/triedb.go index c495433042b4..612496bda535 100644 --- a/graft/evm/firewood/triedb.go +++ b/graft/evm/firewood/triedb.go @@ -63,21 +63,30 @@ type ProposalContext struct { Children []*ProposalContext } +// Config provides necessary parameters for creating a Firewood database. type Config struct { - ChainDataDir string - CleanCacheSize int // Size of the clean cache in bytes - FreeListCacheEntries uint // Number of free list entries to cache - Revisions uint // Number of revisions to keep in memory (must be >= 2) - ReadCacheStrategy ffi.CacheStrategy - ArchiveMode bool + DatabasePath string // directory where the database files will be stored + CacheSizeBytes uint + FreeListCacheEntries uint + RevisionsInMemory uint // must be >= 2 + CacheStrategy ffi.CacheStrategy + Archive bool // whether to write keep all historical revisions on disk } -// Note that `FilePath` is not specified, and must always be set by the user. -var Defaults = Config{ - CleanCacheSize: 1024 * 1024, // 1MB - FreeListCacheEntries: 40_000, - Revisions: 100, - ReadCacheStrategy: ffi.CacheAllReads, +// DefaultConfig returns a default Config with the given directory. +// The default config is: +// - CacheSizeBytes: 1MB +// - FreeListCacheEntries: 40,000 +// - MaxRevisions: 100 +// - CacheStrategy: [ffi.CacheAllReads] +func DefaultConfig(dir string) Config { + return Config{ + DatabasePath: dir, + CacheSizeBytes: 1024 * 1024, // 1MB + FreeListCacheEntries: 40_000, + RevisionsInMemory: 100, + CacheStrategy: ffi.CacheAllReads, + } } func (c Config) BackendConstructor(ethdb.Database) triedb.DBOverride { @@ -89,7 +98,12 @@ func (c Config) BackendConstructor(ethdb.Database) triedb.DBOverride { } type TrieDB struct { - fwDisk *ffi.Database // The underlying Firewood database, used for storing proposals and revisions. + // The underlying Firewood database, used for storing proposals and revisions. + // This is exported with the knowledge that consumer will not close it and the latest state can be modified + // at any time via block execution. The consumer should only use for read operations, + // or ensure that writes occur outside of block execution. + Firewood *ffi.Database + proposalLock sync.RWMutex // proposalMap provides O(1) access by state root to all proposals stored in the proposalTree proposalMap map[common.Hash][]*ProposalContext @@ -103,18 +117,18 @@ type TrieDB struct { // New creates a new Firewood database with the given disk database and configuration. // Any error during creation will cause the program to exit. func New(config Config) (*TrieDB, error) { - path := filepath.Join(config.ChainDataDir, firewoodDir) + path := filepath.Join(config.DatabasePath, firewoodDir) if err := validatePath(path); err != nil { return nil, err } options := []ffi.Option{ - ffi.WithNodeCacheEntries(uint(config.CleanCacheSize / 256)), // TODO(#4750): is 256 bytes per node a good estimate? + ffi.WithNodeCacheEntries(uint(config.CacheSizeBytes / 256)), // TODO(#4750): is 256 bytes per node a good estimate? ffi.WithFreeListCacheEntries(config.FreeListCacheEntries), - ffi.WithRevisions(config.Revisions), - ffi.WithReadCacheStrategy(config.ReadCacheStrategy), + ffi.WithRevisions(config.RevisionsInMemory), + ffi.WithReadCacheStrategy(config.CacheStrategy), } - if config.ArchiveMode { + if config.Archive { options = append(options, ffi.WithRootStore()) } @@ -129,7 +143,7 @@ func New(config Config) (*TrieDB, error) { } return &TrieDB{ - fwDisk: fw, + Firewood: fw, proposalMap: make(map[common.Hash][]*ProposalContext), proposalTree: &ProposalContext{ Root: common.Hash(currentRoot), @@ -173,7 +187,7 @@ func (*TrieDB) Scheme() string { // Initialized checks whether a non-empty genesis block has been written. func (t *TrieDB) Initialized(common.Hash) bool { - root, err := t.fwDisk.Root() + root, err := t.Firewood.Root() if err != nil { log.Error("firewood: error getting current root", "error", err) return false @@ -269,7 +283,7 @@ func (t *TrieDB) propose(root common.Hash, parentRoot common.Hash, hash common.H } log.Debug("firewood: proposing from database root", "root", root.Hex(), "height", block) - p, err := createProposal(t.fwDisk, root, keys, values) + p, err := createProposal(t.Firewood, root, keys, values) if err != nil { return err } @@ -329,7 +343,7 @@ func (t *TrieDB) Commit(root common.Hash, report bool) error { defer t.cleanupCommittedProposal(pCtx) // Assert that the root of the database matches the committed proposal root. - currentRoot, err := t.fwDisk.Root() + currentRoot, err := t.Firewood.Root() if err != nil { return fmt.Errorf("firewood: error getting current root after commit: %w", err) } @@ -382,7 +396,7 @@ func (t *TrieDB) Close() error { // Close the database // This may block momentarily while finalizers for Firewood objects run. - return t.fwDisk.Close(context.Background()) + return t.Firewood.Close(context.Background()) } // createProposal creates a new proposal from the given layer @@ -486,7 +500,7 @@ func (t *TrieDB) removeProposalFromMap(pCtx *ProposalContext) { // Reader retrieves a node reader belonging to the given state root. // An error will be returned if the requested state is not available. func (t *TrieDB) Reader(root common.Hash) (database.Reader, error) { - revision, err := t.fwDisk.Revision(ffi.Hash(root)) + revision, err := t.Firewood.Revision(ffi.Hash(root)) if err != nil { return nil, fmt.Errorf("firewood: unable to retrieve from root %s: %w", root.Hex(), err) } @@ -526,7 +540,7 @@ func (t *TrieDB) getProposalHash(parentRoot common.Hash, keys, values [][]byte) start := time.Now() if t.proposalTree.Root == parentRoot { // Propose from the database root. - p, err = t.fwDisk.Propose(keys, values) + p, err = t.Firewood.Propose(keys, values) if err != nil { return common.Hash{}, fmt.Errorf("firewood: error proposing from root %s: %w", parentRoot.Hex(), err) } diff --git a/graft/subnet-evm/core/blockchain.go b/graft/subnet-evm/core/blockchain.go index 026865b7002a..1e6f69c6c6ef 100644 --- a/graft/subnet-evm/core/blockchain.go +++ b/graft/subnet-evm/core/blockchain.go @@ -243,12 +243,12 @@ func (c *CacheConfig) triedbConfig() *triedb.Config { } config.DBOverride = firewood.Config{ - ChainDataDir: c.ChainDataDir, - CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, - FreeListCacheEntries: firewood.Defaults.FreeListCacheEntries, - Revisions: uint(c.StateHistory), // must be at least 2 - ReadCacheStrategy: ffi.CacheAllReads, - ArchiveMode: !c.Pruning, + DatabasePath: c.ChainDataDir, + CacheSizeBytes: uint(c.TrieCleanLimit * 1024 * 1024), + FreeListCacheEntries: 40_000, // Firewood default + RevisionsInMemory: uint(c.StateHistory), // must be at least 2 + CacheStrategy: ffi.CacheAllReads, + Archive: !c.Pruning, }.BackendConstructor } return config diff --git a/graft/subnet-evm/core/genesis_test.go b/graft/subnet-evm/core/genesis_test.go index 119364956dc9..422c2db3cf4c 100644 --- a/graft/subnet-evm/core/genesis_test.go +++ b/graft/subnet-evm/core/genesis_test.go @@ -370,9 +370,7 @@ func newDbConfig(t *testing.T, scheme string) *triedb.Config { case rawdb.PathScheme: return &triedb.Config{DBOverride: pathdb.Defaults.BackendConstructor} case customrawdb.FirewoodScheme: - fwCfg := firewood.Defaults - // Create a unique temporary directory for each test - fwCfg.ChainDataDir = t.TempDir() + fwCfg := firewood.DefaultConfig(t.TempDir()) return &triedb.Config{DBOverride: fwCfg.BackendConstructor} default: t.Fatalf("unknown scheme %s", scheme) diff --git a/graft/subnet-evm/tests/state_test_util.go b/graft/subnet-evm/tests/state_test_util.go index 0e1f64bb085c..a47a097950ae 100644 --- a/graft/subnet-evm/tests/state_test_util.go +++ b/graft/subnet-evm/tests/state_test_util.go @@ -68,8 +68,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bo case rawdb.PathScheme: tconf.DBOverride = pathdb.Defaults.BackendConstructor case customrawdb.FirewoodScheme: - cfg := firewood.Defaults - cfg.ChainDataDir = tempdir + cfg := firewood.DefaultConfig(tempdir) tconf.DBOverride = cfg.BackendConstructor default: panic("unknown trie database scheme" + scheme) From cd84f27b861bd1376c0d41d6b24746dc137b1a59 Mon Sep 17 00:00:00 2001 From: Austin Larson Date: Tue, 6 Jan 2026 11:01:14 -0500 Subject: [PATCH 2/5] chore: lint --- graft/evm/firewood/triedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graft/evm/firewood/triedb.go b/graft/evm/firewood/triedb.go index 612496bda535..73c22df202c7 100644 --- a/graft/evm/firewood/triedb.go +++ b/graft/evm/firewood/triedb.go @@ -123,7 +123,7 @@ func New(config Config) (*TrieDB, error) { } options := []ffi.Option{ - ffi.WithNodeCacheEntries(uint(config.CacheSizeBytes / 256)), // TODO(#4750): is 256 bytes per node a good estimate? + ffi.WithNodeCacheEntries(config.CacheSizeBytes / 256), // TODO(#4750): is 256 bytes per node a good estimate? ffi.WithFreeListCacheEntries(config.FreeListCacheEntries), ffi.WithRevisions(config.RevisionsInMemory), ffi.WithReadCacheStrategy(config.CacheStrategy), From 13b7c52d7959fc8f0dc84b8a30aad68b4c6a573e Mon Sep 17 00:00:00 2001 From: Austin Larson Date: Tue, 6 Jan 2026 11:50:03 -0500 Subject: [PATCH 3/5] docs: fix variable name --- graft/evm/firewood/triedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graft/evm/firewood/triedb.go b/graft/evm/firewood/triedb.go index 73c22df202c7..7f33f9ab473b 100644 --- a/graft/evm/firewood/triedb.go +++ b/graft/evm/firewood/triedb.go @@ -77,7 +77,7 @@ type Config struct { // The default config is: // - CacheSizeBytes: 1MB // - FreeListCacheEntries: 40,000 -// - MaxRevisions: 100 +// - RevisionsInMemory: 100 // - CacheStrategy: [ffi.CacheAllReads] func DefaultConfig(dir string) Config { return Config{ From 6639250ddb81398aff58687e5dc8240b50976fc2 Mon Sep 17 00:00:00 2001 From: Austin Larson Date: Tue, 6 Jan 2026 13:08:47 -0500 Subject: [PATCH 4/5] style: rename --- graft/coreth/core/blockchain.go | 2 +- graft/evm/firewood/triedb.go | 6 +++--- graft/subnet-evm/core/blockchain.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/graft/coreth/core/blockchain.go b/graft/coreth/core/blockchain.go index e34805ee681c..37ab013b8a98 100644 --- a/graft/coreth/core/blockchain.go +++ b/graft/coreth/core/blockchain.go @@ -231,7 +231,7 @@ func (c *CacheConfig) triedbConfig() *triedb.Config { } config.DBOverride = firewood.Config{ - DatabasePath: c.ChainDataDir, + DatabaseDirPath: c.ChainDataDir, CacheSizeBytes: uint(c.TrieCleanLimit * 1024 * 1024), FreeListCacheEntries: 40_000, // Firewood default RevisionsInMemory: uint(c.StateHistory), // must be at least 2 diff --git a/graft/evm/firewood/triedb.go b/graft/evm/firewood/triedb.go index 7f33f9ab473b..824cb895bed6 100644 --- a/graft/evm/firewood/triedb.go +++ b/graft/evm/firewood/triedb.go @@ -65,7 +65,7 @@ type ProposalContext struct { // Config provides necessary parameters for creating a Firewood database. type Config struct { - DatabasePath string // directory where the database files will be stored + DatabaseDirPath string CacheSizeBytes uint FreeListCacheEntries uint RevisionsInMemory uint // must be >= 2 @@ -81,7 +81,7 @@ type Config struct { // - CacheStrategy: [ffi.CacheAllReads] func DefaultConfig(dir string) Config { return Config{ - DatabasePath: dir, + DatabaseDirPath: dir, CacheSizeBytes: 1024 * 1024, // 1MB FreeListCacheEntries: 40_000, RevisionsInMemory: 100, @@ -117,7 +117,7 @@ type TrieDB struct { // New creates a new Firewood database with the given disk database and configuration. // Any error during creation will cause the program to exit. func New(config Config) (*TrieDB, error) { - path := filepath.Join(config.DatabasePath, firewoodDir) + path := filepath.Join(config.DatabaseDirPath, firewoodDir) if err := validatePath(path); err != nil { return nil, err } diff --git a/graft/subnet-evm/core/blockchain.go b/graft/subnet-evm/core/blockchain.go index 1e6f69c6c6ef..72b0cea058c0 100644 --- a/graft/subnet-evm/core/blockchain.go +++ b/graft/subnet-evm/core/blockchain.go @@ -243,7 +243,7 @@ func (c *CacheConfig) triedbConfig() *triedb.Config { } config.DBOverride = firewood.Config{ - DatabasePath: c.ChainDataDir, + DatabaseDirPath: c.ChainDataDir, CacheSizeBytes: uint(c.TrieCleanLimit * 1024 * 1024), FreeListCacheEntries: 40_000, // Firewood default RevisionsInMemory: uint(c.StateHistory), // must be at least 2 From d1ed814db491bfbbeb01567d4e5487ad5041e7cf Mon Sep 17 00:00:00 2001 From: Austin Larson Date: Tue, 6 Jan 2026 15:44:15 -0500 Subject: [PATCH 5/5] style: comments --- graft/coreth/core/blockchain.go | 4 ++-- graft/evm/firewood/triedb.go | 21 ++++++++++----------- graft/subnet-evm/core/blockchain.go | 4 ++-- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/graft/coreth/core/blockchain.go b/graft/coreth/core/blockchain.go index 37ab013b8a98..1d3443bc8d48 100644 --- a/graft/coreth/core/blockchain.go +++ b/graft/coreth/core/blockchain.go @@ -230,8 +230,8 @@ func (c *CacheConfig) triedbConfig() *triedb.Config { log.Crit("Chain data directory must be specified for Firewood") } - config.DBOverride = firewood.Config{ - DatabaseDirPath: c.ChainDataDir, + config.DBOverride = firewood.TrieDBConfig{ + DatabaseDir: c.ChainDataDir, CacheSizeBytes: uint(c.TrieCleanLimit * 1024 * 1024), FreeListCacheEntries: 40_000, // Firewood default RevisionsInMemory: uint(c.StateHistory), // must be at least 2 diff --git a/graft/evm/firewood/triedb.go b/graft/evm/firewood/triedb.go index 824cb895bed6..4a5fe90e2962 100644 --- a/graft/evm/firewood/triedb.go +++ b/graft/evm/firewood/triedb.go @@ -63,25 +63,24 @@ type ProposalContext struct { Children []*ProposalContext } -// Config provides necessary parameters for creating a Firewood database. -type Config struct { - DatabaseDirPath string +type TrieDBConfig struct { + DatabaseDir string CacheSizeBytes uint FreeListCacheEntries uint RevisionsInMemory uint // must be >= 2 CacheStrategy ffi.CacheStrategy - Archive bool // whether to write keep all historical revisions on disk + Archive bool } -// DefaultConfig returns a default Config with the given directory. +// DefaultConfig returns a sensible TrieDBConfig with the given directory. // The default config is: // - CacheSizeBytes: 1MB // - FreeListCacheEntries: 40,000 // - RevisionsInMemory: 100 // - CacheStrategy: [ffi.CacheAllReads] -func DefaultConfig(dir string) Config { - return Config{ - DatabaseDirPath: dir, +func DefaultConfig(dir string) TrieDBConfig { + return TrieDBConfig{ + DatabaseDir: dir, CacheSizeBytes: 1024 * 1024, // 1MB FreeListCacheEntries: 40_000, RevisionsInMemory: 100, @@ -89,7 +88,7 @@ func DefaultConfig(dir string) Config { } } -func (c Config) BackendConstructor(ethdb.Database) triedb.DBOverride { +func (c TrieDBConfig) BackendConstructor(ethdb.Database) triedb.DBOverride { db, err := New(c) if err != nil { log.Crit("firewood: error creating database", "error", err) @@ -116,8 +115,8 @@ type TrieDB struct { // New creates a new Firewood database with the given disk database and configuration. // Any error during creation will cause the program to exit. -func New(config Config) (*TrieDB, error) { - path := filepath.Join(config.DatabaseDirPath, firewoodDir) +func New(config TrieDBConfig) (*TrieDB, error) { + path := filepath.Join(config.DatabaseDir, firewoodDir) if err := validatePath(path); err != nil { return nil, err } diff --git a/graft/subnet-evm/core/blockchain.go b/graft/subnet-evm/core/blockchain.go index 72b0cea058c0..87e14cc0a6a8 100644 --- a/graft/subnet-evm/core/blockchain.go +++ b/graft/subnet-evm/core/blockchain.go @@ -242,8 +242,8 @@ func (c *CacheConfig) triedbConfig() *triedb.Config { log.Crit("Chain data directory must be specified for Firewood") } - config.DBOverride = firewood.Config{ - DatabaseDirPath: c.ChainDataDir, + config.DBOverride = firewood.TrieDBConfig{ + DatabaseDir: c.ChainDataDir, CacheSizeBytes: uint(c.TrieCleanLimit * 1024 * 1024), FreeListCacheEntries: 40_000, // Firewood default RevisionsInMemory: uint(c.StateHistory), // must be at least 2