creating an automatic benchamrk for parallel execution

This commit is contained in:
idotalk 2026-06-20 15:38:08 +03:00
parent 263d65e7ac
commit 4ed8da87df
6 changed files with 583 additions and 183 deletions

View file

@ -0,0 +1,36 @@
name: Parallel Execution Benchmark
on:
pull_request:
branches:
- master
jobs:
benchmark:
name: Run Parallel Benchmarks
runs-on: ubuntu-latest
permissions:
contents: write # Results will be pushed back to the PR
steps:
- uses: actions/checkout@v4
with:
submodules: false
ref: ${{ github.event.pull_request.head.ref }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: true
- name: Run benchmark
run: |
# The benchmark outputs results to BENCHMARK_OUTPUT_FILE
export BENCHMARK_OUTPUT_FILE=$GITHUB_WORKSPACE/tests/benchmarks/parallel_results.txt
go test -v ./tests -run TestParallelBenchmarkOutput -timeout 15m
- name: Commit benchmark results
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore: update parallel execution benchmark results"
file_pattern: "tests/benchmarks/parallel_results.txt"

5
core/parallel_flags.go Normal file
View file

@ -0,0 +1,5 @@
package core
// ParallelTxGroupingByStorageOverlap controls whether the state processor
// runs transaction execution in parallel using storage overlap grouping.
var ParallelTxGroupingByStorageOverlap = false

View file

View file

@ -0,0 +1,340 @@
package tests
import (
"fmt"
"math/big"
"os"
"path/filepath"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"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"
)
func TestParallelBenchmarkOutput(t *testing.T) {
outPath := os.Getenv("BENCHMARK_OUTPUT_FILE")
if outPath == "" {
t.Skip("BENCHMARK_OUTPUT_FILE not set, skipping benchmark tracking output")
}
txCounts := []int{50, 150, 300, 450, 600}
txDepsTypes := []string{"Isolated", "Contended", "Mixed"}
var results []string
dateStr := time.Now().Format("2006-01-02")
for _, n := range txCounts {
for _, dep := range txDepsTypes {
var blocks []*types.Block
var genesis *core.Genesis
var engine *ethash.Ethash
switch dep {
case "Isolated":
blocks, genesis, engine = createIsolatedBlock(t, n)
case "Contended":
blocks, genesis, engine = createContendedBlock(t, n)
case "Mixed":
blocks, genesis, engine = createMixedBlock(t, n)
}
// Sequential Run
core.ParallelTxGroupingByStorageOverlap = false
seqTime := timeInsert(t, blocks, genesis, engine)
// Parallel Run
core.ParallelTxGroupingByStorageOverlap = true
parTime := timeInsert(t, blocks, genesis, engine)
speedup := float64(seqTime) / float64(parTime)
seqAvgMs := (seqTime.Seconds() / float64(n)) * 1000
parAvgMs := (parTime.Seconds() / float64(n)) * 1000
resLine := fmt.Sprintf("[%s][%d][%s] - Sequential: %.3fs (%.2fms/tx), Parallel: %.3fs (%.2fms/tx), Speedup: %.2fx",
dateStr, n, dep, seqTime.Seconds(), seqAvgMs, parTime.Seconds(), parAvgMs, speedup)
t.Log(resLine)
results = append(results, resLine)
}
}
err := os.MkdirAll(filepath.Dir(outPath), 0755)
if err != nil {
t.Fatalf("failed to create dir: %v", err)
}
f, err := os.OpenFile(outPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
t.Fatalf("failed to open output file: %v", err)
}
defer f.Close()
for _, res := range results {
if _, err := f.WriteString(res + "\n"); err != nil {
t.Fatalf("failed to write result: %v", err)
}
}
}
func timeInsert(t *testing.T, blocks []*types.Block, genesis *core.Genesis, engine *ethash.Ethash) time.Duration {
options := &core.BlockChainConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 0,
Preimages: true,
ArchiveMode: true,
}
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, options)
if err != nil {
t.Fatalf("create chain: %v", err)
}
defer chain.Stop()
// Warm up
if n, err := chain.InsertChain(blocks[:1]); err != nil {
t.Fatalf("warmup block %d failed: %v", n, err)
}
start := time.Now()
// Insert the actual block with transactions
if n, err := chain.InsertChain(blocks[1:]); err != nil {
t.Fatalf("benchmark block %d failed: %v", n, err)
}
return time.Since(start)
}
func createIsolatedBlock(t *testing.T, n int) ([]*types.Block, *core.Genesis, *ethash.Ethash) {
keys, addrs := newTestAccounts(t, n+1)
deployKey := keys[0]
deployFrom := addrs[0]
contractABI := mustReadContractABI(t)
contractBin := mustReadContractBinBlockTest(t)
contractAddrs := make([]common.Address, n)
for i := 0; i < n; i++ {
contractAddrs[i] = crypto.CreateAddress(deployFrom, uint64(i))
}
genesis := &core.Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: genesisAllocEther(addrs[:]...),
GasLimit: uint64(n+1) * 20_000_000,
BaseFee: big.NewInt(params.InitialBaseFee),
}
engine := ethash.NewFaker()
// Create chain sequentially first to avoid race conditions during test setup
core.ParallelTxGroupingByStorageOverlap = false
_, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, 2, func(i int, b *core.BlockGen) {
switch i {
case 0:
for nonce := uint64(0); nonce < uint64(n); nonce++ {
deployTx := mustSignAccessListTx(t, params.AllEthashProtocolChanges, deployKey, &types.AccessListTx{
ChainID: params.AllEthashProtocolChanges.ChainID,
Nonce: nonce,
Gas: 12_000_000,
GasPrice: b.BaseFee(),
Data: contractBin,
})
b.AddTx(deployTx)
}
case 1:
const rounds int64 = 1000
for i := 0; i < n; i++ {
lane := int64(i + 1)
senderKey := keys[i+1]
contract := contractAddrs[i]
data, _ := contractABI.Pack("isolatedJob", big.NewInt(lane), big.NewInt(1), big.NewInt(rounds))
tx := mustSignAccessListTx(t, params.AllEthashProtocolChanges, senderKey, &types.AccessListTx{
ChainID: params.AllEthashProtocolChanges.ChainID,
Nonce: 0,
To: &contract,
Gas: 18_000_000,
GasPrice: b.BaseFee(),
Data: data,
AccessList: isolatedAccessList(contract, uint64(lane)),
})
b.AddTx(tx)
}
}
})
return blocks, genesis, engine
}
func createContendedBlock(t *testing.T, n int) ([]*types.Block, *core.Genesis, *ethash.Ethash) {
keys, addrs := newTestAccounts(t, n+1)
deployKey := keys[0]
deployFrom := addrs[0]
contractABI := mustReadContractABI(t)
contractBin := mustReadContractBinBlockTest(t)
contractAddr := crypto.CreateAddress(deployFrom, 0)
genesis := &core.Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: genesisAllocEther(addrs[:]...),
GasLimit: uint64(n+1) * 20_000_000,
BaseFee: big.NewInt(params.InitialBaseFee),
}
engine := ethash.NewFaker()
core.ParallelTxGroupingByStorageOverlap = false
_, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, 2, func(i int, b *core.BlockGen) {
switch i {
case 0:
deployTx := mustSignAccessListTx(t, params.AllEthashProtocolChanges, deployKey, &types.AccessListTx{
ChainID: params.AllEthashProtocolChanges.ChainID,
Nonce: 0,
Gas: 12_000_000,
GasPrice: b.BaseFee(),
Data: contractBin,
})
b.AddTx(deployTx)
case 1:
const rounds int64 = 1000
contendedAccess := types.AccessList{{
Address: contractAddr,
StorageKeys: []common.Hash{
common.BigToHash(big.NewInt(2)),
common.BigToHash(big.NewInt(3)),
},
}}
for i := 0; i < n; i++ {
senderKey := keys[i+1]
data, _ := contractABI.Pack("contendedJob", big.NewInt(1), big.NewInt(rounds))
tx := mustSignAccessListTx(t, params.AllEthashProtocolChanges, senderKey, &types.AccessListTx{
ChainID: params.AllEthashProtocolChanges.ChainID,
Nonce: 0,
To: &contractAddr,
Gas: 18_000_000,
GasPrice: b.BaseFee(),
Data: data,
AccessList: contendedAccess,
})
b.AddTx(tx)
}
}
})
return blocks, genesis, engine
}
func createMixedBlock(t *testing.T, n int) ([]*types.Block, *core.Genesis, *ethash.Ethash) {
keys, addrs := newTestAccounts(t, n+1)
deployKey := keys[0]
deployFrom := addrs[0]
contractABI := mustReadContractABI(t)
contractBin := mustReadContractBinBlockTest(t)
cShared := crypto.CreateAddress(deployFrom, 0)
// Half shared, half isolated. The shared contract is deployed at nonce 0,
// the rest of the isolated contracts at nonces 1.. n/2.
numIsolated := n / 2
numContended := n - numIsolated
cIsolatedAddrs := make([]common.Address, numIsolated)
for i := 0; i < numIsolated; i++ {
cIsolatedAddrs[i] = crypto.CreateAddress(deployFrom, uint64(i+1))
}
genesis := &core.Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: genesisAllocEther(addrs[:]...),
GasLimit: uint64(n+1) * 20_000_000,
BaseFee: big.NewInt(params.InitialBaseFee),
}
engine := ethash.NewFaker()
core.ParallelTxGroupingByStorageOverlap = false
_, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, 2, func(i int, b *core.BlockGen) {
switch i {
case 0:
// Deploy shared contract
deployTx0 := mustSignAccessListTx(t, params.AllEthashProtocolChanges, deployKey, &types.AccessListTx{
ChainID: params.AllEthashProtocolChanges.ChainID,
Nonce: 0,
Gas: 12_000_000,
GasPrice: b.BaseFee(),
Data: contractBin,
})
b.AddTx(deployTx0)
// Deploy isolated contracts
for i := 0; i < numIsolated; i++ {
deployTx := mustSignAccessListTx(t, params.AllEthashProtocolChanges, deployKey, &types.AccessListTx{
ChainID: params.AllEthashProtocolChanges.ChainID,
Nonce: uint64(i + 1),
Gas: 12_000_000,
GasPrice: b.BaseFee(),
Data: contractBin,
})
b.AddTx(deployTx)
}
case 1:
const rounds int64 = 1000
mixedAccess := func(caddr common.Address, lane uint64) types.AccessList {
return append(
isolatedAccessList(caddr, lane),
types.AccessTuple{
Address: caddr,
StorageKeys: []common.Hash{
common.BigToHash(big.NewInt(2)),
common.BigToHash(big.NewInt(3)),
},
},
)
}
// Add contended transactions
for i := 0; i < numContended; i++ {
senderKey := keys[i+1]
lane := int64(i + 1)
data, _ := contractABI.Pack("mixedJob", big.NewInt(lane), big.NewInt(lane+1), big.NewInt(rounds))
tx := mustSignAccessListTx(t, params.AllEthashProtocolChanges, senderKey, &types.AccessListTx{
ChainID: params.AllEthashProtocolChanges.ChainID,
Nonce: 0,
To: &cShared,
Gas: 18_000_000,
GasPrice: b.BaseFee(),
Data: data,
AccessList: mixedAccess(cShared, uint64(lane)),
})
b.AddTx(tx)
}
// Add isolated transactions
for i := 0; i < numIsolated; i++ {
senderKey := keys[numContended+1+i]
lane := int64(i + 1)
contract := cIsolatedAddrs[i]
data, _ := contractABI.Pack("isolatedJob", big.NewInt(lane), big.NewInt(1), big.NewInt(rounds))
tx := mustSignAccessListTx(t, params.AllEthashProtocolChanges, senderKey, &types.AccessListTx{
ChainID: params.AllEthashProtocolChanges.ChainID,
Nonce: 0,
To: &contract,
Gas: 18_000_000,
GasPrice: b.BaseFee(),
Data: data,
AccessList: isolatedAccessList(contract, uint64(lane)),
})
b.AddTx(tx)
}
}
})
return blocks, genesis, engine
}

View file

@ -1,28 +1,19 @@
package tests package tests
import ( import (
"bytes"
"crypto/ecdsa" "crypto/ecdsa"
"errors"
"math/big" "math/big"
"os"
"path/filepath"
"strings"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb"
"github.com/holiman/uint256"
) )
// Gas budget for TestParallelVMBlockAccessListIsolated: block limit must exceed the // Gas budget for TestParallelVMBlockAccessListIsolated: block limit must exceed the
@ -344,178 +335,4 @@ func TestParallelVMBlockAccessListMixed(t *testing.T) {
}) })
} }
func runAndCheckChain(t *testing.T, genesis *core.Genesis, engine *ethash.Ethash, blocks []*types.Block, check func(*state.StateDB)) {
options := &core.BlockChainConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 0,
Preimages: true,
ArchiveMode: true,
}
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, options)
if err != nil {
t.Fatalf("create chain: %v", err)
}
defer chain.Stop()
if n, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("block %d failed: %s", n, formatInsertChainErrorForDebug(err))
}
statedb := mustStateAtCurrentHead(t, chain)
check(statedb)
}
// formatInsertChainErrorForDebug prints the full errors.Unwrap chain (outer → inner)
// and adds a file hint for known core errors so test failures are easier to trace.
func formatInsertChainErrorForDebug(err error) string {
var b strings.Builder
b.WriteString(err.Error())
for u := errors.Unwrap(err); u != nil; u = errors.Unwrap(u) {
b.WriteString("\n ← unwrap: ")
b.WriteString(u.Error())
}
switch {
case errors.Is(err, core.ErrNonceTooHigh):
b.WriteString("\n [origin] core/state_transition.go — (*stateTransition).preCheck when !msg.SkipNonceChecks (tx nonce > state nonce)")
case errors.Is(err, core.ErrNonceTooLow):
b.WriteString("\n [origin] core/state_transition.go — (*stateTransition).preCheck when !msg.SkipNonceChecks")
case errors.Is(err, core.ErrInsufficientFunds):
b.WriteString("\n [origin] core/state_transition.go — (*stateTransition).buyGas or balance checks in preCheck")
}
return b.String()
}
func newTestAccount(t *testing.T) (*ecdsa.PrivateKey, common.Address) {
t.Helper()
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("generate key: %v", err)
}
return key, crypto.PubkeyToAddress(key.PublicKey)
}
func newTestAccounts(t *testing.T, n int) ([]*ecdsa.PrivateKey, []common.Address) {
t.Helper()
keys := make([]*ecdsa.PrivateKey, n)
addrs := make([]common.Address, n)
for i := 0; i < n; i++ {
keys[i], addrs[i] = newTestAccount(t)
}
return keys, addrs
}
func genesisAllocEther(addrs ...common.Address) types.GenesisAlloc {
bal := new(big.Int).Mul(big.NewInt(1_000_000), big.NewInt(params.Ether))
alloc := make(types.GenesisAlloc, len(addrs))
for _, a := range addrs {
alloc[a] = types.Account{Balance: bal}
}
return alloc
}
func mustReadContractABI(t *testing.T) abi.ABI {
t.Helper()
abiPath := filepath.Join("contracts", "ParallelVMTest.abi")
abiBytes, err := os.ReadFile(abiPath)
if err != nil {
t.Fatalf("read ABI: %v", err)
}
parsedABI, err := abi.JSON(bytes.NewReader(abiBytes))
if err != nil {
t.Fatalf("parse ABI: %v", err)
}
return parsedABI
}
func mustReadContractBinBlockTest(t *testing.T) []byte {
t.Helper()
binPath := filepath.Join("contracts", "ParallelVMTest.bin")
binBytes, err := os.ReadFile(binPath)
if err != nil {
t.Fatalf("read bytecode: %v", err)
}
hexStr := strings.TrimSpace(string(binBytes))
if strings.HasPrefix(hexStr, "0x") {
hexStr = hexStr[2:]
}
return common.FromHex("0x" + hexStr)
}
func mustSignAccessListTx(t *testing.T, cfg *params.ChainConfig, key *ecdsa.PrivateKey, txdata *types.AccessListTx) *types.Transaction {
t.Helper()
signer := types.LatestSigner(cfg)
tx := types.NewTx(txdata)
signed, err := types.SignTx(tx, signer, key)
if err != nil {
t.Fatalf("sign tx: %v", err)
}
return signed
}
func isolatedAccessList(contract common.Address, lane uint64) types.AccessList {
return types.AccessList{
{
Address: contract,
StorageKeys: []common.Hash{
mappingSlotBlock(lane, 0), // laneValue[lane]
mappingSlotBlock(lane, 1), // laneDigest[lane]
},
},
}
}
func mappingSlotBlock(key uint64, baseSlot uint64) common.Hash {
return crypto.Keccak256Hash(
common.LeftPadBytes(new(big.Int).SetUint64(key).Bytes(), 32),
common.LeftPadBytes(new(big.Int).SetUint64(baseSlot).Bytes(), 32),
)
}
func assertStorageUintBlock(t *testing.T, statedb *state.StateDB, addr common.Address, slot common.Hash, want uint64) {
t.Helper()
got := statedb.GetState(addr, slot).Big().Uint64()
if got != want {
t.Fatalf("slot %s got %d want %d", slot.Hex(), got, want)
}
}
func mustStateAtCurrentHead(t *testing.T, chain *core.BlockChain) *state.StateDB {
t.Helper()
root := chain.CurrentBlock().Root
// Most branches expose one of these two shapes:
// 1) chain.StateAt(root)
// 2) rebuild using state.New(root, state.NewDatabase(...))
//
// Try the direct API first if your branch has it.
statedb, err := chain.StateAt(root)
if err == nil {
return statedb
}
// Fallback path for branches without StateAt.
db := rawdb.NewMemoryDatabase()
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
sdb := state.NewDatabase(tdb, nil)
st, err2 := state.New(root, sdb)
if err2 != nil {
t.Fatalf("state lookup failed: direct=%v fallback=%v", err, err2)
}
return st
}
// This exists only to keep imports aligned if your branch keeps tracing/uint256
// in nearby tests and gofmt otherwise complains when you copy patterns around.
var (
_ = tracing.BalanceChangeUnspecified
_ = uint256.NewInt
)

View file

@ -0,0 +1,202 @@
package tests
import (
"bytes"
"crypto/ecdsa"
"errors"
"math/big"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb"
"github.com/holiman/uint256"
)
func runAndCheckChain(t *testing.T, genesis *core.Genesis, engine *ethash.Ethash, blocks []*types.Block, check func(*state.StateDB)) {
options := &core.BlockChainConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 0,
Preimages: true,
ArchiveMode: true,
}
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, options)
if err != nil {
t.Fatalf("create chain: %v", err)
}
defer chain.Stop()
if n, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("block %d failed: %s", n, formatInsertChainErrorForDebug(err))
}
statedb := mustStateAtCurrentHead(t, chain)
check(statedb)
}
// formatInsertChainErrorForDebug prints the full errors.Unwrap chain (outer → inner)
// and adds a file hint for known core errors so test failures are easier to trace.
func formatInsertChainErrorForDebug(err error) string {
var b strings.Builder
b.WriteString(err.Error())
for u := errors.Unwrap(err); u != nil; u = errors.Unwrap(u) {
b.WriteString("\n ← unwrap: ")
b.WriteString(u.Error())
}
switch {
case errors.Is(err, core.ErrNonceTooHigh):
b.WriteString("\n [origin] core/state_transition.go — (*stateTransition).preCheck when !msg.SkipNonceChecks (tx nonce > state nonce)")
case errors.Is(err, core.ErrNonceTooLow):
b.WriteString("\n [origin] core/state_transition.go — (*stateTransition).preCheck when !msg.SkipNonceChecks")
case errors.Is(err, core.ErrInsufficientFunds):
b.WriteString("\n [origin] core/state_transition.go — (*stateTransition).buyGas or balance checks in preCheck")
}
return b.String()
}
func newTestAccount(t *testing.T) (*ecdsa.PrivateKey, common.Address) {
t.Helper()
key, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("generate key: %v", err)
}
return key, crypto.PubkeyToAddress(key.PublicKey)
}
func newTestAccounts(t *testing.T, n int) ([]*ecdsa.PrivateKey, []common.Address) {
t.Helper()
keys := make([]*ecdsa.PrivateKey, n)
addrs := make([]common.Address, n)
for i := 0; i < n; i++ {
keys[i], addrs[i] = newTestAccount(t)
}
return keys, addrs
}
func genesisAllocEther(addrs ...common.Address) types.GenesisAlloc {
bal := new(big.Int).Mul(big.NewInt(1_000_000), big.NewInt(params.Ether))
alloc := make(types.GenesisAlloc, len(addrs))
for _, a := range addrs {
alloc[a] = types.Account{Balance: bal}
}
return alloc
}
func mustReadContractABI(t *testing.T) abi.ABI {
t.Helper()
abiPath := filepath.Join("contracts", "ParallelVMTest.abi")
abiBytes, err := os.ReadFile(abiPath)
if err != nil {
t.Fatalf("read ABI: %v", err)
}
parsedABI, err := abi.JSON(bytes.NewReader(abiBytes))
if err != nil {
t.Fatalf("parse ABI: %v", err)
}
return parsedABI
}
func mustReadContractBinBlockTest(t *testing.T) []byte {
t.Helper()
binPath := filepath.Join("contracts", "ParallelVMTest.bin")
binBytes, err := os.ReadFile(binPath)
if err != nil {
t.Fatalf("read bytecode: %v", err)
}
hexStr := strings.TrimSpace(string(binBytes))
if strings.HasPrefix(hexStr, "0x") {
hexStr = hexStr[2:]
}
return common.FromHex("0x" + hexStr)
}
func mustSignAccessListTx(t *testing.T, cfg *params.ChainConfig, key *ecdsa.PrivateKey, txdata *types.AccessListTx) *types.Transaction {
t.Helper()
signer := types.LatestSigner(cfg)
tx := types.NewTx(txdata)
signed, err := types.SignTx(tx, signer, key)
if err != nil {
t.Fatalf("sign tx: %v", err)
}
return signed
}
func isolatedAccessList(contract common.Address, lane uint64) types.AccessList {
return types.AccessList{
{
Address: contract,
StorageKeys: []common.Hash{
mappingSlotBlock(lane, 0), // laneValue[lane]
mappingSlotBlock(lane, 1), // laneDigest[lane]
},
},
}
}
func mappingSlotBlock(key uint64, baseSlot uint64) common.Hash {
return crypto.Keccak256Hash(
common.LeftPadBytes(new(big.Int).SetUint64(key).Bytes(), 32),
common.LeftPadBytes(new(big.Int).SetUint64(baseSlot).Bytes(), 32),
)
}
func assertStorageUintBlock(t *testing.T, statedb *state.StateDB, addr common.Address, slot common.Hash, want uint64) {
t.Helper()
got := statedb.GetState(addr, slot).Big().Uint64()
if got != want {
t.Fatalf("slot %s got %d want %d", slot.Hex(), got, want)
}
}
func mustStateAtCurrentHead(t *testing.T, chain *core.BlockChain) *state.StateDB {
t.Helper()
root := chain.CurrentBlock().Root
// Most branches expose one of these two shapes:
// 1) chain.StateAt(root)
// 2) rebuild using state.New(root, state.NewDatabase(...))
//
// Try the direct API first if your branch has it.
statedb, err := chain.StateAt(root)
if err == nil {
return statedb
}
// Fallback path for branches without StateAt.
db := rawdb.NewMemoryDatabase()
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
sdb := state.NewDatabase(tdb, nil)
st, err2 := state.New(root, sdb)
if err2 != nil {
t.Fatalf("state lookup failed: direct=%v fallback=%v", err, err2)
}
return st
}
// This exists only to keep imports aligned if your branch keeps tracing/uint256
// in nearby tests and gofmt otherwise complains when you copy patterns around.
var (
_ = tracing.BalanceChangeUnspecified
_ = uint256.NewInt
)