From 53ff723cc70de40dd31e819053fdb7e054f683d0 Mon Sep 17 00:00:00 2001 From: CPerezz <37264926+CPerezz@users.noreply.github.com> Date: Sat, 18 Apr 2026 18:47:22 +0200 Subject: [PATCH] core/state: handle *bintrie.BinaryTrie in mustCopyTrie (#34758) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem `mustCopyTrie` in `core/state/database.go` panics on any trie type not in its type switch: ```go func mustCopyTrie(t Trie) Trie { switch t := t.(type) { case *trie.StateTrie: return t.Copy() case *transitiontrie.TransitionTrie: return t.Copy() default: panic(fmt.Errorf("unknown trie type %T", t)) } } ``` On UBT-backed databases (`state.NewUBTDatabase(...)`, used by `blockchain.go:2124` when the triedb is configured for binary trie), `StateDB.trie` is `*bintrie.BinaryTrie` — so every `StateDB.Copy()` call (hit from `statedb.go:699` and the `*trie.StateTrie` branch of `state_object.go:546`) crashes with `unknown trie type *bintrie.BinaryTrie`. ## Fix Add the `*bintrie.BinaryTrie` case. `BinaryTrie.Copy()` already exists at `trie/bintrie/trie.go:372` and produces a correct deep copy — this just wires it into the switch. --- core/state/database.go | 3 +++ core/state/statedb_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/core/state/database.go b/core/state/database.go index 6de58af63b..b9ccff658f 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/bintrie" "github.com/ethereum/go-ethereum/trie/transitiontrie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/triedb" @@ -181,6 +182,8 @@ func mustCopyTrie(t Trie) Trie { return t.Copy() case *transitiontrie.TransitionTrie: return t.Copy() + case *bintrie.BinaryTrie: + return t.Copy() default: panic(fmt.Errorf("unknown trie type %T", t)) } diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 601970ff20..b5ef42b3e0 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1366,3 +1366,38 @@ func TestStorageDirtiness(t *testing.T) { state.RevertToSnapshot(snap) checkDirty(common.Hash{0x1}, common.Hash{0x1}, true) } + +// TestStateDBCopyUBT exercises StateDB.Copy on a UBT-backed state database. +// Before the mustCopyTrie fix, this panicked with "unknown trie type +// *bintrie.BinaryTrie" because the type switch in mustCopyTrie only covered +// *trie.StateTrie and *transitiontrie.TransitionTrie. +func TestStateDBCopyUBT(t *testing.T) { + disk := rawdb.NewMemoryDatabase() + tdb := triedb.NewDatabase(disk, triedb.UBTDefaults) + sdb := NewDatabase(tdb, nil) + + orig, err := New(types.EmptyRootHash, sdb) + if err != nil { + t.Fatalf("New: %v", err) + } + + // Touch the trie so StateDB.Copy actually has to copy it. + addr := common.HexToAddress("0x1111111111111111111111111111111111111111") + orig.SetBalance(addr, uint256.NewInt(1_000), tracing.BalanceChangeUnspecified) + + // Must not panic. + cpy := orig.Copy() + if cpy == nil { + t.Fatal("Copy returned nil") + } + + // The copy must be independent: mutating the copy does not affect the + // original. Use balance as an observable. + cpy.SetBalance(addr, uint256.NewInt(2_000), tracing.BalanceChangeUnspecified) + if got, want := orig.GetBalance(addr), uint256.NewInt(1_000); got.Cmp(want) != 0 { + t.Fatalf("original balance mutated through copy: got %s, want %s", got, want) + } + if got, want := cpy.GetBalance(addr), uint256.NewInt(2_000); got.Cmp(want) != 0 { + t.Fatalf("copy balance did not update: got %s, want %s", got, want) + } +}