mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-04-10 11:57:30 +00:00
1033 lines
33 KiB
Go
1033 lines
33 KiB
Go
// Copyright 2024 The go-ethereum Authors
|
||
// This file is part of the go-ethereum library.
|
||
//
|
||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||
// it under the terms of the GNU Lesser General Public License as published by
|
||
// the Free Software Foundation, either version 3 of the License, or
|
||
// (at your option) any later version.
|
||
//
|
||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
// GNU Lesser General Public License for more details.
|
||
//
|
||
// You should have received a copy of the GNU Lesser General Public License
|
||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
|
||
|
||
package pathdb
|
||
|
||
import (
|
||
"errors"
|
||
"testing"
|
||
|
||
"github.com/ethereum/go-ethereum/common"
|
||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||
)
|
||
|
||
func newTestLayerTree() *layerTree {
|
||
db := New(rawdb.NewMemoryDatabase(), nil, false)
|
||
l := newDiskLayer(common.Hash{0x1}, 0, db, nil, nil, newBuffer(0, nil, nil, 0), nil)
|
||
t := newLayerTree(l)
|
||
return t
|
||
}
|
||
|
||
func TestLayerCap(t *testing.T) {
|
||
var cases = []struct {
|
||
init func() *layerTree
|
||
head common.Hash
|
||
layers int
|
||
base common.Hash
|
||
snapshot map[common.Hash]struct{}
|
||
}{
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4}, common.Hash{0x3}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
// Chain:
|
||
// C2->C3->C4 (HEAD)
|
||
head: common.Hash{0x4},
|
||
layers: 2,
|
||
base: common.Hash{0x2},
|
||
snapshot: map[common.Hash]struct{}{
|
||
common.Hash{0x2}: {},
|
||
common.Hash{0x3}: {},
|
||
common.Hash{0x4}: {},
|
||
},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4}, common.Hash{0x3}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
// Chain:
|
||
// C3->C4 (HEAD)
|
||
head: common.Hash{0x4},
|
||
layers: 1,
|
||
base: common.Hash{0x3},
|
||
snapshot: map[common.Hash]struct{}{
|
||
common.Hash{0x3}: {},
|
||
common.Hash{0x4}: {},
|
||
},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4}, common.Hash{0x3}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
// Chain:
|
||
// C4 (HEAD)
|
||
head: common.Hash{0x4},
|
||
layers: 0,
|
||
base: common.Hash{0x4},
|
||
snapshot: map[common.Hash]struct{}{
|
||
common.Hash{0x4}: {},
|
||
},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
// ->C2'->C3'->C4'
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2a}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3a}, common.Hash{0x2a}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4a}, common.Hash{0x3a}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x2b}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3b}, common.Hash{0x2b}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4b}, common.Hash{0x3b}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
// Chain:
|
||
// C2->C3->C4 (HEAD)
|
||
head: common.Hash{0x4a},
|
||
layers: 2,
|
||
base: common.Hash{0x2a},
|
||
snapshot: map[common.Hash]struct{}{
|
||
common.Hash{0x4a}: {},
|
||
common.Hash{0x3a}: {},
|
||
common.Hash{0x2a}: {},
|
||
},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
// ->C2'->C3'->C4'
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2a}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3a}, common.Hash{0x2a}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4a}, common.Hash{0x3a}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x2b}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3b}, common.Hash{0x2b}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4b}, common.Hash{0x3b}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
// Chain:
|
||
// C3->C4 (HEAD)
|
||
head: common.Hash{0x4a},
|
||
layers: 1,
|
||
base: common.Hash{0x3a},
|
||
snapshot: map[common.Hash]struct{}{
|
||
common.Hash{0x4a}: {},
|
||
common.Hash{0x3a}: {},
|
||
},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
// ->C3'->C4'
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3a}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4a}, common.Hash{0x3a}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3b}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4b}, common.Hash{0x3b}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
// Chain:
|
||
// C2->C3->C4 (HEAD)
|
||
// ->C3'->C4'
|
||
head: common.Hash{0x4a},
|
||
layers: 2,
|
||
base: common.Hash{0x2},
|
||
snapshot: map[common.Hash]struct{}{
|
||
common.Hash{0x4a}: {},
|
||
common.Hash{0x3a}: {},
|
||
common.Hash{0x4b}: {},
|
||
common.Hash{0x3b}: {},
|
||
common.Hash{0x2}: {},
|
||
},
|
||
},
|
||
}
|
||
for _, c := range cases {
|
||
tr := c.init()
|
||
if err := tr.cap(c.head, c.layers); err != nil {
|
||
t.Fatalf("Failed to cap the layer tree %v", err)
|
||
}
|
||
if tr.bottom().root != c.base {
|
||
t.Fatalf("Unexpected bottom layer tree root, want %v, got %v", c.base, tr.bottom().root)
|
||
}
|
||
if len(c.snapshot) != len(tr.layers) {
|
||
t.Fatalf("Unexpected layer tree size, want %v, got %v", len(c.snapshot), len(tr.layers))
|
||
}
|
||
for h := range tr.layers {
|
||
if _, ok := c.snapshot[h]; !ok {
|
||
t.Fatalf("Unexpected layer %v", h)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestBaseLayer(t *testing.T) {
|
||
tr := newTestLayerTree()
|
||
|
||
var cases = []struct {
|
||
op func()
|
||
base common.Hash
|
||
}{
|
||
// Chain:
|
||
// C1 (HEAD)
|
||
{
|
||
func() {},
|
||
common.Hash{0x1},
|
||
},
|
||
// Chain:
|
||
// C1->C2->C3 (HEAD)
|
||
{
|
||
func() {
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
},
|
||
common.Hash{0x1},
|
||
},
|
||
// Chain:
|
||
// C3 (HEAD)
|
||
{
|
||
func() {
|
||
tr.cap(common.Hash{0x3}, 0)
|
||
},
|
||
common.Hash{0x3},
|
||
},
|
||
// Chain:
|
||
// C4->C5->C6 (HEAD)
|
||
{
|
||
func() {
|
||
tr.add(common.Hash{0x4}, common.Hash{0x3}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x5}, common.Hash{0x4}, 4, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x6}, common.Hash{0x5}, 5, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.cap(common.Hash{0x6}, 2)
|
||
},
|
||
common.Hash{0x4},
|
||
},
|
||
}
|
||
for _, c := range cases {
|
||
c.op()
|
||
if tr.base.rootHash() != c.base {
|
||
t.Fatalf("Unexpected base root, want %v, got: %v", c.base, tr.base.rootHash())
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestDescendant(t *testing.T) {
|
||
var cases = []struct {
|
||
init func() *layerTree
|
||
snapshotA map[common.Hash]map[common.Hash]struct{}
|
||
op func(tr *layerTree)
|
||
snapshotB map[common.Hash]map[common.Hash]struct{}
|
||
}{
|
||
{
|
||
// Chain:
|
||
// C1->C2 (HEAD)
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
snapshotA: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x1}: {
|
||
common.Hash{0x2}: {},
|
||
},
|
||
},
|
||
// Chain:
|
||
// C1->C2->C3 (HEAD)
|
||
op: func(tr *layerTree) {
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
},
|
||
snapshotB: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x1}: {
|
||
common.Hash{0x2}: {},
|
||
common.Hash{0x3}: {},
|
||
},
|
||
common.Hash{0x2}: {
|
||
common.Hash{0x3}: {},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4}, common.Hash{0x3}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
snapshotA: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x1}: {
|
||
common.Hash{0x2}: {},
|
||
common.Hash{0x3}: {},
|
||
common.Hash{0x4}: {},
|
||
},
|
||
common.Hash{0x2}: {
|
||
common.Hash{0x3}: {},
|
||
common.Hash{0x4}: {},
|
||
},
|
||
common.Hash{0x3}: {
|
||
common.Hash{0x4}: {},
|
||
},
|
||
},
|
||
// Chain:
|
||
// C2->C3->C4 (HEAD)
|
||
op: func(tr *layerTree) {
|
||
tr.cap(common.Hash{0x4}, 2)
|
||
},
|
||
snapshotB: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x2}: {
|
||
common.Hash{0x3}: {},
|
||
common.Hash{0x4}: {},
|
||
},
|
||
common.Hash{0x3}: {
|
||
common.Hash{0x4}: {},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4}, common.Hash{0x3}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
snapshotA: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x1}: {
|
||
common.Hash{0x2}: {},
|
||
common.Hash{0x3}: {},
|
||
common.Hash{0x4}: {},
|
||
},
|
||
common.Hash{0x2}: {
|
||
common.Hash{0x3}: {},
|
||
common.Hash{0x4}: {},
|
||
},
|
||
common.Hash{0x3}: {
|
||
common.Hash{0x4}: {},
|
||
},
|
||
},
|
||
// Chain:
|
||
// C3->C4 (HEAD)
|
||
op: func(tr *layerTree) {
|
||
tr.cap(common.Hash{0x4}, 1)
|
||
},
|
||
snapshotB: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x3}: {
|
||
common.Hash{0x4}: {},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4}, common.Hash{0x3}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
snapshotA: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x1}: {
|
||
common.Hash{0x2}: {},
|
||
common.Hash{0x3}: {},
|
||
common.Hash{0x4}: {},
|
||
},
|
||
common.Hash{0x2}: {
|
||
common.Hash{0x3}: {},
|
||
common.Hash{0x4}: {},
|
||
},
|
||
common.Hash{0x3}: {
|
||
common.Hash{0x4}: {},
|
||
},
|
||
},
|
||
// Chain:
|
||
// C4 (HEAD)
|
||
op: func(tr *layerTree) {
|
||
tr.cap(common.Hash{0x4}, 0)
|
||
},
|
||
snapshotB: map[common.Hash]map[common.Hash]struct{}{},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
// ->C2'->C3'->C4'
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2a}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3a}, common.Hash{0x2a}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4a}, common.Hash{0x3a}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x2b}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3b}, common.Hash{0x2b}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4b}, common.Hash{0x3b}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
snapshotA: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x1}: {
|
||
common.Hash{0x2a}: {},
|
||
common.Hash{0x3a}: {},
|
||
common.Hash{0x4a}: {},
|
||
common.Hash{0x2b}: {},
|
||
common.Hash{0x3b}: {},
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
common.Hash{0x2a}: {
|
||
common.Hash{0x3a}: {},
|
||
common.Hash{0x4a}: {},
|
||
},
|
||
common.Hash{0x3a}: {
|
||
common.Hash{0x4a}: {},
|
||
},
|
||
common.Hash{0x2b}: {
|
||
common.Hash{0x3b}: {},
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
common.Hash{0x3b}: {
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
},
|
||
// Chain:
|
||
// C2->C3->C4 (HEAD)
|
||
op: func(tr *layerTree) {
|
||
tr.cap(common.Hash{0x4a}, 2)
|
||
},
|
||
snapshotB: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x2a}: {
|
||
common.Hash{0x3a}: {},
|
||
common.Hash{0x4a}: {},
|
||
},
|
||
common.Hash{0x3a}: {
|
||
common.Hash{0x4a}: {},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
// ->C2'->C3'->C4'
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2a}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3a}, common.Hash{0x2a}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4a}, common.Hash{0x3a}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x2b}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3b}, common.Hash{0x2b}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4b}, common.Hash{0x3b}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
snapshotA: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x1}: {
|
||
common.Hash{0x2a}: {},
|
||
common.Hash{0x3a}: {},
|
||
common.Hash{0x4a}: {},
|
||
common.Hash{0x2b}: {},
|
||
common.Hash{0x3b}: {},
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
common.Hash{0x2a}: {
|
||
common.Hash{0x3a}: {},
|
||
common.Hash{0x4a}: {},
|
||
},
|
||
common.Hash{0x3a}: {
|
||
common.Hash{0x4a}: {},
|
||
},
|
||
common.Hash{0x2b}: {
|
||
common.Hash{0x3b}: {},
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
common.Hash{0x3b}: {
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
},
|
||
// Chain:
|
||
// C3->C4 (HEAD)
|
||
op: func(tr *layerTree) {
|
||
tr.cap(common.Hash{0x4a}, 1)
|
||
},
|
||
snapshotB: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x3a}: {
|
||
common.Hash{0x4a}: {},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
// ->C3'->C4'
|
||
init: func() *layerTree {
|
||
tr := newTestLayerTree()
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3a}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4a}, common.Hash{0x3a}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3b}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4b}, common.Hash{0x3b}, 3, NewNodeSetWithOrigin(nil, nil), NewStateSetWithOrigin(nil, nil, nil, nil, false))
|
||
return tr
|
||
},
|
||
snapshotA: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x1}: {
|
||
common.Hash{0x2}: {},
|
||
common.Hash{0x3a}: {},
|
||
common.Hash{0x4a}: {},
|
||
common.Hash{0x3b}: {},
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
common.Hash{0x2}: {
|
||
common.Hash{0x3a}: {},
|
||
common.Hash{0x4a}: {},
|
||
common.Hash{0x3b}: {},
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
common.Hash{0x3a}: {
|
||
common.Hash{0x4a}: {},
|
||
},
|
||
common.Hash{0x3b}: {
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
},
|
||
// Chain:
|
||
// C2->C3->C4 (HEAD)
|
||
// ->C3'->C4'
|
||
op: func(tr *layerTree) {
|
||
tr.cap(common.Hash{0x4a}, 2)
|
||
},
|
||
snapshotB: map[common.Hash]map[common.Hash]struct{}{
|
||
common.Hash{0x2}: {
|
||
common.Hash{0x3a}: {},
|
||
common.Hash{0x4a}: {},
|
||
common.Hash{0x3b}: {},
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
common.Hash{0x3a}: {
|
||
common.Hash{0x4a}: {},
|
||
},
|
||
common.Hash{0x3b}: {
|
||
common.Hash{0x4b}: {},
|
||
},
|
||
},
|
||
},
|
||
}
|
||
check := func(setA, setB map[common.Hash]map[common.Hash]struct{}) bool {
|
||
if len(setA) != len(setB) {
|
||
return false
|
||
}
|
||
for h, subA := range setA {
|
||
subB, ok := setB[h]
|
||
if !ok {
|
||
return false
|
||
}
|
||
if len(subA) != len(subB) {
|
||
return false
|
||
}
|
||
for hh := range subA {
|
||
if _, ok := subB[hh]; !ok {
|
||
return false
|
||
}
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
for _, c := range cases {
|
||
tr := c.init()
|
||
if !check(c.snapshotA, tr.descendants) {
|
||
t.Fatalf("Unexpected descendants")
|
||
}
|
||
c.op(tr)
|
||
if !check(c.snapshotB, tr.descendants) {
|
||
t.Fatalf("Unexpected descendants")
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestDuplicateRootLookup(t *testing.T) {
|
||
// Chain:
|
||
// C1->C2->C3 (HEAD)
|
||
tr := newTestLayerTree() // base = 0x1
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil), nil, nil, false))
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil), nil, nil, false))
|
||
|
||
// A fork block with the same state root as C2; inserting it must not
|
||
// pollute the lookup history for the canonical descendant C3.
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil), nil, nil, false))
|
||
if n := tr.len(); n != 3 {
|
||
t.Fatalf("duplicate root insert changed layer count, got %d, want 3", n)
|
||
}
|
||
|
||
l, err := tr.lookupAccount(common.HexToHash("0xa"), common.Hash{0x3})
|
||
if err != nil {
|
||
t.Fatalf("account lookup failed: %v", err)
|
||
}
|
||
if l.rootHash() != (common.Hash{0x3}) {
|
||
t.Errorf("unexpected account tip, want %x, got %x", common.Hash{0x3}, l.rootHash())
|
||
}
|
||
|
||
l, err = tr.lookupStorage(common.HexToHash("0xa"), common.HexToHash("0x1"), common.Hash{0x3})
|
||
if err != nil {
|
||
t.Fatalf("storage lookup failed: %v", err)
|
||
}
|
||
if l.rootHash() != (common.Hash{0x3}) {
|
||
t.Errorf("unexpected storage tip, want %x, got %x", common.Hash{0x3}, l.rootHash())
|
||
}
|
||
}
|
||
|
||
func TestAccountLookup(t *testing.T) {
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
tr := newTestLayerTree() // base = 0x1
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(randomAccountSet("0xa"), nil, nil, nil, false))
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(randomAccountSet("0xb"), nil, nil, nil, false))
|
||
tr.add(common.Hash{0x4}, common.Hash{0x3}, 3, NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(randomAccountSet("0xa", "0xc"), nil, nil, nil, false))
|
||
|
||
var cases = []struct {
|
||
account common.Hash
|
||
state common.Hash
|
||
expect common.Hash
|
||
}{
|
||
{
|
||
// unknown account
|
||
common.HexToHash("0xd"), common.Hash{0x4}, common.Hash{0x1},
|
||
},
|
||
/*
|
||
lookup account from the top
|
||
*/
|
||
{
|
||
common.HexToHash("0xa"), common.Hash{0x4}, common.Hash{0x4},
|
||
},
|
||
{
|
||
common.HexToHash("0xb"), common.Hash{0x4}, common.Hash{0x3},
|
||
},
|
||
{
|
||
common.HexToHash("0xc"), common.Hash{0x4}, common.Hash{0x4},
|
||
},
|
||
/*
|
||
lookup account from the middle
|
||
*/
|
||
{
|
||
common.HexToHash("0xa"), common.Hash{0x3}, common.Hash{0x2},
|
||
},
|
||
{
|
||
common.HexToHash("0xb"), common.Hash{0x3}, common.Hash{0x3},
|
||
},
|
||
{
|
||
common.HexToHash("0xc"), common.Hash{0x3}, common.Hash{0x1}, // not found
|
||
},
|
||
{
|
||
common.HexToHash("0xa"), common.Hash{0x2}, common.Hash{0x2},
|
||
},
|
||
{
|
||
common.HexToHash("0xb"), common.Hash{0x2}, common.Hash{0x1}, // not found
|
||
},
|
||
{
|
||
common.HexToHash("0xc"), common.Hash{0x2}, common.Hash{0x1}, // not found
|
||
},
|
||
/*
|
||
lookup account from the bottom
|
||
*/
|
||
{
|
||
common.HexToHash("0xa"), common.Hash{0x1}, common.Hash{0x1}, // not found
|
||
},
|
||
{
|
||
common.HexToHash("0xb"), common.Hash{0x1}, common.Hash{0x1}, // not found
|
||
},
|
||
{
|
||
common.HexToHash("0xc"), common.Hash{0x1}, common.Hash{0x1}, // not found
|
||
},
|
||
}
|
||
for i, c := range cases {
|
||
l, err := tr.lookupAccount(c.account, c.state)
|
||
if err != nil {
|
||
t.Fatalf("%d: %v", i, err)
|
||
}
|
||
if l.rootHash() != c.expect {
|
||
t.Errorf("Unexpected tiphash, %d, want: %x, got: %x", i, c.expect, l.rootHash())
|
||
}
|
||
}
|
||
|
||
// Chain:
|
||
// C3->C4 (HEAD)
|
||
tr.cap(common.Hash{0x4}, 1)
|
||
|
||
cases2 := []struct {
|
||
account common.Hash
|
||
state common.Hash
|
||
expect common.Hash
|
||
expectErr error
|
||
}{
|
||
{
|
||
// unknown account
|
||
common.HexToHash("0xd"), common.Hash{0x4}, common.Hash{0x3}, nil,
|
||
},
|
||
/*
|
||
lookup account from the top
|
||
*/
|
||
{
|
||
common.HexToHash("0xa"), common.Hash{0x4}, common.Hash{0x4}, nil,
|
||
},
|
||
{
|
||
common.HexToHash("0xb"), common.Hash{0x4}, common.Hash{0x3}, nil,
|
||
},
|
||
{
|
||
common.HexToHash("0xc"), common.Hash{0x4}, common.Hash{0x4}, nil,
|
||
},
|
||
/*
|
||
lookup account from the bottom
|
||
*/
|
||
{
|
||
common.HexToHash("0xa"), common.Hash{0x3}, common.Hash{0x3}, nil,
|
||
},
|
||
{
|
||
common.HexToHash("0xb"), common.Hash{0x3}, common.Hash{0x3}, nil,
|
||
},
|
||
{
|
||
common.HexToHash("0xc"), common.Hash{0x3}, common.Hash{0x3}, nil, // not found
|
||
},
|
||
/*
|
||
stale states
|
||
*/
|
||
{
|
||
common.HexToHash("0xa"), common.Hash{0x2}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
{
|
||
common.HexToHash("0xb"), common.Hash{0x2}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
{
|
||
common.HexToHash("0xc"), common.Hash{0x2}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
{
|
||
common.HexToHash("0xa"), common.Hash{0x1}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
{
|
||
common.HexToHash("0xb"), common.Hash{0x1}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
{
|
||
common.HexToHash("0xc"), common.Hash{0x1}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
}
|
||
for i, c := range cases2 {
|
||
l, err := tr.lookupAccount(c.account, c.state)
|
||
if c.expectErr != nil {
|
||
if !errors.Is(err, c.expectErr) {
|
||
t.Fatalf("%d: unexpected error, want %v, got %v", i, c.expectErr, err)
|
||
}
|
||
}
|
||
if c.expectErr == nil {
|
||
if err != nil {
|
||
t.Fatalf("%d: %v", i, err)
|
||
}
|
||
if l.rootHash() != c.expect {
|
||
t.Errorf("Unexpected tiphash, %d, want: %x, got: %x", i, c.expect, l.rootHash())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestStorageLookup(t *testing.T) {
|
||
// Chain:
|
||
// C1->C2->C3->C4 (HEAD)
|
||
tr := newTestLayerTree() // base = 0x1
|
||
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil), nil, nil, false))
|
||
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x2"}}, nil), nil, nil, false))
|
||
tr.add(common.Hash{0x4}, common.Hash{0x3}, 3, NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1", "0x3"}}, nil), nil, nil, false))
|
||
|
||
var cases = []struct {
|
||
storage common.Hash
|
||
state common.Hash
|
||
expect common.Hash
|
||
}{
|
||
{
|
||
// unknown storage slot
|
||
common.HexToHash("0x4"), common.Hash{0x4}, common.Hash{0x1},
|
||
},
|
||
/*
|
||
lookup storage slot from the top
|
||
*/
|
||
{
|
||
common.HexToHash("0x1"), common.Hash{0x4}, common.Hash{0x4},
|
||
},
|
||
{
|
||
common.HexToHash("0x2"), common.Hash{0x4}, common.Hash{0x3},
|
||
},
|
||
{
|
||
common.HexToHash("0x3"), common.Hash{0x4}, common.Hash{0x4},
|
||
},
|
||
/*
|
||
lookup storage slot from the middle
|
||
*/
|
||
{
|
||
common.HexToHash("0x1"), common.Hash{0x3}, common.Hash{0x2},
|
||
},
|
||
{
|
||
common.HexToHash("0x2"), common.Hash{0x3}, common.Hash{0x3},
|
||
},
|
||
{
|
||
common.HexToHash("0x3"), common.Hash{0x3}, common.Hash{0x1}, // not found
|
||
},
|
||
{
|
||
common.HexToHash("0x1"), common.Hash{0x2}, common.Hash{0x2},
|
||
},
|
||
{
|
||
common.HexToHash("0x2"), common.Hash{0x2}, common.Hash{0x1}, // not found
|
||
},
|
||
{
|
||
common.HexToHash("0x3"), common.Hash{0x2}, common.Hash{0x1}, // not found
|
||
},
|
||
/*
|
||
lookup storage slot from the bottom
|
||
*/
|
||
{
|
||
common.HexToHash("0x1"), common.Hash{0x1}, common.Hash{0x1}, // not found
|
||
},
|
||
{
|
||
common.HexToHash("0x2"), common.Hash{0x1}, common.Hash{0x1}, // not found
|
||
},
|
||
{
|
||
common.HexToHash("0x3"), common.Hash{0x1}, common.Hash{0x1}, // not found
|
||
},
|
||
}
|
||
for i, c := range cases {
|
||
l, err := tr.lookupStorage(common.HexToHash("0xa"), c.storage, c.state)
|
||
if err != nil {
|
||
t.Fatalf("%d: %v", i, err)
|
||
}
|
||
if l.rootHash() != c.expect {
|
||
t.Errorf("Unexpected tiphash, %d, want: %x, got: %x", i, c.expect, l.rootHash())
|
||
}
|
||
}
|
||
|
||
// Chain:
|
||
// C3->C4 (HEAD)
|
||
tr.cap(common.Hash{0x4}, 1)
|
||
|
||
cases2 := []struct {
|
||
storage common.Hash
|
||
state common.Hash
|
||
expect common.Hash
|
||
expectErr error
|
||
}{
|
||
{
|
||
// unknown storage slot
|
||
common.HexToHash("0x4"), common.Hash{0x4}, common.Hash{0x3}, nil,
|
||
},
|
||
/*
|
||
lookup account from the top
|
||
*/
|
||
{
|
||
common.HexToHash("0x1"), common.Hash{0x4}, common.Hash{0x4}, nil,
|
||
},
|
||
{
|
||
common.HexToHash("0x2"), common.Hash{0x4}, common.Hash{0x3}, nil,
|
||
},
|
||
{
|
||
common.HexToHash("0x3"), common.Hash{0x4}, common.Hash{0x4}, nil,
|
||
},
|
||
/*
|
||
lookup account from the bottom
|
||
*/
|
||
{
|
||
common.HexToHash("0x1"), common.Hash{0x3}, common.Hash{0x3}, nil,
|
||
},
|
||
{
|
||
common.HexToHash("0x2"), common.Hash{0x3}, common.Hash{0x3}, nil,
|
||
},
|
||
{
|
||
common.HexToHash("0x3"), common.Hash{0x3}, common.Hash{0x3}, nil, // not found
|
||
},
|
||
/*
|
||
stale states
|
||
*/
|
||
{
|
||
common.HexToHash("0x1"), common.Hash{0x2}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
{
|
||
common.HexToHash("0x2"), common.Hash{0x2}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
{
|
||
common.HexToHash("0x3"), common.Hash{0x2}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
{
|
||
common.HexToHash("0x1"), common.Hash{0x1}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
{
|
||
common.HexToHash("0x2"), common.Hash{0x1}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
{
|
||
common.HexToHash("0x3"), common.Hash{0x1}, common.Hash{}, errSnapshotStale,
|
||
},
|
||
}
|
||
for i, c := range cases2 {
|
||
l, err := tr.lookupStorage(common.HexToHash("0xa"), c.storage, c.state)
|
||
if c.expectErr != nil {
|
||
if !errors.Is(err, c.expectErr) {
|
||
t.Fatalf("%d: unexpected error, want %v, got %v", i, c.expectErr, err)
|
||
}
|
||
}
|
||
if c.expectErr == nil {
|
||
if err != nil {
|
||
t.Fatalf("%d: %v", i, err)
|
||
}
|
||
if l.rootHash() != c.expect {
|
||
t.Errorf("Unexpected tiphash, %d, want: %x, got: %x", i, c.expect, l.rootHash())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestLookupZeroBaseRootFallback is a regression test for a sentinel
|
||
// collision in accountTip/storageTip: before the fix they returned
|
||
// common.Hash{} as both the "stale" marker and the disk-layer fallback
|
||
// when the disk root itself happened to be zero. lookupAccount/Storage
|
||
// then misreported a legitimate fallback as errSnapshotStale.
|
||
//
|
||
// On the merkle path the collision was invisible because the empty
|
||
// merkle trie hashes to types.EmptyRootHash (a concrete non-zero
|
||
// keccak), so the disk layer's root was never the zero hash in
|
||
// practice. The bug only surfaces once the disk layer root can
|
||
// legitimately be zero (for example a fresh verkle/bintrie database
|
||
// where the empty binary trie hashes to EmptyVerkleHash ==
|
||
// common.Hash{}).
|
||
//
|
||
// The test constructs a layer tree whose base layer's root IS the zero
|
||
// hash, stacks diff layers on top, and exercises four cases:
|
||
//
|
||
// 1. Look up an account NEVER written → should fall through to the
|
||
// disk layer and return (diskLayer, nil). Before the fix this
|
||
// returned errSnapshotStale because the fallback hash collided
|
||
// with the sentinel.
|
||
// 2. Symmetric case for lookupStorage.
|
||
// 3. Look up an account written in a diff layer → should return that
|
||
// diff layer (the normal happy path is unaffected by the fix).
|
||
// 4. Look up any key at a state root that isn't part of the tree
|
||
// (neither the disk root nor a descendant of it) → MUST still
|
||
// return errSnapshotStale. This pins the "other half" of the
|
||
// contract so a future refactor that always returns ok=true would
|
||
// fail here.
|
||
func TestLookupZeroBaseRootFallback(t *testing.T) {
|
||
// Build a layer tree whose disk-layer root is common.Hash{} —
|
||
// mirrors the bintrie/verkle configuration where the empty trie
|
||
// hashes to EmptyVerkleHash. newTestLayerTree can't be reused
|
||
// because it hard-codes common.Hash{0x1}.
|
||
db := New(rawdb.NewMemoryDatabase(), nil, false)
|
||
base := newDiskLayer(common.Hash{}, 0, db, nil, nil, newBuffer(0, nil, nil, 0), nil)
|
||
tr := newLayerTree(base)
|
||
|
||
// Stack two diff layers on the zero-rooted disk layer, each
|
||
// touching a known account and slot so we have something for the
|
||
// happy-path lookups to find later.
|
||
if err := tr.add(
|
||
common.Hash{0x2}, common.Hash{},
|
||
1,
|
||
NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(
|
||
randomAccountSet("0xa"),
|
||
randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil),
|
||
nil, nil, false),
|
||
); err != nil {
|
||
t.Fatalf("add first diff layer: %v", err)
|
||
}
|
||
if err := tr.add(
|
||
common.Hash{0x3}, common.Hash{0x2},
|
||
2,
|
||
NewNodeSetWithOrigin(nil, nil),
|
||
NewStateSetWithOrigin(
|
||
randomAccountSet("0xb"),
|
||
nil, nil, nil, false),
|
||
); err != nil {
|
||
t.Fatalf("add second diff layer: %v", err)
|
||
}
|
||
|
||
// Case 1: unknown account queried at the head. The lookup must
|
||
// fall through the diff layers, hit the disk-layer fallback at
|
||
// base=common.Hash{}, and return the disk layer with no error —
|
||
// NOT errSnapshotStale.
|
||
l, err := tr.lookupAccount(common.HexToHash("0xdead"), common.Hash{0x3})
|
||
if err != nil {
|
||
t.Fatalf("lookupAccount on zero-base disk layer: unexpected error %v", err)
|
||
}
|
||
if l.rootHash() != (common.Hash{}) {
|
||
t.Errorf("expected fall-through to disk layer (root=0), got %x", l.rootHash())
|
||
}
|
||
|
||
// Case 2: symmetric check for storage. Slot 0x99 was never written,
|
||
// so the lookup must fall through to the disk layer just like
|
||
// Case 1.
|
||
l, err = tr.lookupStorage(
|
||
common.HexToHash("0xdead"), common.HexToHash("0x99"), common.Hash{0x3})
|
||
if err != nil {
|
||
t.Fatalf("lookupStorage on zero-base disk layer: unexpected error %v", err)
|
||
}
|
||
if l.rootHash() != (common.Hash{}) {
|
||
t.Errorf("expected fall-through to disk layer (root=0), got %x", l.rootHash())
|
||
}
|
||
|
||
// Case 3: happy path. Account 0xa was written at diff layer 0x2.
|
||
// The lookup must return that layer, proving the fix didn't break
|
||
// the normal resolution path.
|
||
l, err = tr.lookupAccount(common.HexToHash("0xa"), common.Hash{0x3})
|
||
if err != nil {
|
||
t.Fatalf("lookupAccount(known): %v", err)
|
||
}
|
||
if l.rootHash() != (common.Hash{0x2}) {
|
||
t.Errorf("known account tip: want %x, got %x",
|
||
common.Hash{0x2}, l.rootHash())
|
||
}
|
||
|
||
// Case 4: truly stale state root. This pins the other half of the
|
||
// contract — the boolean must actually signal not-found for an
|
||
// unknown state, otherwise a refactor that always returned
|
||
// ok=true would still pass cases 1–3.
|
||
_, err = tr.lookupAccount(common.HexToHash("0xa"), common.HexToHash("0xdeadbeef"))
|
||
if !errors.Is(err, errSnapshotStale) {
|
||
t.Errorf("lookupAccount(stale state): want errSnapshotStale, got %v", err)
|
||
}
|
||
_, err = tr.lookupStorage(
|
||
common.HexToHash("0xa"), common.HexToHash("0x1"),
|
||
common.HexToHash("0xdeadbeef"))
|
||
if !errors.Is(err, errSnapshotStale) {
|
||
t.Errorf("lookupStorage(stale state): want errSnapshotStale, got %v", err)
|
||
}
|
||
}
|