mirror of
https://github.com/luxfi/evmgpu.git
synced 2026-10-30 16:57:11 +00:00
357 lines
9.2 KiB
Go
357 lines
9.2 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package dag
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"math/rand"
|
|
"testing"
|
|
|
|
"github.com/luxfi/evmgpu/core/state"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/geth/core/vm"
|
|
ethparams "github.com/luxfi/geth/params"
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
// mockApplyFn produces deterministic receipts without requiring real state.
|
|
func mockApplyFn(gasUsed uint64) func(
|
|
*ethparams.ChainConfig,
|
|
*types.Header,
|
|
*types.Transaction,
|
|
*state.StateDB,
|
|
vm.Config,
|
|
int,
|
|
) (*types.Receipt, error) {
|
|
return func(
|
|
_ *ethparams.ChainConfig,
|
|
_ *types.Header,
|
|
tx *types.Transaction,
|
|
_ *state.StateDB,
|
|
_ vm.Config,
|
|
_ int,
|
|
) (*types.Receipt, error) {
|
|
return &types.Receipt{
|
|
Type: tx.Type(),
|
|
Status: types.ReceiptStatusSuccessful,
|
|
TxHash: tx.Hash(),
|
|
GasUsed: gasUsed,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func testHeader() *types.Header {
|
|
return &types.Header{
|
|
Number: big.NewInt(100),
|
|
GasLimit: 30_000_000,
|
|
Time: 1700000000,
|
|
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
|
|
BaseFee: big.NewInt(1_000_000_000),
|
|
}
|
|
}
|
|
|
|
// --- DAGExecutor tests ---
|
|
|
|
func TestDAGExecutorExecuteBlockEmpty(t *testing.T) {
|
|
exec := NewDAGExecutor(DAGExecutorConfig{
|
|
ApplyFn: mockApplyFn(21000),
|
|
Workers: 4,
|
|
})
|
|
|
|
receipts, err := exec.ExecuteBlock(nil, testHeader(), nil, nil, vm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("expected nil error, got %v", err)
|
|
}
|
|
if receipts != nil {
|
|
t.Fatal("expected nil receipts for empty block")
|
|
}
|
|
}
|
|
|
|
func TestDAGExecutorExecuteBlockLinear(t *testing.T) {
|
|
exec := NewDAGExecutor(DAGExecutorConfig{
|
|
ApplyFn: mockApplyFn(21000),
|
|
Workers: 4,
|
|
})
|
|
|
|
txs := types.Transactions{
|
|
testTx(0, common.HexToAddress("0xaaaa")),
|
|
testTx(1, common.HexToAddress("0xbbbb")),
|
|
testTx(2, common.HexToAddress("0xcccc")),
|
|
}
|
|
|
|
receipts, err := exec.ExecuteBlock(nil, testHeader(), txs, nil, vm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("ExecuteBlock failed: %v", err)
|
|
}
|
|
if len(receipts) != 3 {
|
|
t.Fatalf("expected 3 receipts, got %d", len(receipts))
|
|
}
|
|
for i, r := range receipts {
|
|
if r.Status != types.ReceiptStatusSuccessful {
|
|
t.Fatalf("receipt %d: expected success", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDAGExecutorExecuteAntichain(t *testing.T) {
|
|
exec := NewDAGExecutor(DAGExecutorConfig{
|
|
ApplyFn: mockApplyFn(21000),
|
|
Workers: 4,
|
|
})
|
|
|
|
// Create 3 non-conflicting vertices.
|
|
vertices := make([]*EVMVertex, 3)
|
|
for i := 0; i < 3; i++ {
|
|
txs := []*types.Transaction{
|
|
testTx(uint64(i*10), common.HexToAddress(fmt.Sprintf("0x%040x", i+1))),
|
|
testTx(uint64(i*10+1), common.HexToAddress(fmt.Sprintf("0x%040x", i+100))),
|
|
}
|
|
rs := &StorageKeySet{}
|
|
ws := &StorageKeySet{}
|
|
vertices[i] = NewEVMVertex(uint64(i), 0, []ids.ID{ids.Empty}, txs, rs, ws)
|
|
}
|
|
|
|
receipts, err := exec.ExecuteAntichain(nil, testHeader(), vertices, nil, vm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("ExecuteAntichain failed: %v", err)
|
|
}
|
|
if len(receipts) != 6 { // 3 vertices * 2 txs each
|
|
t.Fatalf("expected 6 receipts, got %d", len(receipts))
|
|
}
|
|
}
|
|
|
|
func TestDAGExecutorTopologicalCut(t *testing.T) {
|
|
exec := NewDAGExecutor(DAGExecutorConfig{
|
|
ApplyFn: mockApplyFn(21000),
|
|
Workers: 4,
|
|
})
|
|
|
|
// Create a small DAG:
|
|
// v0 (root)
|
|
// | \
|
|
// v1 v2 (both depend on v0)
|
|
// |
|
|
// v3 (depends on v1)
|
|
|
|
txs := func(n uint64) []*types.Transaction {
|
|
return []*types.Transaction{testTx(n, common.HexToAddress(fmt.Sprintf("0x%040x", n+1)))}
|
|
}
|
|
|
|
v0 := NewEVMVertex(0, 0, nil, txs(0), &StorageKeySet{}, &StorageKeySet{})
|
|
v1 := NewEVMVertex(1, 0, []ids.ID{v0.ID()}, txs(1), &StorageKeySet{}, &StorageKeySet{})
|
|
v2 := NewEVMVertex(1, 0, []ids.ID{v0.ID()}, txs(2), &StorageKeySet{}, &StorageKeySet{})
|
|
v3 := NewEVMVertex(2, 0, []ids.ID{v1.ID()}, txs(3), &StorageKeySet{}, &StorageKeySet{})
|
|
|
|
vertices := []*EVMVertex{v3, v1, v0, v2} // intentionally unordered
|
|
|
|
receipts, err := exec.ExecuteTopologicalCut(nil, testHeader(), vertices, nil, vm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("ExecuteTopologicalCut failed: %v", err)
|
|
}
|
|
if len(receipts) != 4 {
|
|
t.Fatalf("expected 4 receipts, got %d", len(receipts))
|
|
}
|
|
}
|
|
|
|
func TestDAGExecutorBuildVertex(t *testing.T) {
|
|
builder := NewBuilder(BuilderConfig{
|
|
Workers: 2,
|
|
MaxTxsPerVertex: 100,
|
|
FrontierFn: func() []ids.ID { return []ids.ID{{0x01}} },
|
|
HeightFn: func() uint64 { return 1 },
|
|
})
|
|
|
|
exec := NewDAGExecutor(DAGExecutorConfig{
|
|
Builder: builder,
|
|
ApplyFn: mockApplyFn(21000),
|
|
})
|
|
|
|
txs := []*types.Transaction{testTx(0, common.HexToAddress("0xaaaa"))}
|
|
v := exec.BuildVertex(txs)
|
|
if v == nil {
|
|
t.Fatal("BuildVertex returned nil")
|
|
}
|
|
}
|
|
|
|
func TestDAGExecutorMetrics(t *testing.T) {
|
|
exec := NewDAGExecutor(DAGExecutorConfig{
|
|
ApplyFn: mockApplyFn(21000),
|
|
Workers: 2,
|
|
})
|
|
|
|
txs := types.Transactions{
|
|
testTx(0, common.HexToAddress("0xaaaa")),
|
|
testTx(1, common.HexToAddress("0xbbbb")),
|
|
}
|
|
|
|
_, err := exec.ExecuteBlock(nil, testHeader(), txs, nil, vm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("ExecuteBlock failed: %v", err)
|
|
}
|
|
|
|
m := exec.Metrics()
|
|
if m["txs_processed"] != 2 {
|
|
t.Fatalf("expected 2 txs processed, got %d", m["txs_processed"])
|
|
}
|
|
}
|
|
|
|
// --- VertexStore tests ---
|
|
|
|
func TestVertexStore(t *testing.T) {
|
|
store := NewVertexStore()
|
|
|
|
txs := []*types.Transaction{testTx(0, common.HexToAddress("0xaaaa"))}
|
|
v := NewEVMVertex(1, 0, nil, txs, &StorageKeySet{}, &StorageKeySet{})
|
|
|
|
store.Add(v)
|
|
if store.Len() != 1 {
|
|
t.Fatalf("expected 1 vertex, got %d", store.Len())
|
|
}
|
|
|
|
got, ok := store.Get(v.ID())
|
|
if !ok {
|
|
t.Fatal("vertex not found in store")
|
|
}
|
|
if got.ID() != v.ID() {
|
|
t.Fatal("wrong vertex returned")
|
|
}
|
|
|
|
frontier := store.Frontier()
|
|
if len(frontier) != 1 {
|
|
t.Fatalf("expected 1 frontier vertex, got %d", len(frontier))
|
|
}
|
|
}
|
|
|
|
// --- Benchmarks ---
|
|
|
|
// generateRandomTxBatch creates n transactions with approximately conflictRate
|
|
// fraction sharing addresses with other transactions in the batch.
|
|
func generateRandomTxBatch(n int, conflictRate float64, rng *rand.Rand) []*types.Transaction {
|
|
txs := make([]*types.Transaction, n)
|
|
numShared := int(float64(n) * conflictRate)
|
|
if numShared < 1 {
|
|
numShared = 1
|
|
}
|
|
|
|
// Generate shared addresses (these create conflicts).
|
|
sharedAddrs := make([]common.Address, numShared)
|
|
for i := range sharedAddrs {
|
|
var addr common.Address
|
|
rng.Read(addr[:])
|
|
sharedAddrs[i] = addr
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
var to common.Address
|
|
if rng.Float64() < conflictRate && len(sharedAddrs) > 0 {
|
|
to = sharedAddrs[rng.Intn(len(sharedAddrs))]
|
|
} else {
|
|
rng.Read(to[:])
|
|
}
|
|
txs[i] = types.NewTransaction(
|
|
uint64(i),
|
|
to,
|
|
big.NewInt(1000),
|
|
21000,
|
|
big.NewInt(1_000_000_000),
|
|
nil,
|
|
)
|
|
}
|
|
return txs
|
|
}
|
|
|
|
func benchmarkDAGExecute(b *testing.B, numTxs int) {
|
|
rng := rand.New(rand.NewSource(42))
|
|
txs := generateRandomTxBatch(numTxs, 0.10, rng)
|
|
|
|
exec := NewDAGExecutor(DAGExecutorConfig{
|
|
ApplyFn: mockApplyFn(21000),
|
|
Workers: 8,
|
|
})
|
|
|
|
header := testHeader()
|
|
typedTxs := make(types.Transactions, len(txs))
|
|
for i, tx := range txs {
|
|
typedTxs[i] = tx
|
|
}
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
receipts, err := exec.ExecuteBlock(nil, header, typedTxs, nil, vm.Config{})
|
|
if err != nil {
|
|
b.Fatalf("execution failed: %v", err)
|
|
}
|
|
if len(receipts) != numTxs {
|
|
b.Fatalf("expected %d receipts, got %d", numTxs, len(receipts))
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkDAGEVMExecute10K(b *testing.B) { benchmarkDAGExecute(b, 10_000) }
|
|
func BenchmarkDAGEVMExecute50K(b *testing.B) { benchmarkDAGExecute(b, 50_000) }
|
|
func BenchmarkDAGEVMExecute100K(b *testing.B) { benchmarkDAGExecute(b, 100_000) }
|
|
|
|
func benchmarkConflictDetection(b *testing.B, numVertices int) {
|
|
rng := rand.New(rand.NewSource(42))
|
|
|
|
vertices := make([]*EVMVertex, numVertices)
|
|
for i := 0; i < numVertices; i++ {
|
|
rs := &StorageKeySet{}
|
|
ws := &StorageKeySet{}
|
|
// Each vertex touches 5-15 storage keys.
|
|
numKeys := 5 + rng.Intn(11)
|
|
for j := 0; j < numKeys; j++ {
|
|
var key common.Hash
|
|
rng.Read(key[:])
|
|
rs.Add(key)
|
|
if rng.Float64() < 0.5 {
|
|
ws.Add(key)
|
|
}
|
|
}
|
|
txs := []*types.Transaction{testTx(uint64(i), common.HexToAddress(fmt.Sprintf("0x%040x", i+1)))}
|
|
vertices[i] = NewEVMVertex(uint64(i), 0, nil, txs, rs, ws)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
BatchConflicts(vertices)
|
|
}
|
|
}
|
|
|
|
func BenchmarkConflictDetection100(b *testing.B) { benchmarkConflictDetection(b, 100) }
|
|
func BenchmarkConflictDetection500(b *testing.B) { benchmarkConflictDetection(b, 500) }
|
|
func BenchmarkConflictDetection1000(b *testing.B) { benchmarkConflictDetection(b, 1000) }
|
|
|
|
func benchmarkVertexBuild(b *testing.B, numTxs int) {
|
|
rng := rand.New(rand.NewSource(42))
|
|
txs := generateRandomTxBatch(numTxs, 0.10, rng)
|
|
|
|
builder := NewBuilder(BuilderConfig{
|
|
Workers: 8,
|
|
MaxTxsPerVertex: numTxs,
|
|
FrontierFn: func() []ids.ID { return []ids.ID{{0x01}} },
|
|
HeightFn: func() uint64 { return 1 },
|
|
})
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
v := builder.BuildVertex(txs)
|
|
if v == nil {
|
|
b.Fatal("BuildVertex returned nil")
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkVertexBuild10K(b *testing.B) { benchmarkVertexBuild(b, 10_000) }
|
|
func BenchmarkVertexBuild50K(b *testing.B) { benchmarkVertexBuild(b, 50_000) }
|
|
func BenchmarkVertexBuild100K(b *testing.B) { benchmarkVertexBuild(b, 100_000) }
|