mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
core: remove 7702 operations and some unit tests
This commit is contained in:
parent
6f99536fcd
commit
cc6de619f0
6 changed files with 32 additions and 1229 deletions
|
|
@ -2107,24 +2107,11 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
}
|
||||
// Upload the statistics of reader at the end
|
||||
defer func() {
|
||||
pStat := prefetch.GetStats()
|
||||
accountCacheHitPrefetchMeter.Mark(pStat.AccountCacheHit)
|
||||
accountCacheMissPrefetchMeter.Mark(pStat.AccountCacheMiss)
|
||||
storageCacheHitPrefetchMeter.Mark(pStat.StorageCacheHit)
|
||||
storageCacheMissPrefetchMeter.Mark(pStat.StorageCacheMiss)
|
||||
|
||||
rStat := process.GetStats()
|
||||
accountCacheHitMeter.Mark(rStat.AccountCacheHit)
|
||||
accountCacheMissMeter.Mark(rStat.AccountCacheMiss)
|
||||
storageCacheHitMeter.Mark(rStat.StorageCacheHit)
|
||||
storageCacheMissMeter.Mark(rStat.StorageCacheMiss)
|
||||
|
||||
if result != nil {
|
||||
result.stats.StatePrefetchCacheStats = pStat
|
||||
result.stats.StateReadCacheStats = rStat
|
||||
result.stats.StatePrefetchCacheStats = prefetch.GetStats()
|
||||
result.stats.StateReadCacheStats = process.GetStats()
|
||||
}
|
||||
}()
|
||||
|
||||
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
|
||||
// Disable tracing for prefetcher executions.
|
||||
vmCfg := bc.cfg.VmConfig
|
||||
|
|
@ -2239,16 +2226,11 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
stats.StorageLoaded = statedb.StorageLoaded
|
||||
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
||||
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
|
||||
|
||||
stats.CodeLoaded = statedb.CodeLoaded
|
||||
stats.CodeLoadBytes = statedb.CodeLoadBytes
|
||||
|
||||
// EIP-7702 delegation metrics
|
||||
stats.Eip7702DelegationsSet = statedb.Eip7702DelegationsSet
|
||||
stats.Eip7702DelegationsCleared = statedb.Eip7702DelegationsCleared
|
||||
|
||||
// Code write metrics
|
||||
stats.CodeUpdated = statedb.CodeUpdated
|
||||
stats.CodeBytesWrite = statedb.CodeBytesWrite
|
||||
stats.CodeUpdateBytes = statedb.CodeUpdateBytes
|
||||
|
||||
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
|
||||
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
|
||||
|
|
|
|||
|
|
@ -1,284 +0,0 @@
|
|||
// Copyright 2025 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 core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// TestEIP7702DelegationMetricsSet verifies that Eip7702DelegationsSet is correctly
|
||||
// incremented when a SetCodeTx sets a delegation (non-zero address).
|
||||
func TestEIP7702DelegationMetricsSet(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
target = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
addr1: {Balance: funds},
|
||||
target: {Code: []byte{0x60, 0x00}, Balance: big.NewInt(0)}, // Simple PUSH1 0
|
||||
},
|
||||
}
|
||||
|
||||
// Sign authorization to set delegation to target address
|
||||
// Note: Nonce is 1 because key1 is also the tx sender, and sender's nonce
|
||||
// is incremented (0->1) before authorization validation runs.
|
||||
auth, err := types.SignSetCode(key1, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
|
||||
Address: target, // Non-zero address = SET delegation
|
||||
Nonce: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign authorization: %v", err)
|
||||
}
|
||||
|
||||
// Generate block with SetCodeTx
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
txdata := &types.SetCodeTx{
|
||||
ChainID: uint256.MustFromBig(gspec.Config.ChainID),
|
||||
Nonce: 0,
|
||||
To: addr1,
|
||||
Gas: 100000,
|
||||
GasFeeCap: uint256.MustFromBig(newGwei(5)),
|
||||
GasTipCap: uint256.NewInt(2),
|
||||
AuthList: []types.SetCodeAuthorization{auth},
|
||||
}
|
||||
tx := types.MustSignNewTx(key1, signer, txdata)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
// Process block and get stats
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
// Verify SET delegation metric
|
||||
stats := result.Stats()
|
||||
if stats.Eip7702DelegationsSet != 1 {
|
||||
t.Errorf("Expected Eip7702DelegationsSet=1, got %d", stats.Eip7702DelegationsSet)
|
||||
}
|
||||
if stats.Eip7702DelegationsCleared != 0 {
|
||||
t.Errorf("Expected Eip7702DelegationsCleared=0, got %d", stats.Eip7702DelegationsCleared)
|
||||
}
|
||||
|
||||
// Also verify CodeUpdated and CodeBytesWrite are incremented
|
||||
// Delegation code is 23 bytes (0xef0100 + 20-byte address)
|
||||
if stats.CodeUpdated != 1 {
|
||||
t.Errorf("Expected CodeUpdated=1, got %d", stats.CodeUpdated)
|
||||
}
|
||||
if stats.CodeBytesWrite != 23 {
|
||||
t.Errorf("Expected CodeBytesWrite=23 (delegation code size), got %d", stats.CodeBytesWrite)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP7702DelegationMetricsClear verifies that Eip7702DelegationsCleared is correctly
|
||||
// incremented when a SetCodeTx clears a delegation (zero address).
|
||||
func TestEIP7702DelegationMetricsClear(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
target = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
// Start with addr1 already having a delegation set
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
addr1: {
|
||||
Balance: funds,
|
||||
Code: types.AddressToDelegation(target), // Pre-existing delegation
|
||||
Nonce: 1, // Nonce 1 since delegation was "set"
|
||||
},
|
||||
target: {Code: []byte{0x60, 0x00}, Balance: big.NewInt(0)},
|
||||
},
|
||||
}
|
||||
|
||||
// Sign authorization to CLEAR delegation (zero address)
|
||||
// Note: Auth nonce is 2 because addr1 starts with nonce 1, and tx sender's
|
||||
// nonce is incremented (1->2) before authorization validation runs.
|
||||
auth, err := types.SignSetCode(key1, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
|
||||
Address: common.Address{}, // Zero address = CLEAR delegation
|
||||
Nonce: 2, // Post-increment nonce (1->2)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign authorization: %v", err)
|
||||
}
|
||||
|
||||
// Generate block with SetCodeTx that clears delegation
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
txdata := &types.SetCodeTx{
|
||||
ChainID: uint256.MustFromBig(gspec.Config.ChainID),
|
||||
Nonce: 1, // Account starts with nonce 1
|
||||
To: addr1,
|
||||
Gas: 100000,
|
||||
GasFeeCap: uint256.MustFromBig(newGwei(5)),
|
||||
GasTipCap: uint256.NewInt(2),
|
||||
AuthList: []types.SetCodeAuthorization{auth},
|
||||
}
|
||||
tx := types.MustSignNewTx(key1, signer, txdata)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
// Process block and get stats
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
// Verify CLEAR delegation metric
|
||||
stats := result.Stats()
|
||||
if stats.Eip7702DelegationsCleared != 1 {
|
||||
t.Errorf("Expected Eip7702DelegationsCleared=1, got %d", stats.Eip7702DelegationsCleared)
|
||||
}
|
||||
if stats.Eip7702DelegationsSet != 0 {
|
||||
t.Errorf("Expected Eip7702DelegationsSet=0, got %d", stats.Eip7702DelegationsSet)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP7702DelegationMetricsMultiple verifies metrics when multiple authorizations
|
||||
// are included in a single SetCodeTx.
|
||||
func TestEIP7702DelegationMetricsMultiple(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
|
||||
targetA = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
|
||||
targetB = common.HexToAddress("0x000000000000000000000000000000000000bbbb")
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
// addr3 starts with a delegation that will be cleared
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
addr1: {Balance: funds},
|
||||
addr2: {Balance: funds},
|
||||
addr3: {Balance: funds, Code: types.AddressToDelegation(targetA), Nonce: 1},
|
||||
targetA: {Code: []byte{0x60, 0x00}, Balance: big.NewInt(0)},
|
||||
targetB: {Code: []byte{0x60, 0x01}, Balance: big.NewInt(0)},
|
||||
},
|
||||
}
|
||||
|
||||
// Auth 1: addr1 sets delegation to targetA
|
||||
// Note: Nonce is 1 because key1 is also the tx sender (nonce 0->1)
|
||||
auth1, _ := types.SignSetCode(key1, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
|
||||
Address: targetA,
|
||||
Nonce: 1,
|
||||
})
|
||||
|
||||
// Auth 2: addr2 sets delegation to targetB
|
||||
auth2, _ := types.SignSetCode(key2, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
|
||||
Address: targetB,
|
||||
Nonce: 0,
|
||||
})
|
||||
|
||||
// Auth 3: addr3 clears delegation (zero address)
|
||||
auth3, _ := types.SignSetCode(key3, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
|
||||
Address: common.Address{}, // Clear
|
||||
Nonce: 1,
|
||||
})
|
||||
|
||||
// Generate block with all three authorizations
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
txdata := &types.SetCodeTx{
|
||||
ChainID: uint256.MustFromBig(gspec.Config.ChainID),
|
||||
Nonce: 0,
|
||||
To: addr1,
|
||||
Gas: 500000,
|
||||
GasFeeCap: uint256.MustFromBig(newGwei(5)),
|
||||
GasTipCap: uint256.NewInt(2),
|
||||
AuthList: []types.SetCodeAuthorization{auth1, auth2, auth3},
|
||||
}
|
||||
tx := types.MustSignNewTx(key1, signer, txdata)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
// Process block and get stats
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
// Verify metrics: 2 delegations set, 1 cleared
|
||||
stats := result.Stats()
|
||||
if stats.Eip7702DelegationsSet != 2 {
|
||||
t.Errorf("Expected Eip7702DelegationsSet=2, got %d", stats.Eip7702DelegationsSet)
|
||||
}
|
||||
if stats.Eip7702DelegationsCleared != 1 {
|
||||
t.Errorf("Expected Eip7702DelegationsCleared=1, got %d", stats.Eip7702DelegationsCleared)
|
||||
}
|
||||
|
||||
// CodeUpdated should be 2 (two delegations set, clear doesn't add code)
|
||||
if stats.CodeUpdated != 2 {
|
||||
t.Errorf("Expected CodeUpdated=2, got %d", stats.CodeUpdated)
|
||||
}
|
||||
|
||||
// CodeBytesWrite should be 46 (23 bytes per delegation * 2)
|
||||
if stats.CodeBytesWrite != 46 {
|
||||
t.Errorf("Expected CodeBytesWrite=46, got %d", stats.CodeBytesWrite)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,576 +0,0 @@
|
|||
// Copyright 2025 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 core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// TestAccountMetrics verifies that AccountLoaded and AccountUpdated are correctly
|
||||
// tracked when processing a simple ETH transfer transaction.
|
||||
func TestAccountMetrics(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
sender = crypto.PubkeyToAddress(key.PublicKey)
|
||||
receiver = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
sender: {Balance: funds},
|
||||
receiver: {Balance: big.NewInt(1)}, // Pre-existing account
|
||||
},
|
||||
}
|
||||
|
||||
// Generate block with simple ETH transfer
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
tx, _ := types.SignTx(types.NewTransaction(
|
||||
0, // nonce
|
||||
receiver, // to
|
||||
big.NewInt(1000), // value
|
||||
21000, // gas
|
||||
uint256.MustFromBig(newGwei(5)).ToBig(), // gasPrice
|
||||
nil, // data
|
||||
), signer, key)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
// Process block and get stats
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
stats := result.Stats()
|
||||
|
||||
// Both sender and receiver should be loaded from the database
|
||||
if stats.AccountLoaded < 2 {
|
||||
t.Errorf("Expected AccountLoaded >= 2, got %d", stats.AccountLoaded)
|
||||
}
|
||||
|
||||
// Both sender (nonce+balance) and receiver (balance) should be updated
|
||||
if stats.AccountUpdated < 2 {
|
||||
t.Errorf("Expected AccountUpdated >= 2, got %d", stats.AccountUpdated)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStorageLoadedMetric verifies that StorageLoaded is correctly incremented
|
||||
// when a contract reads from storage that was set in genesis.
|
||||
func TestStorageLoadedMetric(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
sender = crypto.PubkeyToAddress(key.PublicKey)
|
||||
contract = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
// Contract that reads slot 0 via SLOAD and returns it
|
||||
// PUSH1 0x00 SLOAD POP STOP
|
||||
contractCode := []byte{
|
||||
byte(vm.PUSH1), 0x00,
|
||||
byte(vm.SLOAD),
|
||||
byte(vm.POP),
|
||||
byte(vm.STOP),
|
||||
}
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
sender: {Balance: funds},
|
||||
contract: {
|
||||
Code: contractCode,
|
||||
Balance: big.NewInt(0),
|
||||
Storage: map[common.Hash]common.Hash{
|
||||
common.HexToHash("0x00"): common.HexToHash("0x42"), // Pre-populated storage
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Generate block that calls the contract
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
tx, _ := types.SignTx(types.NewTransaction(
|
||||
0,
|
||||
contract,
|
||||
big.NewInt(0),
|
||||
50000,
|
||||
uint256.MustFromBig(newGwei(5)).ToBig(),
|
||||
nil,
|
||||
), signer, key)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
stats := result.Stats()
|
||||
|
||||
// Storage slot 0 should be loaded from DB
|
||||
if stats.StorageLoaded < 1 {
|
||||
t.Errorf("Expected StorageLoaded >= 1, got %d", stats.StorageLoaded)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStorageUpdatedMetric verifies that StorageUpdated is correctly incremented
|
||||
// when a contract writes to storage.
|
||||
func TestStorageUpdatedMetric(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
sender = crypto.PubkeyToAddress(key.PublicKey)
|
||||
contract = common.HexToAddress("0x3333333333333333333333333333333333333333")
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
// Contract that writes 0x42 to slot 0x01
|
||||
// PUSH1 0x42 PUSH1 0x01 SSTORE STOP
|
||||
contractCode := []byte{
|
||||
byte(vm.PUSH1), 0x42, // value
|
||||
byte(vm.PUSH1), 0x01, // key
|
||||
byte(vm.SSTORE),
|
||||
byte(vm.STOP),
|
||||
}
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
sender: {Balance: funds},
|
||||
contract: {Code: contractCode, Balance: big.NewInt(0)},
|
||||
},
|
||||
}
|
||||
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
tx, _ := types.SignTx(types.NewTransaction(
|
||||
0,
|
||||
contract,
|
||||
big.NewInt(0),
|
||||
50000,
|
||||
uint256.MustFromBig(newGwei(5)).ToBig(),
|
||||
nil,
|
||||
), signer, key)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
stats := result.Stats()
|
||||
|
||||
// Storage slot 1 should be updated
|
||||
if stats.StorageUpdated < 1 {
|
||||
t.Errorf("Expected StorageUpdated >= 1, got %d", stats.StorageUpdated)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStorageDeletedMetric verifies that StorageDeleted is correctly incremented
|
||||
// when a contract clears a storage slot (sets to zero).
|
||||
func TestStorageDeletedMetric(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
sender = crypto.PubkeyToAddress(key.PublicKey)
|
||||
contract = common.HexToAddress("0x4444444444444444444444444444444444444444")
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
// Contract that clears slot 0x01 (sets to zero)
|
||||
// PUSH1 0x00 PUSH1 0x01 SSTORE STOP
|
||||
contractCode := []byte{
|
||||
byte(vm.PUSH1), 0x00, // value (zero = delete)
|
||||
byte(vm.PUSH1), 0x01, // key
|
||||
byte(vm.SSTORE),
|
||||
byte(vm.STOP),
|
||||
}
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
sender: {Balance: funds},
|
||||
contract: {
|
||||
Code: contractCode,
|
||||
Balance: big.NewInt(0),
|
||||
Storage: map[common.Hash]common.Hash{
|
||||
common.HexToHash("0x01"): common.HexToHash("0x42"), // Pre-existing non-zero value
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
tx, _ := types.SignTx(types.NewTransaction(
|
||||
0,
|
||||
contract,
|
||||
big.NewInt(0),
|
||||
50000,
|
||||
uint256.MustFromBig(newGwei(5)).ToBig(),
|
||||
nil,
|
||||
), signer, key)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
stats := result.Stats()
|
||||
|
||||
// Storage slot 1 should be deleted (cleared)
|
||||
if stats.StorageDeleted < 1 {
|
||||
t.Errorf("Expected StorageDeleted >= 1, got %d", stats.StorageDeleted)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCodeLoadedMetric verifies that CodeLoaded is correctly incremented
|
||||
// when a contract's code is fetched to execute it.
|
||||
func TestCodeLoadedMetric(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
sender = crypto.PubkeyToAddress(key.PublicKey)
|
||||
contract = common.HexToAddress("0x5555555555555555555555555555555555555555")
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
// Simple contract: PUSH1 0x42 POP STOP
|
||||
contractCode := []byte{
|
||||
byte(vm.PUSH1), 0x42,
|
||||
byte(vm.POP),
|
||||
byte(vm.STOP),
|
||||
}
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
sender: {Balance: funds},
|
||||
contract: {Code: contractCode, Balance: big.NewInt(0)},
|
||||
},
|
||||
}
|
||||
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
tx, _ := types.SignTx(types.NewTransaction(
|
||||
0,
|
||||
contract,
|
||||
big.NewInt(0),
|
||||
50000,
|
||||
uint256.MustFromBig(newGwei(5)).ToBig(),
|
||||
nil,
|
||||
), signer, key)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
stats := result.Stats()
|
||||
|
||||
// Contract code should be loaded to execute
|
||||
if stats.CodeLoaded < 1 {
|
||||
t.Errorf("Expected CodeLoaded >= 1, got %d", stats.CodeLoaded)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCodeUpdatedMetricCREATE verifies that CodeUpdated and CodeBytesWrite are correctly
|
||||
// incremented when a contract is deployed via a creation transaction.
|
||||
func TestCodeUpdatedMetricCREATE(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
sender = crypto.PubkeyToAddress(key.PublicKey)
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
// Runtime code: PUSH1 0x42 POP STOP (4 bytes)
|
||||
runtimeCode := []byte{
|
||||
byte(vm.PUSH1), 0x42,
|
||||
byte(vm.POP),
|
||||
byte(vm.STOP),
|
||||
}
|
||||
runtimeLen := len(runtimeCode)
|
||||
|
||||
// Initcode that returns the runtime code
|
||||
// PUSH4 <runtime> PUSH1 0x00 MSTORE PUSH1 <len> PUSH1 0x1c RETURN
|
||||
initCode := []byte{
|
||||
byte(vm.PUSH4),
|
||||
runtimeCode[0], runtimeCode[1], runtimeCode[2], runtimeCode[3],
|
||||
byte(vm.PUSH1), 0x00,
|
||||
byte(vm.MSTORE),
|
||||
byte(vm.PUSH1), byte(runtimeLen), // size
|
||||
byte(vm.PUSH1), 0x1c, // offset (32 - 4 = 28 = 0x1c)
|
||||
byte(vm.RETURN),
|
||||
}
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
sender: {Balance: funds},
|
||||
},
|
||||
}
|
||||
|
||||
// Contract creation transaction (to = nil)
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
tx, _ := types.SignTx(types.NewContractCreation(
|
||||
0,
|
||||
big.NewInt(0),
|
||||
100000,
|
||||
uint256.MustFromBig(newGwei(5)).ToBig(),
|
||||
initCode,
|
||||
), signer, key)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
stats := result.Stats()
|
||||
|
||||
// One contract should be deployed
|
||||
if stats.CodeUpdated != 1 {
|
||||
t.Errorf("Expected CodeUpdated = 1, got %d", stats.CodeUpdated)
|
||||
}
|
||||
|
||||
// Runtime code is 4 bytes
|
||||
if stats.CodeBytesWrite != runtimeLen {
|
||||
t.Errorf("Expected CodeBytesWrite = %d, got %d", runtimeLen, stats.CodeBytesWrite)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAccountDeletedMetric verifies that AccountDeleted is correctly incremented
|
||||
// when a contract self-destructs. Post-Cancun (EIP-6780), this only works if the
|
||||
// contract is created and destroyed in the same transaction.
|
||||
func TestAccountDeletedMetric(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
sender = crypto.PubkeyToAddress(key.PublicKey)
|
||||
beneficiary = common.HexToAddress("0x6666666666666666666666666666666666666666")
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
// Initcode that:
|
||||
// 1. Deploys runtime code that will never execute (we self-destruct in initcode)
|
||||
// 2. Immediately calls SELFDESTRUCT to beneficiary during contract creation
|
||||
// This is EIP-6780 compatible because we self-destruct in the same tx as creation.
|
||||
//
|
||||
// PUSH20 <beneficiary> SELFDESTRUCT
|
||||
initCode := make([]byte, 0, 22)
|
||||
initCode = append(initCode, byte(vm.PUSH20))
|
||||
initCode = append(initCode, beneficiary.Bytes()...)
|
||||
initCode = append(initCode, byte(vm.SELFDESTRUCT))
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
sender: {Balance: funds},
|
||||
beneficiary: {Balance: big.NewInt(1)}, // Pre-existing beneficiary
|
||||
},
|
||||
}
|
||||
|
||||
// Contract creation that immediately self-destructs
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
tx, _ := types.SignTx(types.NewContractCreation(
|
||||
0,
|
||||
big.NewInt(1000), // Send some ETH to the contract
|
||||
100000,
|
||||
uint256.MustFromBig(newGwei(5)).ToBig(),
|
||||
initCode,
|
||||
), signer, key)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
stats := result.Stats()
|
||||
|
||||
// The newly created contract should be deleted (self-destructed)
|
||||
if stats.AccountDeleted < 1 {
|
||||
t.Errorf("Expected AccountDeleted >= 1, got %d", stats.AccountDeleted)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultipleStorageOperationsMetrics verifies that storage metrics correctly
|
||||
// accumulate when multiple operations occur.
|
||||
func TestMultipleStorageOperationsMetrics(t *testing.T) {
|
||||
var (
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
sender = crypto.PubkeyToAddress(key.PublicKey)
|
||||
contract = common.HexToAddress("0x7777777777777777777777777777777777777777")
|
||||
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
|
||||
)
|
||||
|
||||
// Contract that:
|
||||
// 1. Reads slots 0, 1, 2 (3 SLOADs)
|
||||
// 2. Writes to slots 3, 4 (2 SSTOREs with non-zero values)
|
||||
// 3. Clears slot 5 (1 SSTORE with zero, existing value was non-zero)
|
||||
contractCode := []byte{
|
||||
// SLOAD slot 0
|
||||
byte(vm.PUSH1), 0x00, byte(vm.SLOAD), byte(vm.POP),
|
||||
// SLOAD slot 1
|
||||
byte(vm.PUSH1), 0x01, byte(vm.SLOAD), byte(vm.POP),
|
||||
// SLOAD slot 2
|
||||
byte(vm.PUSH1), 0x02, byte(vm.SLOAD), byte(vm.POP),
|
||||
// SSTORE slot 3 = 0xFF
|
||||
byte(vm.PUSH1), 0xFF, byte(vm.PUSH1), 0x03, byte(vm.SSTORE),
|
||||
// SSTORE slot 4 = 0xEE
|
||||
byte(vm.PUSH1), 0xEE, byte(vm.PUSH1), 0x04, byte(vm.SSTORE),
|
||||
// SSTORE slot 5 = 0x00 (delete)
|
||||
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x05, byte(vm.SSTORE),
|
||||
byte(vm.STOP),
|
||||
}
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
sender: {Balance: funds},
|
||||
contract: {
|
||||
Code: contractCode,
|
||||
Balance: big.NewInt(0),
|
||||
Storage: map[common.Hash]common.Hash{
|
||||
common.HexToHash("0x00"): common.HexToHash("0x01"),
|
||||
common.HexToHash("0x01"): common.HexToHash("0x02"),
|
||||
common.HexToHash("0x02"): common.HexToHash("0x03"),
|
||||
common.HexToHash("0x05"): common.HexToHash("0xAA"), // Will be cleared
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
tx, _ := types.SignTx(types.NewTransaction(
|
||||
0,
|
||||
contract,
|
||||
big.NewInt(0),
|
||||
100000,
|
||||
uint256.MustFromBig(newGwei(5)).ToBig(),
|
||||
nil,
|
||||
), signer, key)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
result, err := chain.ProcessBlock(chain.CurrentBlock().Root, blocks[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to process block: %v", err)
|
||||
}
|
||||
|
||||
stats := result.Stats()
|
||||
|
||||
// 3 slots read: 0, 1, 2
|
||||
if stats.StorageLoaded < 3 {
|
||||
t.Errorf("Expected StorageLoaded >= 3, got %d", stats.StorageLoaded)
|
||||
}
|
||||
|
||||
// 2 slots written with non-zero values: 3, 4
|
||||
if stats.StorageUpdated < 2 {
|
||||
t.Errorf("Expected StorageUpdated >= 2, got %d", stats.StorageUpdated)
|
||||
}
|
||||
|
||||
// 1 slot cleared: 5
|
||||
if stats.StorageDeleted < 1 {
|
||||
t.Errorf("Expected StorageDeleted >= 1, got %d", stats.StorageDeleted)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,20 +38,16 @@ type ExecuteStats struct {
|
|||
StorageCommits time.Duration // Time spent on the storage trie commit
|
||||
CodeReads time.Duration // Time spent on the contract code read
|
||||
|
||||
AccountLoaded int // Number of accounts loaded
|
||||
AccountUpdated int // Number of accounts updated
|
||||
AccountDeleted int // Number of accounts deleted
|
||||
StorageLoaded int // Number of storage slots loaded
|
||||
StorageUpdated int // Number of storage slots updated
|
||||
StorageDeleted int // Number of storage slots deleted
|
||||
CodeLoaded int // Number of contract code loaded
|
||||
CodeLoadBytes int // Number of bytes read from contract code
|
||||
CodeUpdated int // Number of contract code written (CREATE/CREATE2 + EIP-7702)
|
||||
CodeBytesWrite int // Total bytes of code written
|
||||
|
||||
// EIP-7702 delegation tracking
|
||||
Eip7702DelegationsSet int // Number of EIP-7702 delegations set
|
||||
Eip7702DelegationsCleared int // Number of EIP-7702 delegations cleared
|
||||
AccountLoaded int // Number of accounts loaded
|
||||
AccountUpdated int // Number of accounts updated
|
||||
AccountDeleted int // Number of accounts deleted
|
||||
StorageLoaded int // Number of storage slots loaded
|
||||
StorageUpdated int // Number of storage slots updated
|
||||
StorageDeleted int // Number of storage slots deleted
|
||||
CodeLoaded int // Number of contract code loaded
|
||||
CodeLoadBytes int // Number of bytes read from contract code
|
||||
CodeUpdated int // Number of contract code written (CREATE/CREATE2 + EIP-7702)
|
||||
CodeUpdateBytes int // Total bytes of code written
|
||||
|
||||
Execution time.Duration // Time spent on the EVM execution
|
||||
Validation time.Duration // Time spent on the block validation
|
||||
|
|
@ -150,14 +146,12 @@ type slowBlockReads struct {
|
|||
}
|
||||
|
||||
type slowBlockWrites struct {
|
||||
Accounts int `json:"accounts"`
|
||||
AccountsDeleted int `json:"accounts_deleted"`
|
||||
StorageSlots int `json:"storage_slots"`
|
||||
StorageSlotsDeleted int `json:"storage_slots_deleted"`
|
||||
Code int `json:"code"`
|
||||
CodeBytes int `json:"code_bytes"`
|
||||
Eip7702DelegationsSet int `json:"eip7702_delegations_set"`
|
||||
Eip7702DelegationsCleared int `json:"eip7702_delegations_cleared"`
|
||||
Accounts int `json:"accounts"`
|
||||
AccountsDeleted int `json:"accounts_deleted"`
|
||||
StorageSlots int `json:"storage_slots"`
|
||||
StorageSlotsDeleted int `json:"storage_slots_deleted"`
|
||||
Code int `json:"code"`
|
||||
CodeBytes int `json:"code_bytes"`
|
||||
}
|
||||
|
||||
// slowBlockCache represents cache hit/miss statistics for cross-client analysis.
|
||||
|
|
@ -209,7 +203,6 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
|
|||
if slowBlockThreshold > 0 && s.TotalTime < slowBlockThreshold {
|
||||
return
|
||||
}
|
||||
|
||||
logEntry := slowBlockLog{
|
||||
Level: "warn",
|
||||
Msg: "Slow block",
|
||||
|
|
@ -236,14 +229,12 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
|
|||
CodeBytes: s.CodeLoadBytes,
|
||||
},
|
||||
StateWrites: slowBlockWrites{
|
||||
Accounts: s.AccountUpdated,
|
||||
AccountsDeleted: s.AccountDeleted,
|
||||
StorageSlots: s.StorageUpdated,
|
||||
StorageSlotsDeleted: s.StorageDeleted,
|
||||
Code: s.CodeUpdated,
|
||||
CodeBytes: s.CodeBytesWrite,
|
||||
Eip7702DelegationsSet: s.Eip7702DelegationsSet,
|
||||
Eip7702DelegationsCleared: s.Eip7702DelegationsCleared,
|
||||
Accounts: s.AccountUpdated,
|
||||
AccountsDeleted: s.AccountDeleted,
|
||||
StorageSlots: s.StorageUpdated,
|
||||
StorageSlotsDeleted: s.StorageDeleted,
|
||||
Code: s.CodeUpdated,
|
||||
CodeBytes: s.CodeUpdateBytes,
|
||||
},
|
||||
Cache: slowBlockCache{
|
||||
Account: slowBlockCacheEntry{
|
||||
|
|
@ -265,7 +256,6 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(logEntry)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal slow block log", "error", err)
|
||||
|
|
|
|||
|
|
@ -1,295 +0,0 @@
|
|||
// Copyright 2025 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 core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// TestLogSlowBlockJSON tests that logSlow outputs valid JSON in the expected format.
|
||||
func TestLogSlowBlockJSON(t *testing.T) {
|
||||
// Create a buffer to capture log output
|
||||
var buf bytes.Buffer
|
||||
handler := log.NewTerminalHandler(&buf, false)
|
||||
log.SetDefault(log.NewLogger(handler))
|
||||
|
||||
// Create a test block
|
||||
header := &types.Header{
|
||||
Number: common.Big1,
|
||||
GasUsed: 21000000,
|
||||
GasLimit: 30000000,
|
||||
}
|
||||
block := types.NewBlockWithHeader(header)
|
||||
|
||||
// Create test stats with cache data
|
||||
stats := &ExecuteStats{
|
||||
Execution: 500 * time.Millisecond,
|
||||
TotalTime: 1200 * time.Millisecond, // > 1s threshold
|
||||
MgasPerSecond: 17.5,
|
||||
AccountLoaded: 100,
|
||||
StorageLoaded: 500,
|
||||
CodeLoaded: 20,
|
||||
AccountUpdated: 50,
|
||||
StorageUpdated: 200,
|
||||
StateReadCacheStats: state.ReaderStats{
|
||||
AccountCacheHit: 4,
|
||||
AccountCacheMiss: 6,
|
||||
StorageCacheHit: 0,
|
||||
StorageCacheMiss: 11,
|
||||
CodeStats: state.ContractCodeReaderStats{
|
||||
CacheHit: 4,
|
||||
CacheMiss: 0,
|
||||
CacheHitBytes: 102400, // ~100KB served from cache
|
||||
CacheMissBytes: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Log the slow block (threshold of 1s, so 1.2s total time should trigger)
|
||||
stats.logSlow(block, 1*time.Second)
|
||||
|
||||
// Get the log output
|
||||
output := buf.String()
|
||||
t.Logf("Log output:\n%s", output)
|
||||
|
||||
// The output should contain the JSON
|
||||
if len(output) == 0 {
|
||||
t.Fatal("Expected log output, got empty string")
|
||||
}
|
||||
|
||||
// Try to extract and parse the JSON from the log line
|
||||
// The log format is: WARN [...] {"level":"warn",...}
|
||||
// We need to find the JSON part
|
||||
jsonStart := bytes.Index(buf.Bytes(), []byte(`{"level"`))
|
||||
if jsonStart == -1 {
|
||||
t.Logf("Full output: %s", output)
|
||||
t.Fatal("Could not find JSON in log output")
|
||||
}
|
||||
|
||||
jsonBytes := buf.Bytes()[jsonStart:]
|
||||
// Find the end of the JSON (newline or end of buffer)
|
||||
jsonEnd := bytes.IndexByte(jsonBytes, '\n')
|
||||
if jsonEnd != -1 {
|
||||
jsonBytes = jsonBytes[:jsonEnd]
|
||||
}
|
||||
|
||||
// Parse the JSON
|
||||
var logEntry slowBlockLog
|
||||
if err := json.Unmarshal(jsonBytes, &logEntry); err != nil {
|
||||
t.Fatalf("Failed to parse JSON: %v\nJSON: %s", err, string(jsonBytes))
|
||||
}
|
||||
|
||||
// Verify the fields
|
||||
if logEntry.Level != "warn" {
|
||||
t.Errorf("Expected level 'warn', got '%s'", logEntry.Level)
|
||||
}
|
||||
if logEntry.Msg != "Slow block" {
|
||||
t.Errorf("Expected msg 'Slow block', got '%s'", logEntry.Msg)
|
||||
}
|
||||
if logEntry.Block.Number != 1 {
|
||||
t.Errorf("Expected block number 1, got %d", logEntry.Block.Number)
|
||||
}
|
||||
if logEntry.Block.GasUsed != 21000000 {
|
||||
t.Errorf("Expected gas used 21000000, got %d", logEntry.Block.GasUsed)
|
||||
}
|
||||
if logEntry.Timing.ExecutionMs != 500.0 {
|
||||
t.Errorf("Expected execution_ms 500.0, got %v", logEntry.Timing.ExecutionMs)
|
||||
}
|
||||
if logEntry.Timing.TotalMs != 1200.0 {
|
||||
t.Errorf("Expected total_ms 1200.0, got %v", logEntry.Timing.TotalMs)
|
||||
}
|
||||
if logEntry.StateReads.Accounts != 100 {
|
||||
t.Errorf("Expected accounts 100, got %d", logEntry.StateReads.Accounts)
|
||||
}
|
||||
if logEntry.StateReads.StorageSlots != 500 {
|
||||
t.Errorf("Expected storage_slots 500, got %d", logEntry.StateReads.StorageSlots)
|
||||
}
|
||||
if logEntry.StateWrites.Accounts != 50 {
|
||||
t.Errorf("Expected accounts updated 50, got %d", logEntry.StateWrites.Accounts)
|
||||
}
|
||||
|
||||
// Verify cache statistics
|
||||
if logEntry.Cache.Account.Hits != 4 {
|
||||
t.Errorf("Expected account cache hits 4, got %d", logEntry.Cache.Account.Hits)
|
||||
}
|
||||
if logEntry.Cache.Account.Misses != 6 {
|
||||
t.Errorf("Expected account cache misses 6, got %d", logEntry.Cache.Account.Misses)
|
||||
}
|
||||
// 4/(4+6) = 0.4 = 40%
|
||||
if logEntry.Cache.Account.HitRate != 40.0 {
|
||||
t.Errorf("Expected account cache hit_rate 40.0, got %f", logEntry.Cache.Account.HitRate)
|
||||
}
|
||||
if logEntry.Cache.Storage.Hits != 0 {
|
||||
t.Errorf("Expected storage cache hits 0, got %d", logEntry.Cache.Storage.Hits)
|
||||
}
|
||||
if logEntry.Cache.Storage.Misses != 11 {
|
||||
t.Errorf("Expected storage cache misses 11, got %d", logEntry.Cache.Storage.Misses)
|
||||
}
|
||||
// 0/(0+11) = 0%
|
||||
if logEntry.Cache.Storage.HitRate != 0.0 {
|
||||
t.Errorf("Expected storage cache hit_rate 0.0, got %f", logEntry.Cache.Storage.HitRate)
|
||||
}
|
||||
if logEntry.Cache.Code.Hits != 4 {
|
||||
t.Errorf("Expected code cache hits 4, got %d", logEntry.Cache.Code.Hits)
|
||||
}
|
||||
if logEntry.Cache.Code.Misses != 0 {
|
||||
t.Errorf("Expected code cache misses 0, got %d", logEntry.Cache.Code.Misses)
|
||||
}
|
||||
// 4/(4+0) = 100%
|
||||
if logEntry.Cache.Code.HitRate != 100.0 {
|
||||
t.Errorf("Expected code cache hit_rate 100.0, got %f", logEntry.Cache.Code.HitRate)
|
||||
}
|
||||
// Verify new byte-level cache statistics
|
||||
if logEntry.Cache.Code.HitBytes != 102400 {
|
||||
t.Errorf("Expected code cache hit_bytes 102400, got %d", logEntry.Cache.Code.HitBytes)
|
||||
}
|
||||
if logEntry.Cache.Code.MissBytes != 0 {
|
||||
t.Errorf("Expected code cache miss_bytes 0, got %d", logEntry.Cache.Code.MissBytes)
|
||||
}
|
||||
|
||||
t.Logf("Parsed JSON:\n%+v", logEntry)
|
||||
}
|
||||
|
||||
// TestLogSlowBlockEIP7702 tests that EIP-7702 delegation fields are properly serialized.
|
||||
func TestLogSlowBlockEIP7702(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
handler := log.NewTerminalHandler(&buf, false)
|
||||
log.SetDefault(log.NewLogger(handler))
|
||||
|
||||
header := &types.Header{
|
||||
Number: common.Big1,
|
||||
GasUsed: 21000000,
|
||||
GasLimit: 30000000,
|
||||
}
|
||||
block := types.NewBlockWithHeader(header)
|
||||
|
||||
// Create test stats with EIP-7702 delegation data and deletion metrics
|
||||
stats := &ExecuteStats{
|
||||
Execution: 500 * time.Millisecond,
|
||||
TotalTime: 1200 * time.Millisecond,
|
||||
MgasPerSecond: 17.5,
|
||||
AccountLoaded: 100,
|
||||
StorageLoaded: 500,
|
||||
CodeLoaded: 20,
|
||||
CodeLoadBytes: 4096,
|
||||
AccountUpdated: 50,
|
||||
AccountDeleted: 2,
|
||||
StorageUpdated: 200,
|
||||
StorageDeleted: 7,
|
||||
CodeUpdated: 5,
|
||||
CodeBytesWrite: 2048,
|
||||
Eip7702DelegationsSet: 3,
|
||||
Eip7702DelegationsCleared: 1,
|
||||
StateReadCacheStats: state.ReaderStats{
|
||||
AccountCacheHit: 4,
|
||||
AccountCacheMiss: 6,
|
||||
},
|
||||
}
|
||||
|
||||
stats.logSlow(block, 1*time.Second)
|
||||
|
||||
// Find and parse the JSON
|
||||
jsonStart := bytes.Index(buf.Bytes(), []byte(`{"level"`))
|
||||
if jsonStart == -1 {
|
||||
t.Fatal("Could not find JSON in log output")
|
||||
}
|
||||
jsonBytes := buf.Bytes()[jsonStart:]
|
||||
if jsonEnd := bytes.IndexByte(jsonBytes, '\n'); jsonEnd != -1 {
|
||||
jsonBytes = jsonBytes[:jsonEnd]
|
||||
}
|
||||
|
||||
var logEntry slowBlockLog
|
||||
if err := json.Unmarshal(jsonBytes, &logEntry); err != nil {
|
||||
t.Fatalf("Failed to parse JSON: %v", err)
|
||||
}
|
||||
|
||||
// Verify EIP-7702 fields
|
||||
if logEntry.StateWrites.Eip7702DelegationsSet != 3 {
|
||||
t.Errorf("Expected eip7702_delegations_set 3, got %d", logEntry.StateWrites.Eip7702DelegationsSet)
|
||||
}
|
||||
if logEntry.StateWrites.Eip7702DelegationsCleared != 1 {
|
||||
t.Errorf("Expected eip7702_delegations_cleared 1, got %d", logEntry.StateWrites.Eip7702DelegationsCleared)
|
||||
}
|
||||
|
||||
// Verify code bytes fields
|
||||
if logEntry.StateReads.CodeBytes != 4096 {
|
||||
t.Errorf("Expected code_bytes read 4096, got %d", logEntry.StateReads.CodeBytes)
|
||||
}
|
||||
if logEntry.StateWrites.CodeBytes != 2048 {
|
||||
t.Errorf("Expected code_bytes write 2048, got %d", logEntry.StateWrites.CodeBytes)
|
||||
}
|
||||
if logEntry.StateWrites.Code != 5 {
|
||||
t.Errorf("Expected code writes 5, got %d", logEntry.StateWrites.Code)
|
||||
}
|
||||
|
||||
// Verify deletion metrics
|
||||
if logEntry.StateWrites.AccountsDeleted != 2 {
|
||||
t.Errorf("Expected accounts_deleted 2, got %d", logEntry.StateWrites.AccountsDeleted)
|
||||
}
|
||||
if logEntry.StateWrites.StorageSlotsDeleted != 7 {
|
||||
t.Errorf("Expected storage_slots_deleted 7, got %d", logEntry.StateWrites.StorageSlotsDeleted)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLogSlowBlockThreshold tests that logSlow respects the threshold.
|
||||
func TestLogSlowBlockThreshold(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
handler := log.NewTerminalHandler(&buf, false)
|
||||
log.SetDefault(log.NewLogger(handler))
|
||||
|
||||
header := &types.Header{Number: common.Big1}
|
||||
block := types.NewBlockWithHeader(header)
|
||||
|
||||
stats := &ExecuteStats{
|
||||
TotalTime: 500 * time.Millisecond, // 0.5s, below 1s threshold
|
||||
}
|
||||
|
||||
// Should NOT log (below threshold)
|
||||
stats.logSlow(block, 1*time.Second)
|
||||
|
||||
if buf.Len() > 0 {
|
||||
t.Errorf("Expected no output for fast block, got: %s", buf.String())
|
||||
}
|
||||
|
||||
// Reset buffer
|
||||
buf.Reset()
|
||||
|
||||
// Test with zero threshold (logs all blocks)
|
||||
stats.logSlow(block, 0)
|
||||
|
||||
if buf.Len() == 0 {
|
||||
t.Errorf("Expected output for zero threshold (logs all), got nothing")
|
||||
}
|
||||
|
||||
// Reset buffer
|
||||
buf.Reset()
|
||||
|
||||
// Test with negative threshold (disabled)
|
||||
stats.logSlow(block, -1)
|
||||
|
||||
if buf.Len() > 0 {
|
||||
t.Errorf("Expected no output for negative threshold (disabled), got: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
|
@ -158,21 +158,15 @@ type StateDB struct {
|
|||
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
|
||||
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
||||
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
|
||||
CodeLoaded int // Number of contract code loaded during the state transition
|
||||
|
||||
// CodeLoadBytes is the total number of bytes read from contract code.
|
||||
// This value may be smaller than the actual number of bytes read, since
|
||||
// some APIs (e.g. CodeSize) may load the entire code from either the
|
||||
// cache or the database when the size is not available in the cache.
|
||||
CodeLoadBytes int
|
||||
|
||||
// EIP-7702 delegation tracking for cross-client execution metrics
|
||||
Eip7702DelegationsSet int // Number of EIP-7702 delegations set
|
||||
Eip7702DelegationsCleared int // Number of EIP-7702 delegations cleared
|
||||
|
||||
// Code write tracking for cross-client execution metrics
|
||||
CodeUpdated int // Number of contracts with code changes that persisted
|
||||
CodeBytesWrite int // Total bytes of persisted code written
|
||||
CodeLoaded int // Number of contract code loaded during the state transition
|
||||
CodeLoadBytes int // Total bytes of resolved code
|
||||
CodeUpdated int // Number of contracts with code changes that persisted
|
||||
CodeUpdateBytes int // Total bytes of persisted code written
|
||||
}
|
||||
|
||||
// New creates a new state from a given trie.
|
||||
|
|
@ -481,14 +475,6 @@ func (s *StateDB) SetNonce(addr common.Address, nonce uint64, reason tracing.Non
|
|||
func (s *StateDB) SetCode(addr common.Address, code []byte, reason tracing.CodeChangeReason) (prev []byte) {
|
||||
stateObject := s.getOrNewStateObject(addr)
|
||||
if stateObject != nil {
|
||||
// EIP-7702 delegation counters are safe here: authorizations are applied
|
||||
// at transaction level (applyAuthorization), outside revertable EVM frames.
|
||||
switch reason {
|
||||
case tracing.CodeChangeAuthorization:
|
||||
s.Eip7702DelegationsSet++
|
||||
case tracing.CodeChangeAuthorizationClear:
|
||||
s.Eip7702DelegationsCleared++
|
||||
}
|
||||
return stateObject.SetCode(crypto.Keccak256Hash(code), code)
|
||||
}
|
||||
return nil
|
||||
|
|
@ -960,11 +946,11 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
obj := s.stateObjects[addr]
|
||||
s.updateStateObject(obj)
|
||||
s.AccountUpdated += 1
|
||||
|
||||
// Count code writes post-Finalise so reverted CREATEs are excluded.
|
||||
// Only count non-empty code (clears like EIP-7702 revocations are not code writes).
|
||||
if obj.dirtyCode && len(obj.code) > 0 {
|
||||
s.CodeUpdated += 1
|
||||
s.CodeBytesWrite += len(obj.code)
|
||||
s.CodeUpdateBytes += len(obj.code)
|
||||
}
|
||||
}
|
||||
usedAddrs = append(usedAddrs, addr) // Copy needed for closure
|
||||
|
|
|
|||
Loading…
Reference in a new issue