mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-14 10:51:35 +00:00
Comprehensive validation suite designed to confirm the flat state is working before running benchmarks. TestBintrieFlatStateConsistencyOracle (core/state): The "if this passes, we're good to benchmark" test. Builds realistic state over 15 blocks (48 accounts, 14 storage slots), explicitly flushes to disk at block 5, and verifies structural invariants of every flat-state read at every block commit. Covers: - Diff-layer chaining across 15 blocks - Disk-layer reads after explicit flush (A1 smoking gun) - Account creation, balance/nonce modification, code deployment - Storage write, update, and tombstone (zero-value clear) - Post-flush state evolution with fresh diff layers on top of disk Stem blob edge cases (triedb/pathdb): TestStemBlobOffset127_128Boundary — validates the bitmap byte boundary between the last header storage offset (127) and the first code chunk offset (128). A bitmapRank off-by-one here would cause extractStemOffset to return the adjacent offset's value. TestStemBlobFull256DeleteMiddle — fully populates all 256 offsets, deletes one from the middle (offset 128), verifies all 255 remaining values survive at the correct positions. TestFlushIdempotency — verifies that flushing the same data twice produces a byte-identical on-disk blob. The RMW in applyWrites must be idempotent to handle overlapping diff-layer merges.
143 lines
4.6 KiB
Go
143 lines
4.6 KiB
Go
// Copyright 2026 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 (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
|
)
|
|
|
|
// TestStemBlobOffset127_128Boundary tests the bitmap byte boundary
|
|
// between offset 127 (last header storage slot) and offset 128
|
|
// (first code chunk). Off-by-one in bitmapRank at this boundary
|
|
// would cause extractStemOffset to return the wrong value.
|
|
func TestStemBlobOffset127_128Boundary(t *testing.T) {
|
|
b := newStemBuilder()
|
|
val127 := bytes.Repeat([]byte{0x7F}, stemBlobValueSize)
|
|
val128 := bytes.Repeat([]byte{0x80}, stemBlobValueSize)
|
|
b.set(127, val127)
|
|
b.set(128, val128)
|
|
|
|
blob := b.encode()
|
|
if blob == nil {
|
|
t.Fatal("encode returned nil for 2-offset builder")
|
|
}
|
|
|
|
got127, err := extractStemOffset(blob, 127)
|
|
if err != nil {
|
|
t.Fatalf("extract offset 127: %v", err)
|
|
}
|
|
if !bytes.Equal(got127, val127) {
|
|
t.Errorf("offset 127: got %x, want %x", got127, val127)
|
|
}
|
|
|
|
got128, err := extractStemOffset(blob, 128)
|
|
if err != nil {
|
|
t.Fatalf("extract offset 128: %v", err)
|
|
}
|
|
if !bytes.Equal(got128, val128) {
|
|
t.Errorf("offset 128: got %x, want %x", got128, val128)
|
|
}
|
|
|
|
// Verify bitmapRank correctness at the byte boundary.
|
|
var bitmap [stemBlobBitmapSize]byte
|
|
copy(bitmap[:], blob[:stemBlobBitmapSize])
|
|
if r := bitmapRank(bitmap, 127); r != 0 {
|
|
t.Errorf("bitmapRank(127) = %d, want 0", r)
|
|
}
|
|
if r := bitmapRank(bitmap, 128); r != 1 {
|
|
t.Errorf("bitmapRank(128) = %d, want 1", r)
|
|
}
|
|
}
|
|
|
|
// TestStemBlobFull256DeleteMiddle tests a fully-populated stem (all 256
|
|
// offsets) where one offset in the middle is deleted.
|
|
func TestStemBlobFull256DeleteMiddle(t *testing.T) {
|
|
b := newStemBuilder()
|
|
for i := range 256 {
|
|
val := bytes.Repeat([]byte{byte(i)}, stemBlobValueSize)
|
|
b.set(byte(i), val)
|
|
}
|
|
if bitmapPopcount(b.bitmap) != 256 {
|
|
t.Fatalf("full builder has popcount %d, want 256", bitmapPopcount(b.bitmap))
|
|
}
|
|
|
|
b.set(128, nil) // delete the middle
|
|
if bitmapPopcount(b.bitmap) != 255 {
|
|
t.Fatalf("after delete: popcount %d, want 255", bitmapPopcount(b.bitmap))
|
|
}
|
|
|
|
blob := b.encode()
|
|
expectedSize := stemBlobBitmapSize + 255*stemBlobValueSize
|
|
if len(blob) != expectedSize {
|
|
t.Fatalf("blob size %d, want %d", len(blob), expectedSize)
|
|
}
|
|
|
|
got128, _ := extractStemOffset(blob, 128)
|
|
if got128 != nil {
|
|
t.Errorf("offset 128 should be absent, got %x", got128)
|
|
}
|
|
|
|
got127, _ := extractStemOffset(blob, 127)
|
|
if !bytes.Equal(got127, bytes.Repeat([]byte{127}, stemBlobValueSize)) {
|
|
t.Errorf("offset 127 corrupted after deleting 128")
|
|
}
|
|
got129, _ := extractStemOffset(blob, 129)
|
|
if !bytes.Equal(got129, bytes.Repeat([]byte{129}, stemBlobValueSize)) {
|
|
t.Errorf("offset 129 corrupted after deleting 128")
|
|
}
|
|
got0, _ := extractStemOffset(blob, 0)
|
|
if !bytes.Equal(got0, bytes.Repeat([]byte{0}, stemBlobValueSize)) {
|
|
t.Errorf("offset 0 corrupted")
|
|
}
|
|
got255, _ := extractStemOffset(blob, 255)
|
|
if !bytes.Equal(got255, bytes.Repeat([]byte{255}, stemBlobValueSize)) {
|
|
t.Errorf("offset 255 corrupted")
|
|
}
|
|
}
|
|
|
|
// TestFlushIdempotency verifies that flushing the same data twice
|
|
// produces an identical on-disk blob.
|
|
func TestFlushIdempotency(t *testing.T) {
|
|
codec, db := newTestBintrieCodec(t)
|
|
stem := bytes.Repeat([]byte{0x55}, bintrie.StemSize)
|
|
mkKey := func(offset byte) common.Hash {
|
|
var k common.Hash
|
|
copy(k[:bintrie.StemSize], stem)
|
|
k[bintrie.StemSize] = offset
|
|
return k
|
|
}
|
|
val := bytes.Repeat([]byte{0xAA}, stemBlobValueSize)
|
|
|
|
batch := db.NewBatch()
|
|
codec.Flush(batch, nil, map[common.Hash][]byte{mkKey(5): val}, nil, nil)
|
|
flushBatch(t, batch)
|
|
blob1 := rawdb.ReadBinTrieStem(db, stem)
|
|
|
|
batch = db.NewBatch()
|
|
codec.Flush(batch, nil, map[common.Hash][]byte{mkKey(5): val}, nil, nil)
|
|
flushBatch(t, batch)
|
|
blob2 := rawdb.ReadBinTrieStem(db, stem)
|
|
|
|
if !bytes.Equal(blob1, blob2) {
|
|
t.Errorf("Flush is not idempotent: blob1 len=%d blob2 len=%d", len(blob1), len(blob2))
|
|
}
|
|
}
|