mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-15 16:33:47 +00:00
Ported various improvements done in the Firehose tracer in other supported chains
This commit is contained in:
parent
4105834d36
commit
ad7fdb83cc
3 changed files with 724 additions and 194 deletions
|
|
@ -12,9 +12,11 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -34,7 +36,12 @@ import (
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Here what you can expect from the debugging levels:
|
||||||
|
// - Info == block start/end + trx start/end
|
||||||
|
// - Debug == Info + call start/end + error
|
||||||
|
// - Trace == Debug + state db changes, log, balance, nonce, code, storage, gas
|
||||||
var firehoseTracerLogLevel = strings.ToLower(os.Getenv("FIREHOSE_ETHEREUM_TRACER_LOG_LEVEL"))
|
var firehoseTracerLogLevel = strings.ToLower(os.Getenv("FIREHOSE_ETHEREUM_TRACER_LOG_LEVEL"))
|
||||||
|
var isFirehoseInfoEnabled = firehoseTracerLogLevel == "info" || firehoseTracerLogLevel == "debug" || firehoseTracerLogLevel == "trace"
|
||||||
var isFirehoseDebugEnabled = firehoseTracerLogLevel == "debug" || firehoseTracerLogLevel == "trace"
|
var isFirehoseDebugEnabled = firehoseTracerLogLevel == "debug" || firehoseTracerLogLevel == "trace"
|
||||||
var isFirehoseTracerEnabled = firehoseTracerLogLevel == "trace"
|
var isFirehoseTracerEnabled = firehoseTracerLogLevel == "trace"
|
||||||
|
|
||||||
|
|
@ -45,10 +52,15 @@ func init() {
|
||||||
staticFirehoseChainValidationOnInit()
|
staticFirehoseChainValidationOnInit()
|
||||||
|
|
||||||
LiveDirectory.Register("firehose", newFirehoseTracer)
|
LiveDirectory.Register("firehose", newFirehoseTracer)
|
||||||
|
|
||||||
|
// Those 2 are defined but not used in this branch, they are kept because used in other branches
|
||||||
|
// so it's easier to keep them here and suppress the warning by faking a usage.
|
||||||
|
_ = new(Firehose).newIsolatedTransactionTracer
|
||||||
|
_ = new(FinalityStatus).populate
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFirehoseTracer(cfg json.RawMessage) (*tracing.Hooks, error) {
|
func newFirehoseTracer(cfg json.RawMessage) (*tracing.Hooks, error) {
|
||||||
firehoseDebug("New firehose tracer")
|
firehoseInfo("new firehose tracer")
|
||||||
|
|
||||||
var config FirehoseConfig
|
var config FirehoseConfig
|
||||||
if len([]byte(cfg)) > 0 {
|
if len([]byte(cfg)) > 0 {
|
||||||
|
|
@ -57,8 +69,10 @@ func newFirehoseTracer(cfg json.RawMessage) (*tracing.Hooks, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracer := NewFirehose()
|
return newTracingHooksFromFirehose(NewFirehose(&config)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTracingHooksFromFirehose(tracer *Firehose) *tracing.Hooks {
|
||||||
return &tracing.Hooks{
|
return &tracing.Hooks{
|
||||||
OnBlockchainInit: tracer.OnBlockchainInit,
|
OnBlockchainInit: tracer.OnBlockchainInit,
|
||||||
OnGenesisBlock: tracer.OnGenesisBlock,
|
OnGenesisBlock: tracer.OnGenesisBlock,
|
||||||
|
|
@ -80,12 +94,16 @@ func newFirehoseTracer(cfg json.RawMessage) (*tracing.Hooks, error) {
|
||||||
OnGasChange: tracer.OnGasChange,
|
OnGasChange: tracer.OnGasChange,
|
||||||
OnLog: tracer.OnLog,
|
OnLog: tracer.OnLog,
|
||||||
|
|
||||||
|
// This should actually be conditional but it's not possible to do it in the hooks
|
||||||
|
// directly because the chain ID will be known only after the `OnBlockchainInit` call.
|
||||||
|
// So we register it unconditionally and the actual `OnNewAccount` hook will decide
|
||||||
|
// what it needs to do.
|
||||||
OnNewAccount: tracer.OnNewAccount,
|
OnNewAccount: tracer.OnNewAccount,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FirehoseConfig struct {
|
type FirehoseConfig struct {
|
||||||
// Nothing for now
|
ApplyBackwardCompatibility *bool `json:"applyBackwardCompatibility"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Firehose struct {
|
type Firehose struct {
|
||||||
|
|
@ -95,6 +113,16 @@ type Firehose struct {
|
||||||
chainConfig *params.ChainConfig
|
chainConfig *params.ChainConfig
|
||||||
hasher crypto.KeccakState // Keccak256 hasher instance shared across tracer needs (non-concurrent safe)
|
hasher crypto.KeccakState // Keccak256 hasher instance shared across tracer needs (non-concurrent safe)
|
||||||
hasherBuf common.Hash // Keccak256 hasher result array shared across tracer needs (non-concurrent safe)
|
hasherBuf common.Hash // Keccak256 hasher result array shared across tracer needs (non-concurrent safe)
|
||||||
|
tracerID string
|
||||||
|
// The FirehoseTracer is used in multiple chains, some for which were produced using a legacy version
|
||||||
|
// of the whole tracing infrastructure. This legacy version had many small bugs here and there that
|
||||||
|
// we must "reproduce" on some chain to ensure that the FirehoseTracer produces the same output
|
||||||
|
// as the legacy version.
|
||||||
|
//
|
||||||
|
// This value is fed from the tracer configuration. If explicitly set, the value set will be used
|
||||||
|
// here. If not set in the config, then we inspect `OnBlockchainInit` the chain config to determine
|
||||||
|
// if it's a network for which we must reproduce the legacy bugs.
|
||||||
|
applyBackwardCompatibility *bool
|
||||||
|
|
||||||
// Block state
|
// Block state
|
||||||
block *pbeth.Block
|
block *pbeth.Block
|
||||||
|
|
@ -102,6 +130,9 @@ type Firehose struct {
|
||||||
blockOrdinal *Ordinal
|
blockOrdinal *Ordinal
|
||||||
blockFinality *FinalityStatus
|
blockFinality *FinalityStatus
|
||||||
blockRules params.Rules
|
blockRules params.Rules
|
||||||
|
blockReorderOrdinal bool
|
||||||
|
blockReorderOrdinalSnapshot uint64
|
||||||
|
blockReorderOrdinalOnce sync.Once
|
||||||
|
|
||||||
// Transaction state
|
// Transaction state
|
||||||
evm *tracing.VMContext
|
evm *tracing.VMContext
|
||||||
|
|
@ -109,6 +140,8 @@ type Firehose struct {
|
||||||
transactionLogIndex uint32
|
transactionLogIndex uint32
|
||||||
inSystemCall bool
|
inSystemCall bool
|
||||||
blockIsPrecompiledAddr func(addr common.Address) bool
|
blockIsPrecompiledAddr func(addr common.Address) bool
|
||||||
|
transactionIsolated bool
|
||||||
|
transactionTransient *pbeth.TransactionTrace
|
||||||
|
|
||||||
// Call state
|
// Call state
|
||||||
callStack *CallStack
|
callStack *CallStack
|
||||||
|
|
@ -118,17 +151,20 @@ type Firehose struct {
|
||||||
|
|
||||||
const FirehoseProtocolVersion = "3.0"
|
const FirehoseProtocolVersion = "3.0"
|
||||||
|
|
||||||
func NewFirehose() *Firehose {
|
func NewFirehose(config *FirehoseConfig) *Firehose {
|
||||||
return &Firehose{
|
return &Firehose{
|
||||||
// Global state
|
// Global state
|
||||||
outputBuffer: bytes.NewBuffer(make([]byte, 0, 100*1024*1024)),
|
outputBuffer: bytes.NewBuffer(make([]byte, 0, 100*1024*1024)),
|
||||||
initSent: new(atomic.Bool),
|
initSent: new(atomic.Bool),
|
||||||
chainConfig: nil,
|
chainConfig: nil,
|
||||||
hasher: crypto.NewKeccakState(),
|
hasher: crypto.NewKeccakState(),
|
||||||
|
tracerID: "global",
|
||||||
|
applyBackwardCompatibility: config.ApplyBackwardCompatibility,
|
||||||
|
|
||||||
// Block state
|
// Block state
|
||||||
blockOrdinal: &Ordinal{},
|
blockOrdinal: &Ordinal{},
|
||||||
blockFinality: &FinalityStatus{},
|
blockFinality: &FinalityStatus{},
|
||||||
|
blockReorderOrdinal: false,
|
||||||
|
|
||||||
// Transaction state
|
// Transaction state
|
||||||
transactionLogIndex: 0,
|
transactionLogIndex: 0,
|
||||||
|
|
@ -140,6 +176,36 @@ func NewFirehose() *Firehose {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Firehose) newIsolatedTransactionTracer(tracerID string) *Firehose {
|
||||||
|
f.ensureInBlock(0)
|
||||||
|
|
||||||
|
return &Firehose{
|
||||||
|
// Global state
|
||||||
|
initSent: f.initSent,
|
||||||
|
chainConfig: f.chainConfig,
|
||||||
|
hasher: crypto.NewKeccakState(),
|
||||||
|
hasherBuf: common.Hash{},
|
||||||
|
tracerID: tracerID,
|
||||||
|
|
||||||
|
// Block state
|
||||||
|
block: f.block,
|
||||||
|
blockBaseFee: f.blockBaseFee,
|
||||||
|
blockOrdinal: &Ordinal{},
|
||||||
|
blockFinality: f.blockFinality,
|
||||||
|
blockIsPrecompiledAddr: f.blockIsPrecompiledAddr,
|
||||||
|
blockRules: f.blockRules,
|
||||||
|
|
||||||
|
// Transaction state
|
||||||
|
transactionLogIndex: 0,
|
||||||
|
transactionIsolated: true,
|
||||||
|
|
||||||
|
// Call state
|
||||||
|
callStack: NewCallStack(),
|
||||||
|
deferredCallState: NewDeferredCallState(),
|
||||||
|
latestCallEnterSuicided: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// resetBlock resets the block state only, do not reset transaction or call state
|
// resetBlock resets the block state only, do not reset transaction or call state
|
||||||
func (f *Firehose) resetBlock() {
|
func (f *Firehose) resetBlock() {
|
||||||
f.block = nil
|
f.block = nil
|
||||||
|
|
@ -148,6 +214,9 @@ func (f *Firehose) resetBlock() {
|
||||||
f.blockFinality.Reset()
|
f.blockFinality.Reset()
|
||||||
f.blockIsPrecompiledAddr = nil
|
f.blockIsPrecompiledAddr = nil
|
||||||
f.blockRules = params.Rules{}
|
f.blockRules = params.Rules{}
|
||||||
|
f.blockReorderOrdinal = false
|
||||||
|
f.blockReorderOrdinalSnapshot = 0
|
||||||
|
f.blockReorderOrdinalOnce = sync.Once{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// resetTransaction resets the transaction state and the call state in one shot
|
// resetTransaction resets the transaction state and the call state in one shot
|
||||||
|
|
@ -156,6 +225,7 @@ func (f *Firehose) resetTransaction() {
|
||||||
f.evm = nil
|
f.evm = nil
|
||||||
f.transactionLogIndex = 0
|
f.transactionLogIndex = 0
|
||||||
f.inSystemCall = false
|
f.inSystemCall = false
|
||||||
|
f.transactionTransient = nil
|
||||||
|
|
||||||
f.callStack.Reset()
|
f.callStack.Reset()
|
||||||
f.latestCallEnterSuicided = false
|
f.latestCallEnterSuicided = false
|
||||||
|
|
@ -168,13 +238,39 @@ func (f *Firehose) OnBlockchainInit(chainConfig *params.ChainConfig) {
|
||||||
if wasNeverSent := f.initSent.CompareAndSwap(false, true); wasNeverSent {
|
if wasNeverSent := f.initSent.CompareAndSwap(false, true); wasNeverSent {
|
||||||
printToFirehose("INIT", FirehoseProtocolVersion, "geth", params.Version)
|
printToFirehose("INIT", FirehoseProtocolVersion, "geth", params.Version)
|
||||||
} else {
|
} else {
|
||||||
f.panicInvalidState("The OnBlockchainInit callback was called more than once")
|
f.panicInvalidState("The OnBlockchainInit callback was called more than once", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.applyBackwardCompatibility == nil {
|
||||||
|
f.applyBackwardCompatibility = ptr(chainNeedsLegacyBackwardCompatibility(chainConfig.ChainID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mainnetChainID = big.NewInt(1)
|
||||||
|
var goerliChainID = big.NewInt(5)
|
||||||
|
var sepoliaChainID = big.NewInt(11155111)
|
||||||
|
var holeskyChainID = big.NewInt(17000)
|
||||||
|
var polygonMainnetChainID = big.NewInt(137)
|
||||||
|
var polygonMumbaiChainID = big.NewInt(80001)
|
||||||
|
var polygonAmoyChainID = big.NewInt(80002)
|
||||||
|
var bscMainnetChainID = big.NewInt(56)
|
||||||
|
var bscTestnetChainID = big.NewInt(97)
|
||||||
|
|
||||||
|
func chainNeedsLegacyBackwardCompatibility(id *big.Int) bool {
|
||||||
|
return id.Cmp(mainnetChainID) == 0 ||
|
||||||
|
id.Cmp(goerliChainID) == 0 ||
|
||||||
|
id.Cmp(sepoliaChainID) == 0 ||
|
||||||
|
id.Cmp(holeskyChainID) == 0 ||
|
||||||
|
id.Cmp(polygonMainnetChainID) == 0 ||
|
||||||
|
id.Cmp(polygonMumbaiChainID) == 0 ||
|
||||||
|
id.Cmp(polygonAmoyChainID) == 0 ||
|
||||||
|
id.Cmp(bscMainnetChainID) == 0 ||
|
||||||
|
id.Cmp(bscTestnetChainID) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) OnBlockStart(event tracing.BlockEvent) {
|
func (f *Firehose) OnBlockStart(event tracing.BlockEvent) {
|
||||||
b := event.Block
|
b := event.Block
|
||||||
firehoseDebug("block start number=%d hash=%s", b.NumberU64(), b.Hash())
|
firehoseInfo("block start (number=%d hash=%s)", b.NumberU64(), b.Hash())
|
||||||
|
|
||||||
f.ensureBlockChainInit()
|
f.ensureBlockChainInit()
|
||||||
|
|
||||||
|
|
@ -186,8 +282,11 @@ func (f *Firehose) OnBlockStart(event tracing.BlockEvent) {
|
||||||
Number: b.Number().Uint64(),
|
Number: b.Number().Uint64(),
|
||||||
Header: newBlockHeaderFromChainHeader(b.Header(), firehoseBigIntFromNative(new(big.Int).Add(event.TD, b.Difficulty()))),
|
Header: newBlockHeaderFromChainHeader(b.Header(), firehoseBigIntFromNative(new(big.Int).Add(event.TD, b.Difficulty()))),
|
||||||
Size: b.Size(),
|
Size: b.Size(),
|
||||||
// Known Firehose issue: If you fix all known Firehose issue for a new chain, don't forget to bump `Ver` to `4`!
|
Ver: 4,
|
||||||
Ver: 3,
|
}
|
||||||
|
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
|
f.block.Ver = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, uncle := range b.Uncles() {
|
for _, uncle := range b.Uncles() {
|
||||||
|
|
@ -203,13 +302,13 @@ func (f *Firehose) OnBlockStart(event tracing.BlockEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) OnSkippedBlock(event tracing.BlockEvent) {
|
func (f *Firehose) OnSkippedBlock(event tracing.BlockEvent) {
|
||||||
// Blocks that are skipped from blockchain that were knwon and should contain 0 transactions.
|
// Blocks that are skipped from blockchain that were known and should contain 0 transactions.
|
||||||
// It happened in the past, on Polygon if I recall right, that we missed block because some block
|
// It happened in the past, on Polygon if I recall right, that we missed block because some block
|
||||||
// went in this code path.
|
// went in this code path.
|
||||||
//
|
//
|
||||||
// See https: //github.com/streamingfast/go-ethereum/blob/a46903cf0cad829479ded66b369017914bf82314/core/blockchain.go#L1797-L1814
|
// See https: //github.com/streamingfast/go-ethereum/blob/a46903cf0cad829479ded66b369017914bf82314/core/blockchain.go#L1797-L1814
|
||||||
if event.Block.Transactions().Len() > 0 {
|
if event.Block.Transactions().Len() > 0 {
|
||||||
panic(fmt.Sprintf("The tracer received an `OnSkippedBlock` block #%d (%s) with transactions (%d), this according to core/blockchain.go should never happen and is an error",
|
panic(fmt.Sprintf("The tracer received an `OnSkippedBlock` block #%d (%s) with %d transactions, this according to core/blockchain.go should never happen and is an error",
|
||||||
event.Block.NumberU64(),
|
event.Block.NumberU64(),
|
||||||
event.Block.Hash().Hex(),
|
event.Block.Hash().Hex(),
|
||||||
event.Block.Transactions().Len(),
|
event.Block.Transactions().Len(),
|
||||||
|
|
@ -236,24 +335,115 @@ func getActivePrecompilesChecker(rules params.Rules) func(addr common.Address) b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) OnBlockEnd(err error) {
|
func (f *Firehose) OnBlockEnd(err error) {
|
||||||
firehoseDebug("block ending err=%s", errorView(err))
|
firehoseInfo("block ending (err=%s)", errorView(err))
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if f.blockReorderOrdinal {
|
||||||
|
f.reorderIsolatedTransactionsAndOrdinals()
|
||||||
|
}
|
||||||
|
|
||||||
f.ensureInBlockAndNotInTrx()
|
f.ensureInBlockAndNotInTrx()
|
||||||
f.printBlockToFirehose(f.block, f.blockFinality)
|
f.printBlockToFirehose(f.block, f.blockFinality)
|
||||||
} else {
|
} else {
|
||||||
// An error occurred, could have happen in transaction/call context, we must not check if in trx/call, only check in block
|
// An error occurred, could have happen in transaction/call context, we must not check if in trx/call, only check in block
|
||||||
f.ensureInBlock()
|
f.ensureInBlock(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.resetBlock()
|
f.resetBlock()
|
||||||
f.resetTransaction()
|
f.resetTransaction()
|
||||||
|
|
||||||
firehoseDebug("block end")
|
firehoseInfo("block end")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reorderIsolatedTransactionsAndOrdinals is called right after all transactions have completed execution. It will sort transactions
|
||||||
|
// according to their index.
|
||||||
|
//
|
||||||
|
// But most importantly, will re-assign all the ordinals of each transaction recursively. When the parallel execution happened,
|
||||||
|
// all ordinal were made relative to the transaction they were contained in. But now, we are going to re-assign them to the
|
||||||
|
// global block ordinal by getting the current ordinal and ad it to the transaction ordinal and so forth.
|
||||||
|
func (f *Firehose) reorderIsolatedTransactionsAndOrdinals() {
|
||||||
|
if !f.blockReorderOrdinal {
|
||||||
|
firehoseInfo("post process isolated transactions skipped (block_reorder_ordinals=false)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ordinalBase := f.blockReorderOrdinalSnapshot
|
||||||
|
firehoseInfo("post processing isolated transactions sorting & re-assigning ordinals (ordinal_base=%d)", ordinalBase)
|
||||||
|
|
||||||
|
slices.SortStableFunc(f.block.TransactionTraces, func(i, j *pbeth.TransactionTrace) int {
|
||||||
|
return int(i.Index) - int(j.Index)
|
||||||
|
})
|
||||||
|
|
||||||
|
baseline := ordinalBase
|
||||||
|
for _, trx := range f.block.TransactionTraces {
|
||||||
|
trx.BeginOrdinal += baseline
|
||||||
|
for _, call := range trx.Calls {
|
||||||
|
f.reorderCallOrdinals(call, baseline)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, log := range trx.Receipt.Logs {
|
||||||
|
log.Ordinal += baseline
|
||||||
|
}
|
||||||
|
|
||||||
|
trx.EndOrdinal += baseline
|
||||||
|
baseline = trx.EndOrdinal
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ch := range f.block.BalanceChanges {
|
||||||
|
if ch.Ordinal >= ordinalBase {
|
||||||
|
ch.Ordinal += baseline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ch := range f.block.CodeChanges {
|
||||||
|
if ch.Ordinal >= ordinalBase {
|
||||||
|
ch.Ordinal += baseline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, call := range f.block.SystemCalls {
|
||||||
|
if call.BeginOrdinal >= ordinalBase {
|
||||||
|
f.reorderCallOrdinals(call, baseline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehose) reorderCallOrdinals(call *pbeth.Call, ordinalBase uint64) (ordinalEnd uint64) {
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
|
if call.BeginOrdinal != 0 {
|
||||||
|
call.BeginOrdinal += ordinalBase // consistent with a known small bug: root call has beginOrdinal set to 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
call.BeginOrdinal += ordinalBase
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, log := range call.Logs {
|
||||||
|
log.Ordinal += ordinalBase
|
||||||
|
}
|
||||||
|
for _, act := range call.AccountCreations {
|
||||||
|
act.Ordinal += ordinalBase
|
||||||
|
}
|
||||||
|
for _, ch := range call.BalanceChanges {
|
||||||
|
ch.Ordinal += ordinalBase
|
||||||
|
}
|
||||||
|
for _, ch := range call.GasChanges {
|
||||||
|
ch.Ordinal += ordinalBase
|
||||||
|
}
|
||||||
|
for _, ch := range call.NonceChanges {
|
||||||
|
ch.Ordinal += ordinalBase
|
||||||
|
}
|
||||||
|
for _, ch := range call.StorageChanges {
|
||||||
|
ch.Ordinal += ordinalBase
|
||||||
|
}
|
||||||
|
for _, ch := range call.CodeChanges {
|
||||||
|
ch.Ordinal += ordinalBase
|
||||||
|
}
|
||||||
|
|
||||||
|
call.EndOrdinal += ordinalBase
|
||||||
|
|
||||||
|
return call.EndOrdinal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) OnBeaconBlockRootStart(root common.Hash) {
|
func (f *Firehose) OnBeaconBlockRootStart(root common.Hash) {
|
||||||
firehoseDebug("system call start for=%s", "beacon_block_root")
|
firehoseInfo("system call start (for=%s)", "beacon_block_root")
|
||||||
f.ensureInBlockAndNotInTrx()
|
f.ensureInBlockAndNotInTrx()
|
||||||
|
|
||||||
f.inSystemCall = true
|
f.inSystemCall = true
|
||||||
|
|
@ -270,7 +460,7 @@ func (f *Firehose) OnBeaconBlockRootEnd() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) OnTxStart(evm *tracing.VMContext, tx *types.Transaction, from common.Address) {
|
func (f *Firehose) OnTxStart(evm *tracing.VMContext, tx *types.Transaction, from common.Address) {
|
||||||
firehoseDebug("trx start hash=%s type=%d gas=%d input=%s", tx.Hash(), tx.Type(), tx.Gas(), inputView(tx.Data()))
|
firehoseInfo("trx start (tracer=%s hash=%s type=%d gas=%d isolated=%t input=%s)", f.tracerID, tx.Hash(), tx.Type(), tx.Gas(), f.transactionIsolated, inputView(tx.Data()))
|
||||||
|
|
||||||
f.ensureInBlockAndNotInTrxAndNotInCall()
|
f.ensureInBlockAndNotInTrxAndNotInCall()
|
||||||
|
|
||||||
|
|
@ -320,20 +510,34 @@ func (f *Firehose) onTxStart(tx *types.Transaction, hash common.Hash, from, to c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) OnTxEnd(receipt *types.Receipt, err error) {
|
func (f *Firehose) OnTxEnd(receipt *types.Receipt, err error) {
|
||||||
firehoseDebug("trx ending")
|
firehoseInfo("trx ending (tracer=%s)", f.tracerID)
|
||||||
f.ensureInBlockAndInTrx()
|
f.ensureInBlockAndInTrx()
|
||||||
|
|
||||||
f.block.TransactionTraces = append(f.block.TransactionTraces, f.completeTransaction(receipt))
|
trxTrace := f.completeTransaction(receipt)
|
||||||
|
|
||||||
|
// In this case, we are in some kind of parallel processing and we must simply add the transaction
|
||||||
|
// to a transient storage (and not in the block directly). Adding it to the block will be done by the
|
||||||
|
// `OnTxCommit` callback.
|
||||||
|
if f.transactionIsolated {
|
||||||
|
f.transactionTransient = trxTrace
|
||||||
|
|
||||||
|
// We must not reset transaction here. In the isolated transaction tracer, the transaction is reset
|
||||||
|
// by the `OnTxReset` callback which comes from outside the tracer. Second, resetting the transaction
|
||||||
|
// also resets the [f.transactionTransient] field which is the one we want to keep on completion
|
||||||
|
// of an isolated transaction.
|
||||||
|
} else {
|
||||||
|
f.block.TransactionTraces = append(f.block.TransactionTraces, trxTrace)
|
||||||
|
|
||||||
// The reset must be done as the very last thing as the CallStack needs to be
|
// The reset must be done as the very last thing as the CallStack needs to be
|
||||||
// properly populated for the `completeTransaction` call above to complete correctly.
|
// properly populated for the `completeTransaction` call above to complete correctly.
|
||||||
f.resetTransaction()
|
f.resetTransaction()
|
||||||
|
}
|
||||||
|
|
||||||
firehoseDebug("trx end")
|
firehoseInfo("trx end (tracer=%s)", f.tracerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) completeTransaction(receipt *types.Receipt) *pbeth.TransactionTrace {
|
func (f *Firehose) completeTransaction(receipt *types.Receipt) *pbeth.TransactionTrace {
|
||||||
firehoseDebug("completing transaction call_count=%d receipt=%s", len(f.transaction.Calls), (*receiptView)(receipt))
|
firehoseInfo("completing transaction (call_count=%d receipt=%s)", len(f.transaction.Calls), (*receiptView)(receipt))
|
||||||
|
|
||||||
// Sorting needs to happen first, before we populate the state reverted
|
// Sorting needs to happen first, before we populate the state reverted
|
||||||
slices.SortFunc(f.transaction.Calls, func(i, j *pbeth.Call) int {
|
slices.SortFunc(f.transaction.Calls, func(i, j *pbeth.Call) int {
|
||||||
|
|
@ -365,8 +569,12 @@ func (f *Firehose) completeTransaction(receipt *types.Receipt) *pbeth.Transactio
|
||||||
f.removeLogBlockIndexOnStateRevertedCalls()
|
f.removeLogBlockIndexOnStateRevertedCalls()
|
||||||
f.assignOrdinalAndIndexToReceiptLogs()
|
f.assignOrdinalAndIndexToReceiptLogs()
|
||||||
|
|
||||||
// Known Firehose issue: This field has never been populated in the old Firehose instrumentation, so it's the same thing for now
|
if *f.applyBackwardCompatibility {
|
||||||
// f.transaction.ReturnData = rootCall.ReturnData
|
// Known Firehose issue: This field has never been populated in the old Firehose instrumentation
|
||||||
|
} else {
|
||||||
|
f.transaction.ReturnData = rootCall.ReturnData
|
||||||
|
}
|
||||||
|
|
||||||
f.transaction.EndOrdinal = f.blockOrdinal.Next()
|
f.transaction.EndOrdinal = f.blockOrdinal.Next()
|
||||||
|
|
||||||
return f.transaction
|
return f.transaction
|
||||||
|
|
@ -417,7 +625,7 @@ func (f *Firehose) assignOrdinalAndIndexToReceiptLogs() {
|
||||||
|
|
||||||
callLogs := []*pbeth.Log{}
|
callLogs := []*pbeth.Log{}
|
||||||
for _, call := range trx.Calls {
|
for _, call := range trx.Calls {
|
||||||
firehoseTrace("checking call reverted=%t logs=%d", call.StateReverted, len(call.Logs))
|
firehoseTrace("checking call (reverted=%t logs=%d)", call.StateReverted, len(call.Logs))
|
||||||
if call.StateReverted {
|
if call.StateReverted {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -498,7 +706,7 @@ func (f *Firehose) OnCallExit(depth int, output []byte, gasUsed uint64, err erro
|
||||||
|
|
||||||
// OnOpcode implements the EVMLogger interface to trace a single step of VM execution.
|
// OnOpcode implements the EVMLogger interface to trace a single step of VM execution.
|
||||||
func (f *Firehose) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
|
func (f *Firehose) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
|
||||||
firehoseTrace("on opcode op=%s gas=%d cost=%d, err=%s", op, gas, cost, errorView(err))
|
firehoseTrace("on opcode (op=%s gas=%d cost=%d, err=%s)", op, gas, cost, errorView(err))
|
||||||
|
|
||||||
if activeCall := f.callStack.Peek(); activeCall != nil {
|
if activeCall := f.callStack.Peek(); activeCall != nil {
|
||||||
opCode := vm.OpCode(op)
|
opCode := vm.OpCode(op)
|
||||||
|
|
@ -515,6 +723,8 @@ func (f *Firehose) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.
|
||||||
//
|
//
|
||||||
// The gas change recording in the previous Firehose patch was done before calling `OnKeccakPreimage` so
|
// The gas change recording in the previous Firehose patch was done before calling `OnKeccakPreimage` so
|
||||||
// we must do the same here.
|
// we must do the same here.
|
||||||
|
//
|
||||||
|
// No need to wrap in apply backward compatibility, the old behavior is fine in all cases.
|
||||||
if cost > 0 {
|
if cost > 0 {
|
||||||
if reason, found := opCodeToGasChangeReasonMap[opCode]; found {
|
if reason, found := opCodeToGasChangeReasonMap[opCode]; found {
|
||||||
activeCall.GasChanges = append(activeCall.GasChanges, f.newGasChange("state", gas, gas-cost, reason))
|
activeCall.GasChanges = append(activeCall.GasChanges, f.newGasChange("state", gas, gas-cost, reason))
|
||||||
|
|
@ -543,18 +753,19 @@ func (f *Firehose) onOpcodeKeccak256(call *pbeth.Call, stack []uint256.Int, memo
|
||||||
f.hasher.Write(preImage)
|
f.hasher.Write(preImage)
|
||||||
f.hasher.Read(f.hasherBuf[:])
|
f.hasher.Read(f.hasherBuf[:])
|
||||||
|
|
||||||
|
encodedData := hex.EncodeToString(preImage)
|
||||||
|
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
// Known Firehose issue: It appears the old Firehose instrumentation have a bug
|
// Known Firehose issue: It appears the old Firehose instrumentation have a bug
|
||||||
// where when the keccak256 preimage is empty, it is written as "." which is
|
// where when the keccak256 preimage is empty, it is written as "." which is
|
||||||
// completely wrong.
|
// completely wrong.
|
||||||
//
|
//
|
||||||
// To keep the same behavior, we will write the preimage as a "." when the encoded
|
// To keep the same behavior, we will write the preimage as a "." when the encoded
|
||||||
// data is an empty string.
|
// data is an empty string.
|
||||||
//
|
|
||||||
// For new chain, this code should be remove so that we just keep `hex.EncodeToString(data)`.
|
|
||||||
encodedData := hex.EncodeToString(preImage)
|
|
||||||
if encodedData == "" {
|
if encodedData == "" {
|
||||||
encodedData = "."
|
encodedData = "."
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
call.KeccakPreimages[hex.EncodeToString(f.hasherBuf[:])] = encodedData
|
call.KeccakPreimages[hex.EncodeToString(f.hasherBuf[:])] = encodedData
|
||||||
}
|
}
|
||||||
|
|
@ -590,32 +801,32 @@ func (f *Firehose) OnOpcodeFault(pc uint64, op byte, gas, cost uint64, scope tra
|
||||||
func (f *Firehose) captureInterpreterStep(activeCall *pbeth.Call, pc uint64, op vm.OpCode, gas, cost uint64, _ tracing.OpContext, rData []byte, depth int, err error) {
|
func (f *Firehose) captureInterpreterStep(activeCall *pbeth.Call, pc uint64, op vm.OpCode, gas, cost uint64, _ tracing.OpContext, rData []byte, depth int, err error) {
|
||||||
_, _, _, _, _, _, _ = pc, op, gas, cost, rData, depth, err
|
_, _, _, _, _, _, _ = pc, op, gas, cost, rData, depth, err
|
||||||
|
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
// for call, we need to process the executed code here
|
// for call, we need to process the executed code here
|
||||||
// since in old firehose executed code calculation depends if the code exist
|
// since in old firehose executed code calculation depends if the code exist
|
||||||
if activeCall.CallType == pbeth.CallType_CALL && !activeCall.ExecutedCode {
|
if activeCall.CallType == pbeth.CallType_CALL && !activeCall.ExecutedCode {
|
||||||
firehoseTrace("Intepreter step for callType_CALL")
|
firehoseTrace("Intepreter step for callType_CALL")
|
||||||
activeCall.ExecutedCode = len(activeCall.Input) > 0
|
activeCall.ExecutedCode = len(activeCall.Input) > 0
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
activeCall.ExecutedCode = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) callStart(source string, callType pbeth.CallType, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
func (f *Firehose) callStart(source string, callType pbeth.CallType, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||||
firehoseDebug("call start source=%s index=%d type=%s input=%s", source, f.callStack.NextIndex(), callType, inputView(input))
|
firehoseDebug("call start (source=%s index=%d type=%s input=%s)", source, f.callStack.NextIndex(), callType, inputView(input))
|
||||||
f.ensureInBlockAndInTrx()
|
f.ensureInBlockAndInTrx()
|
||||||
|
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
// Known Firehose issue: Contract creation call's input is always `nil` in old Firehose patch
|
// Known Firehose issue: Contract creation call's input is always `nil` in old Firehose patch
|
||||||
// due to an oversight that having it in `CodeChange` would be sufficient but this is wrong
|
// due to an oversight that having it in `CodeChange` would be sufficient but this is wrong
|
||||||
// as constructor's input are not part of the code change but part of the call input.
|
// as constructor's input are not part of the code change but part of the call input.
|
||||||
//
|
|
||||||
// New chain integration should remove this `if` statement completely.
|
|
||||||
if callType == pbeth.CallType_CREATE {
|
if callType == pbeth.CallType_CREATE {
|
||||||
input = nil
|
input = nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
call := &pbeth.Call{
|
call := &pbeth.Call{
|
||||||
// Known Firehose issue: Ref 042a2ff03fd623f151d7726314b8aad6 (see below)
|
|
||||||
//
|
|
||||||
// New chain integration should uncomment the code below and remove the `if` statement of the the other ref
|
|
||||||
// BeginOrdinal: f.blockOrdinal.Next(),
|
|
||||||
CallType: callType,
|
CallType: callType,
|
||||||
Depth: 0,
|
Depth: 0,
|
||||||
Caller: from.Bytes(),
|
Caller: from.Bytes(),
|
||||||
|
|
@ -626,23 +837,27 @@ func (f *Firehose) callStart(source string, callType pbeth.CallType, from common
|
||||||
GasLimit: gas,
|
GasLimit: gas,
|
||||||
}
|
}
|
||||||
|
|
||||||
call.ExecutedCode = f.getExecutedCode(f.evm, call)
|
if *f.applyBackwardCompatibility {
|
||||||
|
|
||||||
// Known Firehose issue: The BeginOrdinal of the genesis block root call is never actually
|
// Known Firehose issue: The BeginOrdinal of the genesis block root call is never actually
|
||||||
// incremented and it's always 0.
|
// incremented and it's always 0.
|
||||||
//
|
//
|
||||||
// New chain integration should remove this `if` statement and uncomment code of other ref
|
|
||||||
// above.
|
|
||||||
//
|
|
||||||
// Ref 042a2ff03fd623f151d7726314b8aad6
|
// Ref 042a2ff03fd623f151d7726314b8aad6
|
||||||
|
|
||||||
|
call.BeginOrdinal = 0
|
||||||
|
call.ExecutedCode = f.getExecutedCode(f.evm, call)
|
||||||
|
|
||||||
if f.block.Number != 0 {
|
if f.block.Number != 0 {
|
||||||
call.BeginOrdinal = f.blockOrdinal.Next()
|
call.BeginOrdinal = f.blockOrdinal.Next()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
call.BeginOrdinal = f.blockOrdinal.Next()
|
||||||
|
}
|
||||||
|
|
||||||
if err := f.deferredCallState.MaybePopulateCallAndReset(source, call); err != nil {
|
if err := f.deferredCallState.MaybePopulateCallAndReset(source, call); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
// Known Firehose issue: The `BeginOrdinal` of the root call is incremented but must
|
// Known Firehose issue: The `BeginOrdinal` of the root call is incremented but must
|
||||||
// be assigned back to 0 because of a bug in the console reader. remove on new chain.
|
// be assigned back to 0 because of a bug in the console reader. remove on new chain.
|
||||||
//
|
//
|
||||||
|
|
@ -650,10 +865,32 @@ func (f *Firehose) callStart(source string, callType pbeth.CallType, from common
|
||||||
if source == "root" {
|
if source == "root" {
|
||||||
call.BeginOrdinal = 0
|
call.BeginOrdinal = 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
f.callStack.Push(call)
|
f.callStack.Push(call)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Known Firehose issue: How we computed `executed_code` before was not working for contract's that only
|
||||||
|
// deal with ETH transfer through Solidity `receive()` built-in since those call have `len(input) == 0`
|
||||||
|
//
|
||||||
|
// Older comment keeping for future review:
|
||||||
|
//
|
||||||
|
// For precompiled address however, interpreter does not run so determine there was a bug in Firehose instrumentation where we would
|
||||||
|
//
|
||||||
|
// if call.ExecutedCode || (f.isPrecompiledAddr != nil && f.isPrecompiledAddr(common.BytesToAddress(call.Address))) {
|
||||||
|
// // In this case, we are sure that some code executed. This translates in the old Firehose instrumentation
|
||||||
|
// // that it would have **never** emitted an `account_without_code`.
|
||||||
|
// //
|
||||||
|
// // When no `account_without_code` was executed in the previous Firehose instrumentation,
|
||||||
|
// // the `call.ExecutedCode` defaulted to the condition below
|
||||||
|
// call.ExecutedCode = call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0
|
||||||
|
// } else {
|
||||||
|
//
|
||||||
|
// // In all other cases, we are sure that no code executed. This translates in the old Firehose instrumentation
|
||||||
|
// // that it would have emitted an `account_without_code` and it would have then forced set the `call.ExecutedCode`
|
||||||
|
// // to `false`.
|
||||||
|
// call.ExecutedCode = false
|
||||||
|
// }
|
||||||
func (f *Firehose) getExecutedCode(evm *tracing.VMContext, call *pbeth.Call) bool {
|
func (f *Firehose) getExecutedCode(evm *tracing.VMContext, call *pbeth.Call) bool {
|
||||||
precompile := f.blockIsPrecompiledAddr(common.BytesToAddress(call.Address))
|
precompile := f.blockIsPrecompiledAddr(common.BytesToAddress(call.Address))
|
||||||
|
|
||||||
|
|
@ -661,28 +898,28 @@ func (f *Firehose) getExecutedCode(evm *tracing.VMContext, call *pbeth.Call) boo
|
||||||
if !evm.StateDB.Exist(common.BytesToAddress(call.Address)) &&
|
if !evm.StateDB.Exist(common.BytesToAddress(call.Address)) &&
|
||||||
!precompile && f.blockRules.IsEIP158 &&
|
!precompile && f.blockRules.IsEIP158 &&
|
||||||
(call.Value == nil || call.Value.Native().Sign() == 0) {
|
(call.Value == nil || call.Value.Native().Sign() == 0) {
|
||||||
firehoseDebug("executed code IsSpuriousDragon callType=%s inputLength=%d", call.CallType.String(), len(call.Input))
|
firehoseTrace("executed code IsSpuriousDragon (callType=%s inputLength=%d)", call.CallType.String(), len(call.Input))
|
||||||
return call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0
|
return call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if precompile {
|
if precompile {
|
||||||
firehoseDebug("executed code isprecompile callType=%s inputLength=%d", call.CallType.String(), len(call.Input))
|
firehoseTrace("executed code isprecompile (callType=%s inputLength=%d)", call.CallType.String(), len(call.Input))
|
||||||
return call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0
|
return call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if call.CallType == pbeth.CallType_CALL {
|
if call.CallType == pbeth.CallType_CALL {
|
||||||
firehoseDebug("executed code callType_CALL")
|
firehoseTrace("executed code callType_CALL")
|
||||||
// calculation for executed code will happen in captureInterpreterStep
|
// calculation for executed code will happen in captureInterpreterStep
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
firehoseDebug("executed code default callType=%s inputLength=%d", call.CallType.String(), len(call.Input))
|
firehoseTrace("executed code default (callType=%s inputLength=%d)", call.CallType.String(), len(call.Input))
|
||||||
return call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0
|
return call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) callEnd(source string, output []byte, gasUsed uint64, err error, reverted bool) {
|
func (f *Firehose) callEnd(source string, output []byte, gasUsed uint64, err error, reverted bool) {
|
||||||
firehoseDebug("call end source=%s index=%d output=%s gasUsed=%d err=%s reverted=%t", source, f.callStack.ActiveIndex(), outputView(output), gasUsed, errorView(err), reverted)
|
firehoseDebug("call end (source=%s index=%d output=%s gasUsed=%d err=%s reverted=%t)", source, f.callStack.ActiveIndex(), outputView(output), gasUsed, errorView(err), reverted)
|
||||||
|
|
||||||
if f.latestCallEnterSuicided {
|
if f.latestCallEnterSuicided {
|
||||||
if source != "child" {
|
if source != "child" {
|
||||||
|
|
@ -705,37 +942,6 @@ func (f *Firehose) callEnd(source string, output []byte, gasUsed uint64, err err
|
||||||
call.ReturnData = bytes.Clone(output)
|
call.ReturnData = bytes.Clone(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Known Firehose issue: How we computed `executed_code` before was not working for contract's that only
|
|
||||||
// deal with ETH transfer through Solidity `receive()` built-in since those call have `len(input) == 0`
|
|
||||||
//
|
|
||||||
// New chain should turn the logic into:
|
|
||||||
//
|
|
||||||
// if !call.ExecutedCode && f.isPrecompiledAddr(common.BytesToAddress(call.Address)) {
|
|
||||||
// call.ExecutedCode = true
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// At this point, `call.ExecutedCode` is tied to `EVMInterpreter#Run` execution (in `core/vm/interpreter.go`)
|
|
||||||
// and is `true` if the run/loop of the interpreter executed.
|
|
||||||
//
|
|
||||||
// This means that if `false` the interpreter did not run at all and we would had emitted a
|
|
||||||
// `account_without_code` event in the old Firehose patch which you have set `call.ExecutecCode`
|
|
||||||
// to false
|
|
||||||
//
|
|
||||||
// For precompiled address however, interpreter does not run so determine there was a bug in Firehose instrumentation where we would
|
|
||||||
// if call.ExecutedCode || (f.isPrecompiledAddr != nil && f.isPrecompiledAddr(common.BytesToAddress(call.Address))) {
|
|
||||||
// // In this case, we are sure that some code executed. This translates in the old Firehose instrumentation
|
|
||||||
// // that it would have **never** emitted an `account_without_code`.
|
|
||||||
// //
|
|
||||||
// // When no `account_without_code` was executed in the previous Firehose instrumentation,
|
|
||||||
// // the `call.ExecutedCode` defaulted to the condition below
|
|
||||||
// call.ExecutedCode = call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0
|
|
||||||
// } else {
|
|
||||||
// // In all other cases, we are sure that no code executed. This translates in the old Firehose instrumentation
|
|
||||||
// // that it would have emitted an `account_without_code` and it would have then forced set the `call.ExecutedCode`
|
|
||||||
// // to `false`.
|
|
||||||
// call.ExecutedCode = false
|
|
||||||
// }
|
|
||||||
|
|
||||||
if reverted {
|
if reverted {
|
||||||
failureReason := ""
|
failureReason := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -749,23 +955,23 @@ func (f *Firehose) callEnd(source string, output []byte, gasUsed uint64, err err
|
||||||
// because they do not cost any gas.
|
// because they do not cost any gas.
|
||||||
call.StatusReverted = errors.Is(err, vm.ErrExecutionReverted) || errors.Is(err, vm.ErrInsufficientBalance) || errors.Is(err, vm.ErrDepth)
|
call.StatusReverted = errors.Is(err, vm.ErrExecutionReverted) || errors.Is(err, vm.ErrInsufficientBalance) || errors.Is(err, vm.ErrDepth)
|
||||||
|
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
// Known Firehose issue: FIXME Document!
|
// Known Firehose issue: FIXME Document!
|
||||||
if !call.ExecutedCode && (errors.Is(err, vm.ErrInsufficientBalance) || errors.Is(err, vm.ErrDepth)) {
|
if !call.ExecutedCode && (errors.Is(err, vm.ErrInsufficientBalance) || errors.Is(err, vm.ErrDepth)) {
|
||||||
call.ExecutedCode = call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0
|
call.ExecutedCode = call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
// Known Firehose issue: The EndOrdinal of the genesis block root call is never actually
|
// Known Firehose issue: The EndOrdinal of the genesis block root call is never actually
|
||||||
// incremented and it's always 0.
|
// incremented and it's always 0.
|
||||||
//
|
|
||||||
// New chain should turn the logic into:
|
|
||||||
//
|
|
||||||
// call.EndOrdinal = f.blockOrdinal.Next()
|
|
||||||
//
|
|
||||||
// Removing the condition around the `EndOrdinal` assignment (keeping it!)
|
|
||||||
if f.block.Number != 0 {
|
if f.block.Number != 0 {
|
||||||
call.EndOrdinal = f.blockOrdinal.Next()
|
call.EndOrdinal = f.blockOrdinal.Next()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
call.EndOrdinal = f.blockOrdinal.Next()
|
||||||
|
}
|
||||||
|
|
||||||
f.transaction.Calls = append(f.transaction.Calls, call)
|
f.transaction.Calls = append(f.transaction.Calls, call)
|
||||||
}
|
}
|
||||||
|
|
@ -788,7 +994,9 @@ func (f *Firehose) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) {
|
||||||
for _, addr := range sortedKeys(alloc) {
|
for _, addr := range sortedKeys(alloc) {
|
||||||
account := alloc[addr]
|
account := alloc[addr]
|
||||||
|
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
f.OnNewAccount(addr, false)
|
f.OnNewAccount(addr, false)
|
||||||
|
}
|
||||||
|
|
||||||
if account.Balance != nil && account.Balance.Sign() != 0 {
|
if account.Balance != nil && account.Balance.Sign() != 0 {
|
||||||
activeCall := f.callStack.Peek()
|
activeCall := f.callStack.Peek()
|
||||||
|
|
@ -836,14 +1044,14 @@ func (f *Firehose) OnBalanceChange(a common.Address, prev, new *big.Int, reason
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
// Known Firehose issue: It's possible to burn Ether by sending some ether to a suicided account. In those case,
|
// Known Firehose issue: It's possible to burn Ether by sending some ether to a suicided account. In those case,
|
||||||
// at theend of block producing, StateDB finalize the block by burning ether from the account. This is something
|
// at theend of block producing, StateDB finalize the block by burning ether from the account. This is something
|
||||||
// we were not tracking in the old Firehose instrumentation.
|
// we were not tracking in the old Firehose instrumentation.
|
||||||
//
|
|
||||||
// New chain integration should remove this `if` statement all along.
|
|
||||||
if reason == tracing.BalanceDecreaseSelfdestructBurn {
|
if reason == tracing.BalanceDecreaseSelfdestructBurn {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
f.ensureInBlockOrTrx()
|
f.ensureInBlockOrTrx()
|
||||||
|
|
||||||
|
|
@ -865,7 +1073,7 @@ func (f *Firehose) OnBalanceChange(a common.Address, prev, new *big.Int, reason
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) newBalanceChange(tag string, address common.Address, oldValue, newValue *big.Int, reason pbeth.BalanceChange_Reason) *pbeth.BalanceChange {
|
func (f *Firehose) newBalanceChange(tag string, address common.Address, oldValue, newValue *big.Int, reason pbeth.BalanceChange_Reason) *pbeth.BalanceChange {
|
||||||
firehoseTrace("balance changed tag=%s before=%d after=%d reason=%s", tag, oldValue, newValue, reason)
|
firehoseTrace("balance changed (tag=%s before=%d after=%d reason=%s)", tag, oldValue, newValue, reason)
|
||||||
|
|
||||||
if reason == pbeth.BalanceChange_REASON_UNKNOWN {
|
if reason == pbeth.BalanceChange_REASON_UNKNOWN {
|
||||||
panic(fmt.Errorf("received unknown balance change reason %s", reason))
|
panic(fmt.Errorf("received unknown balance change reason %s", reason))
|
||||||
|
|
@ -915,7 +1123,7 @@ func (f *Firehose) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev
|
||||||
if f.transaction != nil {
|
if f.transaction != nil {
|
||||||
activeCall := f.callStack.Peek()
|
activeCall := f.callStack.Peek()
|
||||||
if activeCall == nil {
|
if activeCall == nil {
|
||||||
f.panicInvalidState("caller expected to be in call state but we were not, this is a bug")
|
f.panicInvalidState("caller expected to be in call state but we were not, this is a bug", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
activeCall.CodeChanges = append(activeCall.CodeChanges, change)
|
activeCall.CodeChanges = append(activeCall.CodeChanges, change)
|
||||||
|
|
@ -946,7 +1154,7 @@ func (f *Firehose) OnLog(l *types.Log) {
|
||||||
}
|
}
|
||||||
|
|
||||||
activeCall := f.callStack.Peek()
|
activeCall := f.callStack.Peek()
|
||||||
firehoseTrace("adding log to call address=%s call=%d (has already %d logs)", l.Address, activeCall.Index, len(activeCall.Logs))
|
firehoseTrace("adding log to call (address=%s call=%d [has already %d logs])", l.Address, activeCall.Index, len(activeCall.Logs))
|
||||||
|
|
||||||
activeCall.Logs = append(activeCall.Logs, &pbeth.Log{
|
activeCall.Logs = append(activeCall.Logs, &pbeth.Log{
|
||||||
Address: l.Address.Bytes(),
|
Address: l.Address.Bytes(),
|
||||||
|
|
@ -961,6 +1169,17 @@ func (f *Firehose) OnLog(l *types.Log) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) OnNewAccount(a common.Address, previousDataExists bool) {
|
func (f *Firehose) OnNewAccount(a common.Address, previousDataExists bool) {
|
||||||
|
// Newer Firehose instrumentation does not track OnNewAccount anymore since it's bogus
|
||||||
|
// and was removed from the Geth live tracer.
|
||||||
|
if !*f.applyBackwardCompatibility {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Known Firehose issue: The current Firehose instrumentation emits multiple
|
||||||
|
// time the same `OnNewAccount` event for the same account when such account
|
||||||
|
// exists in the past. For now, do nothing and keep the legacy behavior.
|
||||||
|
_ = previousDataExists
|
||||||
|
|
||||||
f.ensureInBlockOrTrx()
|
f.ensureInBlockOrTrx()
|
||||||
if f.transaction == nil {
|
if f.transaction == nil {
|
||||||
// We receive OnNewAccount on finalization of the block which means there is no
|
// We receive OnNewAccount on finalization of the block which means there is no
|
||||||
|
|
@ -975,11 +1194,6 @@ func (f *Firehose) OnNewAccount(a common.Address, previousDataExists bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Known Firehose issue: The current Firehose instrumentation emits multiple
|
|
||||||
// time the same `OnNewAccount` event for the same account when such account
|
|
||||||
// exists in the past. For now, do nothing and keep the legacy behavior.
|
|
||||||
_ = previousDataExists
|
|
||||||
|
|
||||||
if call := f.callStack.Peek(); call != nil && call.CallType == pbeth.CallType_STATIC && f.blockIsPrecompiledAddr(common.Address(call.Address)) {
|
if call := f.callStack.Peek(); call != nil && call.CallType == pbeth.CallType_STATIC && f.blockIsPrecompiledAddr(common.Address(call.Address)) {
|
||||||
// Old Firehose ignore those, we do the same
|
// Old Firehose ignore those, we do the same
|
||||||
return
|
return
|
||||||
|
|
@ -1011,11 +1225,10 @@ func (f *Firehose) OnGasChange(old, new uint64, reason tracing.GasChangeReason)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *f.applyBackwardCompatibility {
|
||||||
// Known Firehose issue: New geth native tracer added more gas change, some that we were indeed missing and
|
// Known Firehose issue: New geth native tracer added more gas change, some that we were indeed missing and
|
||||||
// should have included in our previous patch.
|
// should have included in our previous patch.
|
||||||
//
|
//
|
||||||
// For new chain, this code should be remove so that they are included and useful to user.
|
|
||||||
//
|
|
||||||
// Ref eb1916a67d9bea03df16a7a3e2cfac72
|
// Ref eb1916a67d9bea03df16a7a3e2cfac72
|
||||||
if reason == tracing.GasChangeTxInitialBalance ||
|
if reason == tracing.GasChangeTxInitialBalance ||
|
||||||
reason == tracing.GasChangeTxRefunds ||
|
reason == tracing.GasChangeTxRefunds ||
|
||||||
|
|
@ -1024,6 +1237,7 @@ func (f *Firehose) OnGasChange(old, new uint64, reason tracing.GasChangeReason)
|
||||||
reason == tracing.GasChangeCallLeftOverReturned {
|
reason == tracing.GasChangeCallLeftOverReturned {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
activeCall := f.callStack.Peek()
|
activeCall := f.callStack.Peek()
|
||||||
change := f.newGasChange("tracer", old, new, gasChangeReasonFromChain(reason))
|
change := f.newGasChange("tracer", old, new, gasChangeReasonFromChain(reason))
|
||||||
|
|
@ -1038,7 +1252,7 @@ func (f *Firehose) OnGasChange(old, new uint64, reason tracing.GasChangeReason)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) newGasChange(tag string, oldValue, newValue uint64, reason pbeth.GasChange_Reason) *pbeth.GasChange {
|
func (f *Firehose) newGasChange(tag string, oldValue, newValue uint64, reason pbeth.GasChange_Reason) *pbeth.GasChange {
|
||||||
firehoseTrace("gas consumed tag=%s before=%d after=%d reason=%s", tag, oldValue, newValue, reason)
|
firehoseTrace("gas consumed (tag=%s before=%d after=%d reason=%s)", tag, oldValue, newValue, reason)
|
||||||
|
|
||||||
// Should already be checked by the caller, but we keep it here for safety if the code ever change
|
// Should already be checked by the caller, but we keep it here for safety if the code ever change
|
||||||
if reason == pbeth.GasChange_REASON_UNKNOWN {
|
if reason == pbeth.GasChange_REASON_UNKNOWN {
|
||||||
|
|
@ -1055,23 +1269,23 @@ func (f *Firehose) newGasChange(tag string, oldValue, newValue uint64, reason pb
|
||||||
|
|
||||||
func (f *Firehose) ensureBlockChainInit() {
|
func (f *Firehose) ensureBlockChainInit() {
|
||||||
if f.chainConfig == nil {
|
if f.chainConfig == nil {
|
||||||
f.panicInvalidState("the OnBlockchainInit hook should have been called at this point")
|
f.panicInvalidState("the OnBlockchainInit hook should have been called at this point", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) ensureInBlock() {
|
func (f *Firehose) ensureInBlock(callerSkip int) {
|
||||||
if f.block == nil {
|
if f.block == nil {
|
||||||
f.panicInvalidState("caller expected to be in block state but we were not, this is a bug")
|
f.panicInvalidState("caller expected to be in block state but we were not, this is a bug", callerSkip+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.chainConfig == nil {
|
if f.chainConfig == nil {
|
||||||
f.panicInvalidState("the OnBlockchainInit hook should have been called at this point")
|
f.panicInvalidState("the OnBlockchainInit hook should have been called at this point", callerSkip+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) ensureNotInBlock() {
|
func (f *Firehose) ensureNotInBlock(callerSkip int) {
|
||||||
if f.block != nil {
|
if f.block != nil {
|
||||||
f.panicInvalidState("caller expected to not be in block state but we were, this is a bug")
|
f.panicInvalidState("caller expected to not be in block state but we were, this is a bug", callerSkip+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1080,63 +1294,76 @@ func (f *Firehose) ensureNotInBlock() {
|
||||||
var _ = new(Firehose).ensureNotInBlock
|
var _ = new(Firehose).ensureNotInBlock
|
||||||
|
|
||||||
func (f *Firehose) ensureInBlockAndInTrx() {
|
func (f *Firehose) ensureInBlockAndInTrx() {
|
||||||
f.ensureInBlock()
|
f.ensureInBlock(2)
|
||||||
|
|
||||||
if f.transaction == nil {
|
if f.transaction == nil {
|
||||||
f.panicInvalidState("caller expected to be in transaction state but we were not, this is a bug")
|
f.panicInvalidState("caller expected to be in transaction state but we were not, this is a bug", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) ensureInBlockAndNotInTrx() {
|
func (f *Firehose) ensureInBlockAndNotInTrx() {
|
||||||
f.ensureInBlock()
|
f.ensureInBlock(2)
|
||||||
|
|
||||||
if f.transaction != nil {
|
if f.transaction != nil {
|
||||||
f.panicInvalidState("caller expected to not be in transaction state but we were, this is a bug")
|
f.panicInvalidState("caller expected to not be in transaction state but we were, this is a bug", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) ensureInBlockAndNotInTrxAndNotInCall() {
|
func (f *Firehose) ensureInBlockAndNotInTrxAndNotInCall() {
|
||||||
f.ensureInBlock()
|
f.ensureInBlock(2)
|
||||||
|
|
||||||
if f.transaction != nil {
|
if f.transaction != nil {
|
||||||
f.panicInvalidState("caller expected to not be in transaction state but we were, this is a bug")
|
f.panicInvalidState("caller expected to not be in transaction state but we were, this is a bug", 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.callStack.HasActiveCall() {
|
if f.callStack.HasActiveCall() {
|
||||||
f.panicInvalidState("caller expected to not be in call state but we were, this is a bug")
|
f.panicInvalidState("caller expected to not be in call state but we were, this is a bug", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) ensureInBlockOrTrx() {
|
func (f *Firehose) ensureInBlockOrTrx() {
|
||||||
if f.transaction == nil && f.block == nil {
|
if f.transaction == nil && f.block == nil {
|
||||||
f.panicInvalidState("caller expected to be in either block or transaction state but we were not, this is a bug")
|
f.panicInvalidState("caller expected to be in either block or transaction state but we were not, this is a bug", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) ensureInBlockAndInTrxAndInCall() {
|
func (f *Firehose) ensureInBlockAndInTrxAndInCall() {
|
||||||
if f.transaction == nil || f.block == nil {
|
if f.transaction == nil || f.block == nil {
|
||||||
f.panicInvalidState("caller expected to be in block and in transaction but we were not, this is a bug")
|
f.panicInvalidState("caller expected to be in block and in transaction but we were not, this is a bug", 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !f.callStack.HasActiveCall() {
|
if !f.callStack.HasActiveCall() {
|
||||||
f.panicInvalidState("caller expected to be in call state but we were not, this is a bug")
|
f.panicInvalidState("caller expected to be in call state but we were not, this is a bug", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) ensureInCall() {
|
func (f *Firehose) ensureInCall() {
|
||||||
if f.block == nil {
|
if f.block == nil {
|
||||||
f.panicInvalidState("caller expected to be in call state but we were not, this is a bug")
|
f.panicInvalidState("caller expected to be in call state but we were not, this is a bug", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) ensureInSystemCall() {
|
func (f *Firehose) ensureInSystemCall() {
|
||||||
if !f.inSystemCall {
|
if !f.inSystemCall {
|
||||||
f.panicInvalidState("call expected to be in system call state but we were not, this is a bug")
|
f.panicInvalidState("call expected to be in system call state but we were not, this is a bug", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) panicInvalidState(msg string) string {
|
func (f *Firehose) panicInvalidState(msg string, callerSkip int) string {
|
||||||
panic(fmt.Errorf("%s (init=%t, inBlock=%t, inTransaction=%t, inCall=%t)", msg, f.chainConfig != nil, f.block != nil, f.transaction != nil, f.callStack.HasActiveCall()))
|
caller := "N/A"
|
||||||
|
if _, file, line, ok := runtime.Caller(callerSkip); ok {
|
||||||
|
caller = fmt.Sprintf("%s:%d", file, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.block != nil {
|
||||||
|
msg += fmt.Sprintf(" at block #%d (%s)", f.block.Number, hex.EncodeToString(f.block.Hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.transaction != nil {
|
||||||
|
msg += fmt.Sprintf(" in transaction %s", hex.EncodeToString(f.transaction.Hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(fmt.Errorf("%s (caller=%s, init=%t, inBlock=%t, inTransaction=%t, inCall=%t)", msg, caller, f.chainConfig != nil, f.block != nil, f.transaction != nil, f.callStack.HasActiveCall()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// printToFirehose is an easy way to print to Firehose format, it essentially
|
// printToFirehose is an easy way to print to Firehose format, it essentially
|
||||||
|
|
@ -1426,23 +1653,11 @@ func balanceChangeReasonFromChain(reason tracing.BalanceChangeReason) pbeth.Bala
|
||||||
}
|
}
|
||||||
|
|
||||||
var gasChangeReasonToPb = map[tracing.GasChangeReason]pbeth.GasChange_Reason{
|
var gasChangeReasonToPb = map[tracing.GasChangeReason]pbeth.GasChange_Reason{
|
||||||
// Known Firehose issue: Those are new gas change trace that we were missing initially in our old
|
tracing.GasChangeTxInitialBalance: pbeth.GasChange_REASON_TX_INITIAL_BALANCE,
|
||||||
// Firehose patch. See Known Firehose issue referenced eb1916a67d9bea03df16a7a3e2cfac72 for details
|
tracing.GasChangeTxRefunds: pbeth.GasChange_REASON_TX_REFUNDS,
|
||||||
// search for the id within this project to find back all links).
|
tracing.GasChangeTxLeftOverReturned: pbeth.GasChange_REASON_TX_LEFT_OVER_RETURNED,
|
||||||
//
|
tracing.GasChangeCallInitialBalance: pbeth.GasChange_REASON_CALL_INITIAL_BALANCE,
|
||||||
// New chain should uncomment the code below and remove the same assigments to UNKNOWN
|
tracing.GasChangeCallLeftOverReturned: pbeth.GasChange_REASON_CALL_LEFT_OVER_RETURNED,
|
||||||
//
|
|
||||||
// tracing.GasChangeTxInitialBalance: pbeth.GasChange_REASON_TX_INITIAL_BALANCE,
|
|
||||||
// tracing.GasChangeTxRefunds: pbeth.GasChange_REASON_TX_REFUNDS,
|
|
||||||
// tracing.GasChangeTxLeftOverReturned: pbeth.GasChange_REASON_TX_LEFT_OVER_RETURNED,
|
|
||||||
// tracing.GasChangeCallInitialBalance: pbeth.GasChange_REASON_CALL_INITIAL_BALANCE,
|
|
||||||
// tracing.GasChangeCallLeftOverReturned: pbeth.GasChange_REASON_CALL_LEFT_OVER_RETURNED,
|
|
||||||
tracing.GasChangeTxInitialBalance: pbeth.GasChange_REASON_UNKNOWN,
|
|
||||||
tracing.GasChangeTxRefunds: pbeth.GasChange_REASON_UNKNOWN,
|
|
||||||
tracing.GasChangeTxLeftOverReturned: pbeth.GasChange_REASON_UNKNOWN,
|
|
||||||
tracing.GasChangeCallInitialBalance: pbeth.GasChange_REASON_UNKNOWN,
|
|
||||||
tracing.GasChangeCallLeftOverReturned: pbeth.GasChange_REASON_UNKNOWN,
|
|
||||||
|
|
||||||
tracing.GasChangeTxIntrinsicGas: pbeth.GasChange_REASON_INTRINSIC_GAS,
|
tracing.GasChangeTxIntrinsicGas: pbeth.GasChange_REASON_INTRINSIC_GAS,
|
||||||
tracing.GasChangeCallContractCreation: pbeth.GasChange_REASON_CONTRACT_CREATION,
|
tracing.GasChangeCallContractCreation: pbeth.GasChange_REASON_CONTRACT_CREATION,
|
||||||
tracing.GasChangeCallContractCreation2: pbeth.GasChange_REASON_CONTRACT_CREATION2,
|
tracing.GasChangeCallContractCreation2: pbeth.GasChange_REASON_CONTRACT_CREATION2,
|
||||||
|
|
@ -1513,6 +1728,12 @@ func FirehoseDebug(msg string, args ...interface{}) {
|
||||||
firehoseDebug(msg, args...)
|
firehoseDebug(msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func firehoseInfo(msg string, args ...interface{}) {
|
||||||
|
if isFirehoseInfoEnabled {
|
||||||
|
fmt.Fprintf(os.Stderr, "[Firehose] "+msg+"\n", args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func firehoseDebug(msg string, args ...interface{}) {
|
func firehoseDebug(msg string, args ...interface{}) {
|
||||||
if isFirehoseDebugEnabled {
|
if isFirehoseDebugEnabled {
|
||||||
fmt.Fprintf(os.Stderr, "[Firehose] "+msg+"\n", args...)
|
fmt.Fprintf(os.Stderr, "[Firehose] "+msg+"\n", args...)
|
||||||
|
|
@ -1780,6 +2001,11 @@ type FinalityStatus struct {
|
||||||
LastIrreversibleBlockHash []byte
|
LastIrreversibleBlockHash []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FinalityStatus) populate(finalNumber uint64, finalHash []byte) {
|
||||||
|
s.LastIrreversibleBlockNumber = finalNumber
|
||||||
|
s.LastIrreversibleBlockHash = finalHash
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FinalityStatus) populateFromChain(finalHeader *types.Header) {
|
func (s *FinalityStatus) populateFromChain(finalHeader *types.Header) {
|
||||||
if finalHeader == nil {
|
if finalHeader == nil {
|
||||||
s.Reset()
|
s.Reset()
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,22 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
pbeth "github.com/ethereum/go-ethereum/pb/sf/ethereum/type/v2"
|
pbeth "github.com/ethereum/go-ethereum/pb/sf/ethereum/type/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -286,3 +293,178 @@ func filter[S ~[]T, T any](s S, f func(T) bool) (out S) {
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFirehose_reorderIsolatedTransactionsAndOrdinals(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
populate func(t *Firehose)
|
||||||
|
expectedBlockFile string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
populate: func(t *Firehose) {
|
||||||
|
t.OnBlockStart(blockEvent(1))
|
||||||
|
|
||||||
|
// Simulated GetTxTracer being called
|
||||||
|
t.blockReorderOrdinalOnce.Do(func() {
|
||||||
|
t.blockReorderOrdinal = true
|
||||||
|
t.blockReorderOrdinalSnapshot = t.blockOrdinal.value
|
||||||
|
})
|
||||||
|
|
||||||
|
t.blockOrdinal.Reset()
|
||||||
|
t.onTxStart(txEvent(), hex2Hash("CC"), from, to)
|
||||||
|
t.OnCallEnter(0, byte(vm.CALL), from, to, nil, 0, nil)
|
||||||
|
t.OnBalanceChange(empty, b(1), b(2), 0)
|
||||||
|
t.OnCallExit(0, nil, 0, nil, false)
|
||||||
|
t.OnTxEnd(txReceiptEvent(2), nil)
|
||||||
|
|
||||||
|
t.blockOrdinal.Reset()
|
||||||
|
t.onTxStart(txEvent(), hex2Hash("AA"), from, to)
|
||||||
|
t.OnCallEnter(0, byte(vm.CALL), from, to, nil, 0, nil)
|
||||||
|
t.OnBalanceChange(empty, b(1), b(2), 0)
|
||||||
|
t.OnCallExit(0, nil, 0, nil, false)
|
||||||
|
t.OnTxEnd(txReceiptEvent(0), nil)
|
||||||
|
|
||||||
|
t.blockOrdinal.Reset()
|
||||||
|
t.onTxStart(txEvent(), hex2Hash("BB"), from, to)
|
||||||
|
t.OnCallEnter(0, byte(vm.CALL), from, to, nil, 0, nil)
|
||||||
|
t.OnBalanceChange(empty, b(1), b(2), 0)
|
||||||
|
t.OnCallExit(0, nil, 0, nil, false)
|
||||||
|
t.OnTxEnd(txReceiptEvent(1), nil)
|
||||||
|
},
|
||||||
|
expectedBlockFile: "testdata/firehose/reorder-ordinals-empty.golden.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
f := NewFirehose(&FirehoseConfig{
|
||||||
|
ApplyBackwardCompatibility: ptr(false),
|
||||||
|
})
|
||||||
|
f.OnBlockchainInit(params.AllEthashProtocolChanges)
|
||||||
|
|
||||||
|
tt.populate(f)
|
||||||
|
|
||||||
|
f.reorderIsolatedTransactionsAndOrdinals()
|
||||||
|
|
||||||
|
goldenUpdate := os.Getenv("GOLDEN_UPDATE") == "true"
|
||||||
|
goldenPath := tt.expectedBlockFile
|
||||||
|
|
||||||
|
if !goldenUpdate && !fileExits(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(f.block)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if goldenUpdate {
|
||||||
|
require.NoError(t, os.WriteFile(goldenPath, content, os.ModePerm))
|
||||||
|
}
|
||||||
|
|
||||||
|
expected, err := os.ReadFile(goldenPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expectedBlock := &pbeth.Block{}
|
||||||
|
protojson.Unmarshal(expected, expectedBlock)
|
||||||
|
|
||||||
|
if !proto.Equal(expectedBlock, f.block) {
|
||||||
|
assert.Equal(t, expectedBlock, f.block, "Run 'GOLDEN_UPDATE=true go test ./... -run %q' to update golden file", t.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
seenOrdinals := make(map[uint64]int)
|
||||||
|
|
||||||
|
walkChanges(f.block.BalanceChanges, seenOrdinals)
|
||||||
|
walkChanges(f.block.CodeChanges, seenOrdinals)
|
||||||
|
walkCalls(f.block.SystemCalls, seenOrdinals)
|
||||||
|
|
||||||
|
for _, trx := range f.block.TransactionTraces {
|
||||||
|
seenOrdinals[trx.BeginOrdinal] = seenOrdinals[trx.BeginOrdinal] + 1
|
||||||
|
seenOrdinals[trx.EndOrdinal] = seenOrdinals[trx.EndOrdinal] + 1
|
||||||
|
walkCalls(trx.Calls, seenOrdinals)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No ordinal should be seen more than once
|
||||||
|
for ordinal, count := range seenOrdinals {
|
||||||
|
assert.Equal(t, 1, count, "Ordinal %d seen %d times", ordinal, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
ordinals := maps.Keys(seenOrdinals)
|
||||||
|
slices.Sort(ordinals)
|
||||||
|
|
||||||
|
// All ordinals should be in stricly increasing order
|
||||||
|
prev := -1
|
||||||
|
for _, ordinal := range ordinals {
|
||||||
|
if prev != -1 {
|
||||||
|
assert.Equal(t, prev+1, int(ordinal), "Ordinal %d is not in sequence", ordinal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkCalls(calls []*pbeth.Call, ordinals map[uint64]int) {
|
||||||
|
for _, call := range calls {
|
||||||
|
walkCall(call, ordinals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkCall(call *pbeth.Call, ordinals map[uint64]int) {
|
||||||
|
ordinals[call.BeginOrdinal] = ordinals[call.BeginOrdinal] + 1
|
||||||
|
ordinals[call.EndOrdinal] = ordinals[call.EndOrdinal] + 1
|
||||||
|
|
||||||
|
walkChanges(call.BalanceChanges, ordinals)
|
||||||
|
walkChanges(call.CodeChanges, ordinals)
|
||||||
|
walkChanges(call.Logs, ordinals)
|
||||||
|
walkChanges(call.StorageChanges, ordinals)
|
||||||
|
walkChanges(call.NonceChanges, ordinals)
|
||||||
|
walkChanges(call.GasChanges, ordinals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkChanges[T any](changes []T, ordinals map[uint64]int) {
|
||||||
|
for _, change := range changes {
|
||||||
|
var x any = change
|
||||||
|
if v, ok := x.(interface{ GetOrdinal() uint64 }); ok {
|
||||||
|
ordinals[v.GetOrdinal()] = ordinals[v.GetOrdinal()] + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var b = big.NewInt
|
||||||
|
var empty, from, to = common.HexToAddress("00"), common.HexToAddress("01"), common.HexToAddress("02")
|
||||||
|
var hex2Hash = common.HexToHash
|
||||||
|
|
||||||
|
func fileExits(t *testing.T, path string) bool {
|
||||||
|
t.Helper()
|
||||||
|
stat, err := os.Stat(path)
|
||||||
|
return err == nil && !stat.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func txEvent() *types.Transaction {
|
||||||
|
return types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: 0,
|
||||||
|
GasPrice: big.NewInt(1),
|
||||||
|
Gas: 1,
|
||||||
|
To: &to,
|
||||||
|
Value: big.NewInt(1),
|
||||||
|
Data: nil,
|
||||||
|
V: big.NewInt(1),
|
||||||
|
R: big.NewInt(1),
|
||||||
|
S: big.NewInt(1),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func txReceiptEvent(txIndex uint) *types.Receipt {
|
||||||
|
return &types.Receipt{
|
||||||
|
Status: 1,
|
||||||
|
TransactionIndex: txIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockEvent(height uint64) tracing.BlockEvent {
|
||||||
|
return tracing.BlockEvent{
|
||||||
|
Block: types.NewBlock(&types.Header{
|
||||||
|
Number: big.NewInt(int64(height)),
|
||||||
|
}, nil, nil, nil, nil),
|
||||||
|
TD: b(1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
122
eth/tracers/testdata/firehose/reorder-ordinals-empty.golden.json
vendored
Executable file
122
eth/tracers/testdata/firehose/reorder-ordinals-empty.golden.json
vendored
Executable file
|
|
@ -0,0 +1,122 @@
|
||||||
|
{
|
||||||
|
"hash": "sr0uqeYWyrLV1vijvIG6fe+0mu8LRG6FLMHv5UmUxK8=",
|
||||||
|
"number": "1",
|
||||||
|
"size": "501",
|
||||||
|
"header": {
|
||||||
|
"parentHash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"uncleHash": "HcxN6N7HXXqrhbVntszUGtMSRRuUinQT8KFC/UDUk0c=",
|
||||||
|
"coinbase": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"stateRoot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"transactionsRoot": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=",
|
||||||
|
"receiptRoot": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=",
|
||||||
|
"logsBloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||||
|
"difficulty": {
|
||||||
|
"bytes": "AA=="
|
||||||
|
},
|
||||||
|
"totalDifficulty": {
|
||||||
|
"bytes": "AQ=="
|
||||||
|
},
|
||||||
|
"number": "1",
|
||||||
|
"timestamp": "1970-01-01T00:00:00Z",
|
||||||
|
"mixHash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"hash": "sr0uqeYWyrLV1vijvIG6fe+0mu8LRG6FLMHv5UmUxK8="
|
||||||
|
},
|
||||||
|
"transactionTraces": [
|
||||||
|
{
|
||||||
|
"to": "AAAAAAAAAAAAAAAAAAAAAAAAAAI=",
|
||||||
|
"gasPrice": {
|
||||||
|
"bytes": "AQ=="
|
||||||
|
},
|
||||||
|
"gasLimit": "1",
|
||||||
|
"value": {
|
||||||
|
"bytes": "AQ=="
|
||||||
|
},
|
||||||
|
"v": "AQ==",
|
||||||
|
"r": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"s": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKo=",
|
||||||
|
"from": "AAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"beginOrdinal": "1",
|
||||||
|
"endOrdinal": "4",
|
||||||
|
"status": "SUCCEEDED",
|
||||||
|
"receipt": {
|
||||||
|
"logsBloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
|
||||||
|
},
|
||||||
|
"calls": [
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"callType": "CALL",
|
||||||
|
"caller": "AAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"address": "AAAAAAAAAAAAAAAAAAAAAAAAAAI=",
|
||||||
|
"beginOrdinal": "2",
|
||||||
|
"endOrdinal": "3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"to": "AAAAAAAAAAAAAAAAAAAAAAAAAAI=",
|
||||||
|
"gasPrice": {
|
||||||
|
"bytes": "AQ=="
|
||||||
|
},
|
||||||
|
"gasLimit": "1",
|
||||||
|
"value": {
|
||||||
|
"bytes": "AQ=="
|
||||||
|
},
|
||||||
|
"v": "AQ==",
|
||||||
|
"r": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"s": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"index": 1,
|
||||||
|
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALs=",
|
||||||
|
"from": "AAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"beginOrdinal": "5",
|
||||||
|
"endOrdinal": "8",
|
||||||
|
"status": "SUCCEEDED",
|
||||||
|
"receipt": {
|
||||||
|
"logsBloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
|
||||||
|
},
|
||||||
|
"calls": [
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"callType": "CALL",
|
||||||
|
"caller": "AAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"address": "AAAAAAAAAAAAAAAAAAAAAAAAAAI=",
|
||||||
|
"beginOrdinal": "6",
|
||||||
|
"endOrdinal": "7"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"to": "AAAAAAAAAAAAAAAAAAAAAAAAAAI=",
|
||||||
|
"gasPrice": {
|
||||||
|
"bytes": "AQ=="
|
||||||
|
},
|
||||||
|
"gasLimit": "1",
|
||||||
|
"value": {
|
||||||
|
"bytes": "AQ=="
|
||||||
|
},
|
||||||
|
"v": "AQ==",
|
||||||
|
"r": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"s": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"index": 2,
|
||||||
|
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMw=",
|
||||||
|
"from": "AAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"beginOrdinal": "9",
|
||||||
|
"endOrdinal": "12",
|
||||||
|
"status": "SUCCEEDED",
|
||||||
|
"receipt": {
|
||||||
|
"logsBloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
|
||||||
|
},
|
||||||
|
"calls": [
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"callType": "CALL",
|
||||||
|
"caller": "AAAAAAAAAAAAAAAAAAAAAAAAAAE=",
|
||||||
|
"address": "AAAAAAAAAAAAAAAAAAAAAAAAAAI=",
|
||||||
|
"beginOrdinal": "10",
|
||||||
|
"endOrdinal": "11"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ver": 4
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue