From 99c60ebb9cc35de8a734a01185da149becda8ed9 Mon Sep 17 00:00:00 2001 From: sina haseli Date: Fri, 13 Dec 2024 22:23:16 +0330 Subject: [PATCH 1/3] Add tests for debug tracing methods --- eth/tracers/api_test.go | 420 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 420 insertions(+) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index e786853edec9..98687131ff2f 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -23,8 +23,10 @@ import ( "errors" "fmt" "math/big" + "os" "reflect" "slices" + "strings" "sync/atomic" "testing" "time" @@ -45,6 +47,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" ) @@ -547,6 +550,111 @@ func TestTraceTransaction(t *testing.T) { } } +func TestTraceBlockFromFile(t *testing.T) { + t.Parallel() + + // Set up test accounts and a simple genesis block + accounts := newAccounts(2) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + genBlock := 3 + signer := types.HomesteadSigner{} + backend := newTestBackend(nil, genBlock, genesis, func(i int, b *core.BlockGen) { + // Add a simple transaction to the block + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil, + }), signer, accounts[0].key) + b.AddTx(tx) + }) + + defer backend.teardown() + + api := NewAPI(backend) + + var testCases = []struct { + name string + file string + setupFile func(file string) // Optional setup for the file + expectErr bool + expected common.Hash // Expected trace results + expectMsg string // For error assertions + }{ + { + name: "Valid trace from file", + file: "valid_block.rlp", + setupFile: func(file string) { + createTestFile(file, validBlockData(backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64()))) + }, + expected: backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64()).Transactions()[0].Hash(), + }, + { + name: "File not found", + file: "non_existent_file.rlp", + expectErr: true, + expectMsg: "could not read file", + }, + { + name: "Invalid block data", + file: "invalid_block.rlp", + setupFile: func(file string) { + createTestFile(file, []byte("invalid data")) + }, + expectErr: true, + expectMsg: "could not decode block", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if tc.setupFile != nil { + defer os.Remove(tc.file) // Clean up + tc.setupFile(tc.file) + } + + ctx := context.Background() + result, err := api.TraceBlockFromFile(ctx, tc.file, nil) + fmt.Println(&result) + + if tc.expectErr { + if err == nil || !strings.Contains(err.Error(), tc.expectMsg) { + t.Fatalf("expected error containing '%s', got '%v'", tc.expectMsg, err) + } + return + } + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !reflect.DeepEqual(result[0].TxHash, tc.expected) { + t.Errorf("result mismatch, want %v, got %v", tc.expected, result) + } + }) + } +} + +func createTestFile(name string, data []byte) { + _ = os.WriteFile(name, data, 0644) +} + +func validBlockData(block *types.Block) []byte { + // Fetch the first block and RLP encode it + encodedBlock, err := rlp.EncodeToBytes(block) + if err != nil { + panic(fmt.Errorf("failed to encode block: %v", err)) + } + return encodedBlock +} + func TestTraceBlock(t *testing.T) { t.Parallel() @@ -637,6 +745,318 @@ func TestTraceBlock(t *testing.T) { } } +func TestTraceBadBlock(t *testing.T) { + t.Parallel() + + // Setup a test backend with valid transactions + accounts := newAccounts(2) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + signer := types.HomesteadSigner{} + var badBlockHash, genesisHash common.Hash + + backend := newTestBackend(t, 2, genesis, func(i int, b *core.BlockGen) { + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil, + }), signer, accounts[0].key) + b.AddTx(tx) + }) + defer backend.teardown() + + // Add the genesis block to the bad block database + genesisBlock := backend.chain.GetBlockByNumber(0) + if genesisBlock == nil { + t.Fatalf("failed to fetch genesis block") + } + genesisHash = genesisBlock.Hash() + rawdb.WriteBadBlock(backend.chaindb, genesisBlock) + + // Create a bad block by corrupting the first block after genesis + badBlock := backend.chain.GetBlockByNumber(1) + if badBlock == nil { + t.Fatalf("failed to fetch block 1") + } + badBlock.Header().ParentHash = common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") // Simulate corruption + badBlockHash = badBlock.Hash() + rawdb.WriteBadBlock(backend.chaindb, badBlock) + + // Test cases + api := NewAPI(backend) + var testCases = []struct { + name string + hash common.Hash + expectErr bool + expectMsg string + }{ + { + name: "Genesis block", + hash: genesisHash, + expectErr: true, + expectMsg: "genesis is not traceable", + }, + { + name: "Valid bad block", + hash: badBlockHash, + expectErr: false, + }, + { + name: "Non-existent block", + hash: common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + expectErr: true, + expectMsg: fmt.Sprintf("block %s not found", common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")), + }, + } + + // Execute test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + result, err := api.TraceBadBlock(ctx, tc.hash, nil) + + if tc.expectErr { + if err == nil || !strings.Contains(err.Error(), tc.expectMsg) { + t.Fatalf("expected error containing '%s', got: %v", tc.expectMsg, err) + } + return + } + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if tc.name == "Valid bad block" && len(result) == 0 { + t.Fatalf("expected trace results for bad block, got none") + } + }) + } +} + +func TestStandardTraceBadBlockToFile(t *testing.T) { + t.Parallel() + + // Setup a test backend with valid transactions + accounts := newAccounts(2) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + signer := types.HomesteadSigner{} + var badBlockHash common.Hash + + backend := newTestBackend(t, 2, genesis, func(i int, b *core.BlockGen) { + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil, + }), signer, accounts[0].key) + b.AddTx(tx) + }) + defer backend.teardown() + + // Create a bad block by corrupting the first block after genesis + badBlock := backend.chain.GetBlockByNumber(1) + if badBlock == nil { + t.Fatalf("failed to fetch block 1") + } + badBlock.Header().ParentHash = common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") // Simulate corruption + badBlockHash = badBlock.Hash() + rawdb.WriteBadBlock(backend.chaindb, badBlock) + + // Test cases + api := NewAPI(backend) + var testCases = []struct { + name string + hash common.Hash + config *StdTraceConfig + expectErr bool + expectMsg string + validateFn func(files []string) + }{ + { + name: "Valid bad block", + hash: badBlockHash, + config: nil, // Use default StdTraceConfig + expectErr: false, + validateFn: func(files []string) { + if len(files) == 0 { + t.Fatalf("expected at least one output file, got none") + } + for _, file := range files { + if _, err := os.Stat(file); os.IsNotExist(err) { + t.Fatalf("expected output file %s to exist", file) + } + // Clean up the file after test + os.Remove(file) + } + }, + }, + { + name: "Non-existent block", + hash: common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + config: nil, + expectErr: true, + expectMsg: fmt.Sprintf("block %s not found", common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")), + }, + } + + // Execute test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + files, err := api.StandardTraceBadBlockToFile(ctx, tc.hash, tc.config) + + if tc.expectErr { + if err == nil || !strings.Contains(err.Error(), tc.expectMsg) { + t.Fatalf("expected error containing '%s', got: %v", tc.expectMsg, err) + } + return + } + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Validate the output files + if tc.validateFn != nil { + tc.validateFn(files) + } + }) + } +} + +func TestIntermediateRoots(t *testing.T) { + t.Parallel() + + // Setup a test backend with valid transactions + accounts := newAccounts(2) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + signer := types.HomesteadSigner{} + var badBlockHash, genesisHash common.Hash + + backend := newTestBackend(t, 2, genesis, func(i int, b *core.BlockGen) { + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil, + }), signer, accounts[0].key) + b.AddTx(tx) + }) + defer backend.teardown() + + // Fetch a valid block and store its hash + validBlock := backend.chain.GetBlockByNumber(1) + if validBlock == nil { + t.Fatalf("failed to fetch block 1") + } + // validBlockHash = validBlock.Hash() + + // Corrupt the valid block to create a bad block + badBlock := backend.chain.GetBlockByNumber(1) + badBlock.Header().ParentHash = common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + badBlockHash = badBlock.Hash() + rawdb.WriteBadBlock(backend.chaindb, badBlock) + + // Add the genesis block for testing + genesisBlock := backend.chain.GetBlockByNumber(0) + genesisHash = genesisBlock.Hash() + + // Test cases + api := NewAPI(backend) + var testCases = []struct { + name string + hash common.Hash + config *TraceConfig + expectErr bool + expectMsg string + validateFn func(roots []common.Hash) + }{ + // { + // name: "Valid block", + // hash: validBlockHash, + // config: nil, // Default TraceConfig + // expectErr: false, + // validateFn: func(roots []common.Hash) { + // if len(roots) == 0 { + // t.Fatalf("expected non-empty intermediate roots, got none") + // } + // if roots[len(roots)-1] != validBlock.Root() { + // t.Fatalf("final intermediate root does not match block state root") + // } + // }, + // }, + { + name: "Bad block", + hash: badBlockHash, + config: nil, + expectErr: false, + validateFn: func(roots []common.Hash) { + if len(roots) == 0 { + t.Fatalf("expected non-empty intermediate roots, got none") + } + }, + }, + { + name: "Genesis block", + hash: genesisHash, + config: nil, + expectErr: true, + expectMsg: "genesis is not traceable", + }, + { + name: "Non-existent block", + hash: common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + config: nil, + expectErr: true, + expectMsg: fmt.Sprintf("block %s not found", common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")), + }, + } + + // Execute test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + roots, err := api.IntermediateRoots(ctx, tc.hash, tc.config) + + if tc.expectErr { + if err == nil || !strings.Contains(err.Error(), tc.expectMsg) { + t.Fatalf("expected error containing '%s', got: %v", tc.expectMsg, err) + } + return + } + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if tc.validateFn != nil { + tc.validateFn(roots) + } + }) + } +} + func TestTracingWithOverrides(t *testing.T) { t.Parallel() // Initialize test accounts From dad7d109a6f34ee475b496b45c2e907f1a60914a Mon Sep 17 00:00:00 2001 From: sina haseli Date: Sat, 14 Dec 2024 00:19:46 +0330 Subject: [PATCH 2/3] formats the code by go fmt --- eth/tracers/api_test.go | 232 ++++++++++++++++++++-------------------- 1 file changed, 116 insertions(+), 116 deletions(-) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 98687131ff2f..ded5d03a2e31 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -939,122 +939,122 @@ func TestStandardTraceBadBlockToFile(t *testing.T) { } func TestIntermediateRoots(t *testing.T) { - t.Parallel() - - // Setup a test backend with valid transactions - accounts := newAccounts(2) - genesis := &core.Genesis{ - Config: params.TestChainConfig, - Alloc: types.GenesisAlloc{ - accounts[0].addr: {Balance: big.NewInt(params.Ether)}, - }, - } - signer := types.HomesteadSigner{} - var badBlockHash, genesisHash common.Hash - - backend := newTestBackend(t, 2, genesis, func(i int, b *core.BlockGen) { - tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ - Nonce: uint64(i), - To: &accounts[1].addr, - Value: big.NewInt(1000), - Gas: params.TxGas, - GasPrice: b.BaseFee(), - Data: nil, - }), signer, accounts[0].key) - b.AddTx(tx) - }) - defer backend.teardown() - - // Fetch a valid block and store its hash - validBlock := backend.chain.GetBlockByNumber(1) - if validBlock == nil { - t.Fatalf("failed to fetch block 1") - } - // validBlockHash = validBlock.Hash() - - // Corrupt the valid block to create a bad block - badBlock := backend.chain.GetBlockByNumber(1) - badBlock.Header().ParentHash = common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - badBlockHash = badBlock.Hash() - rawdb.WriteBadBlock(backend.chaindb, badBlock) - - // Add the genesis block for testing - genesisBlock := backend.chain.GetBlockByNumber(0) - genesisHash = genesisBlock.Hash() - - // Test cases - api := NewAPI(backend) - var testCases = []struct { - name string - hash common.Hash - config *TraceConfig - expectErr bool - expectMsg string - validateFn func(roots []common.Hash) - }{ - // { - // name: "Valid block", - // hash: validBlockHash, - // config: nil, // Default TraceConfig - // expectErr: false, - // validateFn: func(roots []common.Hash) { - // if len(roots) == 0 { - // t.Fatalf("expected non-empty intermediate roots, got none") - // } - // if roots[len(roots)-1] != validBlock.Root() { - // t.Fatalf("final intermediate root does not match block state root") - // } - // }, - // }, - { - name: "Bad block", - hash: badBlockHash, - config: nil, - expectErr: false, - validateFn: func(roots []common.Hash) { - if len(roots) == 0 { - t.Fatalf("expected non-empty intermediate roots, got none") - } - }, - }, - { - name: "Genesis block", - hash: genesisHash, - config: nil, - expectErr: true, - expectMsg: "genesis is not traceable", - }, - { - name: "Non-existent block", - hash: common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), - config: nil, - expectErr: true, - expectMsg: fmt.Sprintf("block %s not found", common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")), - }, - } - - // Execute test cases - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - roots, err := api.IntermediateRoots(ctx, tc.hash, tc.config) - - if tc.expectErr { - if err == nil || !strings.Contains(err.Error(), tc.expectMsg) { - t.Fatalf("expected error containing '%s', got: %v", tc.expectMsg, err) - } - return - } - - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - if tc.validateFn != nil { - tc.validateFn(roots) - } - }) - } + t.Parallel() + + // Setup a test backend with valid transactions + accounts := newAccounts(2) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + signer := types.HomesteadSigner{} + var badBlockHash, genesisHash common.Hash + + backend := newTestBackend(t, 2, genesis, func(i int, b *core.BlockGen) { + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil, + }), signer, accounts[0].key) + b.AddTx(tx) + }) + defer backend.teardown() + + // Fetch a valid block and store its hash + validBlock := backend.chain.GetBlockByNumber(1) + if validBlock == nil { + t.Fatalf("failed to fetch block 1") + } + // validBlockHash = validBlock.Hash() + + // Corrupt the valid block to create a bad block + badBlock := backend.chain.GetBlockByNumber(1) + badBlock.Header().ParentHash = common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + badBlockHash = badBlock.Hash() + rawdb.WriteBadBlock(backend.chaindb, badBlock) + + // Add the genesis block for testing + genesisBlock := backend.chain.GetBlockByNumber(0) + genesisHash = genesisBlock.Hash() + + // Test cases + api := NewAPI(backend) + var testCases = []struct { + name string + hash common.Hash + config *TraceConfig + expectErr bool + expectMsg string + validateFn func(roots []common.Hash) + }{ + // { + // name: "Valid block", + // hash: validBlockHash, + // config: nil, // Default TraceConfig + // expectErr: false, + // validateFn: func(roots []common.Hash) { + // if len(roots) == 0 { + // t.Fatalf("expected non-empty intermediate roots, got none") + // } + // if roots[len(roots)-1] != validBlock.Root() { + // t.Fatalf("final intermediate root does not match block state root") + // } + // }, + // }, + { + name: "Bad block", + hash: badBlockHash, + config: nil, + expectErr: false, + validateFn: func(roots []common.Hash) { + if len(roots) == 0 { + t.Fatalf("expected non-empty intermediate roots, got none") + } + }, + }, + { + name: "Genesis block", + hash: genesisHash, + config: nil, + expectErr: true, + expectMsg: "genesis is not traceable", + }, + { + name: "Non-existent block", + hash: common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + config: nil, + expectErr: true, + expectMsg: fmt.Sprintf("block %s not found", common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")), + }, + } + + // Execute test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + roots, err := api.IntermediateRoots(ctx, tc.hash, tc.config) + + if tc.expectErr { + if err == nil || !strings.Contains(err.Error(), tc.expectMsg) { + t.Fatalf("expected error containing '%s', got: %v", tc.expectMsg, err) + } + return + } + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if tc.validateFn != nil { + tc.validateFn(roots) + } + }) + } } func TestTracingWithOverrides(t *testing.T) { From 6a0f25a1f23a119455fbedfdcc088a62599852d4 Mon Sep 17 00:00:00 2001 From: sina haseli Date: Tue, 17 Dec 2024 00:04:47 +0330 Subject: [PATCH 3/3] handle all threades that created by S1na and make code more readable and clear --- eth/tracers/api_test.go | 333 ++++++++++++++++++++++++++++------------ 1 file changed, 236 insertions(+), 97 deletions(-) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index ded5d03a2e31..29d524fc5ea3 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -24,6 +24,7 @@ import ( "fmt" "math/big" "os" + "path/filepath" "reflect" "slices" "strings" @@ -564,16 +565,36 @@ func TestTraceBlockFromFile(t *testing.T) { genBlock := 3 signer := types.HomesteadSigner{} backend := newTestBackend(nil, genBlock, genesis, func(i int, b *core.BlockGen) { - // Add a simple transaction to the block - tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ - Nonce: uint64(i), - To: &accounts[1].addr, - Value: big.NewInt(1000), - Gas: params.TxGas, - GasPrice: b.BaseFee(), - Data: nil, - }), signer, accounts[0].key) - b.AddTx(tx) + // Deploy a simple smart contract + if i == 0 { + // Simple contract with a storage setter + contractCode := []byte{ + 0x60, 0x0a, 0x60, 0x00, 0x55, // PUSH1 10 PUSH1 0 SSTORE + 0x60, 0x00, 0x60, 0x20, 0x52, // PUSH1 0 PUSH1 32 MSTORE + 0x60, 0x20, 0x60, 0x00, 0xf3, // RETURN + } + + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + Value: big.NewInt(0), + Gas: 53204, + GasPrice: b.BaseFee(), + Data: contractCode, + }), signer, accounts[0].key) + b.AddTx(tx) + } else { + // Interact with the contract (store a value) + contractAddr := crypto.CreateAddress(accounts[0].addr, 0) // First tx contract address + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &contractAddr, + Value: big.NewInt(0), + Gas: 53204, + GasPrice: b.BaseFee(), + Data: []byte{0x60, 0x05, 0x60, 0x00, 0x55}, // PUSH1 5 PUSH1 0 SSTORE + }), signer, accounts[0].key) + b.AddTx(tx) + } }) defer backend.teardown() @@ -583,18 +604,18 @@ func TestTraceBlockFromFile(t *testing.T) { var testCases = []struct { name string file string - setupFile func(file string) // Optional setup for the file + setupFile func(file string) string // Optional setup for the file expectErr bool - expected common.Hash // Expected trace results - expectMsg string // For error assertions + expected string // Expected trace results + expectMsg string // For error assertions }{ { name: "Valid trace from file", file: "valid_block.rlp", - setupFile: func(file string) { - createTestFile(file, validBlockData(backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64()))) + setupFile: func(file string) string { + return createTestFile(t, file, validBlockData(t, backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64()))) }, - expected: backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64()).Transactions()[0].Hash(), + expected: fmt.Sprintf(`{"txHash":"%s","result":{"gas":21068,"failed":false,"returnValue":"","structLogs":[]}}`, backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64()).Transactions()[0].Hash()), }, { name: "File not found", @@ -605,8 +626,8 @@ func TestTraceBlockFromFile(t *testing.T) { { name: "Invalid block data", file: "invalid_block.rlp", - setupFile: func(file string) { - createTestFile(file, []byte("invalid data")) + setupFile: func(file string) string { + return createTestFile(t, file, []byte("invalid data")) }, expectErr: true, expectMsg: "could not decode block", @@ -615,14 +636,14 @@ func TestTraceBlockFromFile(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + var filePath string if tc.setupFile != nil { - defer os.Remove(tc.file) // Clean up - tc.setupFile(tc.file) + defer os.Remove(filePath) // Clean up + filePath = tc.setupFile(tc.file) } ctx := context.Background() - result, err := api.TraceBlockFromFile(ctx, tc.file, nil) - fmt.Println(&result) + result, err := api.TraceBlockFromFile(ctx, filePath, nil) if tc.expectErr { if err == nil || !strings.Contains(err.Error(), tc.expectMsg) { @@ -635,22 +656,35 @@ func TestTraceBlockFromFile(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - if !reflect.DeepEqual(result[0].TxHash, tc.expected) { - t.Errorf("result mismatch, want %v, got %v", tc.expected, result) + have, _ := json.Marshal(result[0]) + want := tc.expected + if string(have) != want { + t.Errorf("result mismatch, have\n%v\n, want\n%v\n", string(have), want) } }) } } -func createTestFile(name string, data []byte) { - _ = os.WriteFile(name, data, 0644) +func createTestFile(t *testing.T, name string, data []byte) string { + tempDir := t.TempDir() + + filePath := filepath.Join(tempDir, name) + + if err := os.WriteFile(filePath, data, 0644); err != nil { + t.Fatalf("failed to create test file '%s': %v", filePath, err) + } + + return filePath } -func validBlockData(block *types.Block) []byte { +func validBlockData(t *testing.T, block *types.Block) []byte { + if block == nil { + t.Fatalf("block is nil") + } // Fetch the first block and RLP encode it encodedBlock, err := rlp.EncodeToBytes(block) if err != nil { - panic(fmt.Errorf("failed to encode block: %v", err)) + t.Fatalf("failed to encode block: %v", err) } return encodedBlock } @@ -943,106 +977,64 @@ func TestIntermediateRoots(t *testing.T) { // Setup a test backend with valid transactions accounts := newAccounts(2) - genesis := &core.Genesis{ - Config: params.TestChainConfig, - Alloc: types.GenesisAlloc{ - accounts[0].addr: {Balance: big.NewInt(params.Ether)}, - }, - } - signer := types.HomesteadSigner{} - var badBlockHash, genesisHash common.Hash - - backend := newTestBackend(t, 2, genesis, func(i int, b *core.BlockGen) { - tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ - Nonce: uint64(i), - To: &accounts[1].addr, - Value: big.NewInt(1000), - Gas: params.TxGas, - GasPrice: b.BaseFee(), - Data: nil, - }), signer, accounts[0].key) - b.AddTx(tx) - }) + genesis := setupGenesis(accounts) + backend := setupTestBackend(t, genesis, accounts) defer backend.teardown() - // Fetch a valid block and store its hash - validBlock := backend.chain.GetBlockByNumber(1) - if validBlock == nil { - t.Fatalf("failed to fetch block 1") - } - // validBlockHash = validBlock.Hash() - - // Corrupt the valid block to create a bad block - badBlock := backend.chain.GetBlockByNumber(1) - badBlock.Header().ParentHash = common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - badBlockHash = badBlock.Hash() - rawdb.WriteBadBlock(backend.chaindb, badBlock) + // Precompute important block hashes + validBlock := fetchBlockByNumber(t, backend.chain, 1) + headBlock := fetchBlockByNumber(t, backend.chain, 5) + genesisBlock := fetchBlockByNumber(t, backend.chain, 0) - // Add the genesis block for testing - genesisBlock := backend.chain.GetBlockByNumber(0) - genesisHash = genesisBlock.Hash() + validBlockHash := validBlock.Hash() + genesisHash := genesisBlock.Hash() + badBlockHash := corruptBlock(backend, headBlock) - // Test cases - api := NewAPI(backend) - var testCases = []struct { + // Define test cases + testCases := []struct { name string hash common.Hash config *TraceConfig expectErr bool expectMsg string - validateFn func(roots []common.Hash) + validateFn func(t *testing.T, roots []common.Hash) }{ - // { - // name: "Valid block", - // hash: validBlockHash, - // config: nil, // Default TraceConfig - // expectErr: false, - // validateFn: func(roots []common.Hash) { - // if len(roots) == 0 { - // t.Fatalf("expected non-empty intermediate roots, got none") - // } - // if roots[len(roots)-1] != validBlock.Root() { - // t.Fatalf("final intermediate root does not match block state root") - // } - // }, - // }, { - name: "Bad block", - hash: badBlockHash, - config: nil, - expectErr: false, - validateFn: func(roots []common.Hash) { - if len(roots) == 0 { - t.Fatalf("expected non-empty intermediate roots, got none") - } + name: "Valid block", + hash: validBlockHash, + validateFn: func(t *testing.T, roots []common.Hash) { + validateIntermediateRoots(t, backend, validBlock, roots) + }, + }, + { + name: "Bad block", + hash: badBlockHash, + validateFn: func(t *testing.T, roots []common.Hash) { + validateIntermediateRoots(t, backend, headBlock, roots) }, }, { name: "Genesis block", hash: genesisHash, - config: nil, expectErr: true, expectMsg: "genesis is not traceable", }, { name: "Non-existent block", hash: common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), - config: nil, expectErr: true, expectMsg: fmt.Sprintf("block %s not found", common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")), }, } - // Execute test cases + // Run test cases for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { ctx := context.Background() - roots, err := api.IntermediateRoots(ctx, tc.hash, tc.config) + roots, err := NewAPI(backend).IntermediateRoots(ctx, tc.hash, tc.config) if tc.expectErr { - if err == nil || !strings.Contains(err.Error(), tc.expectMsg) { - t.Fatalf("expected error containing '%s', got: %v", tc.expectMsg, err) - } + assertErrorContains(t, err, tc.expectMsg) return } @@ -1051,12 +1043,159 @@ func TestIntermediateRoots(t *testing.T) { } if tc.validateFn != nil { - tc.validateFn(roots) + tc.validateFn(t, roots) } }) } } +// Helpers +func setupGenesis(accounts []Account) *core.Genesis { + return &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + }, + } +} + +func setupTestBackend(t *testing.T, genesis *core.Genesis, accounts []Account) *testBackend { + return newTestBackend(t, 5, genesis, func(i int, b *core.BlockGen) { + populateTransactions(t, i, b, accounts) + }) +} + +func populateTransactions(t *testing.T, i int, b *core.BlockGen, accounts []Account) { + signer := types.HomesteadSigner{} + + switch i { + case 0: + deployContract(b, signer, accounts[0]) + case 1: + interactWithContract(b, signer, accounts[0]) + default: + transferFunds(i, b, signer, accounts[0], accounts[1]) + } +} + +func deployContract(b *core.BlockGen, signer types.Signer, account Account) { + contractCode := []byte{ + 0x60, 0x0a, 0x60, 0x00, 0x55, + 0x60, 0x00, 0x60, 0x20, 0x52, + 0x60, 0x20, 0x60, 0x00, 0xf3, + } + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 0, + Value: big.NewInt(0), + Gas: 3000000, + GasPrice: b.BaseFee(), + Data: contractCode, + }), signer, account.key) + + b.AddTx(tx) +} + +func interactWithContract(b *core.BlockGen, signer types.Signer, account Account) { + contractAddress := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + setMethodSig := []byte{0x60, 0x0b} + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 1, + To: &contractAddress, + Value: big.NewInt(0), + Gas: 100000, + GasPrice: b.BaseFee(), + Data: setMethodSig, + }), signer, account.key) + + b.AddTx(tx) +} + +func transferFunds(i int, b *core.BlockGen, signer types.Signer, from, to Account) { + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &to.addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil, + }), signer, from.key) + b.AddTx(tx) +} + +func fetchBlockByNumber(t *testing.T, chain *core.BlockChain, number uint64) *types.Block { + block := chain.GetBlockByNumber(number) + if block == nil { + t.Fatalf("failed to fetch block %d", number) + } + return block +} + +func corruptBlock(backend *testBackend, headBlock *types.Block) common.Hash { + badBlock := *headBlock + badBlock.Header().ParentHash = common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + badBlockHash := badBlock.Hash() + rawdb.WriteBadBlock(backend.chaindb, &badBlock) + return badBlockHash +} + +func validateIntermediateRoots(t *testing.T, backend *testBackend, block *types.Block, roots []common.Hash) { + // Fetch the parent block + parentBlock := backend.chain.GetBlockByNumber(block.NumberU64() - 1) + if parentBlock == nil { + t.Fatalf("failed to fetch parent block") + } + + // Initialize state for the parent block + statedb, _ := backend.chain.StateAt(parentBlock.Root()) + vmctx := core.NewEVMBlockContext(block.Header(), backend.chain, nil) + evm := vm.NewEVM(vmctx, statedb, backend.chainConfig, vm.Config{}) + deleteEmptyObjects := backend.chainConfig.IsEIP158(block.Number()) + + // Process beacon and parent hash if applicable + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + core.ProcessBeaconBlockRoot(*beaconRoot, evm) + } + if backend.chainConfig.IsPrague(block.Number(), block.Time()) { + core.ProcessParentBlockHash(block.ParentHash(), evm) + } + + // Compute expected intermediate roots + expectedRoots := make([]common.Hash, 0) + signer := types.MakeSigner(backend.chainConfig, block.Number(), block.Time()) + + for i, tx := range block.Transactions() { + // Convert the transaction to a message + msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) + statedb.SetTxContext(tx.Hash(), i) + + // Apply the transaction + if _, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil { + t.Logf("Transaction failed at index %d: %v", i, err) + // Continue with other transactions even if one fails + } + + // Append the intermediate root after applying the transaction + expectedRoots = append(expectedRoots, statedb.IntermediateRoot(deleteEmptyObjects)) + } + + // Validate the roots returned by the API + if len(roots) != len(expectedRoots) { + t.Fatalf("unexpected number of intermediate roots: got %d, want %d", len(roots), len(expectedRoots)) + } + + for i, root := range roots { + if root != expectedRoots[i] { + t.Fatalf("unexpected intermediate root at index %d: got %s, want %s", i, root.Hex(), expectedRoots[i].Hex()) + } + } +} + +func assertErrorContains(t *testing.T, err error, msg string) { + if err == nil || !strings.Contains(err.Error(), msg) { + t.Fatalf("expected error containing '%s', got: %v", msg, err) + } +} + func TestTracingWithOverrides(t *testing.T) { t.Parallel() // Initialize test accounts