Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ var (
utils.VMEnableDebugFlag,
utils.VMTraceFlag,
utils.VMTraceJsonConfigFlag,
utils.VMWitnessStatsFlag,
utils.VMStatelessSelfValidationFlag,
utils.NetworkIdFlag,
utils.EthStatsURLFlag,
utils.GpoBlocksFlag,
Expand Down
22 changes: 22 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,16 @@ var (
Value: "{}",
Category: flags.VMCategory,
}
VMWitnessStatsFlag = &cli.BoolFlag{
Name: "vmwitnessstats",
Usage: "Enable collection of witness trie access statistics (automatically enables witness generation)",
Category: flags.VMCategory,
}
VMStatelessSelfValidationFlag = &cli.BoolFlag{
Name: "stateless-self-validation",
Usage: "Generate execution witnesses and self-check against them (testing purpose)",
Category: flags.VMCategory,
}
// API options.
RPCGlobalGasCapFlag = &cli.Uint64Flag{
Name: "rpc.gascap",
Expand Down Expand Up @@ -1707,6 +1717,16 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(VMEnableDebugFlag.Name) {
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
}
if ctx.IsSet(VMWitnessStatsFlag.Name) {
cfg.EnableWitnessStats = ctx.Bool(VMWitnessStatsFlag.Name)
}
if ctx.IsSet(VMStatelessSelfValidationFlag.Name) {
cfg.StatelessSelfValidation = ctx.Bool(VMStatelessSelfValidationFlag.Name)
}
// Auto-enable StatelessSelfValidation when witness stats are enabled
if ctx.Bool(VMWitnessStatsFlag.Name) {
cfg.StatelessSelfValidation = true
}

if ctx.IsSet(RPCGlobalGasCapFlag.Name) {
cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name)
Expand Down Expand Up @@ -2243,6 +2263,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
}
vmcfg := vm.Config{
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
EnableWitnessStats: ctx.Bool(VMWitnessStatsFlag.Name),
StatelessSelfValidation: ctx.Bool(VMStatelessSelfValidationFlag.Name) || ctx.Bool(VMWitnessStatsFlag.Name),
}
if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" {
Expand Down
2 changes: 1 addition & 1 deletion core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2153,7 +2153,7 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
}
// Report the collected witness statistics
if witnessStats != nil {
witnessStats.ReportMetrics()
witnessStats.ReportMetrics(block.NumberU64())
}

// Update the metrics touched during block commit
Expand Down
16 changes: 15 additions & 1 deletion core/stateless/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
package stateless

import (
"encoding/json"
"maps"
"slices"
"sort"
"strconv"
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
)

Expand Down Expand Up @@ -70,7 +72,19 @@ func (s *WitnessStats) Add(nodes map[string][]byte, owner common.Hash) {
}

// ReportMetrics reports the collected statistics to the global metrics registry.
func (s *WitnessStats) ReportMetrics() {
func (s *WitnessStats) ReportMetrics(blockNumber uint64) {
// Encode the metrics as JSON for easier consumption
accountLeavesJson, _ := json.Marshal(s.accountTrieLeaves)
storageLeavesJson, _ := json.Marshal(s.storageTrieLeaves)

// Log account trie depth statistics
log.Info("Account trie depth stats",
"block", blockNumber,
"leavesAtDepth", string(accountLeavesJson))
log.Info("Storage trie depth stats",
"block", blockNumber,
"leavesAtDepth", string(storageLeavesJson))

for i := 0; i < 16; i++ {
accountTrieLeavesAtDepth[i].Inc(s.accountTrieLeaves[i])
storageTrieLeavesAtDepth[i].Inc(s.storageTrieLeaves[i])
Expand Down
58 changes: 58 additions & 0 deletions core/stateless/stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,64 @@ func TestWitnessStatsAdd(t *testing.T) {
}
}

func TestWitnessStatsMinMax(t *testing.T) {
stats := NewWitnessStats()

// Add some account trie nodes with varying depths
stats.Add(map[string][]byte{
"a": []byte("data1"),
"ab": []byte("data2"),
"abc": []byte("data3"),
"abcd": []byte("data4"),
"abcde": []byte("data5"),
}, common.Hash{})

// Only "abcde" is a leaf (depth 5)
for i, v := range stats.accountTrieLeaves {
if v != 0 && i != 5 {
t.Errorf("leaf found at invalid depth %d", i)
}
}

// Add more leaves with different depths
stats.Add(map[string][]byte{
"x": []byte("data6"),
"yz": []byte("data7"),
}, common.Hash{})

// Now we have leaves at depths 1, 2, and 5
for i, v := range stats.accountTrieLeaves {
if v != 0 && (i != 5 && i != 2 && i != 1) {
t.Errorf("leaf found at invalid depth %d", i)
}
}
}

func TestWitnessStatsAverage(t *testing.T) {
stats := NewWitnessStats()

// Add nodes that will create leaves at depths 2, 3, and 4
stats.Add(map[string][]byte{
"aa": []byte("data1"),
"bb": []byte("data2"),
"ccc": []byte("data3"),
"dddd": []byte("data4"),
}, common.Hash{})

// All are leaves: 2 + 2 + 3 + 4 = 11 total, 4 samples
expectedAvg := int64(11) / int64(4)
var actualAvg, totalSamples int64
for i, c := range stats.accountTrieLeaves {
actualAvg += c * int64(i)
totalSamples += c
}
actualAvg = actualAvg / totalSamples

if actualAvg != expectedAvg {
t.Errorf("Account trie average depth = %d, want %d", actualAvg, expectedAvg)
}
}

func BenchmarkWitnessStatsAdd(b *testing.B) {
// Create a realistic trie node structure
nodes := make(map[string][]byte)
Expand Down
2 changes: 2 additions & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
VmConfig: vm.Config{
EnablePreimageRecording: config.EnablePreimageRecording,
EnableWitnessStats: config.EnableWitnessStats,
StatelessSelfValidation: config.StatelessSelfValidation,
},
// Enables file journaling for the trie database. The journal files will be stored
// within the data directory. The corresponding paths will be either:
Expand Down
6 changes: 6 additions & 0 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ type Config struct {
// Enables tracking of SHA3 preimages in the VM
EnablePreimageRecording bool

// Enables collection of witness trie access statistics
EnableWitnessStats bool

// Generate execution witnesses and self-check against them (testing purpose)
StatelessSelfValidation bool

// Enables tracking of state size
EnableStateSizeTracking bool

Expand Down
12 changes: 12 additions & 0 deletions eth/ethconfig/gen_config.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.