Refactored tests and added new based on "prestate" testing

This commit is contained in:
Matthieu Vachon 2024-05-01 13:28:41 -04:00
parent dccb2f36cd
commit bc4162e99f
8 changed files with 845 additions and 288 deletions

View file

@ -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
}

View file

@ -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)
})
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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"
}
}

View file

@ -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
}