core/state: handle *bintrie.BinaryTrie in mustCopyTrie (#34758)
Some checks failed
/ Linux Build (push) Has been cancelled
/ Linux Build (arm) (push) Has been cancelled
/ Keeper Build (push) Has been cancelled
/ Windows Build (push) Has been cancelled
/ Docker Image (push) Has been cancelled

## 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.
This commit is contained in:
CPerezz 2026-04-18 18:47:22 +02:00 committed by GitHub
parent 61bfacc52f
commit 53ff723cc7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 38 additions and 0 deletions

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie" "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/transitiontrie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
@ -181,6 +182,8 @@ func mustCopyTrie(t Trie) Trie {
return t.Copy() return t.Copy()
case *transitiontrie.TransitionTrie: case *transitiontrie.TransitionTrie:
return t.Copy() return t.Copy()
case *bintrie.BinaryTrie:
return t.Copy()
default: default:
panic(fmt.Errorf("unknown trie type %T", t)) panic(fmt.Errorf("unknown trie type %T", t))
} }

View file

@ -1366,3 +1366,38 @@ func TestStorageDirtiness(t *testing.T) {
state.RevertToSnapshot(snap) state.RevertToSnapshot(snap)
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true) 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)
}
}