diff --git a/eth/tracers/internal/tracetest/firehose/blockchain_test.go b/eth/tracers/internal/tracetest/firehose/blockchain_test.go new file mode 100644 index 0000000000..dbea8a15f2 --- /dev/null +++ b/eth/tracers/internal/tracetest/firehose/blockchain_test.go @@ -0,0 +1,139 @@ +package firehose_test + +import ( + "hash" + "math/big" + "os" + "testing" + + "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/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/tests" + "github.com/ethereum/go-ethereum/trie" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" +) + +func runPrestateBlock(t *testing.T, prestatePath string, hooks *tracing.Hooks) { + t.Helper() + + prestate := readPrestateData(t, prestatePath) + + tx := new(types.Transaction) + require.NoError(t, rlp.DecodeBytes(common.FromHex(prestate.Input), tx)) + + context := prestate.Context.toBlockContext(prestate.Genesis) + + state := tests.MakePreState(rawdb.NewMemoryDatabase(), prestate.Genesis.Alloc, false, rawdb.HashScheme) + defer state.Close() + + state.StateDB.SetLogger(hooks) + state.StateDB.SetTxContext(tx.Hash(), 0) + + block := types.NewBlock(&types.Header{ + ParentHash: prestate.Genesis.ToBlock().Hash(), + Number: context.BlockNumber, + Difficulty: context.Difficulty, + Coinbase: context.Coinbase, + Time: context.Time, + GasLimit: context.GasLimit, + BaseFee: context.BaseFee, + ParentBeaconRoot: ptr(common.Hash{}), + }, []*types.Transaction{tx}, nil, nil, trie.NewStackTrie(nil)) + + hooks.OnBlockchainInit(prestate.Genesis.Config) + hooks.OnBlockStart(tracing.BlockEvent{ + Block: block, + TD: prestate.TotalDifficulty, + }) + + usedGas := uint64(0) + _, err := core.ApplyTransaction( + prestate.Genesis.Config, + prestate, + &context.Coinbase, + new(core.GasPool).AddGas(block.GasLimit()), + state.StateDB, + block.Header(), + tx, + &usedGas, + vm.Config{Tracer: hooks}, + ) + require.NoError(t, err) + + hooks.OnBlockEnd(nil) +} + +func newBlockchain(t *testing.T, alloc types.GenesisAlloc, context vm.BlockContext, tracer *tracing.Hooks) (*core.Genesis, *core.BlockChain) { + t.Helper() + + genesis := &core.Genesis{ + Difficulty: new(big.Int).Sub(context.Difficulty, big.NewInt(1)), + Timestamp: context.Time - 1, + Number: new(big.Int).Sub(context.BlockNumber, big.NewInt(1)).Uint64(), + BaseFee: big.NewInt(params.InitialBaseFee), + Coinbase: context.Coinbase, + Config: params.AllEthashProtocolChanges, + Alloc: alloc, + } + + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, false))) + defer log.SetDefault(log.NewLogger(log.DiscardHandler())) + + blockchain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), core.DefaultCacheConfigWithScheme(rawdb.HashScheme), genesis, nil, ethash.NewFullFaker(), vm.Config{ + Tracer: tracer, + }, nil, nil) + require.NoError(t, err) + + return genesis, blockchain +} + +// testHasher is the helper tool for transaction/receipt list hashing. +// The original hasher is trie, in order to get rid of import cycle, +// use the testing hasher instead. +type testHasher struct { + hasher hash.Hash +} + +// NewHasher returns a new testHasher instance. +func NewHasher() *testHasher { + return &testHasher{hasher: sha3.NewLegacyKeccak256()} +} + +// Reset resets the hash state. +func (h *testHasher) Reset() { + h.hasher.Reset() +} + +// Update updates the hash state with the given key and value. +func (h *testHasher) Update(key, val []byte) error { + h.hasher.Write(key) + h.hasher.Write(val) + return nil +} + +// Hash returns the hash value. +func (h *testHasher) Hash() common.Hash { + return common.BytesToHash(h.hasher.Sum(nil)) +} + +type ignoreValidateStateValidator struct { + core.Validator +} + +func (v ignoreValidateStateValidator) ValidateBody(block *types.Block) error { + return v.Validator.ValidateBody(block) +} + +func (v ignoreValidateStateValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error { + return nil +} diff --git a/eth/tracers/internal/tracetest/firehose/firehose_test.go b/eth/tracers/internal/tracetest/firehose/firehose_test.go new file mode 100644 index 0000000000..d918c08fa4 --- /dev/null +++ b/eth/tracers/internal/tracetest/firehose/firehose_test.go @@ -0,0 +1,98 @@ +package firehose_test + +import ( + "encoding/json" + "math/big" + "path/filepath" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/trie" + "github.com/stretchr/testify/require" +) + +func TestFirehoseChain(t *testing.T) { + context := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: common.Address{}, + BlockNumber: new(big.Int).SetUint64(uint64(1)), + Time: 1, + Difficulty: big.NewInt(2), + GasLimit: uint64(1000000), + BaseFee: big.NewInt(8), + } + + tracer, err := tracers.NewFirehoseFromRawJSON(json.RawMessage(`{ + "applyBackwardsCompatibility": true, + "_private": { + "flushToTestBuffer": true, + "ignoreGenesisBlock": true + } + }`)) + require.NoError(t, err) + + hooks := tracers.NewTracingHooksFromFirehose(tracer) + + genesis, blockchain := newBlockchain(t, types.GenesisAlloc{}, context, hooks) + + block := types.NewBlock(&types.Header{ + ParentHash: genesis.ToBlock().Hash(), + Number: context.BlockNumber, + Difficulty: context.Difficulty, + Coinbase: context.Coinbase, + Time: context.Time, + GasLimit: context.GasLimit, + BaseFee: context.BaseFee, + ParentBeaconRoot: ptr(common.Hash{}), + }, nil, nil, nil, trie.NewStackTrie(nil)) + + blockchain.SetBlockValidatorAndProcessorForTesting( + ignoreValidateStateValidator{core.NewBlockValidator(genesis.Config, blockchain, blockchain.Engine())}, + core.NewStateProcessor(genesis.Config, blockchain, blockchain.Engine()), + ) + + n, err := blockchain.InsertChain(types.Blocks{block}) + require.NoError(t, err) + require.Equal(t, 1, n) + + genesisLine, blockLines, unknownLines := readTracerFirehoseLines(t, tracer) + require.Len(t, unknownLines, 0, "Lines:\n%s", strings.Join(slicesMap(unknownLines, func(l unknwonLine) string { return "- '" + string(l) + "'" }), "\n")) + require.NotNil(t, genesisLine) + blockLines.assertEquals(t, filepath.Join("testdata", t.Name()), + firehoseBlockLineParams{"1", "8e6ee4b1054d94df1d8a51fb983447dc2e27a854590c3ac0061f994284be8150", "0", "845bad515694a416bab4b8d44e22cf97a8c894a8502110ab807883940e185ce0", "0", "1000000000"}, + ) +} + +func TestFirehosePrestate(t *testing.T) { + testFolders := []string{ + "./testdata/TestFirehosePrestate/keccak256_too_few_memory_bytes_get_padded", + } + + for _, folder := range testFolders { + name := filepath.Base(folder) + + t.Run(name, func(t *testing.T) { + tracer, err := tracers.NewFirehoseFromRawJSON(json.RawMessage(`{ + "applyBackwardsCompatibility": true, + "_private": { + "flushToTestBuffer": true + } + }`)) + require.NoError(t, err) + + runPrestateBlock(t, filepath.Join(folder, "prestate.json"), tracers.NewTracingHooksFromFirehose(tracer)) + + genesisLine, blockLines, unknownLines := readTracerFirehoseLines(t, tracer) + require.Len(t, unknownLines, 0, "Lines:\n%s", strings.Join(slicesMap(unknownLines, func(l unknwonLine) string { return "- '" + string(l) + "'" }), "\n")) + require.NotNil(t, genesisLine) + blockLines.assertOnlyBlockEquals(t, folder, 1) + }) + } + +} diff --git a/eth/tracers/internal/tracetest/firehose/helpers_test.go b/eth/tracers/internal/tracetest/firehose/helpers_test.go new file mode 100644 index 0000000000..e346c01f6c --- /dev/null +++ b/eth/tracers/internal/tracetest/firehose/helpers_test.go @@ -0,0 +1,159 @@ +package firehose_test + +import ( + "bytes" + "encoding/base64" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/ethereum/go-ethereum/eth/tracers" + pbeth "github.com/ethereum/go-ethereum/pb/sf/ethereum/type/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +type firehoseInitLine struct { + ProtocolVersion string + NodeName string + NodeVersion string +} + +type firehoseBlockLines []firehoseBlockLine + +func (lines firehoseBlockLines) assertEquals(t *testing.T, goldenDir string, expected ...firehoseBlockLineParams) { + actualParams := slicesMap(lines, func(l firehoseBlockLine) firehoseBlockLineParams { return l.Params }) + require.Equal(t, expected, actualParams, "Actual lines block params do not match expected lines block params") + + lines.assertOnlyBlockEquals(t, goldenDir, len(expected)) +} + +func (lines firehoseBlockLines) assertOnlyBlockEquals(t *testing.T, goldenDir string, expectedBlockCount int) { + t.Helper() + + require.Len(t, lines, expectedBlockCount, "Expected %d blocks, got %d", expectedBlockCount, len(lines)) + goldenUpdate := os.Getenv("GOLDEN_UPDATE") == "true" + + for _, line := range lines { + goldenPath := filepath.Join(goldenDir, fmt.Sprintf("block.%d.golden.json", line.Block.Header.Number)) + if !goldenUpdate && !fileExists(t, goldenPath) { + t.Fatalf("the golden file %q does not exist, re-run with 'GOLDEN_UPDATE=true go test ./... -run %q' to generate the intial version", goldenPath, t.Name()) + } + + content, err := protojson.MarshalOptions{Indent: " "}.Marshal(line.Block) + require.NoError(t, err) + + if goldenUpdate { + require.NoError(t, os.MkdirAll(filepath.Dir(goldenPath), 0755)) + require.NoError(t, os.WriteFile(goldenPath, content, 0644)) + } + + expected, err := os.ReadFile(goldenPath) + require.NoError(t, err) + + expectedBlock := &pbeth.Block{} + require.NoError(t, protojson.Unmarshal(expected, expectedBlock)) + + if !proto.Equal(expectedBlock, line.Block) { + assert.EqualExportedValues(t, expectedBlock, line.Block, "Run 'GOLDEN_UPDATE=true go test ./... -run %q' to update golden file", t.Name()) + } + } +} + +func fileExists(t *testing.T, path string) bool { + t.Helper() + stat, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return false + } + + t.Fatal(err) + } + + return !stat.IsDir() +} + +func slicesMap[T any, U any](s []T, f func(T) U) []U { + result := make([]U, len(s)) + for i, v := range s { + result[i] = f(v) + } + return result +} + +type firehoseBlockLine struct { + // We split params and block to make it easier to compare stuff + Params firehoseBlockLineParams + Block *pbeth.Block +} + +type firehoseBlockLineParams struct { + Number string + Hash string + PreviousNum string + PreviousHash string + LibNum string + Time string +} + +type unknwonLine string + +func readTracerFirehoseLines(t *testing.T, tracer *tracers.Firehose) (genesisLine *firehoseInitLine, blockLines firehoseBlockLines, unknownLines []unknwonLine) { + t.Helper() + + lines := bytes.Split(tracer.InternalTestingBuffer().Bytes(), []byte{'\n'}) + for _, line := range lines { + if len(line) == 0 { + continue + } + + parts := bytes.Split(line, []byte{' '}) + if len(parts) == 0 || string(parts[0]) != "FIRE" { + unknownLines = append(unknownLines, unknwonLine(line)) + continue + } + + action := string(parts[1]) + fireParts := parts[2:] + switch action { + case "INIT": + genesisLine = &firehoseInitLine{ + ProtocolVersion: string(fireParts[0]), + NodeName: string(fireParts[1]), + NodeVersion: string(fireParts[2]), + } + + case "BLOCK": + protoBytes, err := base64.StdEncoding.DecodeString(string(fireParts[6])) + require.NoError(t, err) + + block := &pbeth.Block{} + require.NoError(t, proto.Unmarshal(protoBytes, block)) + + blockLines = append(blockLines, firehoseBlockLine{ + Params: firehoseBlockLineParams{ + Number: string(fireParts[0]), + Hash: string(fireParts[1]), + PreviousNum: string(fireParts[2]), + PreviousHash: string(fireParts[3]), + LibNum: string(fireParts[4]), + Time: string(fireParts[5]), + }, + Block: block, + }) + + default: + unknownLines = append(unknownLines, unknwonLine(line)) + } + } + + return +} + +func ptr[T any](v T) *T { + return &v +} diff --git a/eth/tracers/internal/tracetest/firehose/prestate_test.go b/eth/tracers/internal/tracetest/firehose/prestate_test.go new file mode 100644 index 0000000000..a83bb70dd2 --- /dev/null +++ b/eth/tracers/internal/tracetest/firehose/prestate_test.go @@ -0,0 +1,108 @@ +package firehose_test + +import ( + "encoding/json" + "math/big" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/stretchr/testify/require" +) + +func readPrestateData(t *testing.T, path string) *prestateData { + t.Helper() + + // Call tracer test found, read if from disk + blob, err := os.ReadFile(path) + require.NoError(t, err) + + test := new(prestateData) + require.NoError(t, json.Unmarshal(blob, test)) + + var genesisWithTD struct { + Genesis struct { + TotalDifficulty *math.HexOrDecimal256 `json:"totalDifficulty"` + } `json:"genesis"` + } + if err := json.Unmarshal(blob, &genesisWithTD); err == nil { + test.TotalDifficulty = (*big.Int)(genesisWithTD.Genesis.TotalDifficulty) + } + + return test +} + +var _ core.ChainContext = (*prestateData)(nil) + +type prestateData struct { + Genesis *core.Genesis `json:"genesis"` + Context *callContext `json:"context"` + Input string `json:"input"` + TotalDifficulty *big.Int `json:"-"` + TracerConfig json.RawMessage `json:"tracerConfig"` + + // Populated after loading + genesisBlock *types.Block +} + +// Engine implements core.ChainContext. +func (p *prestateData) Engine() consensus.Engine { + return ethash.NewFullFaker() +} + +// GetHeader implements core.ChainContext. +func (p *prestateData) GetHeader(hash common.Hash, number uint64) *types.Header { + if p.Genesis == nil { + return nil + } + + if p.genesisBlock == nil { + p.genesisBlock = p.Genesis.ToBlock() + } + + if hash == p.genesisBlock.Hash() { + return p.genesisBlock.Header() + } + + if number == p.genesisBlock.NumberU64() { + return p.genesisBlock.Header() + } + + return nil +} + +type callContext struct { + Number math.HexOrDecimal64 `json:"number"` + Difficulty *math.HexOrDecimal256 `json:"difficulty"` + Time math.HexOrDecimal64 `json:"timestamp"` + GasLimit math.HexOrDecimal64 `json:"gasLimit"` + Miner common.Address `json:"miner"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` +} + +func (c *callContext) toBlockContext(genesis *core.Genesis) vm.BlockContext { + context := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: c.Miner, + BlockNumber: new(big.Int).SetUint64(uint64(c.Number)), + Time: uint64(c.Time), + Difficulty: (*big.Int)(c.Difficulty), + GasLimit: uint64(c.GasLimit), + } + if genesis.Config.IsLondon(context.BlockNumber) { + context.BaseFee = (*big.Int)(c.BaseFee) + } + if genesis.ExcessBlobGas != nil && genesis.BlobGasUsed != nil { + excessBlobGas := eip4844.CalcExcessBlobGas(*genesis.ExcessBlobGas, *genesis.BlobGasUsed) + context.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) + } + return context +} diff --git a/eth/tracers/tests/firehose/testdata/TestFirehoseIntegrationTest/block.1.golden.json b/eth/tracers/internal/tracetest/firehose/testdata/TestFirehoseChain/block.1.golden.json similarity index 100% rename from eth/tracers/tests/firehose/testdata/TestFirehoseIntegrationTest/block.1.golden.json rename to eth/tracers/internal/tracetest/firehose/testdata/TestFirehoseChain/block.1.golden.json diff --git a/eth/tracers/internal/tracetest/firehose/testdata/TestFirehosePrestate/keccak256_too_few_memory_bytes_get_padded/block.3727495.golden.json b/eth/tracers/internal/tracetest/firehose/testdata/TestFirehosePrestate/keccak256_too_few_memory_bytes_get_padded/block.3727495.golden.json new file mode 100644 index 0000000000..d889b925f0 --- /dev/null +++ b/eth/tracers/internal/tracetest/firehose/testdata/TestFirehosePrestate/keccak256_too_few_memory_bytes_get_padded/block.3727495.golden.json @@ -0,0 +1,167 @@ +{ + "hash": "SKLPDUOjSrOGLDSmQ92rvI79Tx7rbpi+MnMdw1ta0jQ=", + "number": "3727495", + "size": "723", + "header": { + "parentHash": "aYf8zdg87hj7kTMGYReWUn8vvkT0i92fQ04NAsNDcDc=", + "uncleHash": "HcxN6N7HXXqrhbVntszUGtMSRRuUinQT8KFC/UDUk0c=", + "coinbase": "AAAAaRaoe4IzP0JFBGYjsjeUxlw=", + "stateRoot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "transactionsRoot": "bDCtoar4SKuDMt3K7Ay0vDV8FJj/S9YTAtfuy7yJYL0=", + "receiptRoot": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", + "logsBloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "difficulty": { + "bytes": "AA==" + }, + "totalDifficulty": { + "bytes": "PGVtIwKasA==" + }, + "number": "3727495", + "gasLimit": "30000000", + "timestamp": "2023-06-19T23:23:48Z", + "mixHash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "hash": "SKLPDUOjSrOGLDSmQ92rvI79Tx7rbpi+MnMdw1ta0jQ=", + "baseFeePerGas": { + "bytes": "DQ==" + }, + "parentBeaconRoot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + }, + "transactionTraces": [ + { + "to": "3Z+V4sEFOnYIm1AXgpuyMwy2rkc=", + "nonce": "5", + "gasPrice": { + "bytes": "AoQ=" + }, + "gasLimit": "200000", + "input": "QMEPGQAAAAAAAAAAAAAAAOs9PWFvPkPp7TYe3S0OZE0Y0Xm3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPo=", + "v": "AVRtcg==", + "r": "Xww4NKZlcXSDEHF8P5UnXU9PLqL6aBoOU5VFipWpVaM=", + "s": "BOdJVNNAB7fPBAA48slaL2uQd1vOwb423lDacpucotM=", + "gasUsed": "69865", + "hash": "tF3PNXHZNWA+icLD8DyDTxT8x76iGVd8sDejy7wEQx8=", + "from": "6z09YW8+Q+ntNh7dLQ5kTRjRebc=", + "beginOrdinal": "1", + "endOrdinal": "13", + "status": "SUCCEEDED", + "receipt": { + "cumulativeGasUsed": "69865", + "logsBloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAIAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAIAAAAAAAAAAAAIAAAAAAAAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAA==", + "logs": [ + { + "address": "3Z+V4sEFOnYIm1AXgpuyMwy2rkc=", + "topics": [ + "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "AAAAAAAAAAAAAAAA6z09YW8+Q+ntNh7dLQ5kTRjRebc=" + ], + "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPo=", + "ordinal": "9" + } + ] + }, + "calls": [ + { + "index": 1, + "callType": "CALL", + "caller": "6z09YW8+Q+ntNh7dLQ5kTRjRebc=", + "address": "3Z+V4sEFOnYIm1AXgpuyMwy2rkc=", + "gasLimit": "178428", + "gasConsumed": "48293", + "input": "QMEPGQAAAAAAAAAAAAAAAOs9PWFvPkPp7TYe3S0OZE0Y0Xm3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPo=", + "executedCode": true, + "keccakPreimages": { + "bb65ff7a03b11b79143b011da0de7c67f6d9bb8f57df2381d88d503cdb07b0b6": "000000000000000000000000eb3d3d616f3e43e9ed361edd2d0e644d18d179b70000000000000000000000000000000000000000000000000000000000000000" + }, + "storageChanges": [ + { + "address": "3Z+V4sEFOnYIm1AXgpuyMwy2rkc=", + "key": "u2X/egOxG3kUOwEdoN58Z/bZu49X3yOB2I1QPNsHsLY=", + "oldValue": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "newValue": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPo=", + "ordinal": "6" + }, + { + "address": "3Z+V4sEFOnYIm1AXgpuyMwy2rkc=", + "key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=", + "oldValue": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "newValue": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPo=", + "ordinal": "7" + } + ], + "balanceChanges": [ + { + "address": "6z09YW8+Q+ntNh7dLQ5kTRjRebc=", + "oldValue": { + "bytes": "B5sluWe3fF0=" + }, + "newValue": { + "bytes": "B5sluWAKJ10=" + }, + "reason": "REASON_GAS_BUY", + "ordinal": "2" + }, + { + "address": "6z09YW8+Q+ntNh7dLQ5kTRjRebc=", + "oldValue": { + "bytes": "B5sluWAKJ10=" + }, + "newValue": { + "bytes": "B5sluWUI8jk=" + }, + "reason": "REASON_GAS_REFUND", + "ordinal": "11" + }, + { + "address": "AAAAaRaoe4IzP0JFBGYjsjeUxlw=", + "oldValue": { + "bytes": "AecopRVH6fvBC6c=" + }, + "newValue": { + "bytes": "AecopRVH6f5hufY=" + }, + "reason": "REASON_REWARD_TRANSACTION_FEE", + "ordinal": "12" + } + ], + "nonceChanges": [ + { + "address": "6z09YW8+Q+ntNh7dLQ5kTRjRebc=", + "oldValue": "5", + "newValue": "6", + "ordinal": "4" + } + ], + "logs": [ + { + "address": "3Z+V4sEFOnYIm1AXgpuyMwy2rkc=", + "topics": [ + "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "AAAAAAAAAAAAAAAA6z09YW8+Q+ntNh7dLQ5kTRjRebc=" + ], + "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPo=", + "ordinal": "9" + } + ], + "gasChanges": [ + { + "oldValue": "200000", + "newValue": "178428", + "reason": "REASON_INTRINSIC_GAS", + "ordinal": "3" + }, + { + "oldValue": "131897", + "newValue": "130141", + "reason": "REASON_EVENT_LOG", + "ordinal": "8" + } + ], + "endOrdinal": "10" + } + ] + } + ], + "ver": 3 +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/firehose/testdata/TestFirehosePrestate/keccak256_too_few_memory_bytes_get_padded/prestate.json b/eth/tracers/internal/tracetest/firehose/testdata/TestFirehosePrestate/keccak256_too_few_memory_bytes_get_padded/prestate.json new file mode 100644 index 0000000000..0725326ef8 --- /dev/null +++ b/eth/tracers/internal/tracetest/firehose/testdata/TestFirehosePrestate/keccak256_too_few_memory_bytes_get_padded/prestate.json @@ -0,0 +1,174 @@ +{ + "genesis": { + "baseFeePerGas": "14", + "difficulty": "0", + "extraData": "0x6769756c696f207761732068657265", + "gasLimit": "30000000", + "hash": "0xff41623ef4e84cea4e957676a0b7e5868f1480e343ad533914133c448157764c", + "miner": "0x008b3b2f992c0e14edaa6e2c662bec549caa8df1", + "mixHash": "0xf04431d1d4ccb65f5d1b2e88ec010737ae82d68426b287a7158b9b16aa45a0bc", + "nonce": "0x0000000000000000", + "number": "3727494", + "stateRoot": "0xe3f9e4ff1577f21330eded302784f548f6fed4d4ca7a1f6884dde9ad38743279", + "timestamp": "1687217016", + "totalDifficulty": "17000018015853232", + "withdrawals": [ + { + "index": "0xb3bf91", + "validatorIndex": "0x38b", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x244a5" + }, + { + "index": "0xb3bf92", + "validatorIndex": "0x38c", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x2d66a" + }, + { + "index": "0xb3bf93", + "validatorIndex": "0x38d", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x2d66a" + }, + { + "index": "0xb3bf94", + "validatorIndex": "0x38e", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x2d66a" + }, + { + "index": "0xb3bf95", + "validatorIndex": "0x38f", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x2d66a" + }, + { + "index": "0xb3bf96", + "validatorIndex": "0x390", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x2d66a" + }, + { + "index": "0xb3bf97", + "validatorIndex": "0x391", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x244a5" + }, + { + "index": "0xb3bf98", + "validatorIndex": "0x392", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x2d66a" + }, + { + "index": "0xb3bf99", + "validatorIndex": "0x393", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x25b2b" + }, + { + "index": "0xb3bf9a", + "validatorIndex": "0x394", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x244a5" + }, + { + "index": "0xb3bf9b", + "validatorIndex": "0x395", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x2d66a" + }, + { + "index": "0xb3bf9c", + "validatorIndex": "0x396", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x244a5" + }, + { + "index": "0xb3bf9d", + "validatorIndex": "0x397", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x244a5" + }, + { + "index": "0xb3bf9e", + "validatorIndex": "0x398", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x2d66a" + }, + { + "index": "0xb3bf9f", + "validatorIndex": "0x399", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x2d66a" + }, + { + "index": "0xb3bfa0", + "validatorIndex": "0x39a", + "address": "0xe276bc378a527a8792b353cdca5b5e53263dfb9e", + "amount": "0x2ecf0" + } + ], + "withdrawalsRoot": "0xceabf2dc1561db0f29da25b6282082e5353c2f594942cfdcdccfeb176542411c", + "alloc": { + "0x0000006916a87b82333f4245046623b23794c65c": { + "balance": "0x1e728a51547e9fbc10ba7", + "nonce": "5" + }, + "0xdd9f95e2c1053a76089b5017829bb2330cb6ae47": { + "balance": "0x0", + "code": "0x60003560e01c8063095ea7b31461005c57806340c10f1914610062578063a9059cbb146100d957806323b872dd146100df57806318160ddd1461015d57806370a082311461016e5763dd62ed3e1461018e573461018e5760006000f35b60006000f35b60043573ffffffffffffffffffffffffffffffffffffffff1660007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000602435856000526040600020805482018083119155600254820180831190600255600154331415171761018e57600052a360006000f35b60006000f35b60043573ffffffffffffffffffffffffffffffffffffffff16337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000602435856000526040600020805482018083119155336000526040600020805483900380841091553417176101535760006000fd5b600052a360206000f35b3461018e5760025460005260206000f35b3461018e5760043560005260406000205460005260206000f35b60006000f35b60006000fd", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000000000000000000000000eb3d3d616f3e43e9ed361edd2d0e644d18d179b7", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xbb65ff7a03b11b79143b011da0de7c67f6d9bb8f57df2381d88d503cdb07b0b6": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0xeb3d3d616f3e43e9ed361edd2d0e644d18d179b7": { + "balance": "0x79b25b967b77c5d", + "nonce": "5" + } + }, + "config": { + "chainId": 11155111, + "homesteadBlock": 0, + "daoForkSupport": true, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "mergeNetsplitBlock": 1735371, + "shanghaiTime": 1677557088, + "cancunTime": 1706655072, + "terminalTotalDifficulty": 17000000000000000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "3727495", + "difficulty": "0", + "timestamp": "1687217028", + "gasLimit": "30000000", + "miner": "0x0000006916a87b82333f4245046623b23794c65c", + "baseFeePerGas": "13" + }, + "input": "0xf8ab0582028483030d4094dd9f95e2c1053a76089b5017829bb2330cb6ae4780b84440c10f19000000000000000000000000eb3d3d616f3e43e9ed361edd2d0e644d18d179b700000000000000000000000000000000000000000000000000000000000000fa8401546d72a05f0c3834a66571748310717c3f95275d4f4f2ea2fa681a0e5395458a95a955a3a004e74954d34007b7cf040038f2c95a2f6b90775bcec1be36de50da729b9ca2d3", + "result": { + "from": "0xeb3d3d616f3e43e9ed361edd2d0e644d18d179b7", + "gas": "0x30d40", + "gasUsed": "0x110e9", + "to": "0xdd9f95e2c1053a76089b5017829bb2330cb6ae47", + "input": "0x40c10f19000000000000000000000000eb3d3d616f3e43e9ed361edd2d0e644d18d179b700000000000000000000000000000000000000000000000000000000000000fa", + "value": "0x0", + "type": "CALL" + } +} diff --git a/eth/tracers/tests/firehose/integration_test.go b/eth/tracers/tests/firehose/integration_test.go deleted file mode 100644 index 96c616470b..0000000000 --- a/eth/tracers/tests/firehose/integration_test.go +++ /dev/null @@ -1,288 +0,0 @@ -package firehose_test - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "hash" - "math/big" - "os" - "path/filepath" - "strings" - "testing" - - "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/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers" - "github.com/ethereum/go-ethereum/params" - pbeth "github.com/ethereum/go-ethereum/pb/sf/ethereum/type/v2" - "github.com/ethereum/go-ethereum/trie" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "golang.org/x/crypto/sha3" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" -) - -func TestFirehoseIntegrationTest(t *testing.T) { - context := vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - Coinbase: common.Address{}, - BlockNumber: new(big.Int).SetUint64(uint64(1)), - Time: 1, - Difficulty: big.NewInt(2), - GasLimit: uint64(1000000), - BaseFee: big.NewInt(8), - } - - tracer, err := tracers.NewFirehoseFromRawJSON(json.RawMessage(`{ - "applyBackwardsCompatibility": true, - "_private": { - "flushToTestBuffer": true, - "ignoreGenesisBlock": true - } - }`)) - if err != nil { - t.Fatal(fmt.Errorf("failed to create firehose tracer: %w", err)) - } - hooks := tracers.NewTracingHooksFromFirehose(tracer) - - genesis, blockchain := newCanonical(t, types.GenesisAlloc{}, context, hooks) - - block := types.NewBlock(&types.Header{ - ParentHash: genesis.ToBlock().Hash(), - Number: context.BlockNumber, - Difficulty: context.Difficulty, - Coinbase: context.Coinbase, - Time: context.Time, - GasLimit: context.GasLimit, - BaseFee: context.BaseFee, - ParentBeaconRoot: ptr(common.Hash{}), - }, nil, nil, nil, trie.NewStackTrie(nil)) - - blockchain.SetBlockValidatorAndProcessorForTesting( - ignoreValidateStateValidator{core.NewBlockValidator(genesis.Config, blockchain, blockchain.Engine())}, - core.NewStateProcessor(genesis.Config, blockchain, blockchain.Engine()), - ) - - n, err := blockchain.InsertChain(types.Blocks{block}) - require.NoError(t, err) - require.Equal(t, 1, n) - - genesisLine, blockLines, unknownLines := readTracerFirehoseLines(t, tracer) - require.Len(t, unknownLines, 0, "Lines:\n%s", strings.Join(slicesMap(unknownLines, func(l unknwonLine) string { return "- '" + string(l) + "'" }), "\n")) - require.NotNil(t, genesisLine) - blockLines.assertEquals(t, - firehoseBlockLineParams{"1", "8e6ee4b1054d94df1d8a51fb983447dc2e27a854590c3ac0061f994284be8150", "0", "845bad515694a416bab4b8d44e22cf97a8c894a8502110ab807883940e185ce0", "0", "1000000000"}, - ) -} - -type firehoseInitLine struct { - ProtocolVersion string - NodeName string - NodeVersion string -} - -type firehoseBlockLines []firehoseBlockLine - -func (lines firehoseBlockLines) assertEquals(t *testing.T, expected ...firehoseBlockLineParams) { - actualParams := slicesMap(lines, func(l firehoseBlockLine) firehoseBlockLineParams { return l.Params }) - require.Equal(t, expected, actualParams, "Actual lines block params do not match expected lines block params") - - goldenUpdate := os.Getenv("GOLDEN_UPDATE") == "true" - - for _, line := range lines { - goldenPath := fmt.Sprintf("testdata/%s/block.%d.golden.json", t.Name(), line.Block.Header.Number) - if !goldenUpdate && !fileExists(t, goldenPath) { - t.Fatalf("the golden file %q does not exist, re-run with 'GOLDEN_UPDATE=true go test ./... -run %q' to generate the intial version", goldenPath, t.Name()) - } - - content, err := protojson.MarshalOptions{Indent: " "}.Marshal(line.Block) - require.NoError(t, err) - - if goldenUpdate { - require.NoError(t, os.MkdirAll(filepath.Dir(goldenPath), 0755)) - require.NoError(t, os.WriteFile(goldenPath, content, 0644)) - } - - expected, err := os.ReadFile(goldenPath) - require.NoError(t, err) - - expectedBlock := &pbeth.Block{} - require.NoError(t, protojson.Unmarshal(expected, expectedBlock)) - - if !proto.Equal(expectedBlock, line.Block) { - assert.EqualExportedValues(t, expectedBlock, line.Block, "Run 'GOLDEN_UPDATE=true go test ./... -run %q' to update golden file", t.Name()) - } - } -} - -func fileExists(t *testing.T, path string) bool { - t.Helper() - stat, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - return false - } - - t.Fatal(err) - } - - return !stat.IsDir() -} - -func slicesMap[T any, U any](s []T, f func(T) U) []U { - result := make([]U, len(s)) - for i, v := range s { - result[i] = f(v) - } - return result -} - -type firehoseBlockLine struct { - // We split params and block to make it easier to compare stuff - Params firehoseBlockLineParams - Block *pbeth.Block -} - -type firehoseBlockLineParams struct { - Number string - Hash string - PreviousNum string - PreviousHash string - LibNum string - Time string -} - -type unknwonLine string - -func readTracerFirehoseLines(t *testing.T, tracer *tracers.Firehose) (genesisLine *firehoseInitLine, blockLines firehoseBlockLines, unknownLines []unknwonLine) { - t.Helper() - - lines := bytes.Split(tracer.InternalTestingBuffer().Bytes(), []byte{'\n'}) - for _, line := range lines { - if len(line) == 0 { - continue - } - - parts := bytes.Split(line, []byte{' '}) - if len(parts) == 0 || string(parts[0]) != "FIRE" { - unknownLines = append(unknownLines, unknwonLine(line)) - continue - } - - action := string(parts[1]) - fireParts := parts[2:] - switch action { - case "INIT": - genesisLine = &firehoseInitLine{ - ProtocolVersion: string(fireParts[0]), - NodeName: string(fireParts[1]), - NodeVersion: string(fireParts[2]), - } - - case "BLOCK": - protoBytes, err := base64.StdEncoding.DecodeString(string(fireParts[6])) - require.NoError(t, err) - - block := &pbeth.Block{} - require.NoError(t, proto.Unmarshal(protoBytes, block)) - - blockLines = append(blockLines, firehoseBlockLine{ - Params: firehoseBlockLineParams{ - Number: string(fireParts[0]), - Hash: string(fireParts[1]), - PreviousNum: string(fireParts[2]), - PreviousHash: string(fireParts[3]), - LibNum: string(fireParts[4]), - Time: string(fireParts[5]), - }, - Block: block, - }) - - default: - unknownLines = append(unknownLines, unknwonLine(line)) - } - } - - return -} - -func newCanonical(t *testing.T, alloc types.GenesisAlloc, context vm.BlockContext, tracer *tracing.Hooks) (*core.Genesis, *core.BlockChain) { - t.Helper() - - var ( - engine = ethash.NewFullFaker() - genesis = &core.Genesis{ - Difficulty: new(big.Int).Sub(context.Difficulty, big.NewInt(1)), - Timestamp: context.Time - 1, - Number: new(big.Int).Sub(context.BlockNumber, big.NewInt(1)).Uint64(), - BaseFee: big.NewInt(params.InitialBaseFee), - Coinbase: context.Coinbase, - Config: params.AllEthashProtocolChanges, - Alloc: alloc, - } - ) - // Initialize a fresh chain with only a genesis block - blockchain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), core.DefaultCacheConfigWithScheme(rawdb.HashScheme), genesis, nil, engine, vm.Config{ - Tracer: tracer, - }, nil, nil) - if err != nil { - t.Fatal(err) - } - - return genesis, blockchain -} - -// testHasher is the helper tool for transaction/receipt list hashing. -// The original hasher is trie, in order to get rid of import cycle, -// use the testing hasher instead. -type testHasher struct { - hasher hash.Hash -} - -// NewHasher returns a new testHasher instance. -func NewHasher() *testHasher { - return &testHasher{hasher: sha3.NewLegacyKeccak256()} -} - -// Reset resets the hash state. -func (h *testHasher) Reset() { - h.hasher.Reset() -} - -// Update updates the hash state with the given key and value. -func (h *testHasher) Update(key, val []byte) error { - h.hasher.Write(key) - h.hasher.Write(val) - return nil -} - -// Hash returns the hash value. -func (h *testHasher) Hash() common.Hash { - return common.BytesToHash(h.hasher.Sum(nil)) -} - -type ignoreValidateStateValidator struct { - core.Validator -} - -func (v ignoreValidateStateValidator) ValidateBody(block *types.Block) error { - return v.Validator.ValidateBody(block) -} - -func (v ignoreValidateStateValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error { - return nil -} - -func ptr[T any](v T) *T { - return &v -}