From e73963ab402dcf49c0efbf77c1d967fa20a689e7 Mon Sep 17 00:00:00 2001 From: Matthieu Vachon Date: Wed, 10 Apr 2024 12:18:54 -0400 Subject: [PATCH] Firehose tracer addition --- app/app.go | 83 +- app/eth_replay.go | 4 +- buf.gen.yaml | 6 + evmrpc/config.go | 14 + evmrpc/config_test.go | 8 +- go.sum | 2 + pb/sf/ethereum/type/v2/type.go | 27 + pb/sf/ethereum/type/v2/type.pb.go | 3691 +++++++++++++++++ precompiles/bank/bank.go | 18 +- precompiles/bank/bank_test.go | 39 + precompiles/wasmd/wasmd.go | 6 +- x/evm/keeper/msg_server.go | 90 +- x/evm/module.go | 19 + x/evm/module_test.go | 54 +- x/evm/state/balance.go | 1 - x/evm/tracers/balances.go | 82 + x/evm/tracers/firehose.go | 2259 ++++++++++ x/evm/tracers/firehose_test.go | 471 +++ x/evm/tracers/registry.go | 31 + .../reorder-ordinals-empty.golden.json | 122 + x/evm/tracers/tracing.go | 112 + x/evm/tracing/hooks.go | 77 + 22 files changed, 7193 insertions(+), 23 deletions(-) create mode 100644 buf.gen.yaml create mode 100644 pb/sf/ethereum/type/v2/type.go create mode 100644 pb/sf/ethereum/type/v2/type.pb.go create mode 100644 x/evm/tracers/balances.go create mode 100644 x/evm/tracers/firehose.go create mode 100644 x/evm/tracers/firehose_test.go create mode 100644 x/evm/tracers/registry.go create mode 100755 x/evm/tracers/testdata/firehose/reorder-ordinals-empty.golden.json create mode 100644 x/evm/tracers/tracing.go create mode 100644 x/evm/tracing/hooks.go diff --git a/app/app.go b/app/app.go index fc9fa4c43..d10533cb3 100644 --- a/app/app.go +++ b/app/app.go @@ -7,14 +7,19 @@ import ( "encoding/json" "fmt" "io" + "math/big" "os" "path/filepath" "strings" "sync" "time" + ethcommon "github.com/ethereum/go-ethereum/common" + ethhexutil "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" ethrpc "github.com/ethereum/go-ethereum/rpc" + "github.com/sei-protocol/sei-chain/app/antedecorators" "github.com/sei-protocol/sei-chain/evmrpc" "github.com/sei-protocol/sei-chain/precompiles" @@ -126,6 +131,8 @@ import ( evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" "github.com/sei-protocol/sei-chain/x/evm/querier" "github.com/sei-protocol/sei-chain/x/evm/replay" + evmtracers "github.com/sei-protocol/sei-chain/x/evm/tracers" + "github.com/sei-protocol/sei-chain/x/evm/tracing" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/spf13/cast" abci "github.com/tendermint/tendermint/abci/types" @@ -375,6 +382,7 @@ type App struct { encodingConfig appparams.EncodingConfig evmRPCConfig evmrpc.Config + evmTracer *tracing.Hooks } // New returns a reference to an initialized blockchain app @@ -622,6 +630,15 @@ func New( app.EvmKeeper.EthClient = ethclient.NewClient(rpcclient) } + if app.evmRPCConfig.LiveEVMTracer != "" { + chainConfig := evmtypes.DefaultChainConfig().EthereumConfig(app.EvmKeeper.ChainID()) + evmTracer, err := evmtracers.NewBlockchainTracer(evmtracers.GlobalLiveTracerRegistry, app.evmRPCConfig.LiveEVMTracer, chainConfig) + if err != nil { + panic(fmt.Sprintf("error creating EVM tracer due to %s", err)) + } + app.evmTracer = evmTracer + } + customDependencyGenerators := aclmapping.NewCustomDependencyGenerator() aclOpts = append(aclOpts, aclkeeper.WithResourceTypeToStoreKeyMap(aclutils.ResourceTypeToStoreKeyMap)) aclOpts = append(aclOpts, aclkeeper.WithDependencyGeneratorMappings(customDependencyGenerators.GetCustomDependencyGenerators(app.EvmKeeper))) @@ -1417,12 +1434,23 @@ func (app *App) ProcessTXsWithOCC(ctx sdk.Context, txs [][]byte, typedTxs []sdk. wg.Add(1) go func(txIndex int, tx []byte) { defer wg.Done() + + var txTracer sdk.TxTracer + if app.evmTracer != nil { + txTracer = app.evmTracer + if app.evmTracer.GetTxTracer != nil { + txTracer = app.evmTracer.GetTxTracer(absoluteTxIndices[txIndex]) + } + } + deliverTxEntry := &sdk.DeliverTxEntry{ Request: abci.RequestDeliverTx{Tx: tx}, SdkTx: typedTxs[txIndex], Checksum: sha256.Sum256(tx), AbsoluteIndex: absoluteTxIndices[txIndex], + TxTracer: txTracer, } + // get prefill estimate estimatedWritesets, err := app.AccessControlKeeper.GenerateEstimatedWritesets(ctx, app.GetAnteDepGenerator(), txIndex, typedTxs[txIndex]) // if no error, then we assign the mapped writesets for prefill estimate @@ -1483,12 +1511,17 @@ func (app *App) BuildDependenciesAndRunTxs(ctx sdk.Context, txs [][]byte, typedT return app.ProcessBlockSynchronous(ctx, txs, typedTxs, absoluteTxIndices), ctx } -func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequest, lastCommit abci.CommitInfo) ([]abci.Event, []*abci.ExecTxResult, abci.ResponseEndBlock, error) { +func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequest, lastCommit abci.CommitInfo) (events []abci.Event, txResults []*abci.ExecTxResult, endBlockResp abci.ResponseEndBlock, err error) { ctx = ctx.WithIsOCCEnabled(app.OccEnabled()) + goCtx := app.decorateContextWithDexMemState(ctx.Context()) ctx = ctx.WithContext(goCtx) - events := []abci.Event{} + if app.evmTracer != nil { + ctx = evmtracers.SetCtxBlockchainTracer(ctx, app.evmTracer) + } + + events = []abci.Event{} beginBlockReq := abci.RequestBeginBlock{ Hash: req.GetHash(), ByzantineValidators: utils.Map(req.GetByzantineValidators(), func(mis abci.Misbehavior) abci.Evidence { @@ -1513,9 +1546,16 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ beginBlockResp := app.BeginBlock(ctx, beginBlockReq) events = append(events, beginBlockResp.Events...) - txResults := make([]*abci.ExecTxResult, len(txs)) + txResults = make([]*abci.ExecTxResult, len(txs)) typedTxs := app.DecodeTransactionsConcurrently(ctx, txs) + if app.evmTracer != nil { + header := ctx.BlockHeader() + app.evmTracer.OnSeiBlockStart(req.GetHash(), uint64(header.Size()), TmBlockHeaderToEVM(ctx, header, &app.EvmKeeper)) + defer func() { + app.evmTracer.OnSeiBlockEnd(err) + }() + } prioritizedTxs, otherTxs, prioritizedTypedTxs, otherTypedTxs, prioritizedIndices, otherIndices := app.PartitionPrioritizedTxs(ctx, txs, typedTxs) // run the prioritized txs @@ -1541,7 +1581,7 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ lazyWriteEvents := app.BankKeeper.WriteDeferredBalances(ctx) events = append(events, lazyWriteEvents...) - endBlockResp := app.EndBlock(ctx, abci.RequestEndBlock{ + endBlockResp = app.EndBlock(ctx, abci.RequestEndBlock{ Height: req.GetHeight(), }) @@ -1852,3 +1892,38 @@ func init() { // override max wasm size to 2MB wasmtypes.MaxWasmSize = 2 * 1024 * 1024 } + +func TmBlockHeaderToEVM( + ctx sdk.Context, + block tmproto.Header, + k *evmkeeper.Keeper, +) (header *ethtypes.Header) { + number := big.NewInt(block.Height) + lastHash := ethcommon.BytesToHash(block.LastBlockId.Hash) + appHash := ethcommon.BytesToHash(block.AppHash) + txHash := ethcommon.BytesToHash(block.DataHash) + resultHash := ethcommon.BytesToHash(block.LastResultsHash) + miner := ethcommon.BytesToAddress(block.ProposerAddress) + gasLimit, gasWanted := uint64(0), uint64(0) + + header = ðtypes.Header{ + Number: number, + ParentHash: lastHash, + Nonce: ethtypes.BlockNonce{}, // inapplicable to Sei + MixDigest: ethcommon.Hash{}, // inapplicable to Sei + UncleHash: ethtypes.EmptyUncleHash, // inapplicable to Sei + Bloom: k.GetBlockBloom(ctx, block.Height), + Root: appHash, + Coinbase: miner, + Difficulty: big.NewInt(0), // inapplicable to Sei + Extra: ethhexutil.Bytes{}, // inapplicable to Sei + GasLimit: gasLimit, + GasUsed: gasWanted, + Time: uint64(block.Time.Unix()), + TxHash: txHash, + ReceiptHash: resultHash, + BaseFee: k.GetBaseFeePerGas(ctx).RoundInt().BigInt(), + } + + return +} diff --git a/app/eth_replay.go b/app/eth_replay.go index 80b045068..8ea8b47d8 100644 --- a/app/eth_replay.go +++ b/app/eth_replay.go @@ -13,7 +13,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" ethcore "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/tracing" + ethtracing "github.com/ethereum/go-ethereum/core/tracing" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" ethtests "github.com/ethereum/go-ethereum/tests" @@ -85,7 +85,7 @@ func Replay(a *App) { for _, w := range b.Withdrawals() { amount := new(big.Int).SetUint64(w.Amount) amount = amount.Mul(amount, big.NewInt(params.GWei)) - s.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) + s.AddBalance(w.Address, amount, ethtracing.BalanceIncreaseWithdrawal) } _, _ = s.Finalize() for _, tx := range b.Txs { diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 000000000..6275bdee3 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,6 @@ +version: v1 +plugins: + - plugin: buf.build/protocolbuffers/go:v1.31.0 + out: pb + opt: paths=source_relative + diff --git a/evmrpc/config.go b/evmrpc/config.go index ea5278838..e465c3276 100644 --- a/evmrpc/config.go +++ b/evmrpc/config.go @@ -53,6 +53,12 @@ type Config struct { // Timeout for EVM call in simulation SimulationEVMTimeout time.Duration `mapstructure:"simulation_evm_timeout"` + // The EVM tracer to use when doing node synchronization, applies to + // all block produced but traces only EVM transactions. + // + // Refer to x/evm/tracers/registry.go#GlobalLiveTracerRegistry for registered tracers. + LiveEVMTracer string `mapstructure:"live_evm_tracer"` + // list of CORS allowed origins, separated by comma CORSOrigins string `mapstructure:"cors_origins"` @@ -92,6 +98,7 @@ var DefaultConfig = Config{ IdleTimeout: rpc.DefaultHTTPTimeouts.IdleTimeout, SimulationGasLimit: 10_000_000, // 10M SimulationEVMTimeout: 60 * time.Second, + LiveEVMTracer: "", CORSOrigins: "*", WSOrigins: "*", FilterTimeout: 120 * time.Second, @@ -114,6 +121,7 @@ const ( flagIdleTimeout = "evm.idle_timeout" flagSimulationGasLimit = "evm.simulation_gas_limit" flagSimulationEVMTimeout = "evm.simulation_evm_timeout" + flagLiveEVMTracer = "evm.live_evm_tracer" flagCORSOrigins = "evm.cors_origins" flagWSOrigins = "evm.ws_origins" flagFilterTimeout = "evm.filter_timeout" @@ -178,6 +186,11 @@ func ReadConfig(opts servertypes.AppOptions) (Config, error) { return cfg, err } } + if v := opts.Get(flagLiveEVMTracer); v != nil { + if cfg.LiveEVMTracer, err = cast.ToStringE(v); err != nil { + return cfg, err + } + } if v := opts.Get(flagCORSOrigins); v != nil { if cfg.CORSOrigins, err = cast.ToStringE(v); err != nil { return cfg, err @@ -223,5 +236,6 @@ func ReadConfig(opts servertypes.AppOptions) (Config, error) { return cfg, err } } + return cfg, nil } diff --git a/evmrpc/config_test.go b/evmrpc/config_test.go index c29d53bc0..e37ced634 100644 --- a/evmrpc/config_test.go +++ b/evmrpc/config_test.go @@ -1,6 +1,7 @@ package evmrpc_test import ( + "fmt" "testing" "time" @@ -19,6 +20,7 @@ type opts struct { idleTimeout interface{} simulationGasLimit interface{} simulationEVMTimeout interface{} + liveEVMTracer interface{} corsOrigins interface{} wsOrigins interface{} filterTimeout interface{} @@ -61,6 +63,9 @@ func (o *opts) Get(k string) interface{} { if k == "evm.simulation_evm_timeout" { return o.simulationEVMTimeout } + if k == "evm.live_evm_tracer" { + return o.liveEVMTracer + } if k == "evm.cors_origins" { return o.corsOrigins } @@ -88,7 +93,7 @@ func (o *opts) Get(k string) interface{} { if k == "evm.max_blocks_for_log" { return o.maxBlocksForLog } - panic("unknown key") + panic(fmt.Errorf("unknown key: %s", k)) } func TestReadConfig(t *testing.T) { @@ -105,6 +110,7 @@ func TestReadConfig(t *testing.T) { time.Duration(60), "", "", + "", time.Duration(5), time.Duration(5), 1000, diff --git a/go.sum b/go.sum index ad3f0d014..1fac80d21 100644 --- a/go.sum +++ b/go.sum @@ -1430,6 +1430,8 @@ github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8L github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/streamingfast/sei-cosmos v0.0.0-20240327153024-8e22d1da5334 h1:+4kXknHWASf4Qq0yo67kDmZcBDeSUxVICb34CDEY6Kw= +github.com/streamingfast/sei-cosmos v0.0.0-20240327153024-8e22d1da5334/go.mod h1:ib/gp0gCxN7FXUZ40j5+x8BeyoI7AcX+rTvf53JoDsY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= diff --git a/pb/sf/ethereum/type/v2/type.go b/pb/sf/ethereum/type/v2/type.go new file mode 100644 index 000000000..38b6881fc --- /dev/null +++ b/pb/sf/ethereum/type/v2/type.go @@ -0,0 +1,27 @@ +package pbeth + +import ( + "encoding/hex" + "math/big" + "time" +) + +var b0 = big.NewInt(0) + +func (b *Block) PreviousID() string { + return hex.EncodeToString(b.Header.ParentHash) +} + +func (b *Block) Time() time.Time { + return b.Header.Timestamp.AsTime() +} + +func (m *BigInt) Native() *big.Int { + if m == nil { + return b0 + } + + z := new(big.Int) + z.SetBytes(m.Bytes) + return z +} diff --git a/pb/sf/ethereum/type/v2/type.pb.go b/pb/sf/ethereum/type/v2/type.pb.go new file mode 100644 index 000000000..8103b2f18 --- /dev/null +++ b/pb/sf/ethereum/type/v2/type.pb.go @@ -0,0 +1,3691 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: sf/ethereum/type/v2/type.proto + +package pbeth + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TransactionTraceStatus int32 + +const ( + TransactionTraceStatus_UNKNOWN TransactionTraceStatus = 0 + TransactionTraceStatus_SUCCEEDED TransactionTraceStatus = 1 + TransactionTraceStatus_FAILED TransactionTraceStatus = 2 + TransactionTraceStatus_REVERTED TransactionTraceStatus = 3 +) + +// Enum value maps for TransactionTraceStatus. +var ( + TransactionTraceStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SUCCEEDED", + 2: "FAILED", + 3: "REVERTED", + } + TransactionTraceStatus_value = map[string]int32{ + "UNKNOWN": 0, + "SUCCEEDED": 1, + "FAILED": 2, + "REVERTED": 3, + } +) + +func (x TransactionTraceStatus) Enum() *TransactionTraceStatus { + p := new(TransactionTraceStatus) + *p = x + return p +} + +func (x TransactionTraceStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TransactionTraceStatus) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[0].Descriptor() +} + +func (TransactionTraceStatus) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[0] +} + +func (x TransactionTraceStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TransactionTraceStatus.Descriptor instead. +func (TransactionTraceStatus) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{0} +} + +type CallType int32 + +const ( + CallType_UNSPECIFIED CallType = 0 + CallType_CALL CallType = 1 // direct? what's the name for `Call` alone? + CallType_CALLCODE CallType = 2 + CallType_DELEGATE CallType = 3 + CallType_STATIC CallType = 4 + CallType_CREATE CallType = 5 // create2 ? any other form of calls? +) + +// Enum value maps for CallType. +var ( + CallType_name = map[int32]string{ + 0: "UNSPECIFIED", + 1: "CALL", + 2: "CALLCODE", + 3: "DELEGATE", + 4: "STATIC", + 5: "CREATE", + } + CallType_value = map[string]int32{ + "UNSPECIFIED": 0, + "CALL": 1, + "CALLCODE": 2, + "DELEGATE": 3, + "STATIC": 4, + "CREATE": 5, + } +) + +func (x CallType) Enum() *CallType { + p := new(CallType) + *p = x + return p +} + +func (x CallType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CallType) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[1].Descriptor() +} + +func (CallType) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[1] +} + +func (x CallType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CallType.Descriptor instead. +func (CallType) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{1} +} + +type Block_DetailLevel int32 + +const ( + Block_DETAILLEVEL_EXTENDED Block_DetailLevel = 0 + // DETAILLEVEL_TRACE = 1; // TBD + Block_DETAILLEVEL_BASE Block_DetailLevel = 2 +) + +// Enum value maps for Block_DetailLevel. +var ( + Block_DetailLevel_name = map[int32]string{ + 0: "DETAILLEVEL_EXTENDED", + 2: "DETAILLEVEL_BASE", + } + Block_DetailLevel_value = map[string]int32{ + "DETAILLEVEL_EXTENDED": 0, + "DETAILLEVEL_BASE": 2, + } +) + +func (x Block_DetailLevel) Enum() *Block_DetailLevel { + p := new(Block_DetailLevel) + *p = x + return p +} + +func (x Block_DetailLevel) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Block_DetailLevel) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[2].Descriptor() +} + +func (Block_DetailLevel) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[2] +} + +func (x Block_DetailLevel) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Block_DetailLevel.Descriptor instead. +func (Block_DetailLevel) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{0, 0} +} + +type TransactionTrace_Type int32 + +const ( + // All transactions that ever existed prior Berlin fork before EIP-2718 was implemented. + TransactionTrace_TRX_TYPE_LEGACY TransactionTrace_Type = 0 + // Transaction that specicy an access list of contract/storage_keys that is going to be used + // in this transaction. + // + // Added in Berlin fork (EIP-2930). + TransactionTrace_TRX_TYPE_ACCESS_LIST TransactionTrace_Type = 1 + // Transaction that specifis an access list just like TRX_TYPE_ACCESS_LIST but in addition defines the + // max base gas gee and max priority gas fee to pay for this transaction. Transaction's of those type are + // executed against EIP-1559 rules which dictates a dynamic gas cost based on the congestion of the network. + TransactionTrace_TRX_TYPE_DYNAMIC_FEE TransactionTrace_Type = 2 + // Transaction which contain a large amount of data that cannot be accessed by EVM execution, but whose commitment + // can be accessed. The format is intended to be fully compatible with the format that will be used in full sharding. + // + // Transaction that defines specifis an access list just like TRX_TYPE_ACCESS_LIST and enables dynamic fee just like + // TRX_TYPE_DYNAMIC_FEE but in addition defines the fields 'max_fee_per_data_gas' of type 'uint256' and the fields + // 'blob_versioned_hashes' field represents a list of hash outputs from 'kzg_to_versioned_hash'. + // + // Activated in Dencun + TransactionTrace_TRX_TYPE_BLOB TransactionTrace_Type = 3 + // Arbitrum-specific transactions + TransactionTrace_TRX_TYPE_ARBITRUM_DEPOSIT TransactionTrace_Type = 100 + TransactionTrace_TRX_TYPE_ARBITRUM_UNSIGNED TransactionTrace_Type = 101 + TransactionTrace_TRX_TYPE_ARBITRUM_CONTRACT TransactionTrace_Type = 102 + TransactionTrace_TRX_TYPE_ARBITRUM_RETRY TransactionTrace_Type = 104 + TransactionTrace_TRX_TYPE_ARBITRUM_SUBMIT_RETRYABLE TransactionTrace_Type = 105 + TransactionTrace_TRX_TYPE_ARBITRUM_INTERNAL TransactionTrace_Type = 106 + TransactionTrace_TRX_TYPE_ARBITRUM_LEGACY TransactionTrace_Type = 120 +) + +// Enum value maps for TransactionTrace_Type. +var ( + TransactionTrace_Type_name = map[int32]string{ + 0: "TRX_TYPE_LEGACY", + 1: "TRX_TYPE_ACCESS_LIST", + 2: "TRX_TYPE_DYNAMIC_FEE", + 3: "TRX_TYPE_BLOB", + 100: "TRX_TYPE_ARBITRUM_DEPOSIT", + 101: "TRX_TYPE_ARBITRUM_UNSIGNED", + 102: "TRX_TYPE_ARBITRUM_CONTRACT", + 104: "TRX_TYPE_ARBITRUM_RETRY", + 105: "TRX_TYPE_ARBITRUM_SUBMIT_RETRYABLE", + 106: "TRX_TYPE_ARBITRUM_INTERNAL", + 120: "TRX_TYPE_ARBITRUM_LEGACY", + } + TransactionTrace_Type_value = map[string]int32{ + "TRX_TYPE_LEGACY": 0, + "TRX_TYPE_ACCESS_LIST": 1, + "TRX_TYPE_DYNAMIC_FEE": 2, + "TRX_TYPE_BLOB": 3, + "TRX_TYPE_ARBITRUM_DEPOSIT": 100, + "TRX_TYPE_ARBITRUM_UNSIGNED": 101, + "TRX_TYPE_ARBITRUM_CONTRACT": 102, + "TRX_TYPE_ARBITRUM_RETRY": 104, + "TRX_TYPE_ARBITRUM_SUBMIT_RETRYABLE": 105, + "TRX_TYPE_ARBITRUM_INTERNAL": 106, + "TRX_TYPE_ARBITRUM_LEGACY": 120, + } +) + +func (x TransactionTrace_Type) Enum() *TransactionTrace_Type { + p := new(TransactionTrace_Type) + *p = x + return p +} + +func (x TransactionTrace_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TransactionTrace_Type) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[3].Descriptor() +} + +func (TransactionTrace_Type) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[3] +} + +func (x TransactionTrace_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TransactionTrace_Type.Descriptor instead. +func (TransactionTrace_Type) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{5, 0} +} + +// Obtain all balanche change reasons under deep mind repository: +// +// ```shell +// ack -ho 'BalanceChangeReason\(".*"\)' | grep -Eo '".*"' | sort | uniq +// ``` +type BalanceChange_Reason int32 + +const ( + BalanceChange_REASON_UNKNOWN BalanceChange_Reason = 0 + BalanceChange_REASON_REWARD_MINE_UNCLE BalanceChange_Reason = 1 + BalanceChange_REASON_REWARD_MINE_BLOCK BalanceChange_Reason = 2 + BalanceChange_REASON_DAO_REFUND_CONTRACT BalanceChange_Reason = 3 + BalanceChange_REASON_DAO_ADJUST_BALANCE BalanceChange_Reason = 4 + BalanceChange_REASON_TRANSFER BalanceChange_Reason = 5 + BalanceChange_REASON_GENESIS_BALANCE BalanceChange_Reason = 6 + BalanceChange_REASON_GAS_BUY BalanceChange_Reason = 7 + BalanceChange_REASON_REWARD_TRANSACTION_FEE BalanceChange_Reason = 8 + BalanceChange_REASON_REWARD_FEE_RESET BalanceChange_Reason = 14 + BalanceChange_REASON_GAS_REFUND BalanceChange_Reason = 9 + BalanceChange_REASON_TOUCH_ACCOUNT BalanceChange_Reason = 10 + BalanceChange_REASON_SUICIDE_REFUND BalanceChange_Reason = 11 + BalanceChange_REASON_SUICIDE_WITHDRAW BalanceChange_Reason = 13 + BalanceChange_REASON_CALL_BALANCE_OVERRIDE BalanceChange_Reason = 12 + // Used on chain(s) where some Ether burning happens + BalanceChange_REASON_BURN BalanceChange_Reason = 15 + BalanceChange_REASON_WITHDRAWAL BalanceChange_Reason = 16 +) + +// Enum value maps for BalanceChange_Reason. +var ( + BalanceChange_Reason_name = map[int32]string{ + 0: "REASON_UNKNOWN", + 1: "REASON_REWARD_MINE_UNCLE", + 2: "REASON_REWARD_MINE_BLOCK", + 3: "REASON_DAO_REFUND_CONTRACT", + 4: "REASON_DAO_ADJUST_BALANCE", + 5: "REASON_TRANSFER", + 6: "REASON_GENESIS_BALANCE", + 7: "REASON_GAS_BUY", + 8: "REASON_REWARD_TRANSACTION_FEE", + 14: "REASON_REWARD_FEE_RESET", + 9: "REASON_GAS_REFUND", + 10: "REASON_TOUCH_ACCOUNT", + 11: "REASON_SUICIDE_REFUND", + 13: "REASON_SUICIDE_WITHDRAW", + 12: "REASON_CALL_BALANCE_OVERRIDE", + 15: "REASON_BURN", + 16: "REASON_WITHDRAWAL", + } + BalanceChange_Reason_value = map[string]int32{ + "REASON_UNKNOWN": 0, + "REASON_REWARD_MINE_UNCLE": 1, + "REASON_REWARD_MINE_BLOCK": 2, + "REASON_DAO_REFUND_CONTRACT": 3, + "REASON_DAO_ADJUST_BALANCE": 4, + "REASON_TRANSFER": 5, + "REASON_GENESIS_BALANCE": 6, + "REASON_GAS_BUY": 7, + "REASON_REWARD_TRANSACTION_FEE": 8, + "REASON_REWARD_FEE_RESET": 14, + "REASON_GAS_REFUND": 9, + "REASON_TOUCH_ACCOUNT": 10, + "REASON_SUICIDE_REFUND": 11, + "REASON_SUICIDE_WITHDRAW": 13, + "REASON_CALL_BALANCE_OVERRIDE": 12, + "REASON_BURN": 15, + "REASON_WITHDRAWAL": 16, + } +) + +func (x BalanceChange_Reason) Enum() *BalanceChange_Reason { + p := new(BalanceChange_Reason) + *p = x + return p +} + +func (x BalanceChange_Reason) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (BalanceChange_Reason) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[4].Descriptor() +} + +func (BalanceChange_Reason) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[4] +} + +func (x BalanceChange_Reason) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use BalanceChange_Reason.Descriptor instead. +func (BalanceChange_Reason) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{11, 0} +} + +// Obtain all gas change reasons under deep mind repository: +// +// ```shell +// ack -ho 'GasChangeReason\(".*"\)' | grep -Eo '".*"' | sort | uniq +// ``` +type GasChange_Reason int32 + +const ( + GasChange_REASON_UNKNOWN GasChange_Reason = 0 + // REASON_CALL is the amount of gas that will be charged for a 'CALL' opcode executed by the EVM + GasChange_REASON_CALL GasChange_Reason = 1 + // REASON_CALL_CODE is the amount of gas that will be charged for a 'CALLCODE' opcode executed by the EVM + GasChange_REASON_CALL_CODE GasChange_Reason = 2 + // REASON_CALL_DATA_COPY is the amount of gas that will be charged for a 'CALLDATACOPY' opcode executed by the EVM + GasChange_REASON_CALL_DATA_COPY GasChange_Reason = 3 + // REASON_CODE_COPY is the amount of gas that will be charged for a 'CALLDATACOPY' opcode executed by the EVM + GasChange_REASON_CODE_COPY GasChange_Reason = 4 + // REASON_CODE_STORAGE is the amount of gas that will be charged for code storage + GasChange_REASON_CODE_STORAGE GasChange_Reason = 5 + // REASON_CONTRACT_CREATION is the amount of gas that will be charged for a 'CREATE' opcode executed by the EVM and for the gas + // burned for a CREATE, today controlled by EIP150 rules + GasChange_REASON_CONTRACT_CREATION GasChange_Reason = 6 + // REASON_CONTRACT_CREATION2 is the amount of gas that will be charged for a 'CREATE2' opcode executed by the EVM and for the gas + // burned for a CREATE2, today controlled by EIP150 rules + GasChange_REASON_CONTRACT_CREATION2 GasChange_Reason = 7 + // REASON_DELEGATE_CALL is the amount of gas that will be charged for a 'DELEGATECALL' opcode executed by the EVM + GasChange_REASON_DELEGATE_CALL GasChange_Reason = 8 + // REASON_EVENT_LOG is the amount of gas that will be charged for a 'LOG' opcode executed by the EVM + GasChange_REASON_EVENT_LOG GasChange_Reason = 9 + // REASON_EXT_CODE_COPY is the amount of gas that will be charged for a 'LOG' opcode executed by the EVM + GasChange_REASON_EXT_CODE_COPY GasChange_Reason = 10 + // REASON_FAILED_EXECUTION is the burning of the remaining gas when the execution failed without a revert + GasChange_REASON_FAILED_EXECUTION GasChange_Reason = 11 + // REASON_INTRINSIC_GAS is the amount of gas that will be charged for the intrinsic cost of the transaction, there is + // always exactly one of those per transaction + GasChange_REASON_INTRINSIC_GAS GasChange_Reason = 12 + // GasChangePrecompiledContract is the amount of gas that will be charged for a precompiled contract execution + GasChange_REASON_PRECOMPILED_CONTRACT GasChange_Reason = 13 + // REASON_REFUND_AFTER_EXECUTION is the amount of gas that will be refunded to the caller after the execution of the call, + // if there is left over at the end of execution + GasChange_REASON_REFUND_AFTER_EXECUTION GasChange_Reason = 14 + // REASON_RETURN is the amount of gas that will be charged for a 'RETURN' opcode executed by the EVM + GasChange_REASON_RETURN GasChange_Reason = 15 + // REASON_RETURN_DATA_COPY is the amount of gas that will be charged for a 'RETURNDATACOPY' opcode executed by the EVM + GasChange_REASON_RETURN_DATA_COPY GasChange_Reason = 16 + // REASON_REVERT is the amount of gas that will be charged for a 'REVERT' opcode executed by the EVM + GasChange_REASON_REVERT GasChange_Reason = 17 + // REASON_SELF_DESTRUCT is the amount of gas that will be charged for a 'SELFDESTRUCT' opcode executed by the EVM + GasChange_REASON_SELF_DESTRUCT GasChange_Reason = 18 + // REASON_STATIC_CALL is the amount of gas that will be charged for a 'STATICALL' opcode executed by the EVM + GasChange_REASON_STATIC_CALL GasChange_Reason = 19 + // REASON_STATE_COLD_ACCESS is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules + // + // Added in Berlin fork (Geth 1.10+) + GasChange_REASON_STATE_COLD_ACCESS GasChange_Reason = 20 + // REASON_TX_INITIAL_BALANCE is the initial balance for the call which will be equal to the gasLimit of the call + // + // Added as new tracing reason in Geth, available only on some chains + GasChange_REASON_TX_INITIAL_BALANCE GasChange_Reason = 21 + // REASON_TX_REFUNDS is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared) + // this generates an increase in gas. There is only one such gas change per transaction. + // + // Added as new tracing reason in Geth, available only on some chains + GasChange_REASON_TX_REFUNDS GasChange_Reason = 22 + // REASON_TX_LEFT_OVER_RETURNED is the amount of gas left over at the end of transaction's execution that will be returned + // to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas + // left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. + // There is at most one of such gas change per transaction. + // + // Added as new tracing reason in Geth, available only on some chains + GasChange_REASON_TX_LEFT_OVER_RETURNED GasChange_Reason = 23 + // REASON_CALL_INITIAL_BALANCE is the initial balance for the call which will be equal to the gasLimit of the call. There is only + // one such gas change per call. + // + // Added as new tracing reason in Geth, available only on some chains + GasChange_REASON_CALL_INITIAL_BALANCE GasChange_Reason = 24 + // REASON_CALL_LEFT_OVER_RETURNED is the amount of gas left over that will be returned to the caller, this change will always + // be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even + // will be emitted. + GasChange_REASON_CALL_LEFT_OVER_RETURNED GasChange_Reason = 25 +) + +// Enum value maps for GasChange_Reason. +var ( + GasChange_Reason_name = map[int32]string{ + 0: "REASON_UNKNOWN", + 1: "REASON_CALL", + 2: "REASON_CALL_CODE", + 3: "REASON_CALL_DATA_COPY", + 4: "REASON_CODE_COPY", + 5: "REASON_CODE_STORAGE", + 6: "REASON_CONTRACT_CREATION", + 7: "REASON_CONTRACT_CREATION2", + 8: "REASON_DELEGATE_CALL", + 9: "REASON_EVENT_LOG", + 10: "REASON_EXT_CODE_COPY", + 11: "REASON_FAILED_EXECUTION", + 12: "REASON_INTRINSIC_GAS", + 13: "REASON_PRECOMPILED_CONTRACT", + 14: "REASON_REFUND_AFTER_EXECUTION", + 15: "REASON_RETURN", + 16: "REASON_RETURN_DATA_COPY", + 17: "REASON_REVERT", + 18: "REASON_SELF_DESTRUCT", + 19: "REASON_STATIC_CALL", + 20: "REASON_STATE_COLD_ACCESS", + 21: "REASON_TX_INITIAL_BALANCE", + 22: "REASON_TX_REFUNDS", + 23: "REASON_TX_LEFT_OVER_RETURNED", + 24: "REASON_CALL_INITIAL_BALANCE", + 25: "REASON_CALL_LEFT_OVER_RETURNED", + } + GasChange_Reason_value = map[string]int32{ + "REASON_UNKNOWN": 0, + "REASON_CALL": 1, + "REASON_CALL_CODE": 2, + "REASON_CALL_DATA_COPY": 3, + "REASON_CODE_COPY": 4, + "REASON_CODE_STORAGE": 5, + "REASON_CONTRACT_CREATION": 6, + "REASON_CONTRACT_CREATION2": 7, + "REASON_DELEGATE_CALL": 8, + "REASON_EVENT_LOG": 9, + "REASON_EXT_CODE_COPY": 10, + "REASON_FAILED_EXECUTION": 11, + "REASON_INTRINSIC_GAS": 12, + "REASON_PRECOMPILED_CONTRACT": 13, + "REASON_REFUND_AFTER_EXECUTION": 14, + "REASON_RETURN": 15, + "REASON_RETURN_DATA_COPY": 16, + "REASON_REVERT": 17, + "REASON_SELF_DESTRUCT": 18, + "REASON_STATIC_CALL": 19, + "REASON_STATE_COLD_ACCESS": 20, + "REASON_TX_INITIAL_BALANCE": 21, + "REASON_TX_REFUNDS": 22, + "REASON_TX_LEFT_OVER_RETURNED": 23, + "REASON_CALL_INITIAL_BALANCE": 24, + "REASON_CALL_LEFT_OVER_RETURNED": 25, + } +) + +func (x GasChange_Reason) Enum() *GasChange_Reason { + p := new(GasChange_Reason) + *p = x + return p +} + +func (x GasChange_Reason) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (GasChange_Reason) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[5].Descriptor() +} + +func (GasChange_Reason) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[5] +} + +func (x GasChange_Reason) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use GasChange_Reason.Descriptor instead. +func (GasChange_Reason) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{15, 0} +} + +type Block struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Hash is the block's hash. + Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` + // Number is the block's height at which this block was mined. + Number uint64 `protobuf:"varint,3,opt,name=number,proto3" json:"number,omitempty"` + // Size is the size in bytes of the RLP encoding of the block according to Ethereum + // rules. + Size uint64 `protobuf:"varint,4,opt,name=size,proto3" json:"size,omitempty"` + // Header contain's the block's header information like its parent hash, the merkel root hash + // and all other information the form a block. + Header *BlockHeader `protobuf:"bytes,5,opt,name=header,proto3" json:"header,omitempty"` + // Uncles represents block produced with a valid solution but were not actually choosen + // as the canonical block for the given height so they are mostly "forked" blocks. + // + // If the Block has been produced using the Proof of Stake consensus algorithm, this + // field will actually be always empty. + Uncles []*BlockHeader `protobuf:"bytes,6,rep,name=uncles,proto3" json:"uncles,omitempty"` + // TransactionTraces hold the execute trace of all the transactions that were executed + // in this block. In in there that you will find most of the Ethereum data model. + // + // They are ordered by the order of execution of the transaction in the block. + TransactionTraces []*TransactionTrace `protobuf:"bytes,10,rep,name=transaction_traces,json=transactionTraces,proto3" json:"transaction_traces,omitempty"` + // BalanceChanges here is the array of ETH transfer that happened at the block level + // outside of the normal transaction flow of a block. The best example of this is mining + // reward for the block mined, the transfer of ETH to the miner happens outside the normal + // transaction flow of the chain and is recorded as a `BalanceChange` here since we cannot + // attached it to any transaction. + // + // Only available in DetailLevel: EXTENDED + BalanceChanges []*BalanceChange `protobuf:"bytes,11,rep,name=balance_changes,json=balanceChanges,proto3" json:"balance_changes,omitempty"` + // DetailLevel affects the data available in this block. + // + // EXTENDED describes the most complete block, with traces, balance changes, storage changes. It is extracted during the execution of the block. + // BASE describes a block that contains only the block header, transaction receipts and event logs: everything that can be extracted using the base JSON-RPC interface (https://ethereum.org/en/developers/docs/apis/json-rpc/#json-rpc-methods) + // + // Furthermore, the eth_getTransactionReceipt call has been avoided because it brings only minimal improvements at the cost of requiring an archive node or a full node with complete transaction index. + DetailLevel Block_DetailLevel `protobuf:"varint,12,opt,name=detail_level,json=detailLevel,proto3,enum=sf.ethereum.type.v2.Block_DetailLevel" json:"detail_level,omitempty"` + // CodeChanges here is the array of smart code change that happened that happened at the block level + // outside of the normal transaction flow of a block. Some Ethereum's fork like BSC and Polygon + // has some capabilities to upgrade internal smart contracts used usually to track the validator + // list. + // + // On hard fork, some procedure runs to upgrade the smart contract code to a new version. In those + // network, a `CodeChange` for each modified smart contract on upgrade would be present here. Note + // that this happen rarely, so the vast majority of block will have an empty list here. + // Only available in DetailLevel: EXTENDED + CodeChanges []*CodeChange `protobuf:"bytes,20,rep,name=code_changes,json=codeChanges,proto3" json:"code_changes,omitempty"` + // System calls are introduced in Cancun, along with blobs. They are executed outside of transactions but affect the state. + // Only available in DetailLevel: EXTENDED + SystemCalls []*Call `protobuf:"bytes,21,rep,name=system_calls,json=systemCalls,proto3" json:"system_calls,omitempty"` + // Ver represents that data model version of the block, it is used internally by Firehose on Ethereum + // as a validation that we are reading the correct version. + Ver int32 `protobuf:"varint,1,opt,name=ver,proto3" json:"ver,omitempty"` +} + +func (x *Block) Reset() { + *x = Block{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Block) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Block) ProtoMessage() {} + +func (x *Block) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Block.ProtoReflect.Descriptor instead. +func (*Block) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{0} +} + +func (x *Block) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + +func (x *Block) GetNumber() uint64 { + if x != nil { + return x.Number + } + return 0 +} + +func (x *Block) GetSize() uint64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *Block) GetHeader() *BlockHeader { + if x != nil { + return x.Header + } + return nil +} + +func (x *Block) GetUncles() []*BlockHeader { + if x != nil { + return x.Uncles + } + return nil +} + +func (x *Block) GetTransactionTraces() []*TransactionTrace { + if x != nil { + return x.TransactionTraces + } + return nil +} + +func (x *Block) GetBalanceChanges() []*BalanceChange { + if x != nil { + return x.BalanceChanges + } + return nil +} + +func (x *Block) GetDetailLevel() Block_DetailLevel { + if x != nil { + return x.DetailLevel + } + return Block_DETAILLEVEL_EXTENDED +} + +func (x *Block) GetCodeChanges() []*CodeChange { + if x != nil { + return x.CodeChanges + } + return nil +} + +func (x *Block) GetSystemCalls() []*Call { + if x != nil { + return x.SystemCalls + } + return nil +} + +func (x *Block) GetVer() int32 { + if x != nil { + return x.Ver + } + return 0 +} + +type BlockHeader struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ParentHash []byte `protobuf:"bytes,1,opt,name=parent_hash,json=parentHash,proto3" json:"parent_hash,omitempty"` + // Uncle hash of the block, some reference it as `sha3Uncles`, but `sha3“ is badly worded, so we prefer `uncle_hash`, also + // referred as `ommers` in EIP specification. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to `0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347`. + UncleHash []byte `protobuf:"bytes,2,opt,name=uncle_hash,json=uncleHash,proto3" json:"uncle_hash,omitempty"` + Coinbase []byte `protobuf:"bytes,3,opt,name=coinbase,proto3" json:"coinbase,omitempty"` + StateRoot []byte `protobuf:"bytes,4,opt,name=state_root,json=stateRoot,proto3" json:"state_root,omitempty"` + TransactionsRoot []byte `protobuf:"bytes,5,opt,name=transactions_root,json=transactionsRoot,proto3" json:"transactions_root,omitempty"` + ReceiptRoot []byte `protobuf:"bytes,6,opt,name=receipt_root,json=receiptRoot,proto3" json:"receipt_root,omitempty"` + LogsBloom []byte `protobuf:"bytes,7,opt,name=logs_bloom,json=logsBloom,proto3" json:"logs_bloom,omitempty"` + // Difficulty is the difficulty of the Proof of Work algorithm that was required to compute a solution. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to `0x00`. + Difficulty *BigInt `protobuf:"bytes,8,opt,name=difficulty,proto3" json:"difficulty,omitempty"` + // TotalDifficulty is the sum of all previous blocks difficulty including this block difficulty. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to the terminal total difficulty + // that was required to transition to Proof of Stake algorithm, which varies per network. It is set to + // 58 750 000 000 000 000 000 000 on Ethereum Mainnet and to 10 790 000 on Ethereum Testnet Goerli. + TotalDifficulty *BigInt `protobuf:"bytes,17,opt,name=total_difficulty,json=totalDifficulty,proto3" json:"total_difficulty,omitempty"` + Number uint64 `protobuf:"varint,9,opt,name=number,proto3" json:"number,omitempty"` + GasLimit uint64 `protobuf:"varint,10,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` + GasUsed uint64 `protobuf:"varint,11,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // ExtraData is free-form bytes included in the block by the "miner". While on Yellow paper of + // Ethereum this value is maxed to 32 bytes, other consensus algorithm like Clique and some other + // forks are using bigger values to carry special consensus data. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field is strictly enforced to be <= 32 bytes. + ExtraData []byte `protobuf:"bytes,13,opt,name=extra_data,json=extraData,proto3" json:"extra_data,omitempty"` + // MixHash is used to prove, when combined with the `nonce` that sufficient amount of computation has been + // achieved and that the solution found is valid. + MixHash []byte `protobuf:"bytes,14,opt,name=mix_hash,json=mixHash,proto3" json:"mix_hash,omitempty"` + // Nonce is used to prove, when combined with the `mix_hash` that sufficient amount of computation has been + // achieved and that the solution found is valid. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to `0`. + Nonce uint64 `protobuf:"varint,15,opt,name=nonce,proto3" json:"nonce,omitempty"` + // Hash is the hash of the block which is actually the computation: + // + // Keccak256(rlp([ + // parent_hash, + // uncle_hash, + // coinbase, + // state_root, + // transactions_root, + // receipt_root, + // logs_bloom, + // difficulty, + // number, + // gas_limit, + // gas_used, + // timestamp, + // extra_data, + // mix_hash, + // nonce, + // base_fee_per_gas (to be included only if London fork is active) + // withdrawals_root (to be included only if Shangai fork is active) + // blob_gas_used (to be included only if Cancun fork is active) + // excess_blob_gas (to be included only if Cancun fork is active) + // parent_beacon_root (to be included only if Cancun fork is active) + // ])) + Hash []byte `protobuf:"bytes,16,opt,name=hash,proto3" json:"hash,omitempty"` + // Base fee per gas according to EIP-1559 (e.g. London Fork) rules, only set if London is present/active on the chain. + BaseFeePerGas *BigInt `protobuf:"bytes,18,opt,name=base_fee_per_gas,json=baseFeePerGas,proto3" json:"base_fee_per_gas,omitempty"` + // Withdrawals root hash according to EIP-4895 (e.g. Shangai Fork) rules, only set if Shangai is present/active on the chain. + // + // Only available in DetailLevel: EXTENDED + WithdrawalsRoot []byte `protobuf:"bytes,19,opt,name=withdrawals_root,json=withdrawalsRoot,proto3" json:"withdrawals_root,omitempty"` + // Only available in DetailLevel: EXTENDED + TxDependency *Uint64NestedArray `protobuf:"bytes,20,opt,name=tx_dependency,json=txDependency,proto3" json:"tx_dependency,omitempty"` + // BlobGasUsed was added by EIP-4844 and is ignored in legacy headers. + BlobGasUsed *uint64 `protobuf:"varint,22,opt,name=blob_gas_used,json=blobGasUsed,proto3,oneof" json:"blob_gas_used,omitempty"` + // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers. + ExcessBlobGas *uint64 `protobuf:"varint,23,opt,name=excess_blob_gas,json=excessBlobGas,proto3,oneof" json:"excess_blob_gas,omitempty"` + // ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers. + ParentBeaconRoot []byte `protobuf:"bytes,24,opt,name=parent_beacon_root,json=parentBeaconRoot,proto3" json:"parent_beacon_root,omitempty"` +} + +func (x *BlockHeader) Reset() { + *x = BlockHeader{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockHeader) ProtoMessage() {} + +func (x *BlockHeader) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockHeader.ProtoReflect.Descriptor instead. +func (*BlockHeader) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{1} +} + +func (x *BlockHeader) GetParentHash() []byte { + if x != nil { + return x.ParentHash + } + return nil +} + +func (x *BlockHeader) GetUncleHash() []byte { + if x != nil { + return x.UncleHash + } + return nil +} + +func (x *BlockHeader) GetCoinbase() []byte { + if x != nil { + return x.Coinbase + } + return nil +} + +func (x *BlockHeader) GetStateRoot() []byte { + if x != nil { + return x.StateRoot + } + return nil +} + +func (x *BlockHeader) GetTransactionsRoot() []byte { + if x != nil { + return x.TransactionsRoot + } + return nil +} + +func (x *BlockHeader) GetReceiptRoot() []byte { + if x != nil { + return x.ReceiptRoot + } + return nil +} + +func (x *BlockHeader) GetLogsBloom() []byte { + if x != nil { + return x.LogsBloom + } + return nil +} + +func (x *BlockHeader) GetDifficulty() *BigInt { + if x != nil { + return x.Difficulty + } + return nil +} + +func (x *BlockHeader) GetTotalDifficulty() *BigInt { + if x != nil { + return x.TotalDifficulty + } + return nil +} + +func (x *BlockHeader) GetNumber() uint64 { + if x != nil { + return x.Number + } + return 0 +} + +func (x *BlockHeader) GetGasLimit() uint64 { + if x != nil { + return x.GasLimit + } + return 0 +} + +func (x *BlockHeader) GetGasUsed() uint64 { + if x != nil { + return x.GasUsed + } + return 0 +} + +func (x *BlockHeader) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +func (x *BlockHeader) GetExtraData() []byte { + if x != nil { + return x.ExtraData + } + return nil +} + +func (x *BlockHeader) GetMixHash() []byte { + if x != nil { + return x.MixHash + } + return nil +} + +func (x *BlockHeader) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *BlockHeader) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + +func (x *BlockHeader) GetBaseFeePerGas() *BigInt { + if x != nil { + return x.BaseFeePerGas + } + return nil +} + +func (x *BlockHeader) GetWithdrawalsRoot() []byte { + if x != nil { + return x.WithdrawalsRoot + } + return nil +} + +func (x *BlockHeader) GetTxDependency() *Uint64NestedArray { + if x != nil { + return x.TxDependency + } + return nil +} + +func (x *BlockHeader) GetBlobGasUsed() uint64 { + if x != nil && x.BlobGasUsed != nil { + return *x.BlobGasUsed + } + return 0 +} + +func (x *BlockHeader) GetExcessBlobGas() uint64 { + if x != nil && x.ExcessBlobGas != nil { + return *x.ExcessBlobGas + } + return 0 +} + +func (x *BlockHeader) GetParentBeaconRoot() []byte { + if x != nil { + return x.ParentBeaconRoot + } + return nil +} + +type Uint64NestedArray struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Val []*Uint64Array `protobuf:"bytes,1,rep,name=val,proto3" json:"val,omitempty"` +} + +func (x *Uint64NestedArray) Reset() { + *x = Uint64NestedArray{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Uint64NestedArray) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Uint64NestedArray) ProtoMessage() {} + +func (x *Uint64NestedArray) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Uint64NestedArray.ProtoReflect.Descriptor instead. +func (*Uint64NestedArray) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{2} +} + +func (x *Uint64NestedArray) GetVal() []*Uint64Array { + if x != nil { + return x.Val + } + return nil +} + +type Uint64Array struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Val []uint64 `protobuf:"varint,1,rep,packed,name=val,proto3" json:"val,omitempty"` +} + +func (x *Uint64Array) Reset() { + *x = Uint64Array{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Uint64Array) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Uint64Array) ProtoMessage() {} + +func (x *Uint64Array) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Uint64Array.ProtoReflect.Descriptor instead. +func (*Uint64Array) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{3} +} + +func (x *Uint64Array) GetVal() []uint64 { + if x != nil { + return x.Val + } + return nil +} + +type BigInt struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3" json:"bytes,omitempty"` +} + +func (x *BigInt) Reset() { + *x = BigInt{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BigInt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BigInt) ProtoMessage() {} + +func (x *BigInt) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BigInt.ProtoReflect.Descriptor instead. +func (*BigInt) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{4} +} + +func (x *BigInt) GetBytes() []byte { + if x != nil { + return x.Bytes + } + return nil +} + +// TransactionTrace is full trace of execution of the transaction when the +// it actually executed on chain. +// +// It contains all the transaction details like `from`, `to`, `gas`, etc. +// as well as all the internal calls that were made during the transaction. +// +// The `calls` vector contains Call objects which have balance changes, events +// storage changes, etc. +// +// If ordering is important between elements, almost each message like `Log`, +// `Call`, `StorageChange`, etc. have an ordinal field that is represents "execution" +// order of the said element against all other elements in this block. +// +// Due to how the call tree works doing "naively", looping through all calls then +// through a Call's element like `logs` while not yielding the elements in the order +// they were executed on chain. A log in call could have been done before or after +// another in another call depending on the actual call tree. +// +// The `calls` are ordered by creation order and the call tree can be re-computing +// using fields found in `Call` object (parent/child relationship). +// +// Another important thing to note is that even if a transaction succeed, some calls +// within it could have been reverted internally, if this is important to you, you must +// check the field `state_reverted` on the `Call` to determine if it was fully committed +// to the chain or not. +type TransactionTrace struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // consensus + To []byte `protobuf:"bytes,1,opt,name=to,proto3" json:"to,omitempty"` + Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"` + // GasPrice represents the effective price that has been paid for each gas unit of this transaction. Over time, the + // Ethereum rules changes regarding GasPrice field here. Before London fork, the GasPrice was always set to the + // fixed gas price. After London fork, this value has different meaning depending on the transaction type (see `Type` field). + // + // In cases where `TransactionTrace.Type == TRX_TYPE_LEGACY || TRX_TYPE_ACCESS_LIST`, then GasPrice has the same meaning + // as before the London fork. + // + // In cases where `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE`, then GasPrice is the effective gas price paid + // for the transaction which is equals to `BlockHeader.BaseFeePerGas + TransactionTrace.` + GasPrice *BigInt `protobuf:"bytes,3,opt,name=gas_price,json=gasPrice,proto3" json:"gas_price,omitempty"` + // GasLimit is the maximum of gas unit the sender of the transaction is willing to consume when perform the EVM + // execution of the whole transaction + GasLimit uint64 `protobuf:"varint,4,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` + // Value is the amount of Ether transferred as part of this transaction. + Value *BigInt `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"` + // Input data the transaction will receive for execution of EVM. + Input []byte `protobuf:"bytes,6,opt,name=input,proto3" json:"input,omitempty"` + // V is the recovery ID value for the signature Y point. + V []byte `protobuf:"bytes,7,opt,name=v,proto3" json:"v,omitempty"` + // R is the signature's X point on the elliptic curve (32 bytes). + R []byte `protobuf:"bytes,8,opt,name=r,proto3" json:"r,omitempty"` + // S is the signature's Y point on the elliptic curve (32 bytes). + S []byte `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"` + // GasUsed is the total amount of gas unit used for the whole execution of the transaction. + GasUsed uint64 `protobuf:"varint,10,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` + // Type represents the Ethereum transaction type, available only since EIP-2718 & EIP-2930 activation which happened on Berlin fork. + // The value is always set even for transaction before Berlin fork because those before the fork are still legacy transactions. + Type TransactionTrace_Type `protobuf:"varint,12,opt,name=type,proto3,enum=sf.ethereum.type.v2.TransactionTrace_Type" json:"type,omitempty"` + // AcccessList represents the storage access this transaction has agreed to do in which case those storage + // access cost less gas unit per access. + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_ACCESS_LIST || TRX_TYPE_DYNAMIC_FEE` which + // is possible only if Berlin (TRX_TYPE_ACCESS_LIST) nor London (TRX_TYPE_DYNAMIC_FEE) fork are active on the chain. + AccessList []*AccessTuple `protobuf:"bytes,14,rep,name=access_list,json=accessList,proto3" json:"access_list,omitempty"` + // MaxFeePerGas is the maximum fee per gas the user is willing to pay for the transaction gas used. + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE` which is possible only + // if Londong fork is active on the chain. + // + // Only available in DetailLevel: EXTENDED + MaxFeePerGas *BigInt `protobuf:"bytes,11,opt,name=max_fee_per_gas,json=maxFeePerGas,proto3" json:"max_fee_per_gas,omitempty"` + // MaxPriorityFeePerGas is priority fee per gas the user to pay in extra to the miner on top of the block's + // base fee. + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE` which is possible only + // if London fork is active on the chain. + // + // Only available in DetailLevel: EXTENDED + MaxPriorityFeePerGas *BigInt `protobuf:"bytes,13,opt,name=max_priority_fee_per_gas,json=maxPriorityFeePerGas,proto3" json:"max_priority_fee_per_gas,omitempty"` + // meta + Index uint32 `protobuf:"varint,20,opt,name=index,proto3" json:"index,omitempty"` + Hash []byte `protobuf:"bytes,21,opt,name=hash,proto3" json:"hash,omitempty"` + From []byte `protobuf:"bytes,22,opt,name=from,proto3" json:"from,omitempty"` + // Only available in DetailLevel: EXTENDED + ReturnData []byte `protobuf:"bytes,23,opt,name=return_data,json=returnData,proto3" json:"return_data,omitempty"` + // Only available in DetailLevel: EXTENDED + PublicKey []byte `protobuf:"bytes,24,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + BeginOrdinal uint64 `protobuf:"varint,25,opt,name=begin_ordinal,json=beginOrdinal,proto3" json:"begin_ordinal,omitempty"` + EndOrdinal uint64 `protobuf:"varint,26,opt,name=end_ordinal,json=endOrdinal,proto3" json:"end_ordinal,omitempty"` + // TransactionTraceStatus is the status of the transaction execution and will let you know if the transaction + // was successful or not. + // + // A successful transaction has been recorded to the blockchain's state for calls in it that were successful. + // This means it's possible only a subset of the calls were properly recorded, refer to [calls[].state_reverted] field + // to determine which calls were reverted. + // + // A quirks of the Ethereum protocol is that a transaction `FAILED` or `REVERTED` still affects the blockchain's + // state for **some** of the state changes. Indeed, in those cases, the transactions fees are still paid to the miner + // which means there is a balance change for the transaction's emitter (e.g. `from`) to pay the gas fees, an optional + // balance change for gas refunded to the transaction's emitter (e.g. `from`) and a balance change for the miner who + // received the transaction fees. There is also a nonce change for the transaction's emitter (e.g. `from`). + // + // This means that to properly record the state changes for a transaction, you need to conditionally procees the + // transaction's status. + // + // For a `SUCCEEDED` transaction, you iterate over the `calls` array and record the state changes for each call for + // which `state_reverted == false` (if a transaction succeeded, the call at #0 will always `state_reverted == false` + // because it aligns with the transaction). + // + // For a `FAILED` or `REVERTED` transaction, you iterate over the root call (e.g. at #0, will always exist) for + // balance changes you process those where `reason` is either `REASON_GAS_BUY`, `REASON_GAS_REFUND` or + // `REASON_REWARD_TRANSACTION_FEE` and for nonce change, still on the root call, you pick the nonce change which the + // smallest ordinal (if more than one). + Status TransactionTraceStatus `protobuf:"varint,30,opt,name=status,proto3,enum=sf.ethereum.type.v2.TransactionTraceStatus" json:"status,omitempty"` + Receipt *TransactionReceipt `protobuf:"bytes,31,opt,name=receipt,proto3" json:"receipt,omitempty"` + // Only available in DetailLevel: EXTENDED + Calls []*Call `protobuf:"bytes,32,rep,name=calls,proto3" json:"calls,omitempty"` + // BlobGas is the amount of gas the transaction is going to pay for the blobs, this is a computed value + // equivalent to `self.blob_gas_fee_cap * len(self.blob_hashes)` and provided in the model for convenience. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-4844 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + // if Cancun fork is active on the chain. + BlobGas *uint64 `protobuf:"varint,33,opt,name=blob_gas,json=blobGas,proto3,oneof" json:"blob_gas,omitempty"` + // BlobGasFeeCap is the maximum fee per data gas the user is willing to pay for the data gas used. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-4844 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + // if Cancun fork is active on the chain. + BlobGasFeeCap *BigInt `protobuf:"bytes,34,opt,name=blob_gas_fee_cap,json=blobGasFeeCap,proto3,oneof" json:"blob_gas_fee_cap,omitempty"` + // BlobHashes field represents a list of hash outputs from 'kzg_to_versioned_hash' which + // essentially is a version byte + the sha256 hash of the blob commitment (e.g. + // `BLOB_COMMITMENT_VERSION_KZG + sha256(commitment)[1:]`. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-4844 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + // if Cancun fork is active on the chain. + BlobHashes [][]byte `protobuf:"bytes,35,rep,name=blob_hashes,json=blobHashes,proto3" json:"blob_hashes,omitempty"` +} + +func (x *TransactionTrace) Reset() { + *x = TransactionTrace{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransactionTrace) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionTrace) ProtoMessage() {} + +func (x *TransactionTrace) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionTrace.ProtoReflect.Descriptor instead. +func (*TransactionTrace) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{5} +} + +func (x *TransactionTrace) GetTo() []byte { + if x != nil { + return x.To + } + return nil +} + +func (x *TransactionTrace) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *TransactionTrace) GetGasPrice() *BigInt { + if x != nil { + return x.GasPrice + } + return nil +} + +func (x *TransactionTrace) GetGasLimit() uint64 { + if x != nil { + return x.GasLimit + } + return 0 +} + +func (x *TransactionTrace) GetValue() *BigInt { + if x != nil { + return x.Value + } + return nil +} + +func (x *TransactionTrace) GetInput() []byte { + if x != nil { + return x.Input + } + return nil +} + +func (x *TransactionTrace) GetV() []byte { + if x != nil { + return x.V + } + return nil +} + +func (x *TransactionTrace) GetR() []byte { + if x != nil { + return x.R + } + return nil +} + +func (x *TransactionTrace) GetS() []byte { + if x != nil { + return x.S + } + return nil +} + +func (x *TransactionTrace) GetGasUsed() uint64 { + if x != nil { + return x.GasUsed + } + return 0 +} + +func (x *TransactionTrace) GetType() TransactionTrace_Type { + if x != nil { + return x.Type + } + return TransactionTrace_TRX_TYPE_LEGACY +} + +func (x *TransactionTrace) GetAccessList() []*AccessTuple { + if x != nil { + return x.AccessList + } + return nil +} + +func (x *TransactionTrace) GetMaxFeePerGas() *BigInt { + if x != nil { + return x.MaxFeePerGas + } + return nil +} + +func (x *TransactionTrace) GetMaxPriorityFeePerGas() *BigInt { + if x != nil { + return x.MaxPriorityFeePerGas + } + return nil +} + +func (x *TransactionTrace) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *TransactionTrace) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + +func (x *TransactionTrace) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *TransactionTrace) GetReturnData() []byte { + if x != nil { + return x.ReturnData + } + return nil +} + +func (x *TransactionTrace) GetPublicKey() []byte { + if x != nil { + return x.PublicKey + } + return nil +} + +func (x *TransactionTrace) GetBeginOrdinal() uint64 { + if x != nil { + return x.BeginOrdinal + } + return 0 +} + +func (x *TransactionTrace) GetEndOrdinal() uint64 { + if x != nil { + return x.EndOrdinal + } + return 0 +} + +func (x *TransactionTrace) GetStatus() TransactionTraceStatus { + if x != nil { + return x.Status + } + return TransactionTraceStatus_UNKNOWN +} + +func (x *TransactionTrace) GetReceipt() *TransactionReceipt { + if x != nil { + return x.Receipt + } + return nil +} + +func (x *TransactionTrace) GetCalls() []*Call { + if x != nil { + return x.Calls + } + return nil +} + +func (x *TransactionTrace) GetBlobGas() uint64 { + if x != nil && x.BlobGas != nil { + return *x.BlobGas + } + return 0 +} + +func (x *TransactionTrace) GetBlobGasFeeCap() *BigInt { + if x != nil { + return x.BlobGasFeeCap + } + return nil +} + +func (x *TransactionTrace) GetBlobHashes() [][]byte { + if x != nil { + return x.BlobHashes + } + return nil +} + +// AccessTuple represents a list of storage keys for a given contract's address and is used +// for AccessList construction. +type AccessTuple struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + StorageKeys [][]byte `protobuf:"bytes,2,rep,name=storage_keys,json=storageKeys,proto3" json:"storage_keys,omitempty"` +} + +func (x *AccessTuple) Reset() { + *x = AccessTuple{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AccessTuple) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AccessTuple) ProtoMessage() {} + +func (x *AccessTuple) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AccessTuple.ProtoReflect.Descriptor instead. +func (*AccessTuple) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{6} +} + +func (x *AccessTuple) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *AccessTuple) GetStorageKeys() [][]byte { + if x != nil { + return x.StorageKeys + } + return nil +} + +type TransactionReceipt struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // State root is an intermediate state_root hash, computed in-between transactions to make + // **sure** you could build a proof and point to state in the middle of a block. Geth client + // uses `PostState + root + PostStateOrStatus“ while Parity used `status_code, root...“ this piles + // hardforks, see (read the EIPs first): + // - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md + // + // Moreover, the notion of `Outcome“ in parity, which segregates the two concepts, which are + // stored in the same field `status_code“ can be computed based on such a hack of the `state_root` + // field, following `EIP-658`. + // + // Before Byzantinium hard fork, this field is always empty. + StateRoot []byte `protobuf:"bytes,1,opt,name=state_root,json=stateRoot,proto3" json:"state_root,omitempty"` + CumulativeGasUsed uint64 `protobuf:"varint,2,opt,name=cumulative_gas_used,json=cumulativeGasUsed,proto3" json:"cumulative_gas_used,omitempty"` + LogsBloom []byte `protobuf:"bytes,3,opt,name=logs_bloom,json=logsBloom,proto3" json:"logs_bloom,omitempty"` + Logs []*Log `protobuf:"bytes,4,rep,name=logs,proto3" json:"logs,omitempty"` + // BlobGasUsed is the amount of blob gas that has been used within this transaction. At time + // of writing, this is equal to `self.blob_gas_fee_cap * len(self.blob_hashes)`. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-4844 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + // if Cancun fork is active on the chain. + BlobGasUsed *uint64 `protobuf:"varint,5,opt,name=blob_gas_used,json=blobGasUsed,proto3,oneof" json:"blob_gas_used,omitempty"` + // BlobGasPrice is the amount to pay per blob item in the transaction. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-4844 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + // if Cancun fork is active on the chain. + BlobGasPrice *BigInt `protobuf:"bytes,6,opt,name=blob_gas_price,json=blobGasPrice,proto3,oneof" json:"blob_gas_price,omitempty"` +} + +func (x *TransactionReceipt) Reset() { + *x = TransactionReceipt{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransactionReceipt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionReceipt) ProtoMessage() {} + +func (x *TransactionReceipt) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionReceipt.ProtoReflect.Descriptor instead. +func (*TransactionReceipt) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{7} +} + +func (x *TransactionReceipt) GetStateRoot() []byte { + if x != nil { + return x.StateRoot + } + return nil +} + +func (x *TransactionReceipt) GetCumulativeGasUsed() uint64 { + if x != nil { + return x.CumulativeGasUsed + } + return 0 +} + +func (x *TransactionReceipt) GetLogsBloom() []byte { + if x != nil { + return x.LogsBloom + } + return nil +} + +func (x *TransactionReceipt) GetLogs() []*Log { + if x != nil { + return x.Logs + } + return nil +} + +func (x *TransactionReceipt) GetBlobGasUsed() uint64 { + if x != nil && x.BlobGasUsed != nil { + return *x.BlobGasUsed + } + return 0 +} + +func (x *TransactionReceipt) GetBlobGasPrice() *BigInt { + if x != nil { + return x.BlobGasPrice + } + return nil +} + +type Log struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Topics [][]byte `protobuf:"bytes,2,rep,name=topics,proto3" json:"topics,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + // Index is the index of the log relative to the transaction. This index + // is always populated regardless of the state revertion of the the call + // that emitted this log. + // + // Only available in DetailLevel: EXTENDED + Index uint32 `protobuf:"varint,4,opt,name=index,proto3" json:"index,omitempty"` + // BlockIndex represents the index of the log relative to the Block. + // + // An **important** notice is that this field will be 0 when the call + // that emitted the log has been reverted by the chain. + // + // Currently, there is two locations where a Log can be obtained: + // - block.transaction_traces[].receipt.logs[] + // - block.transaction_traces[].calls[].logs[] + // + // In the `receipt` case, the logs will be populated only when the call + // that emitted them has not been reverted by the chain and when in this + // position, the `blockIndex` is always populated correctly. + // + // In the case of `calls` case, for `call` where `stateReverted == true`, + // the `blockIndex` value will always be 0. + BlockIndex uint32 `protobuf:"varint,6,opt,name=blockIndex,proto3" json:"blockIndex,omitempty"` + Ordinal uint64 `protobuf:"varint,7,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *Log) Reset() { + *x = Log{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Log) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Log) ProtoMessage() {} + +func (x *Log) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Log.ProtoReflect.Descriptor instead. +func (*Log) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{8} +} + +func (x *Log) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *Log) GetTopics() [][]byte { + if x != nil { + return x.Topics + } + return nil +} + +func (x *Log) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *Log) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *Log) GetBlockIndex() uint32 { + if x != nil { + return x.BlockIndex + } + return 0 +} + +func (x *Log) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +type Call struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Index uint32 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"` + ParentIndex uint32 `protobuf:"varint,2,opt,name=parent_index,json=parentIndex,proto3" json:"parent_index,omitempty"` + Depth uint32 `protobuf:"varint,3,opt,name=depth,proto3" json:"depth,omitempty"` + CallType CallType `protobuf:"varint,4,opt,name=call_type,json=callType,proto3,enum=sf.ethereum.type.v2.CallType" json:"call_type,omitempty"` + Caller []byte `protobuf:"bytes,5,opt,name=caller,proto3" json:"caller,omitempty"` + Address []byte `protobuf:"bytes,6,opt,name=address,proto3" json:"address,omitempty"` + Value *BigInt `protobuf:"bytes,7,opt,name=value,proto3" json:"value,omitempty"` + GasLimit uint64 `protobuf:"varint,8,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` + GasConsumed uint64 `protobuf:"varint,9,opt,name=gas_consumed,json=gasConsumed,proto3" json:"gas_consumed,omitempty"` + ReturnData []byte `protobuf:"bytes,13,opt,name=return_data,json=returnData,proto3" json:"return_data,omitempty"` + Input []byte `protobuf:"bytes,14,opt,name=input,proto3" json:"input,omitempty"` + ExecutedCode bool `protobuf:"varint,15,opt,name=executed_code,json=executedCode,proto3" json:"executed_code,omitempty"` + Suicide bool `protobuf:"varint,16,opt,name=suicide,proto3" json:"suicide,omitempty"` + // hex representation of the hash -> preimage + KeccakPreimages map[string]string `protobuf:"bytes,20,rep,name=keccak_preimages,json=keccakPreimages,proto3" json:"keccak_preimages,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + StorageChanges []*StorageChange `protobuf:"bytes,21,rep,name=storage_changes,json=storageChanges,proto3" json:"storage_changes,omitempty"` + BalanceChanges []*BalanceChange `protobuf:"bytes,22,rep,name=balance_changes,json=balanceChanges,proto3" json:"balance_changes,omitempty"` + NonceChanges []*NonceChange `protobuf:"bytes,24,rep,name=nonce_changes,json=nonceChanges,proto3" json:"nonce_changes,omitempty"` + Logs []*Log `protobuf:"bytes,25,rep,name=logs,proto3" json:"logs,omitempty"` + CodeChanges []*CodeChange `protobuf:"bytes,26,rep,name=code_changes,json=codeChanges,proto3" json:"code_changes,omitempty"` + GasChanges []*GasChange `protobuf:"bytes,28,rep,name=gas_changes,json=gasChanges,proto3" json:"gas_changes,omitempty"` + // In Ethereum, a call can be either: + // - Successfull, execution passes without any problem encountered + // - Failed, execution failed, and remaining gas should be consumed + // - Reverted, execution failed, but only gas consumed so far is billed, remaining gas is refunded + // + // When a call is either `failed` or `reverted`, the `status_failed` field + // below is set to `true`. If the status is `reverted`, then both `status_failed` + // and `status_reverted` are going to be set to `true`. + StatusFailed bool `protobuf:"varint,10,opt,name=status_failed,json=statusFailed,proto3" json:"status_failed,omitempty"` + StatusReverted bool `protobuf:"varint,12,opt,name=status_reverted,json=statusReverted,proto3" json:"status_reverted,omitempty"` + // Populated when a call either failed or reverted, so when `status_failed == true`, + // see above for details about those flags. + FailureReason string `protobuf:"bytes,11,opt,name=failure_reason,json=failureReason,proto3" json:"failure_reason,omitempty"` + // This field represents wheter or not the state changes performed + // by this call were correctly recorded by the blockchain. + // + // On Ethereum, a transaction can record state changes even if some + // of its inner nested calls failed. This is problematic however since + // a call will invalidate all its state changes as well as all state + // changes performed by its child call. This means that even if a call + // has a status of `SUCCESS`, the chain might have reverted all the state + // changes it performed. + // + // ```text + // + // Trx 1 + // Call #1 + // Call #2 + // Call #3 + // |--- Failure here + // Call #4 + // + // ``` + // + // In the transaction above, while Call #2 and Call #3 would have the + // status `EXECUTED`. + // + // If you check all calls and check only `state_reverted` flag, you might be missing + // some balance changes and nonce changes. This is because when a full transaction fails + // in ethereum (e.g. `calls.all(x.state_reverted == true)`), there is still the transaction + // fee that are recorded to the chain. + // + // Refer to [TransactionTrace#status] field for more details about the handling you must + // perform. + StateReverted bool `protobuf:"varint,30,opt,name=state_reverted,json=stateReverted,proto3" json:"state_reverted,omitempty"` + BeginOrdinal uint64 `protobuf:"varint,31,opt,name=begin_ordinal,json=beginOrdinal,proto3" json:"begin_ordinal,omitempty"` + EndOrdinal uint64 `protobuf:"varint,32,opt,name=end_ordinal,json=endOrdinal,proto3" json:"end_ordinal,omitempty"` + AccountCreations []*AccountCreation `protobuf:"bytes,33,rep,name=account_creations,json=accountCreations,proto3" json:"account_creations,omitempty"` +} + +func (x *Call) Reset() { + *x = Call{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Call) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Call) ProtoMessage() {} + +func (x *Call) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Call.ProtoReflect.Descriptor instead. +func (*Call) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{9} +} + +func (x *Call) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *Call) GetParentIndex() uint32 { + if x != nil { + return x.ParentIndex + } + return 0 +} + +func (x *Call) GetDepth() uint32 { + if x != nil { + return x.Depth + } + return 0 +} + +func (x *Call) GetCallType() CallType { + if x != nil { + return x.CallType + } + return CallType_UNSPECIFIED +} + +func (x *Call) GetCaller() []byte { + if x != nil { + return x.Caller + } + return nil +} + +func (x *Call) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *Call) GetValue() *BigInt { + if x != nil { + return x.Value + } + return nil +} + +func (x *Call) GetGasLimit() uint64 { + if x != nil { + return x.GasLimit + } + return 0 +} + +func (x *Call) GetGasConsumed() uint64 { + if x != nil { + return x.GasConsumed + } + return 0 +} + +func (x *Call) GetReturnData() []byte { + if x != nil { + return x.ReturnData + } + return nil +} + +func (x *Call) GetInput() []byte { + if x != nil { + return x.Input + } + return nil +} + +func (x *Call) GetExecutedCode() bool { + if x != nil { + return x.ExecutedCode + } + return false +} + +func (x *Call) GetSuicide() bool { + if x != nil { + return x.Suicide + } + return false +} + +func (x *Call) GetKeccakPreimages() map[string]string { + if x != nil { + return x.KeccakPreimages + } + return nil +} + +func (x *Call) GetStorageChanges() []*StorageChange { + if x != nil { + return x.StorageChanges + } + return nil +} + +func (x *Call) GetBalanceChanges() []*BalanceChange { + if x != nil { + return x.BalanceChanges + } + return nil +} + +func (x *Call) GetNonceChanges() []*NonceChange { + if x != nil { + return x.NonceChanges + } + return nil +} + +func (x *Call) GetLogs() []*Log { + if x != nil { + return x.Logs + } + return nil +} + +func (x *Call) GetCodeChanges() []*CodeChange { + if x != nil { + return x.CodeChanges + } + return nil +} + +func (x *Call) GetGasChanges() []*GasChange { + if x != nil { + return x.GasChanges + } + return nil +} + +func (x *Call) GetStatusFailed() bool { + if x != nil { + return x.StatusFailed + } + return false +} + +func (x *Call) GetStatusReverted() bool { + if x != nil { + return x.StatusReverted + } + return false +} + +func (x *Call) GetFailureReason() string { + if x != nil { + return x.FailureReason + } + return "" +} + +func (x *Call) GetStateReverted() bool { + if x != nil { + return x.StateReverted + } + return false +} + +func (x *Call) GetBeginOrdinal() uint64 { + if x != nil { + return x.BeginOrdinal + } + return 0 +} + +func (x *Call) GetEndOrdinal() uint64 { + if x != nil { + return x.EndOrdinal + } + return 0 +} + +func (x *Call) GetAccountCreations() []*AccountCreation { + if x != nil { + return x.AccountCreations + } + return nil +} + +type StorageChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + OldValue []byte `protobuf:"bytes,3,opt,name=old_value,json=oldValue,proto3" json:"old_value,omitempty"` + NewValue []byte `protobuf:"bytes,4,opt,name=new_value,json=newValue,proto3" json:"new_value,omitempty"` + Ordinal uint64 `protobuf:"varint,5,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *StorageChange) Reset() { + *x = StorageChange{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StorageChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StorageChange) ProtoMessage() {} + +func (x *StorageChange) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StorageChange.ProtoReflect.Descriptor instead. +func (*StorageChange) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{10} +} + +func (x *StorageChange) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *StorageChange) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +func (x *StorageChange) GetOldValue() []byte { + if x != nil { + return x.OldValue + } + return nil +} + +func (x *StorageChange) GetNewValue() []byte { + if x != nil { + return x.NewValue + } + return nil +} + +func (x *StorageChange) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +type BalanceChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + OldValue *BigInt `protobuf:"bytes,2,opt,name=old_value,json=oldValue,proto3" json:"old_value,omitempty"` + NewValue *BigInt `protobuf:"bytes,3,opt,name=new_value,json=newValue,proto3" json:"new_value,omitempty"` + Reason BalanceChange_Reason `protobuf:"varint,4,opt,name=reason,proto3,enum=sf.ethereum.type.v2.BalanceChange_Reason" json:"reason,omitempty"` + Ordinal uint64 `protobuf:"varint,5,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *BalanceChange) Reset() { + *x = BalanceChange{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BalanceChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BalanceChange) ProtoMessage() {} + +func (x *BalanceChange) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BalanceChange.ProtoReflect.Descriptor instead. +func (*BalanceChange) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{11} +} + +func (x *BalanceChange) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *BalanceChange) GetOldValue() *BigInt { + if x != nil { + return x.OldValue + } + return nil +} + +func (x *BalanceChange) GetNewValue() *BigInt { + if x != nil { + return x.NewValue + } + return nil +} + +func (x *BalanceChange) GetReason() BalanceChange_Reason { + if x != nil { + return x.Reason + } + return BalanceChange_REASON_UNKNOWN +} + +func (x *BalanceChange) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +type NonceChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + OldValue uint64 `protobuf:"varint,2,opt,name=old_value,json=oldValue,proto3" json:"old_value,omitempty"` + NewValue uint64 `protobuf:"varint,3,opt,name=new_value,json=newValue,proto3" json:"new_value,omitempty"` + Ordinal uint64 `protobuf:"varint,4,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *NonceChange) Reset() { + *x = NonceChange{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NonceChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NonceChange) ProtoMessage() {} + +func (x *NonceChange) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NonceChange.ProtoReflect.Descriptor instead. +func (*NonceChange) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{12} +} + +func (x *NonceChange) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *NonceChange) GetOldValue() uint64 { + if x != nil { + return x.OldValue + } + return 0 +} + +func (x *NonceChange) GetNewValue() uint64 { + if x != nil { + return x.NewValue + } + return 0 +} + +func (x *NonceChange) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +type AccountCreation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Account []byte `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` + Ordinal uint64 `protobuf:"varint,2,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *AccountCreation) Reset() { + *x = AccountCreation{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AccountCreation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AccountCreation) ProtoMessage() {} + +func (x *AccountCreation) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AccountCreation.ProtoReflect.Descriptor instead. +func (*AccountCreation) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{13} +} + +func (x *AccountCreation) GetAccount() []byte { + if x != nil { + return x.Account + } + return nil +} + +func (x *AccountCreation) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +type CodeChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + OldHash []byte `protobuf:"bytes,2,opt,name=old_hash,json=oldHash,proto3" json:"old_hash,omitempty"` + OldCode []byte `protobuf:"bytes,3,opt,name=old_code,json=oldCode,proto3" json:"old_code,omitempty"` + NewHash []byte `protobuf:"bytes,4,opt,name=new_hash,json=newHash,proto3" json:"new_hash,omitempty"` + NewCode []byte `protobuf:"bytes,5,opt,name=new_code,json=newCode,proto3" json:"new_code,omitempty"` + Ordinal uint64 `protobuf:"varint,6,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *CodeChange) Reset() { + *x = CodeChange{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CodeChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CodeChange) ProtoMessage() {} + +func (x *CodeChange) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CodeChange.ProtoReflect.Descriptor instead. +func (*CodeChange) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{14} +} + +func (x *CodeChange) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *CodeChange) GetOldHash() []byte { + if x != nil { + return x.OldHash + } + return nil +} + +func (x *CodeChange) GetOldCode() []byte { + if x != nil { + return x.OldCode + } + return nil +} + +func (x *CodeChange) GetNewHash() []byte { + if x != nil { + return x.NewHash + } + return nil +} + +func (x *CodeChange) GetNewCode() []byte { + if x != nil { + return x.NewCode + } + return nil +} + +func (x *CodeChange) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +// The gas change model represents the reason why some gas cost has occurred. +// The gas is computed per actual op codes. Doing them completely might prove +// overwhelming in most cases. +// +// Hence, we only index some of them, those that are costy like all the calls +// one, log events, return data, etc. +type GasChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OldValue uint64 `protobuf:"varint,1,opt,name=old_value,json=oldValue,proto3" json:"old_value,omitempty"` + NewValue uint64 `protobuf:"varint,2,opt,name=new_value,json=newValue,proto3" json:"new_value,omitempty"` + Reason GasChange_Reason `protobuf:"varint,3,opt,name=reason,proto3,enum=sf.ethereum.type.v2.GasChange_Reason" json:"reason,omitempty"` + Ordinal uint64 `protobuf:"varint,4,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *GasChange) Reset() { + *x = GasChange{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GasChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GasChange) ProtoMessage() {} + +func (x *GasChange) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GasChange.ProtoReflect.Descriptor instead. +func (*GasChange) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{15} +} + +func (x *GasChange) GetOldValue() uint64 { + if x != nil { + return x.OldValue + } + return 0 +} + +func (x *GasChange) GetNewValue() uint64 { + if x != nil { + return x.NewValue + } + return 0 +} + +func (x *GasChange) GetReason() GasChange_Reason { + if x != nil { + return x.Reason + } + return GasChange_REASON_UNKNOWN +} + +func (x *GasChange) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +// HeaderOnlyBlock is used to optimally unpack the [Block] structure (note the +// corresponding message number for the `header` field) while consuming less +// memory, when only the `header` is desired. +// +// WARN: this is a client-side optimization pattern and should be moved in the +// consuming code. +type HeaderOnlyBlock struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Header *BlockHeader `protobuf:"bytes,5,opt,name=header,proto3" json:"header,omitempty"` +} + +func (x *HeaderOnlyBlock) Reset() { + *x = HeaderOnlyBlock{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeaderOnlyBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeaderOnlyBlock) ProtoMessage() {} + +func (x *HeaderOnlyBlock) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeaderOnlyBlock.ProtoReflect.Descriptor instead. +func (*HeaderOnlyBlock) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{16} +} + +func (x *HeaderOnlyBlock) GetHeader() *BlockHeader { + if x != nil { + return x.Header + } + return nil +} + +// BlockWithRefs is a lightweight block, with traces and transactions +// purged from the `block` within, and only. It is used in transports +// to pass block data around. +type BlockWithRefs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Block *Block `protobuf:"bytes,2,opt,name=block,proto3" json:"block,omitempty"` + TransactionTraceRefs *TransactionRefs `protobuf:"bytes,3,opt,name=transaction_trace_refs,json=transactionTraceRefs,proto3" json:"transaction_trace_refs,omitempty"` + Irreversible bool `protobuf:"varint,4,opt,name=irreversible,proto3" json:"irreversible,omitempty"` +} + +func (x *BlockWithRefs) Reset() { + *x = BlockWithRefs{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockWithRefs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockWithRefs) ProtoMessage() {} + +func (x *BlockWithRefs) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockWithRefs.ProtoReflect.Descriptor instead. +func (*BlockWithRefs) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{17} +} + +func (x *BlockWithRefs) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *BlockWithRefs) GetBlock() *Block { + if x != nil { + return x.Block + } + return nil +} + +func (x *BlockWithRefs) GetTransactionTraceRefs() *TransactionRefs { + if x != nil { + return x.TransactionTraceRefs + } + return nil +} + +func (x *BlockWithRefs) GetIrreversible() bool { + if x != nil { + return x.Irreversible + } + return false +} + +type TransactionTraceWithBlockRef struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Trace *TransactionTrace `protobuf:"bytes,1,opt,name=trace,proto3" json:"trace,omitempty"` + BlockRef *BlockRef `protobuf:"bytes,2,opt,name=block_ref,json=blockRef,proto3" json:"block_ref,omitempty"` +} + +func (x *TransactionTraceWithBlockRef) Reset() { + *x = TransactionTraceWithBlockRef{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransactionTraceWithBlockRef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionTraceWithBlockRef) ProtoMessage() {} + +func (x *TransactionTraceWithBlockRef) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionTraceWithBlockRef.ProtoReflect.Descriptor instead. +func (*TransactionTraceWithBlockRef) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{18} +} + +func (x *TransactionTraceWithBlockRef) GetTrace() *TransactionTrace { + if x != nil { + return x.Trace + } + return nil +} + +func (x *TransactionTraceWithBlockRef) GetBlockRef() *BlockRef { + if x != nil { + return x.BlockRef + } + return nil +} + +type TransactionRefs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hashes [][]byte `protobuf:"bytes,1,rep,name=hashes,proto3" json:"hashes,omitempty"` +} + +func (x *TransactionRefs) Reset() { + *x = TransactionRefs{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransactionRefs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionRefs) ProtoMessage() {} + +func (x *TransactionRefs) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionRefs.ProtoReflect.Descriptor instead. +func (*TransactionRefs) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{19} +} + +func (x *TransactionRefs) GetHashes() [][]byte { + if x != nil { + return x.Hashes + } + return nil +} + +type BlockRef struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + Number uint64 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"` +} + +func (x *BlockRef) Reset() { + *x = BlockRef{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockRef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockRef) ProtoMessage() {} + +func (x *BlockRef) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockRef.ProtoReflect.Descriptor instead. +func (*BlockRef) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{20} +} + +func (x *BlockRef) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + +func (x *BlockRef) GetNumber() uint64 { + if x != nil { + return x.Number + } + return 0 +} + +var File_sf_ethereum_type_v2_type_proto protoreflect.FileDescriptor + +var file_sf_ethereum_type_v2_type_proto_rawDesc = []byte{ + 0x0a, 0x1e, 0x73, 0x66, 0x2f, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x74, 0x79, + 0x70, 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x13, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x2e, 0x76, 0x32, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8e, 0x05, 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, + 0x12, 0x38, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x06, 0x75, 0x6e, + 0x63, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, + 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x75, 0x6e, + 0x63, 0x6c, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x12, 0x4b, 0x0a, 0x0f, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x0b, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x0c, 0x64, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, + 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0b, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x12, 0x42, 0x0a, 0x0c, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x43, + 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0b, 0x63, 0x6f, 0x64, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0c, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, + 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, + 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x0b, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43, + 0x61, 0x6c, 0x6c, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x03, 0x76, 0x65, 0x72, 0x22, 0x3d, 0x0a, 0x0b, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x4c, + 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x44, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x14, 0x0a, 0x10, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x42, + 0x41, 0x53, 0x45, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x28, 0x10, 0x29, 0x4a, 0x04, 0x08, 0x29, 0x10, + 0x2a, 0x4a, 0x04, 0x08, 0x2a, 0x10, 0x2b, 0x22, 0xd2, 0x07, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x61, + 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x63, 0x6c, + 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x75, 0x6e, + 0x63, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x69, 0x6e, 0x62, + 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6f, 0x69, 0x6e, 0x62, + 0x61, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, + 0x6f, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x6f, 0x6f, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x6f, + 0x6f, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x6f, 0x6d, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6c, 0x6f, 0x67, 0x73, 0x42, 0x6c, 0x6f, 0x6f, + 0x6d, 0x12, 0x3b, 0x0a, 0x0a, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, + 0x6e, 0x74, 0x52, 0x0a, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x12, 0x46, + 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, + 0x74, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, + 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x69, 0x66, 0x66, + 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1b, + 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x67, + 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, + 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x78, 0x74, 0x72, 0x61, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0e, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x07, 0x6d, 0x69, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, + 0x6e, 0x63, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x12, 0x44, 0x0a, 0x10, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, + 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x0d, 0x62, 0x61, 0x73, + 0x65, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x77, 0x69, + 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x13, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, + 0x73, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x4b, 0x0a, 0x0d, 0x74, 0x78, 0x5f, 0x64, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x73, + 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, + 0x76, 0x32, 0x2e, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x41, + 0x72, 0x72, 0x61, 0x79, 0x52, 0x0c, 0x74, 0x78, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, + 0x63, 0x79, 0x12, 0x27, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x67, 0x61, 0x73, 0x5f, 0x75, + 0x73, 0x65, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x0b, 0x62, 0x6c, 0x6f, + 0x62, 0x47, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x2b, 0x0a, 0x0f, 0x65, + 0x78, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x17, + 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0d, 0x65, 0x78, 0x63, 0x65, 0x73, 0x73, 0x42, 0x6c, + 0x6f, 0x62, 0x47, 0x61, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x5f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x18, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x65, 0x61, 0x63, + 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, + 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x65, 0x78, 0x63, + 0x65, 0x73, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x67, 0x61, 0x73, 0x22, 0x47, 0x0a, 0x11, + 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x41, 0x72, 0x72, 0x61, + 0x79, 0x12, 0x32, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, + 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x41, 0x72, 0x72, 0x61, 0x79, + 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1f, 0x0a, 0x0b, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x41, + 0x72, 0x72, 0x61, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x04, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1e, 0x0a, 0x06, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x22, 0xab, 0x0b, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x74, + 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x6e, + 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, + 0x65, 0x12, 0x38, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, + 0x74, 0x52, 0x08, 0x67, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, + 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, + 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, + 0x67, 0x49, 0x6e, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, + 0x6e, 0x70, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, + 0x74, 0x12, 0x0c, 0x0a, 0x01, 0x76, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x76, 0x12, + 0x0c, 0x0a, 0x01, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x72, 0x12, 0x0c, 0x0a, + 0x01, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x67, + 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, + 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x3e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, + 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, + 0x32, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x52, 0x0a, 0x61, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0f, 0x6d, 0x61, 0x78, + 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, + 0x0c, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, 0x73, 0x12, 0x53, 0x0a, + 0x18, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x66, 0x65, + 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x14, 0x6d, 0x61, + 0x78, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, + 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x15, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, + 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, + 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x17, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x18, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, + 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, + 0x6c, 0x18, 0x19, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x4f, 0x72, + 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x72, 0x64, + 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x6e, 0x64, 0x4f, + 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x07, 0x72, + 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x73, + 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, + 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x07, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x2f, + 0x0a, 0x05, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x18, 0x20, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, + 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x05, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x12, + 0x1e, 0x0a, 0x08, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x21, 0x20, 0x01, 0x28, + 0x04, 0x48, 0x00, 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x62, 0x47, 0x61, 0x73, 0x88, 0x01, 0x01, 0x12, + 0x49, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x67, 0x61, 0x73, 0x5f, 0x66, 0x65, 0x65, 0x5f, + 0x63, 0x61, 0x70, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, + 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, + 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x48, 0x01, 0x52, 0x0d, 0x62, 0x6c, 0x6f, 0x62, 0x47, 0x61, + 0x73, 0x46, 0x65, 0x65, 0x43, 0x61, 0x70, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, + 0x6f, 0x62, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x23, 0x20, 0x03, 0x28, 0x0c, 0x52, + 0x0a, 0x62, 0x6c, 0x6f, 0x62, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x22, 0xc4, 0x02, 0x0a, 0x04, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x52, 0x58, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x4c, 0x49, 0x53, + 0x54, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x44, 0x59, 0x4e, 0x41, 0x4d, 0x49, 0x43, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x02, 0x12, 0x11, 0x0a, + 0x0d, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x4c, 0x4f, 0x42, 0x10, 0x03, + 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, + 0x49, 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x44, 0x45, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x10, 0x64, 0x12, + 0x1e, 0x0a, 0x1a, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, + 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x10, 0x65, 0x12, + 0x1e, 0x0a, 0x1a, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, + 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x54, 0x10, 0x66, 0x12, + 0x1b, 0x0a, 0x17, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, + 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x10, 0x68, 0x12, 0x26, 0x0a, 0x22, + 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, 0x55, + 0x4d, 0x5f, 0x53, 0x55, 0x42, 0x4d, 0x49, 0x54, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x41, 0x42, + 0x4c, 0x45, 0x10, 0x69, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, + 0x41, 0x4c, 0x10, 0x6a, 0x12, 0x1c, 0x0a, 0x18, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, + 0x10, 0x78, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x67, 0x61, 0x73, 0x42, + 0x13, 0x0a, 0x11, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x67, 0x61, 0x73, 0x5f, 0x66, 0x65, 0x65, + 0x5f, 0x63, 0x61, 0x70, 0x22, 0x4a, 0x0a, 0x0b, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x75, + 0x70, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, + 0x0c, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x73, + 0x22, 0xc6, 0x02, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, + 0x74, 0x69, 0x76, 0x65, 0x5f, 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x47, + 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x73, 0x5f, 0x62, + 0x6c, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6c, 0x6f, 0x67, 0x73, + 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x12, 0x2c, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, + 0x6f, 0x67, 0x73, 0x12, 0x27, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x67, 0x61, 0x73, 0x5f, + 0x75, 0x73, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x0b, 0x62, 0x6c, + 0x6f, 0x62, 0x47, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x46, 0x0a, 0x0e, + 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x67, 0x61, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, + 0x74, 0x48, 0x01, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x62, 0x47, 0x61, 0x73, 0x50, 0x72, 0x69, 0x63, + 0x65, 0x88, 0x01, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x67, 0x61, + 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, + 0x67, 0x61, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x22, 0x9b, 0x01, 0x0a, 0x03, 0x4c, 0x6f, + 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x74, + 0x6f, 0x70, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, 0x70, + 0x69, 0x63, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1e, 0x0a, + 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, 0x0a, + 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, + 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0xb2, 0x0a, 0x0a, 0x04, 0x43, 0x61, 0x6c, 0x6c, + 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, + 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x61, + 0x72, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x70, + 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x12, + 0x3a, 0x0a, 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, + 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x61, 0x6c, + 0x6c, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x31, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, + 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, + 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x21, 0x0a, + 0x0c, 0x67, 0x61, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x67, 0x61, 0x73, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, + 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x75, 0x69, 0x63, 0x69, 0x64, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, + 0x75, 0x69, 0x63, 0x69, 0x64, 0x65, 0x12, 0x59, 0x0a, 0x10, 0x6b, 0x65, 0x63, 0x63, 0x61, 0x6b, + 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2e, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x2e, 0x4b, 0x65, 0x63, 0x63, + 0x61, 0x6b, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x0f, 0x6b, 0x65, 0x63, 0x63, 0x61, 0x6b, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x73, 0x12, 0x4b, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x66, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, + 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x4b, + 0x0a, 0x0f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x62, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x0d, 0x6e, + 0x6f, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x18, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0c, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, + 0x12, 0x42, 0x0a, 0x0c, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, + 0x18, 0x1a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x64, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0b, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x0b, 0x67, 0x61, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x73, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x66, 0x2e, 0x65, + 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, + 0x47, 0x61, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0a, 0x67, 0x61, 0x73, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, + 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x5f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x76, 0x65, 0x72, + 0x74, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x18, 0x1e, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, 0x65, + 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x6c, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x4f, + 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x72, + 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x20, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x6e, 0x64, + 0x4f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x51, 0x0a, 0x11, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x21, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x42, 0x0a, 0x14, 0x4b, 0x65, + 0x63, 0x63, 0x61, 0x6b, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, + 0x08, 0x1b, 0x10, 0x1c, 0x4a, 0x04, 0x08, 0x1d, 0x10, 0x1e, 0x4a, 0x04, 0x08, 0x32, 0x10, 0x33, + 0x4a, 0x04, 0x08, 0x33, 0x10, 0x34, 0x4a, 0x04, 0x08, 0x3c, 0x10, 0x3d, 0x22, 0x8f, 0x01, 0x0a, + 0x0d, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x6c, + 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6f, + 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0xcc, + 0x05, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x38, 0x0a, 0x09, 0x6f, 0x6c, + 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, + 0x67, 0x49, 0x6e, 0x74, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x41, + 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, + 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0xcf, 0x03, 0x0a, 0x06, + 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, + 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x4d, 0x49, 0x4e, 0x45, + 0x5f, 0x55, 0x4e, 0x43, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x4d, 0x49, 0x4e, 0x45, 0x5f, 0x42, + 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x44, 0x41, 0x4f, 0x5f, 0x52, 0x45, 0x46, 0x55, 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x54, + 0x52, 0x41, 0x43, 0x54, 0x10, 0x03, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x44, 0x41, 0x4f, 0x5f, 0x41, 0x44, 0x4a, 0x55, 0x53, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, + 0x4e, 0x43, 0x45, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x52, 0x45, + 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53, 0x5f, 0x42, 0x41, 0x4c, + 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x47, 0x41, 0x53, 0x5f, 0x42, 0x55, 0x59, 0x10, 0x07, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, + 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x54, 0x52, 0x41, 0x4e, + 0x53, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x08, 0x12, 0x1b, 0x0a, + 0x17, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x46, + 0x45, 0x45, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x54, 0x10, 0x0e, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, + 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x47, 0x41, 0x53, 0x5f, 0x52, 0x45, 0x46, 0x55, 0x4e, 0x44, 0x10, + 0x09, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x4f, 0x55, 0x43, + 0x48, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x0a, 0x12, 0x19, 0x0a, 0x15, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x55, 0x49, 0x43, 0x49, 0x44, 0x45, 0x5f, 0x52, 0x45, + 0x46, 0x55, 0x4e, 0x44, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x53, 0x55, 0x49, 0x43, 0x49, 0x44, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x44, 0x52, 0x41, + 0x57, 0x10, 0x0d, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, + 0x4c, 0x4c, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x52, + 0x49, 0x44, 0x45, 0x10, 0x0c, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x42, 0x55, 0x52, 0x4e, 0x10, 0x0f, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x57, 0x49, 0x54, 0x48, 0x44, 0x52, 0x41, 0x57, 0x41, 0x4c, 0x10, 0x10, 0x22, 0x7b, 0x0a, + 0x0b, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0x45, 0x0a, 0x0f, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, + 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, + 0x6c, 0x22, 0xac, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6c, + 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x6c, + 0x64, 0x48, 0x61, 0x73, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x6c, 0x64, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x48, 0x61, 0x73, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x6e, + 0x65, 0x77, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6e, + 0x65, 0x77, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, + 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, + 0x22, 0xe0, 0x06, 0x0a, 0x09, 0x47, 0x61, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, + 0x65, 0x77, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, + 0x6e, 0x65, 0x77, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3d, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x47, + 0x61, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, + 0x6c, 0x22, 0xbf, 0x05, 0x0a, 0x06, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x0e, + 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, + 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, + 0x01, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4c, 0x4c, + 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x43, 0x4f, 0x50, 0x59, + 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x4f, 0x44, + 0x45, 0x5f, 0x43, 0x4f, 0x50, 0x59, 0x10, 0x04, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x10, + 0x05, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x4f, 0x4e, 0x54, + 0x52, 0x41, 0x43, 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x06, 0x12, + 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x41, + 0x43, 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x32, 0x10, 0x07, 0x12, 0x18, + 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x47, 0x41, 0x54, + 0x45, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x08, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x4f, 0x47, 0x10, 0x09, 0x12, 0x18, + 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x58, 0x54, 0x5f, 0x43, 0x4f, 0x44, + 0x45, 0x5f, 0x43, 0x4f, 0x50, 0x59, 0x10, 0x0a, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, + 0x49, 0x4f, 0x4e, 0x10, 0x0b, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x49, 0x4e, 0x54, 0x52, 0x49, 0x4e, 0x53, 0x49, 0x43, 0x5f, 0x47, 0x41, 0x53, 0x10, 0x0c, 0x12, + 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, + 0x50, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x54, 0x10, 0x0d, + 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x46, 0x55, 0x4e, + 0x44, 0x5f, 0x41, 0x46, 0x54, 0x45, 0x52, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4f, + 0x4e, 0x10, 0x0e, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, + 0x54, 0x55, 0x52, 0x4e, 0x10, 0x0f, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x43, 0x4f, 0x50, + 0x59, 0x10, 0x10, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, + 0x56, 0x45, 0x52, 0x54, 0x10, 0x11, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x44, 0x45, 0x53, 0x54, 0x52, 0x55, 0x43, 0x54, 0x10, 0x12, + 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x49, + 0x43, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x13, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4c, 0x44, 0x5f, 0x41, 0x43, + 0x43, 0x45, 0x53, 0x53, 0x10, 0x14, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x54, 0x58, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x42, 0x41, 0x4c, 0x41, + 0x4e, 0x43, 0x45, 0x10, 0x15, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x54, 0x58, 0x5f, 0x52, 0x45, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x10, 0x16, 0x12, 0x20, 0x0a, 0x1c, + 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x58, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x5f, 0x4f, + 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, 0x45, 0x44, 0x10, 0x17, 0x12, 0x1f, + 0x0a, 0x1b, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x49, 0x4e, + 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x18, 0x12, + 0x22, 0x0a, 0x1e, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x4c, + 0x45, 0x46, 0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, 0x45, + 0x44, 0x10, 0x19, 0x22, 0x4b, 0x0a, 0x0f, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4f, 0x6e, 0x6c, + 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x38, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x22, 0xd1, 0x01, 0x0a, 0x0d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x69, 0x74, 0x68, 0x52, 0x65, + 0x66, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x5a, 0x0a, 0x16, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x73, 0x52, 0x14, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x66, 0x73, + 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x72, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x69, 0x62, 0x6c, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x72, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x62, 0x6c, 0x65, 0x22, 0x97, 0x01, 0x0a, 0x1c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x52, 0x65, 0x66, 0x12, 0x3b, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x05, 0x74, 0x72, 0x61, + 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x72, 0x65, 0x66, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x52, 0x65, 0x66, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x66, 0x22, 0x29, + 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, + 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x06, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x22, 0x36, 0x0a, 0x08, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x52, 0x65, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x2a, 0x4e, 0x0a, 0x16, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, + 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, + 0x44, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x45, 0x44, 0x10, + 0x03, 0x2a, 0x59, 0x0a, 0x08, 0x43, 0x61, 0x6c, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, + 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, + 0x0a, 0x04, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4c, 0x4c, + 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x45, 0x4c, 0x45, 0x47, 0x41, + 0x54, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x10, 0x04, + 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x05, 0x42, 0x4f, 0x5a, 0x4d, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x73, 0x74, 0x2f, 0x66, 0x69, 0x72, 0x65, 0x68, 0x6f, 0x73, + 0x65, 0x2d, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x66, 0x2f, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, + 0x74, 0x79, 0x70, 0x65, 0x2f, 0x76, 0x32, 0x3b, 0x70, 0x62, 0x65, 0x74, 0x68, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_sf_ethereum_type_v2_type_proto_rawDescOnce sync.Once + file_sf_ethereum_type_v2_type_proto_rawDescData = file_sf_ethereum_type_v2_type_proto_rawDesc +) + +func file_sf_ethereum_type_v2_type_proto_rawDescGZIP() []byte { + file_sf_ethereum_type_v2_type_proto_rawDescOnce.Do(func() { + file_sf_ethereum_type_v2_type_proto_rawDescData = protoimpl.X.CompressGZIP(file_sf_ethereum_type_v2_type_proto_rawDescData) + }) + return file_sf_ethereum_type_v2_type_proto_rawDescData +} + +var file_sf_ethereum_type_v2_type_proto_enumTypes = make([]protoimpl.EnumInfo, 6) +var file_sf_ethereum_type_v2_type_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_sf_ethereum_type_v2_type_proto_goTypes = []interface{}{ + (TransactionTraceStatus)(0), // 0: sf.ethereum.type.v2.TransactionTraceStatus + (CallType)(0), // 1: sf.ethereum.type.v2.CallType + (Block_DetailLevel)(0), // 2: sf.ethereum.type.v2.Block.DetailLevel + (TransactionTrace_Type)(0), // 3: sf.ethereum.type.v2.TransactionTrace.Type + (BalanceChange_Reason)(0), // 4: sf.ethereum.type.v2.BalanceChange.Reason + (GasChange_Reason)(0), // 5: sf.ethereum.type.v2.GasChange.Reason + (*Block)(nil), // 6: sf.ethereum.type.v2.Block + (*BlockHeader)(nil), // 7: sf.ethereum.type.v2.BlockHeader + (*Uint64NestedArray)(nil), // 8: sf.ethereum.type.v2.Uint64NestedArray + (*Uint64Array)(nil), // 9: sf.ethereum.type.v2.Uint64Array + (*BigInt)(nil), // 10: sf.ethereum.type.v2.BigInt + (*TransactionTrace)(nil), // 11: sf.ethereum.type.v2.TransactionTrace + (*AccessTuple)(nil), // 12: sf.ethereum.type.v2.AccessTuple + (*TransactionReceipt)(nil), // 13: sf.ethereum.type.v2.TransactionReceipt + (*Log)(nil), // 14: sf.ethereum.type.v2.Log + (*Call)(nil), // 15: sf.ethereum.type.v2.Call + (*StorageChange)(nil), // 16: sf.ethereum.type.v2.StorageChange + (*BalanceChange)(nil), // 17: sf.ethereum.type.v2.BalanceChange + (*NonceChange)(nil), // 18: sf.ethereum.type.v2.NonceChange + (*AccountCreation)(nil), // 19: sf.ethereum.type.v2.AccountCreation + (*CodeChange)(nil), // 20: sf.ethereum.type.v2.CodeChange + (*GasChange)(nil), // 21: sf.ethereum.type.v2.GasChange + (*HeaderOnlyBlock)(nil), // 22: sf.ethereum.type.v2.HeaderOnlyBlock + (*BlockWithRefs)(nil), // 23: sf.ethereum.type.v2.BlockWithRefs + (*TransactionTraceWithBlockRef)(nil), // 24: sf.ethereum.type.v2.TransactionTraceWithBlockRef + (*TransactionRefs)(nil), // 25: sf.ethereum.type.v2.TransactionRefs + (*BlockRef)(nil), // 26: sf.ethereum.type.v2.BlockRef + nil, // 27: sf.ethereum.type.v2.Call.KeccakPreimagesEntry + (*timestamppb.Timestamp)(nil), // 28: google.protobuf.Timestamp +} +var file_sf_ethereum_type_v2_type_proto_depIdxs = []int32{ + 7, // 0: sf.ethereum.type.v2.Block.header:type_name -> sf.ethereum.type.v2.BlockHeader + 7, // 1: sf.ethereum.type.v2.Block.uncles:type_name -> sf.ethereum.type.v2.BlockHeader + 11, // 2: sf.ethereum.type.v2.Block.transaction_traces:type_name -> sf.ethereum.type.v2.TransactionTrace + 17, // 3: sf.ethereum.type.v2.Block.balance_changes:type_name -> sf.ethereum.type.v2.BalanceChange + 2, // 4: sf.ethereum.type.v2.Block.detail_level:type_name -> sf.ethereum.type.v2.Block.DetailLevel + 20, // 5: sf.ethereum.type.v2.Block.code_changes:type_name -> sf.ethereum.type.v2.CodeChange + 15, // 6: sf.ethereum.type.v2.Block.system_calls:type_name -> sf.ethereum.type.v2.Call + 10, // 7: sf.ethereum.type.v2.BlockHeader.difficulty:type_name -> sf.ethereum.type.v2.BigInt + 10, // 8: sf.ethereum.type.v2.BlockHeader.total_difficulty:type_name -> sf.ethereum.type.v2.BigInt + 28, // 9: sf.ethereum.type.v2.BlockHeader.timestamp:type_name -> google.protobuf.Timestamp + 10, // 10: sf.ethereum.type.v2.BlockHeader.base_fee_per_gas:type_name -> sf.ethereum.type.v2.BigInt + 8, // 11: sf.ethereum.type.v2.BlockHeader.tx_dependency:type_name -> sf.ethereum.type.v2.Uint64NestedArray + 9, // 12: sf.ethereum.type.v2.Uint64NestedArray.val:type_name -> sf.ethereum.type.v2.Uint64Array + 10, // 13: sf.ethereum.type.v2.TransactionTrace.gas_price:type_name -> sf.ethereum.type.v2.BigInt + 10, // 14: sf.ethereum.type.v2.TransactionTrace.value:type_name -> sf.ethereum.type.v2.BigInt + 3, // 15: sf.ethereum.type.v2.TransactionTrace.type:type_name -> sf.ethereum.type.v2.TransactionTrace.Type + 12, // 16: sf.ethereum.type.v2.TransactionTrace.access_list:type_name -> sf.ethereum.type.v2.AccessTuple + 10, // 17: sf.ethereum.type.v2.TransactionTrace.max_fee_per_gas:type_name -> sf.ethereum.type.v2.BigInt + 10, // 18: sf.ethereum.type.v2.TransactionTrace.max_priority_fee_per_gas:type_name -> sf.ethereum.type.v2.BigInt + 0, // 19: sf.ethereum.type.v2.TransactionTrace.status:type_name -> sf.ethereum.type.v2.TransactionTraceStatus + 13, // 20: sf.ethereum.type.v2.TransactionTrace.receipt:type_name -> sf.ethereum.type.v2.TransactionReceipt + 15, // 21: sf.ethereum.type.v2.TransactionTrace.calls:type_name -> sf.ethereum.type.v2.Call + 10, // 22: sf.ethereum.type.v2.TransactionTrace.blob_gas_fee_cap:type_name -> sf.ethereum.type.v2.BigInt + 14, // 23: sf.ethereum.type.v2.TransactionReceipt.logs:type_name -> sf.ethereum.type.v2.Log + 10, // 24: sf.ethereum.type.v2.TransactionReceipt.blob_gas_price:type_name -> sf.ethereum.type.v2.BigInt + 1, // 25: sf.ethereum.type.v2.Call.call_type:type_name -> sf.ethereum.type.v2.CallType + 10, // 26: sf.ethereum.type.v2.Call.value:type_name -> sf.ethereum.type.v2.BigInt + 27, // 27: sf.ethereum.type.v2.Call.keccak_preimages:type_name -> sf.ethereum.type.v2.Call.KeccakPreimagesEntry + 16, // 28: sf.ethereum.type.v2.Call.storage_changes:type_name -> sf.ethereum.type.v2.StorageChange + 17, // 29: sf.ethereum.type.v2.Call.balance_changes:type_name -> sf.ethereum.type.v2.BalanceChange + 18, // 30: sf.ethereum.type.v2.Call.nonce_changes:type_name -> sf.ethereum.type.v2.NonceChange + 14, // 31: sf.ethereum.type.v2.Call.logs:type_name -> sf.ethereum.type.v2.Log + 20, // 32: sf.ethereum.type.v2.Call.code_changes:type_name -> sf.ethereum.type.v2.CodeChange + 21, // 33: sf.ethereum.type.v2.Call.gas_changes:type_name -> sf.ethereum.type.v2.GasChange + 19, // 34: sf.ethereum.type.v2.Call.account_creations:type_name -> sf.ethereum.type.v2.AccountCreation + 10, // 35: sf.ethereum.type.v2.BalanceChange.old_value:type_name -> sf.ethereum.type.v2.BigInt + 10, // 36: sf.ethereum.type.v2.BalanceChange.new_value:type_name -> sf.ethereum.type.v2.BigInt + 4, // 37: sf.ethereum.type.v2.BalanceChange.reason:type_name -> sf.ethereum.type.v2.BalanceChange.Reason + 5, // 38: sf.ethereum.type.v2.GasChange.reason:type_name -> sf.ethereum.type.v2.GasChange.Reason + 7, // 39: sf.ethereum.type.v2.HeaderOnlyBlock.header:type_name -> sf.ethereum.type.v2.BlockHeader + 6, // 40: sf.ethereum.type.v2.BlockWithRefs.block:type_name -> sf.ethereum.type.v2.Block + 25, // 41: sf.ethereum.type.v2.BlockWithRefs.transaction_trace_refs:type_name -> sf.ethereum.type.v2.TransactionRefs + 11, // 42: sf.ethereum.type.v2.TransactionTraceWithBlockRef.trace:type_name -> sf.ethereum.type.v2.TransactionTrace + 26, // 43: sf.ethereum.type.v2.TransactionTraceWithBlockRef.block_ref:type_name -> sf.ethereum.type.v2.BlockRef + 44, // [44:44] is the sub-list for method output_type + 44, // [44:44] is the sub-list for method input_type + 44, // [44:44] is the sub-list for extension type_name + 44, // [44:44] is the sub-list for extension extendee + 0, // [0:44] is the sub-list for field type_name +} + +func init() { file_sf_ethereum_type_v2_type_proto_init() } +func file_sf_ethereum_type_v2_type_proto_init() { + if File_sf_ethereum_type_v2_type_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_sf_ethereum_type_v2_type_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Block); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockHeader); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Uint64NestedArray); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Uint64Array); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BigInt); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionTrace); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AccessTuple); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionReceipt); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Log); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Call); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StorageChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BalanceChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NonceChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AccountCreation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CodeChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GasChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeaderOnlyBlock); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockWithRefs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionTraceWithBlockRef); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionRefs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockRef); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[1].OneofWrappers = []interface{}{} + file_sf_ethereum_type_v2_type_proto_msgTypes[5].OneofWrappers = []interface{}{} + file_sf_ethereum_type_v2_type_proto_msgTypes[7].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_sf_ethereum_type_v2_type_proto_rawDesc, + NumEnums: 6, + NumMessages: 22, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_sf_ethereum_type_v2_type_proto_goTypes, + DependencyIndexes: file_sf_ethereum_type_v2_type_proto_depIdxs, + EnumInfos: file_sf_ethereum_type_v2_type_proto_enumTypes, + MessageInfos: file_sf_ethereum_type_v2_type_proto_msgTypes, + }.Build() + File_sf_ethereum_type_v2_type_proto = out.File + file_sf_ethereum_type_v2_type_proto_rawDesc = nil + file_sf_ethereum_type_v2_type_proto_goTypes = nil + file_sf_ethereum_type_v2_type_proto_depIdxs = nil +} diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index da076d46b..b461903f2 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" pcommon "github.com/sei-protocol/sei-chain/precompiles/common" "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/tracers" "github.com/tendermint/tendermint/libs/log" ) @@ -227,15 +228,30 @@ func (p Precompile) sendNative(ctx sdk.Context, method *abi.Method, args []inter return nil, err } - usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderSeiAddr, value, p.bankKeeper) + precompiledSeiAddr := p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address) + + usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, precompiledSeiAddr, senderSeiAddr, value, p.bankKeeper) if err != nil { return nil, err } + if hooks := tracers.GetCtxEthTracingHooks(ctx); hooks != nil && hooks.OnBalanceChange != nil && (value.Sign() != 0) { + tracers.TraceTransferEVMValue(ctx, hooks, p.bankKeeper, precompiledSeiAddr, p.address, senderSeiAddr, caller, value) + } + if err := p.bankKeeper.SendCoinsAndWei(ctx, senderSeiAddr, receiverSeiAddr, usei, wei); err != nil { return nil, err } + if hooks := tracers.GetCtxEthTracingHooks(ctx); hooks != nil && hooks.OnBalanceChange != nil && (value.Sign() != 0) { + receveirEvmAddr, found := p.evmKeeper.GetEVMAddress(ctx, receiverSeiAddr) + if !found { + return nil, fmt.Errorf("sei address %s is not associated, this shouldn't happen at this point since SendCoinsAndWei above worked", receiverSeiAddr) + } + + tracers.TraceTransferEVMValue(ctx, hooks, p.bankKeeper, senderSeiAddr, caller, receiverSeiAddr, receveirEvmAddr, value) + } + return method.Outputs.Pack(true) } diff --git a/precompiles/bank/bank_test.go b/precompiles/bank/bank_test.go index 8552b3fd3..a4130dfca 100644 --- a/precompiles/bank/bank_test.go +++ b/precompiles/bank/bank_test.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/tx/signing" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/ethereum/go-ethereum/common" + ethtracing "github.com/ethereum/go-ethereum/core/tracing" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -20,6 +21,8 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/ante" "github.com/sei-protocol/sei-chain/x/evm/keeper" "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/tracers" + "github.com/sei-protocol/sei-chain/x/evm/tracing" "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/sei-protocol/sei-chain/x/evm/types/ethtx" "github.com/stretchr/testify/require" @@ -39,7 +42,18 @@ func (tx mockTx) GetSignaturesV2() ([]signing.SignatureV2, error) { return nil, func TestRun(t *testing.T) { testApp := testkeeper.EVMTestApp + + var balanceChanges []balanceChange + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + ctx = tracers.SetCtxBlockchainTracer(ctx, &tracing.Hooks{ + Hooks: ðtracing.Hooks{ + OnBalanceChange: func(addr common.Address, prev, new *big.Int, reason ethtracing.BalanceChangeReason) { + balanceChanges = append(balanceChanges, balanceChange{prev.String(), new.String(), reason}) + }, + }, + }) + k := &testApp.EvmKeeper // Setup sender addresses and environment @@ -137,6 +151,15 @@ func TestRun(t *testing.T) { require.Nil(t, err) require.Empty(t, res.VmError) + // Test balance changes, there is 8 but we care about the first 4 here + require.Equal(t, 8, len(balanceChanges)) + require.Equal(t, []balanceChange{ + {"9800000000000000000", "9799989999999999900", ethtracing.BalanceChangeTransfer}, + {"0", "10000000000100", ethtracing.BalanceChangeTransfer}, + {"10000000000100", "0", ethtracing.BalanceChangeTransfer}, + {"9799989999999999900", "9800000000000000000", ethtracing.BalanceChangeTransfer}, + }, balanceChanges[0:4], "balance changes do not match, actual are:\n\n%s", balanceChangesValues(balanceChanges[0:4])) + evts := ctx.EventManager().ABCIEvents() for _, evt := range evts { @@ -289,3 +312,19 @@ func TestAddress(t *testing.T) { require.Nil(t, err) require.Equal(t, common.HexToAddress(bank.BankAddress), p.Address()) } + +type balanceChange struct { + // We use string to avoid big.Int equality issues + old string + new string + reason ethtracing.BalanceChangeReason +} + +func balanceChangesValues(changes []balanceChange) string { + out := make([]string, len(changes)) + for i, change := range changes { + out[i] = fmt.Sprintf("{%q, %q, ethtracing.BalanceChangeReason(%d)}", change.old, change.new, change.reason) + } + + return strings.Join(out, "\n") +} diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index 226ae9637..400a7c336 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -129,7 +129,7 @@ func (p Precompile) GetName() string { return "wasmd" } -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool) (ret []byte, remainingGas uint64, err error) { +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, logger *tracing.Hooks, readOnly bool) (ret []byte, remainingGas uint64, err error) { ctx, method, args, err := p.Prepare(evm, input) if err != nil { return nil, 0, err @@ -141,6 +141,10 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, calli } ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimitBigInt.Uint64())) + if logger != nil && logger.OnGasChange != nil { + defer func() { logger.OnGasChange(suppliedGas, remainingGas, tracing.GasChangeCallPrecompiledContract) }() + } + switch method.Name { case InstantiateMethod: return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 29ad789fa..a0c87eab2 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -26,6 +26,7 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc721" artifactsutils "github.com/sei-protocol/sei-chain/x/evm/artifacts/utils" "github.com/sei-protocol/sei-chain/x/evm/state" + evmtracers "github.com/sei-protocol/sei-chain/x/evm/tracers" "github.com/sei-protocol/sei-chain/x/evm/types" ) @@ -126,7 +127,7 @@ func (server msgServer) EVMTransaction(goCtx context.Context, msg *types.MsgEVMT originalGasMeter.ConsumeGas(adjustedGasUsed.TruncateInt().Uint64(), "evm transaction") }() - res, applyErr := server.applyEVMMessage(ctx, emsg, stateDB, gp) + res, applyErr := server.applyEVMTx(ctx, tx, emsg, stateDB, gp) serverRes = &types.MsgEVMTransactionResponse{ Hash: tx.Hash().Hex(), } @@ -191,18 +192,67 @@ func (k *Keeper) GetEVMMessage(ctx sdk.Context, tx *ethtypes.Transaction, sender return msg } -func (server msgServer) applyEVMMessage(ctx sdk.Context, msg *core.Message, stateDB *state.DBImpl, gp core.GasPool) (*core.ExecutionResult, error) { +func (server msgServer) applyEVMTx(ctx sdk.Context, tx *ethtypes.Transaction, msg *core.Message, stateDB *state.DBImpl, gp core.GasPool) (res *core.ExecutionResult, err error) { + evmHooks := evmtracers.GetCtxEthTracingHooks(ctx) + + var onStart func(vm *vm.EVM) + if evmHooks != nil && evmHooks.OnTxStart != nil { + onStart = func(evmInstance *vm.EVM) { + evmHooks.OnTxStart(evmInstance.GetVMContext(), tx, msg.From) + } + } + var onEnd func(res *core.ExecutionResult, err error) + if evmHooks != nil && evmHooks.OnTxEnd != nil { + onEnd = func(res *core.ExecutionResult, err error) { + var receipt *ethtypes.Receipt + if err == nil { + receipt = getEthReceipt(ctx, tx, msg, res, stateDB) + } + + var txErr error + if res != nil { + txErr = res.Err + } + + evmHooks.OnTxEnd(receipt, txErr) + } + } + + return server.applyEVMMessageWithTracing(ctx, msg, stateDB, gp, onStart, onEnd) +} + +func (server msgServer) applyEVMMessageWithTracing( + ctx sdk.Context, + msg *core.Message, + stateDB *state.DBImpl, + gp core.GasPool, + onStart func(vm *vm.EVM), + onEnd func(res *core.ExecutionResult, err error), +) (res *core.ExecutionResult, err error) { blockCtx, err := server.GetVMBlockContext(ctx, gp) if err != nil { return nil, err } cfg := types.DefaultChainConfig().EthereumConfig(server.ChainID()) txCtx := core.NewEVMTxContext(msg) - evmInstance := vm.NewEVM(*blockCtx, txCtx, stateDB, cfg, vm.Config{}) + evmHooks := evmtracers.GetCtxEthTracingHooks(ctx) + evmInstance := vm.NewEVM(*blockCtx, txCtx, stateDB, cfg, vm.Config{ + Tracer: evmHooks, + }) stateDB.SetEVM(evmInstance) + stateDB.SetLogger(evmHooks) + + if onStart != nil { + onStart(evmInstance) + } + if onEnd != nil { + defer func() { + onEnd(res, err) + }() + } + st := core.NewStateTransition(evmInstance, msg, &gp, true) // fee already charged in ante handler - res, err := st.TransitionDb() - return res, err + return st.TransitionDb() } func (server msgServer) writeReceipt(ctx sdk.Context, origMsg *types.MsgEVMTransaction, tx *ethtypes.Transaction, msg *core.Message, response *types.MsgEVMTransactionResponse, stateDB *state.DBImpl) (*types.Receipt, error) { @@ -314,3 +364,33 @@ func (server msgServer) RegisterPointer(goCtx context.Context, msg *types.MsgReg } return &types.MsgRegisterPointerResponse{PointerAddress: pointerAddr.String()}, err } + +func getEthReceipt(ctx sdk.Context, tx *ethtypes.Transaction, msg *core.Message, res *core.ExecutionResult, stateDB *state.DBImpl) *ethtypes.Receipt { + ethLogs := stateDB.GetAllLogs() + receipt := ðtypes.Receipt{ + Type: tx.Type(), + CumulativeGasUsed: uint64(0), + Logs: ethLogs, + TxHash: tx.Hash(), + GasUsed: res.UsedGas, + EffectiveGasPrice: tx.GasPrice(), + TransactionIndex: uint(ctx.TxIndex()), + } + receipt.Bloom = ethtypes.CreateBloom(ethtypes.Receipts{receipt}) + + if msg.To == nil { + receipt.ContractAddress = crypto.CreateAddress(msg.From, msg.Nonce) + } else { + if len(msg.Data) > 0 { + receipt.ContractAddress = *msg.To + } + } + + if res.Err == nil { + receipt.Status = ethtypes.ReceiptStatusSuccessful + } else { + receipt.Status = ethtypes.ReceiptStatusFailed + } + + return receipt +} diff --git a/x/evm/module.go b/x/evm/module.go index 2e1ec5606..0cab67a1e 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -28,6 +28,7 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/keeper" "github.com/sei-protocol/sei-chain/x/evm/migrations" "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/tracers" "github.com/sei-protocol/sei-chain/x/evm/types" ) @@ -212,6 +213,13 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val coinbase = am.keeper.AccountKeeper().GetModuleAddress(authtypes.FeeCollectorName) } evmTxDeferredInfoList := am.keeper.GetEVMTxDeferredInfo(ctx) + evmHooks := tracers.GetCtxEthTracingHooks(ctx) + + var coinbaseEVMAddress common.Address + if evmHooks != nil { + coinbaseEVMAddress = am.keeper.GetEVMAddressOrDefault(ctx, coinbase) + } + denom := am.keeper.GetBaseDenom(ctx) surplus := utils.Sdk0 for _, deferredInfo := range evmTxDeferredInfoList { @@ -231,9 +239,14 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val if err := am.keeper.BankKeeper().SendCoinsAndWei(ctx, coinbaseAddress, coinbase, balance.Amount, weiBalance); err != nil { panic(err) } + + if evmHooks != nil && evmHooks.OnBalanceChange != nil { + tracers.TraceTransactionRewards(ctx, evmHooks, am.keeper.BankKeeper(), coinbase, coinbaseEVMAddress, balance.Amount, weiBalance) + } } surplus = surplus.Add(deferredInfo.Surplus) } + surplusUsei, surplusWei := state.SplitUseiWeiAmount(surplus.BigInt()) if surplusUsei.GT(sdk.ZeroInt()) { if err := am.keeper.BankKeeper().AddCoins(ctx, am.keeper.AccountKeeper().GetModuleAddress(types.ModuleName), sdk.NewCoins(sdk.NewCoin(am.keeper.GetBaseDenom(ctx), surplusUsei)), true); err != nil { @@ -245,6 +258,12 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val ctx.Logger().Error("failed to send wei surplus of %s to EVM module account", surplusWei) } } + + if evmHooks != nil && evmHooks.OnBalanceChange != nil && (surplusUsei.GT(sdk.ZeroInt()) || surplusWei.GT(sdk.ZeroInt())) { + evmModuleAddress := am.keeper.AccountKeeper().GetModuleAddress(types.ModuleName) + tracers.TraceBlockReward(ctx, evmHooks, am.keeper.BankKeeper(), evmModuleAddress, am.keeper.GetEVMAddressOrDefault(ctx, evmModuleAddress), surplusUsei, surplusWei) + } + am.keeper.SetTxHashesOnHeight(ctx, ctx.BlockHeight(), utils.Map(evmTxDeferredInfoList, func(i keeper.EvmTxDeferredInfo) common.Hash { return i.TxHash })) am.keeper.SetBlockBloom(ctx, ctx.BlockHeight(), utils.Map(evmTxDeferredInfoList, func(i keeper.EvmTxDeferredInfo) ethtypes.Bloom { return i.TxBloom })) return []abci.ValidatorUpdate{} diff --git a/x/evm/module_test.go b/x/evm/module_test.go index 108603d85..58e8ac16a 100644 --- a/x/evm/module_test.go +++ b/x/evm/module_test.go @@ -1,17 +1,21 @@ package evm_test import ( + "fmt" "math/big" + "strings" "testing" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" + ethtracing "github.com/ethereum/go-ethereum/core/tracing" ethtypes "github.com/ethereum/go-ethereum/core/types" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm" "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/tracers" + "github.com/sei-protocol/sei-chain/x/evm/tracing" "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -29,19 +33,19 @@ func TestABCI(t *testing.T) { m.BeginBlock(ctx, abci.RequestBeginBlock{}) // 1st tx s := state.NewDBImpl(ctx.WithTxIndex(1), k, false) - s.SubBalance(evmAddr1, big.NewInt(10000000000000), tracing.BalanceChangeUnspecified) - s.AddBalance(evmAddr2, big.NewInt(8000000000000), tracing.BalanceChangeUnspecified) + s.SubBalance(evmAddr1, big.NewInt(10000000000000), ethtracing.BalanceChangeUnspecified) + s.AddBalance(evmAddr2, big.NewInt(8000000000000), ethtracing.BalanceChangeUnspecified) feeCollectorAddr, err := k.GetFeeCollectorAddress(ctx) require.Nil(t, err) - s.AddBalance(feeCollectorAddr, big.NewInt(2000000000000), tracing.BalanceChangeUnspecified) + s.AddBalance(feeCollectorAddr, big.NewInt(2000000000000), ethtracing.BalanceChangeUnspecified) surplus, err := s.Finalize() require.Nil(t, err) require.Equal(t, sdk.ZeroInt(), surplus) k.AppendToEvmTxDeferredInfo(ctx.WithTxIndex(1), ethtypes.Bloom{}, common.Hash{}, surplus) // 3rd tx s = state.NewDBImpl(ctx.WithTxIndex(3), k, false) - s.SubBalance(evmAddr2, big.NewInt(5000000000000), tracing.BalanceChangeUnspecified) - s.AddBalance(evmAddr1, big.NewInt(5000000000000), tracing.BalanceChangeUnspecified) + s.SubBalance(evmAddr2, big.NewInt(5000000000000), ethtracing.BalanceChangeUnspecified) + s.AddBalance(evmAddr1, big.NewInt(5000000000000), ethtracing.BalanceChangeUnspecified) surplus, err = s.Finalize() require.Nil(t, err) require.Equal(t, sdk.ZeroInt(), surplus) @@ -54,9 +58,12 @@ func TestABCI(t *testing.T) { // second block m.BeginBlock(ctx, abci.RequestBeginBlock{}) // 2nd tx + var balanceChanges []evmBalanceChange + ctx = addTestBalanceChangeTracerToCtx(ctx, &balanceChanges) + s = state.NewDBImpl(ctx.WithTxIndex(2), k, false) - s.SubBalance(evmAddr2, big.NewInt(3000000000000), tracing.BalanceChangeUnspecified) - s.AddBalance(evmAddr1, big.NewInt(2000000000000), tracing.BalanceChangeUnspecified) + s.SubBalance(evmAddr2, big.NewInt(3000000000000), ethtracing.BalanceChangeUnspecified) + s.AddBalance(evmAddr1, big.NewInt(2000000000000), ethtracing.BalanceChangeUnspecified) surplus, err = s.Finalize() require.Nil(t, err) require.Equal(t, sdk.NewInt(1000000000000), surplus) @@ -66,6 +73,11 @@ func TestABCI(t *testing.T) { require.Equal(t, uint64(1), k.BankKeeper().GetBalance(ctx, k.AccountKeeper().GetModuleAddress(types.ModuleName), "usei").Amount.Uint64()) require.Equal(t, uint64(2), k.BankKeeper().GetBalance(ctx, k.AccountKeeper().GetModuleAddress(authtypes.FeeCollectorName), "usei").Amount.Uint64()) + require.Equal(t, 1, len(balanceChanges)) + require.Equal(t, []evmBalanceChange{ + {"0", "1000000000000", ethtracing.BalanceIncreaseRewardMineBlock}, + }, balanceChanges, "balance changes do not match, actual are:\n\n%s", balanceChangesValues(balanceChanges)) + // third block m.BeginBlock(ctx, abci.RequestBeginBlock{}) k.AppendErrorToEvmTxDeferredInfo(ctx.WithTxIndex(0), common.Hash{1}, "test error") @@ -75,3 +87,29 @@ func TestABCI(t *testing.T) { require.Nil(t, err) require.Equal(t, receipt.VmError, "test error") } + +func addTestBalanceChangeTracerToCtx(ctx sdk.Context, balanceChanges *[]evmBalanceChange) sdk.Context { + return tracers.SetCtxBlockchainTracer(ctx, &tracing.Hooks{ + Hooks: ðtracing.Hooks{ + OnBalanceChange: func(addr common.Address, prev, new *big.Int, reason ethtracing.BalanceChangeReason) { + *balanceChanges = append(*balanceChanges, evmBalanceChange{prev.String(), new.String(), reason}) + }, + }, + }) +} + +type evmBalanceChange struct { + // We use string to avoid big.Int equality issues + old string + new string + reason ethtracing.BalanceChangeReason +} + +func balanceChangesValues(changes []evmBalanceChange) string { + out := make([]string, len(changes)) + for i, change := range changes { + out[i] = fmt.Sprintf("{%q, %q, ethtracing.BalanceChangeReason(%d)}", change.old, change.new, change.reason) + } + + return strings.Join(out, "\n") +} diff --git a/x/evm/state/balance.go b/x/evm/state/balance.go index 493c8e495..cdb1e243e 100644 --- a/x/evm/state/balance.go +++ b/x/evm/state/balance.go @@ -76,7 +76,6 @@ func (s *DBImpl) AddBalance(evmAddr common.Address, amt *big.Int, reason tracing } if s.logger != nil && s.logger.OnBalanceChange != nil { - // We could modify AddWei instead so it returns us the old/new balance directly. newBalance := s.GetBalance(evmAddr) oldBalance := new(big.Int).Sub(newBalance, amt) diff --git a/x/evm/tracers/balances.go b/x/evm/tracers/balances.go new file mode 100644 index 000000000..563028379 --- /dev/null +++ b/x/evm/tracers/balances.go @@ -0,0 +1,82 @@ +package tracers + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +type BankBalanceKeeper interface { + GetBalance(sdk.Context, sdk.AccAddress, string) sdk.Coin + GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Int +} + +// TraceTransactionRewards is a helper function to trace the payment of the transaction rewards +// to the coinbase address. +func TraceTransactionRewards( + ctx sdk.Context, + hooks *tracing.Hooks, + bankKeeper BankBalanceKeeper, + toSeiAddr sdk.AccAddress, + toEVMAddr common.Address, + usei sdk.Int, + wei sdk.Int, +) { + value := usei.Mul(state.SdkUseiToSweiMultiplier).Add(wei).BigInt() + + newBalance := getEVMBalance(ctx, bankKeeper, toSeiAddr) + oldBalance := new(big.Int).Sub(newBalance, value) + + hooks.OnBalanceChange(toEVMAddr, oldBalance, newBalance, tracing.BalanceIncreaseRewardTransactionFee) +} + +func TraceTransferEVMValue( + ctx sdk.Context, + hooks *tracing.Hooks, + bankKeeper BankBalanceKeeper, + fromSeiAddr sdk.AccAddress, + fromEVMAddr common.Address, + toSeiAddr sdk.AccAddress, + toEVMAddr common.Address, + value *big.Int, +) { + // From address got value removed from it + newBalance := getEVMBalance(ctx, bankKeeper, fromSeiAddr) + oldBalance := new(big.Int).Add(newBalance, value) + + hooks.OnBalanceChange(fromEVMAddr, oldBalance, newBalance, tracing.BalanceChangeTransfer) + + // To received valye from the sender + newBalance = getEVMBalance(ctx, bankKeeper, toSeiAddr) + oldBalance = new(big.Int).Sub(newBalance, value) + + hooks.OnBalanceChange(toEVMAddr, oldBalance, newBalance, tracing.BalanceChangeTransfer) +} + +func TraceBlockReward( + ctx sdk.Context, + hooks *tracing.Hooks, + bankKeeper BankBalanceKeeper, + toSeiAddr sdk.AccAddress, + toEVMAddr common.Address, + usei sdk.Int, + wei sdk.Int, +) { + value := usei.Mul(state.SdkUseiToSweiMultiplier).Add(wei).BigInt() + + // To received value + newBalance := getEVMBalance(ctx, bankKeeper, toSeiAddr) + oldBalance := new(big.Int).Sub(newBalance, value) + + hooks.OnBalanceChange(toEVMAddr, oldBalance, newBalance, tracing.BalanceIncreaseRewardMineBlock) +} + +func getEVMBalance(ctx sdk.Context, bankKeeper BankBalanceKeeper, addr sdk.AccAddress) *big.Int { + swei := bankKeeper.GetBalance(ctx, addr, sdk.MustGetBaseDenom()).Amount.Mul(state.SdkUseiToSweiMultiplier) + wei := bankKeeper.GetWeiBalance(ctx, addr) + + return swei.Add(wei).BigInt() +} diff --git a/x/evm/tracers/firehose.go b/x/evm/tracers/firehose.go new file mode 100644 index 000000000..808c40cb0 --- /dev/null +++ b/x/evm/tracers/firehose.go @@ -0,0 +1,2259 @@ +package tracers + +import ( + "bytes" + "cmp" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "net/url" + "os" + "regexp" + "runtime" + "runtime/debug" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "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/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" + pbeth "github.com/sei-protocol/sei-chain/pb/sf/ethereum/type/v2" + seitracing "github.com/sei-protocol/sei-chain/x/evm/tracing" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + firehoseTraceLevel = "trace" + firehoseDebugLevel = "debug" + firehoseInfoLevel = "info" +) + +const ( + callSourceRoot = "root" + callSourceChild = "child" +) + +// 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 isFirehoseInfoEnabled = firehoseTracerLogLevel == firehoseInfoLevel || firehoseTracerLogLevel == firehoseDebugLevel || firehoseTracerLogLevel == firehoseTraceLevel +var isFirehoseDebugEnabled = firehoseTracerLogLevel == firehoseDebugLevel || firehoseTracerLogLevel == firehoseTraceLevel +var isFirehoseTracerEnabled = firehoseTracerLogLevel == firehoseTraceLevel + +var emptyCommonAddress = common.Address{} +var emptyCommonHash = common.Hash{} + +func init() { + staticFirehoseChainValidationOnInit() + + tracers.LiveDirectory.Register("firehose", newFirehoseTracer) + GlobalLiveTracerRegistry.Register("firehose", newSeiFirehoseTracer) +} + +func newFirehoseTracer(cfg json.RawMessage) (*tracing.Hooks, error) { + firehoseInfo("new firehose tracer") + + var config FirehoseConfig + if len([]byte(cfg)) > 0 { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, fmt.Errorf("failed to parse Firehose config: %w", err) + } + } + + return newTracingHooksFromFirehose(NewFirehose(&config)), nil +} + +func newTracingHooksFromFirehose(tracer *Firehose) *tracing.Hooks { + return &tracing.Hooks{ + OnBlockchainInit: tracer.OnBlockchainInit, + OnGenesisBlock: tracer.OnGenesisBlock, + OnBlockStart: tracer.OnBlockStart, + OnBlockEnd: tracer.OnBlockEnd, + OnSkippedBlock: tracer.OnSkippedBlock, + + OnTxStart: tracer.OnTxStart, + OnTxEnd: tracer.OnTxEnd, + OnEnter: tracer.OnCallEnter, + OnExit: tracer.OnCallExit, + OnOpcode: tracer.OnOpcode, + OnFault: tracer.OnOpcodeFault, + + OnBalanceChange: tracer.OnBalanceChange, + OnNonceChange: tracer.OnNonceChange, + OnCodeChange: tracer.OnCodeChange, + OnStorageChange: tracer.OnStorageChange, + OnGasChange: tracer.OnGasChange, + OnLog: tracer.OnLog, + } +} + +func newSeiFirehoseTracer(tracerURL *url.URL) (*seitracing.Hooks, error) { + tracerConfig, err := new(FirehoseConfig).fromURLParameters(tracerURL.Query()) + if err != nil { + return nil, fmt.Errorf("failed to parse Firehose config: %w", err) + } + + tracer := NewFirehose(tracerConfig) + + commitLock := new(sync.Mutex) + + return &seitracing.Hooks{ + Hooks: newTracingHooksFromFirehose(tracer), + + OnSeiBlockchainInit: tracer.OnBlockchainInit, + OnSeiBlockStart: tracer.OnSeiBlockStart, + OnSeiBlockEnd: tracer.OnBlockEnd, + + GetTxTracer: func(txIndex int) sdk.TxTracer { + tracer.blockReorderOrdinalOnce.Do(func() { + tracer.blockReorderOrdinal = true + tracer.blockReorderOrdinalSnapshot = tracer.blockOrdinal.value + }) + + // Created first so we can get the pointer id everywhere + isolatedTracer := &TxTracerHooks{} + isolatedTracerID := fmt.Sprintf("%03d-%p", txIndex, isolatedTracer) + isolatedTxTracer := tracer.newIsolatedTransactionTracer(isolatedTracerID) + + firehoseInfo("new isolated transaction tracer (tracer=%s)", isolatedTracerID) + isolatedTracer.Hooks = &seitracing.Hooks{ + Hooks: newTracingHooksFromFirehose(isolatedTxTracer), + } + + isolatedTracer.OnTxReset = func() { + firehoseDebug("resetting isolated transaction tracer (tracer=%s)", isolatedTracerID) + isolatedTxTracer.resetTransaction() + } + + isolatedTracer.OnTxCommit = func() { + firehoseInfo("committing isolated transaction tracer (tracer=%s)", isolatedTracerID) + + if isolatedTxTracer.transactionTransient == nil { + panic(fmt.Errorf("hook OnTxCommit called without a transaction being completed, this is invalid (tracer=%s)", isolatedTracerID)) + } + + commitLock.Lock() + tracer.block.TransactionTraces = append(tracer.block.TransactionTraces, isolatedTxTracer.transactionTransient) + commitLock.Unlock() + } + + return isolatedTracer + }, + }, nil +} + +type FirehoseConfig struct { + ApplyBackwardCompatibility *bool `json:"applyBackwardCompatibility"` +} + +func (c *FirehoseConfig) fromURLParameters(query url.Values) (*FirehoseConfig, error) { + if v := query.Get("applyBackwardCompatibility"); v != "" { + applyBackwardCompatibility, err := strconv.ParseBool(v) + if err != nil { + return nil, fmt.Errorf("invalid applyBackwardCompatibility value: %w", err) + } + + c.ApplyBackwardCompatibility = &applyBackwardCompatibility + } + + return c, nil +} + +type Firehose struct { + // Global state + outputBuffer *bytes.Buffer + initSent *atomic.Bool + chainConfig *params.ChainConfig + 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) + 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 *pbeth.Block + blockBaseFee *big.Int + blockOrdinal *Ordinal + blockFinality *FinalityStatus + blockRules params.Rules + blockReorderOrdinal bool + blockReorderOrdinalSnapshot uint64 + blockReorderOrdinalOnce sync.Once + + // Transaction state + evm *tracing.VMContext + transaction *pbeth.TransactionTrace + transactionLogIndex uint32 + inSystemCall bool + blockIsPrecompiledAddr func(addr common.Address) bool + transactionIsolated bool + transactionTransient *pbeth.TransactionTrace + + // Call state + callStack *CallStack + deferredCallState *DeferredCallState + latestCallEnterSuicided bool +} + +const FirehoseProtocolVersion = "3.0" + +func NewFirehose(config *FirehoseConfig) *Firehose { + return &Firehose{ + // Global state + outputBuffer: bytes.NewBuffer(make([]byte, 0, 100*1024*1024)), + initSent: new(atomic.Bool), + chainConfig: nil, + hasher: crypto.NewKeccakState(), + tracerID: "global", + applyBackwardCompatibility: config.ApplyBackwardCompatibility, + + // Block state + blockOrdinal: &Ordinal{}, + blockFinality: &FinalityStatus{}, + blockReorderOrdinal: false, + + // Transaction state + transactionLogIndex: 0, + + // Call state + callStack: NewCallStack(), + deferredCallState: NewDeferredCallState(), + latestCallEnterSuicided: false, + } +} + +func (f *Firehose) newIsolatedTransactionTracer(traceId string) *Firehose { + f.ensureInBlock(0) + + return &Firehose{ + // Global state + initSent: f.initSent, + chainConfig: f.chainConfig, + hasher: crypto.NewKeccakState(), + hasherBuf: common.Hash{}, + tracerID: traceId, + + // 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 +func (f *Firehose) resetBlock() { + f.block = nil + f.blockBaseFee = nil + f.blockOrdinal.Reset() + f.blockFinality.Reset() + f.blockIsPrecompiledAddr = nil + 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 +func (f *Firehose) resetTransaction() { + f.transaction = nil + f.evm = nil + f.transactionLogIndex = 0 + f.inSystemCall = false + f.transactionTransient = nil + + f.callStack.Reset() + f.latestCallEnterSuicided = false + f.deferredCallState.Reset() +} + +func (f *Firehose) OnBlockchainInit(chainConfig *params.ChainConfig) { + f.chainConfig = chainConfig + + if wasNeverSent := f.initSent.CompareAndSwap(false, true); wasNeverSent { + printToFirehose("INIT", FirehoseProtocolVersion, "geth", params.Version) + } else { + 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 polygonMainnetChainID = big.NewInt(137) +var polygonMumbaiChainID = big.NewInt(80001) +var bscMainnetChainID = big.NewInt(56) +var bscTestnetChainID = big.NewInt(97) + +func chainNeedsLegacyBackwardCompatibility(id *big.Int) bool { + return id.Cmp(mainnetChainID) == 0 || id.Cmp(polygonMainnetChainID) == 0 || id.Cmp(polygonMumbaiChainID) == 0 || id.Cmp(bscMainnetChainID) == 0 || id.Cmp(bscTestnetChainID) == 0 +} + +func (f *Firehose) OnSeiBlockStart(hash []byte, size uint64, b *types.Header) { + firehoseInfo("block start (number=%d hash=%s)", b.Number, byteView(hash)) + + f.blockRules = f.chainConfig.Rules(b.Number, f.chainConfig.TerminalTotalDifficultyPassed, b.Time) + f.blockIsPrecompiledAddr = getActivePrecompilesChecker(f.blockRules) + + f.block = &pbeth.Block{ + Hash: hash, + Number: b.Number.Uint64(), + Header: newBlockHeaderFromChainHeader(b, &pbeth.BigInt{}), + Size: size, + Ver: 4, + } + + if f.block.Header.BaseFeePerGas != nil { + f.blockBaseFee = f.block.Header.BaseFeePerGas.Native() + } + + f.blockFinality.populate(b.Number.Uint64()-1, b.ParentHash[:]) +} + +func (f *Firehose) OnBlockStart(event tracing.BlockEvent) { + b := event.Block + firehoseInfo("block start (number=%d hash=%s)", b.NumberU64(), b.Hash()) + + f.ensureBlockChainInit() + + f.blockRules = f.chainConfig.Rules(b.Number(), f.chainConfig.TerminalTotalDifficultyPassed, b.Time()) + f.blockIsPrecompiledAddr = getActivePrecompilesChecker(f.blockRules) + + f.block = &pbeth.Block{ + Hash: b.Hash().Bytes(), + Number: b.Number().Uint64(), + Header: newBlockHeaderFromChainHeader(b.Header(), firehoseBigIntFromNative(new(big.Int).Add(event.TD, b.Difficulty()))), + Size: b.Size(), + Ver: 4, + } + + if *f.applyBackwardCompatibility { + f.block.Ver = 3 + } + + for _, uncle := range b.Uncles() { + // TODO: check if td should be part of uncles + f.block.Uncles = append(f.block.Uncles, newBlockHeaderFromChainHeader(uncle, nil)) + } + + if f.block.Header.BaseFeePerGas != nil { + f.blockBaseFee = f.block.Header.BaseFeePerGas.Native() + } + + f.blockFinality.populateFromChain(event.Finalized) +} + +func (f *Firehose) OnSkippedBlock(event tracing.BlockEvent) { + // 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 + // went in this code path. + // + // See https: //github.com/streamingfast/go-ethereum/blob/a46903cf0cad829479ded66b369017914bf82314/core/blockchain.go#L1797-L1814 + if event.Block.Transactions().Len() > 0 { + 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.Hash().Hex(), + event.Block.Transactions().Len(), + )) + } + + // Trace the block as normal, worst case the Firehose system will simply discard it at some point + f.OnBlockStart(event) + f.OnBlockEnd(nil) +} + +func getActivePrecompilesChecker(rules params.Rules) func(addr common.Address) bool { + activePrecompiles := vm.ActivePrecompiles(rules) + + activePrecompilesMap := make(map[common.Address]bool, len(activePrecompiles)) + for _, addr := range activePrecompiles { + activePrecompilesMap[addr] = true + } + + return func(addr common.Address) bool { + _, found := activePrecompilesMap[addr] + return found + } +} + +func (f *Firehose) OnBlockEnd(err error) { + firehoseInfo("block ending (err=%s)", errorView(err)) + + if err == nil { + if f.blockReorderOrdinal { + f.reorderIsolatedTransactionsAndOrdinals() + } + + f.ensureInBlockAndNotInTrx() + f.printBlockToFirehose(f.block, f.blockFinality) + } else { + // An error occurred, could have happen in transaction/call context, we must not check if in trx/call, only check in block + f.ensureInBlock(0) + } + + f.resetBlock() + f.resetTransaction() + + 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) { + firehoseInfo("system call start (for=%s)", "beacon_block_root") + f.ensureInBlockAndNotInTrx() + + f.inSystemCall = true + f.transaction = &pbeth.TransactionTrace{} +} + +func (f *Firehose) OnBeaconBlockRootEnd() { + f.ensureInBlockAndInTrx() + f.ensureInSystemCall() + + f.block.SystemCalls = append(f.block.SystemCalls, f.transaction.Calls...) + + f.resetTransaction() +} + +func (f *Firehose) OnTxStart(evm *tracing.VMContext, tx *types.Transaction, from common.Address) { + 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.evm = evm + var to common.Address + if tx.To() == nil { + to = crypto.CreateAddress(from, evm.StateDB.GetNonce(from)) + } else { + to = *tx.To() + } + + f.onTxStart(tx, tx.Hash(), from, to) +} + +// onTxStart is used internally a two places, in the normal "tracer" and in the "OnGenesisBlock", +// we manually pass some override to the `tx` because genesis block has a different way of creating +// the transaction that wraps the genesis block. +func (f *Firehose) onTxStart(tx *types.Transaction, hash common.Hash, from, to common.Address) { + v, r, s := tx.RawSignatureValues() + + var blobGas *uint64 + if tx.Type() == types.BlobTxType { + blobGas = ptr(tx.BlobGas()) + } + + f.transaction = &pbeth.TransactionTrace{ + BeginOrdinal: f.blockOrdinal.Next(), + Hash: hash.Bytes(), + From: from.Bytes(), + To: to.Bytes(), + Nonce: tx.Nonce(), + GasLimit: tx.Gas(), + GasPrice: gasPrice(tx, f.blockBaseFee), + Value: firehoseBigIntFromNative(tx.Value()), + Input: tx.Data(), + V: emptyBytesToNil(v.Bytes()), + R: normalizeSignaturePoint(r.Bytes()), + S: normalizeSignaturePoint(s.Bytes()), + Type: transactionTypeFromChainTxType(tx.Type()), + AccessList: newAccessListFromChain(tx.AccessList()), + MaxFeePerGas: maxFeePerGas(tx), + MaxPriorityFeePerGas: maxPriorityFeePerGas(tx), + BlobGas: blobGas, + BlobGasFeeCap: firehoseBigIntFromNative(tx.BlobGasFeeCap()), + BlobHashes: newBlobHashesFromChain(tx.BlobHashes()), + } +} + +func (f *Firehose) OnTxEnd(receipt *types.Receipt, err error) { + firehoseInfo("trx ending (tracer=%s)", f.tracerID) + f.ensureInBlockAndInTrx() + + 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 + // properly populated for the `completeTransaction` call above to complete correctly. + f.resetTransaction() + } + + firehoseInfo("trx end (tracer=%s)", f.tracerID) +} + +func (f *Firehose) completeTransaction(receipt *types.Receipt) *pbeth.TransactionTrace { + 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 + slices.SortFunc(f.transaction.Calls, func(i, j *pbeth.Call) int { + return cmp.Compare(i.Index, j.Index) + }) + + rootCall := f.transaction.Calls[0] + + if !f.deferredCallState.IsEmpty() { + if err := f.deferredCallState.MaybePopulateCallAndReset(callSourceRoot, rootCall); err != nil { + panic(err) + } + } + + // Receipt can be nil if an error occurred during the transaction execution, right now we don't have it + if receipt != nil { + f.transaction.Index = uint32(receipt.TransactionIndex) + f.transaction.GasUsed = receipt.GasUsed + f.transaction.Receipt = newTxReceiptFromChain(receipt, f.transaction.Type) + f.transaction.Status = transactionStatusFromChainTxReceipt(receipt.Status) + } + + // It's possible that the transaction was reverted, but we still have a receipt, in that case, we must + // check the root call + if rootCall.StatusReverted { + f.transaction.Status = pbeth.TransactionTraceStatus_REVERTED + } + + // Order is important, we must populate the state reverted before we remove the log block index and re-assign ordinals + f.populateStateReverted() + f.removeLogBlockIndexOnStateRevertedCalls() + f.assignOrdinalAndIndexToReceiptLogs() + + if *f.applyBackwardCompatibility { + // 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() + + return f.transaction +} + +func (f *Firehose) populateStateReverted() { + // Calls are ordered by execution index. So the algo is quite simple. + // We loop through the flat calls, at each call, if the parent is present + // and reverted, the current call is reverted. Otherwise, if the current call + // is failed, the state is reverted. In all other cases, we simply continue + // our iteration loop. + // + // This works because we see the parent before its children, and since we + // trickle down the state reverted value down the children, checking the parent + // of a call will always tell us if the whole chain of parent/child should + // be reverted + // + calls := f.transaction.Calls + for _, call := range f.transaction.Calls { + var parent *pbeth.Call + if call.ParentIndex > 0 { + parent = calls[call.ParentIndex-1] + } + + call.StateReverted = (parent != nil && parent.StateReverted) || call.StatusFailed + } +} + +func (f *Firehose) removeLogBlockIndexOnStateRevertedCalls() { + for _, call := range f.transaction.Calls { + if call.StateReverted { + for _, log := range call.Logs { + log.BlockIndex = 0 + } + } + } +} + +func (f *Firehose) assignOrdinalAndIndexToReceiptLogs() { + firehoseTrace("assigning ordinal and index to logs") + defer func() { + firehoseTrace("assigning ordinal and index to logs terminated") + }() + + trx := f.transaction + + receiptsLogs := trx.Receipt.Logs + + callLogs := []*pbeth.Log{} + for _, call := range trx.Calls { + firehoseTrace("checking call (reverted=%t logs=%d)", call.StateReverted, len(call.Logs)) + if call.StateReverted { + continue + } + + callLogs = append(callLogs, call.Logs...) + } + + slices.SortFunc(callLogs, func(i, j *pbeth.Log) int { + return cmp.Compare(i.Ordinal, j.Ordinal) + }) + + if len(callLogs) != len(receiptsLogs) { + panic(fmt.Errorf( + "mismatch between Firehose call logs and Ethereum transaction %s receipt logs at block #%d, transaction receipt has %d logs but there is %d Firehose call logs", + hex.EncodeToString(trx.Hash), + f.block.Number, + len(receiptsLogs), + len(callLogs), + )) + } + + for i := 0; i < len(callLogs); i++ { + callLog := callLogs[i] + receiptsLog := receiptsLogs[i] + + result := &validationResult{} + // Ordinal must **not** be checked as we are assigning it here below after the validations + validateBytesField(result, "Address", callLog.Address, receiptsLog.Address) + validateUint32Field(result, "BlockIndex", callLog.BlockIndex, receiptsLog.BlockIndex) + validateBytesField(result, "Data", callLog.Data, receiptsLog.Data) + validateArrayOfBytesField(result, "Topics", callLog.Topics, receiptsLog.Topics) + + if len(result.failures) > 0 { + result.panicOnAnyFailures("mismatch between Firehose call log and Ethereum transaction receipt log at index %d", i) + } + + receiptsLog.Index = callLog.Index + receiptsLog.Ordinal = callLog.Ordinal + } +} + +// OnCallEnter implements the EVMLogger interface to initialize the tracing operation. +func (f *Firehose) OnCallEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + opCode := vm.OpCode(typ) + + var callType pbeth.CallType + if isRootCall := depth == 0; isRootCall { + callType = rootCallType(opCode == vm.CREATE) + } else { + // The invokation for vm.SELFDESTRUCT is called while already in another call and is recorded specially + // in the Geth tracer and generates `OnEnter/OnExit` callbacks. However in Firehose, self destruction + // simply sets the call as having called suicided so there is no extra call. + // + // So we ignore `OnEnter/OnExit` callbacks for `SELFDESTRUCT` opcode, we ignore it here and set + // a special sentinel variable that will tell `OnExit` to ignore itself. + if opCode == vm.SELFDESTRUCT { + f.ensureInCall() + f.callStack.Peek().Suicide = true + + // The next OnCallExit must be ignored, this variable will make the next OnCallExit to be ignored + f.latestCallEnterSuicided = true + return + } + + callType = callTypeFromOpCode(opCode) + if callType == pbeth.CallType_UNSPECIFIED { + panic(fmt.Errorf("unexpected call type, received OpCode %s but only call related opcode (CALL, CREATE, CREATE2, STATIC, DELEGATECALL and CALLCODE) or SELFDESTRUCT is accepted", opCode)) + } + } + + f.callStart(computeCallSource(depth), callType, from, to, input, gas, value) +} + +// OnCallExit is called after the call finishes to finalize the tracing. +func (f *Firehose) OnCallExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + f.callEnd(computeCallSource(depth), output, gasUsed, err, reverted) +} + +// 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) { + firehoseTrace("on opcode (op=%s gas=%d cost=%d, err=%s)", op, gas, cost, errorView(err)) + + if activeCall := f.callStack.Peek(); activeCall != nil { + opCode := vm.OpCode(op) + f.captureInterpreterStep(activeCall) + + // The rest of the logic expects that a call succeeded, nothing to do more here if the interpreter failed on this OpCode + if err != nil { + return + } + + // The gas change must come first to retain Firehose backward compatibility. Indeed, before Firehose 3.0, + // we had a specific method `OnKeccakPreimage` that was called during the KECCAK256 opcode. However, in + // the new model, we do it through `OnOpcode`. + // + // The gas change recording in the previous Firehose patch was done before calling `OnKeccakPreimage` so + // 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 reason, found := opCodeToGasChangeReasonMap[opCode]; found { + activeCall.GasChanges = append(activeCall.GasChanges, f.newGasChange("state", gas, gas-cost, reason)) + } + } + + if opCode == vm.KECCAK256 { + f.onOpcodeKeccak256(activeCall, scope.StackData(), Memory(scope.MemoryData())) + } + } +} + +// onOpcodeKeccak256 is called during the SHA3 (a.k.a KECCAK256) opcode it's known +// in Firehose tracer as Keccak preimages. The preimage is the input data that +// was used to produce the given keccak hash. +func (f *Firehose) onOpcodeKeccak256(call *pbeth.Call, stack []uint256.Int, memory Memory) { + if call.KeccakPreimages == nil { + call.KeccakPreimages = make(map[string]string) + } + + offset, size := stack[len(stack)-1], stack[len(stack)-2] + preImage := memory.GetPtrUint256(&offset, &size) + + // We should have exclusive access to the hasher, we can safely reset it. + f.hasher.Reset() + f.hasher.Write(preImage) + if _, err := f.hasher.Read(f.hasherBuf[:]); err != nil { + panic(fmt.Errorf("failed to read keccak256 hash: %w", err)) + } + + encodedData := hex.EncodeToString(preImage) + + if *f.applyBackwardCompatibility { + // 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 + // completely wrong. + // + // To keep the same behavior, we will write the preimage as a "." when the encoded + // data is an empty string. + if encodedData == "" { + encodedData = "." + } + } + + call.KeccakPreimages[hex.EncodeToString(f.hasherBuf[:])] = encodedData +} + +var opCodeToGasChangeReasonMap = map[vm.OpCode]pbeth.GasChange_Reason{ + vm.CREATE: pbeth.GasChange_REASON_CONTRACT_CREATION, + vm.CREATE2: pbeth.GasChange_REASON_CONTRACT_CREATION2, + vm.CALL: pbeth.GasChange_REASON_CALL, + vm.STATICCALL: pbeth.GasChange_REASON_STATIC_CALL, + vm.CALLCODE: pbeth.GasChange_REASON_CALL_CODE, + vm.DELEGATECALL: pbeth.GasChange_REASON_DELEGATE_CALL, + vm.RETURN: pbeth.GasChange_REASON_RETURN, + vm.REVERT: pbeth.GasChange_REASON_REVERT, + vm.LOG0: pbeth.GasChange_REASON_EVENT_LOG, + vm.LOG1: pbeth.GasChange_REASON_EVENT_LOG, + vm.LOG2: pbeth.GasChange_REASON_EVENT_LOG, + vm.LOG3: pbeth.GasChange_REASON_EVENT_LOG, + vm.LOG4: pbeth.GasChange_REASON_EVENT_LOG, + vm.SELFDESTRUCT: pbeth.GasChange_REASON_SELF_DESTRUCT, + vm.CALLDATACOPY: pbeth.GasChange_REASON_CALL_DATA_COPY, + vm.CODECOPY: pbeth.GasChange_REASON_CODE_COPY, + vm.EXTCODECOPY: pbeth.GasChange_REASON_EXT_CODE_COPY, + vm.RETURNDATACOPY: pbeth.GasChange_REASON_RETURN_DATA_COPY, +} + +// OnOpcodeFault implements the EVMLogger interface to trace an execution fault. +func (f *Firehose) OnOpcodeFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { + if activeCall := f.callStack.Peek(); activeCall != nil { + f.captureInterpreterStep(activeCall) + } +} + +func (f *Firehose) captureInterpreterStep(activeCall *pbeth.Call) { + if *f.applyBackwardCompatibility { + // for call, we need to process the executed code here + // since in old firehose executed code calculation depends if the code exist + if activeCall.CallType == pbeth.CallType_CALL && !activeCall.ExecutedCode { + firehoseTrace("Intepreter step for callType_CALL") + 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) { + firehoseDebug("call start (source=%s index=%d type=%s input=%s)", source, f.callStack.NextIndex(), callType, inputView(input)) + f.ensureInBlockAndInTrx() + + if *f.applyBackwardCompatibility { + // 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 + // as constructor's input are not part of the code change but part of the call input. + if callType == pbeth.CallType_CREATE { + input = nil + } + } + + call := &pbeth.Call{ + CallType: callType, + Depth: 0, + Caller: from.Bytes(), + Address: to.Bytes(), + // We need to clone `input` received by the tracer as it's re-used within Geth! + Input: bytes.Clone(input), + Value: firehoseBigIntFromNative(value), + GasLimit: gas, + } + + if *f.applyBackwardCompatibility { + // Known Firehose issue: The BeginOrdinal of the genesis block root call is never actually + // incremented and it's always 0. + // + // Ref 042a2ff03fd623f151d7726314b8aad6 + + call.BeginOrdinal = 0 + call.ExecutedCode = f.getExecutedCode(f.evm, call) + + if f.block.Number != 0 { + call.BeginOrdinal = f.blockOrdinal.Next() + } + } else { + call.BeginOrdinal = f.blockOrdinal.Next() + } + + if err := f.deferredCallState.MaybePopulateCallAndReset(source, call); err != nil { + panic(err) + } + + if *f.applyBackwardCompatibility { + // 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. + // + // New chain integration should remove this `if` statement + if source == callSourceRoot { + call.BeginOrdinal = 0 + } + } + + 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 { + precompile := f.blockIsPrecompiledAddr(common.BytesToAddress(call.Address)) + + if evm != nil && call.CallType == pbeth.CallType_CALL { + if !evm.StateDB.Exist(common.BytesToAddress(call.Address)) && + !precompile && f.blockRules.IsEIP158 && + (call.Value == nil || call.Value.Native().Sign() == 0) { + firehoseTrace("executed code IsSpuriousDragon (callType=%s inputLength=%d)", call.CallType.String(), len(call.Input)) + return call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0 + } + } + + if precompile { + firehoseTrace("executed code isprecompile (callType=%s inputLength=%d)", call.CallType.String(), len(call.Input)) + return call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0 + } + + if call.CallType == pbeth.CallType_CALL { + firehoseTrace("executed code callType_CALL") + // calculation for executed code will happen in captureInterpreterStep + return false + } + + firehoseTrace("executed code default (callType=%s inputLength=%d)", call.CallType.String(), len(call.Input)) + return call.CallType != pbeth.CallType_CREATE && len(call.Input) > 0 +} + +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) + + if f.latestCallEnterSuicided { + if source != callSourceChild { + panic(fmt.Errorf("unexpected source for suicided call end, expected child but got %s, suicide are always produced on a 'child' source", source)) + } + + // Geth native tracer does a `OnEnter(SELFDESTRUCT, ...)/OnExit(...)`, we must skip the `OnExit` call + // in that case because we did not push it on our CallStack. + f.latestCallEnterSuicided = false + return + } + + f.ensureInBlockAndInTrxAndInCall() + + call := f.callStack.Pop() + call.GasConsumed = gasUsed + + // For create call, we do not save the returned value which is the actual contract's code + if call.CallType != pbeth.CallType_CREATE { + call.ReturnData = bytes.Clone(output) + } + + if reverted { + failureReason := "" + if err != nil { + failureReason = err.Error() + } + + call.FailureReason = failureReason + call.StatusFailed = true + + // We also treat ErrInsufficientBalance and ErrDepth as reverted in Firehose model + // because they do not cost any gas. + 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! + if !call.ExecutedCode && (errors.Is(err, vm.ErrInsufficientBalance) || errors.Is(err, vm.ErrDepth)) { + 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 + // incremented and it's always 0. + if f.block.Number != 0 { + call.EndOrdinal = f.blockOrdinal.Next() + } + } else { + call.EndOrdinal = f.blockOrdinal.Next() + } + + f.transaction.Calls = append(f.transaction.Calls, call) +} + +func computeCallSource(depth int) string { + if depth == 0 { + return callSourceRoot + } + + return callSourceChild +} + +func (f *Firehose) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { + f.ensureBlockChainInit() + + f.OnBlockStart(tracing.BlockEvent{Block: b, TD: big.NewInt(0), Finalized: nil, Safe: nil}) + f.onTxStart(types.NewTx(&types.LegacyTx{}), emptyCommonHash, emptyCommonAddress, emptyCommonAddress) + f.OnCallEnter(0, byte(vm.CALL), emptyCommonAddress, emptyCommonAddress, nil, 0, nil) + + for _, addr := range sortedKeys(alloc) { + account := alloc[addr] + + if account.Balance != nil && account.Balance.Sign() != 0 { + activeCall := f.callStack.Peek() + activeCall.BalanceChanges = append(activeCall.BalanceChanges, f.newBalanceChange("genesis", addr, common.Big0, account.Balance, pbeth.BalanceChange_REASON_GENESIS_BALANCE)) + } + + if len(account.Code) > 0 { + f.OnCodeChange(addr, emptyCommonHash, nil, common.BytesToHash(crypto.Keccak256(account.Code)), account.Code) + } + + if account.Nonce > 0 { + f.OnNonceChange(addr, 0, account.Nonce) + } + + for _, key := range sortedKeys(account.Storage) { + f.OnStorageChange(addr, key, emptyCommonHash, account.Storage[key]) + } + } + + f.OnCallExit(0, nil, 0, nil, false) + f.OnTxEnd(&types.Receipt{ + PostState: b.Root().Bytes(), + Status: types.ReceiptStatusSuccessful, + }, nil) + f.OnBlockEnd(nil) +} + +type bytesGetter interface { + comparable + Bytes() []byte +} + +func sortedKeys[K bytesGetter, V any](m map[K]V) []K { + keys := maps.Keys(m) + slices.SortFunc(keys, func(i, j K) int { + return bytes.Compare(i.Bytes(), j.Bytes()) + }) + + return keys +} + +func (f *Firehose) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) { + if reason == tracing.BalanceChangeUnspecified { + // We ignore those, if they are mislabelled, too bad so particular attention needs to be ported to this + return + } + + if *f.applyBackwardCompatibility { + // 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 + // we were not tracking in the old Firehose instrumentation. + if reason == tracing.BalanceDecreaseSelfdestructBurn { + return + } + } + + f.ensureInBlockOrTrx() + + change := f.newBalanceChange("tracer", a, prev, new, balanceChangeReasonFromChain(reason)) + + if f.transaction != nil { + activeCall := f.callStack.Peek() + + // There is an initial transfer happening will the call is not yet started, we track it manually + if activeCall == nil { + f.deferredCallState.balanceChanges = append(f.deferredCallState.balanceChanges, change) + return + } + + activeCall.BalanceChanges = append(activeCall.BalanceChanges, change) + } else { + f.block.BalanceChanges = append(f.block.BalanceChanges, change) + } +} + +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) + + if reason == pbeth.BalanceChange_REASON_UNKNOWN { + panic(fmt.Errorf("received unknown balance change reason %s", reason)) + } + + return &pbeth.BalanceChange{ + Ordinal: f.blockOrdinal.Next(), + Address: address.Bytes(), + OldValue: firehoseBigIntFromNative(oldValue), + NewValue: firehoseBigIntFromNative(newValue), + Reason: reason, + } +} + +func (f *Firehose) OnNonceChange(a common.Address, prev, new uint64) { + f.ensureInBlockAndInTrx() + + activeCall := f.callStack.Peek() + change := &pbeth.NonceChange{ + Address: a.Bytes(), + OldValue: prev, + NewValue: new, + Ordinal: f.blockOrdinal.Next(), + } + + // There is an initial nonce change happening when the call is not yet started, we track it manually + if activeCall == nil { + f.deferredCallState.nonceChanges = append(f.deferredCallState.nonceChanges, change) + return + } + + activeCall.NonceChanges = append(activeCall.NonceChanges, change) +} + +func (f *Firehose) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { + f.ensureInBlockOrTrx() + + change := &pbeth.CodeChange{ + Address: a.Bytes(), + OldHash: prevCodeHash.Bytes(), + OldCode: prev, + NewHash: codeHash.Bytes(), + NewCode: code, + Ordinal: f.blockOrdinal.Next(), + } + + if f.transaction != nil { + activeCall := f.callStack.Peek() + if activeCall == nil { + f.panicInvalidState("caller expected to be in call state but we were not, this is a bug", 0) + } + + activeCall.CodeChanges = append(activeCall.CodeChanges, change) + } else { + f.block.CodeChanges = append(f.block.CodeChanges, change) + } +} + +func (f *Firehose) OnStorageChange(a common.Address, k, prev, new common.Hash) { + f.ensureInBlockAndInTrxAndInCall() + + activeCall := f.callStack.Peek() + activeCall.StorageChanges = append(activeCall.StorageChanges, &pbeth.StorageChange{ + Address: a.Bytes(), + Key: k.Bytes(), + OldValue: prev.Bytes(), + NewValue: new.Bytes(), + Ordinal: f.blockOrdinal.Next(), + }) +} + +func (f *Firehose) OnLog(l *types.Log) { + f.ensureInBlockAndInTrxAndInCall() + + topics := make([][]byte, len(l.Topics)) + for i, topic := range l.Topics { + topics[i] = topic.Bytes() + } + + activeCall := f.callStack.Peek() + 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{ + Address: l.Address.Bytes(), + Topics: topics, + Data: l.Data, + Index: f.transactionLogIndex, + BlockIndex: uint32(l.Index), + Ordinal: f.blockOrdinal.Next(), + }) + + f.transactionLogIndex++ +} + +func (f *Firehose) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { + f.ensureInBlockAndInTrx() + + if old == new { + return + } + + if reason == tracing.GasChangeCallOpCode { + // We ignore those because we track OpCode gas consumption manually by tracking the gas value at `OnOpcode` call + return + } + + if *f.applyBackwardCompatibility { + // 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. + // + // Ref eb1916a67d9bea03df16a7a3e2cfac72 + if reason == tracing.GasChangeTxInitialBalance || + reason == tracing.GasChangeTxRefunds || + reason == tracing.GasChangeTxLeftOverReturned || + reason == tracing.GasChangeCallInitialBalance || + reason == tracing.GasChangeCallLeftOverReturned { + return + } + } + + activeCall := f.callStack.Peek() + change := f.newGasChange("tracer", old, new, gasChangeReasonFromChain(reason)) + + // There is an initial gas consumption happening will the call is not yet started, we track it manually + if activeCall == nil { + f.deferredCallState.gasChanges = append(f.deferredCallState.gasChanges, change) + return + } + + activeCall.GasChanges = append(activeCall.GasChanges, change) +} + +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) + + // 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 { + panic(fmt.Errorf("received unknown gas change reason %s", reason)) + } + + return &pbeth.GasChange{ + OldValue: oldValue, + NewValue: newValue, + Ordinal: f.blockOrdinal.Next(), + Reason: reason, + } +} + +func (f *Firehose) ensureBlockChainInit() { + if f.chainConfig == nil { + f.panicInvalidState("the OnBlockchainInit hook should have been called at this point", 2) + } +} + +func (f *Firehose) ensureInBlock(callerSkip int) { + if f.block == nil { + f.panicInvalidState("caller expected to be in block state but we were not, this is a bug", callerSkip+1) + } + + if f.chainConfig == nil { + f.panicInvalidState("the OnBlockchainInit hook should have been called at this point", callerSkip+1) + } +} + +func (f *Firehose) ensureNotInBlock(callerSkip int) { + if f.block != nil { + f.panicInvalidState("caller expected to not be in block state but we were, this is a bug", callerSkip+1) + } +} + +// Suppress lint warning about unusued method, we keep it in the patch because it's used in other +// network which pulls this branch. +var _ = new(Firehose).ensureNotInBlock + +func (f *Firehose) ensureInBlockAndInTrx() { + f.ensureInBlock(2) + + if f.transaction == nil { + f.panicInvalidState("caller expected to be in transaction state but we were not, this is a bug", 2) + } +} + +func (f *Firehose) ensureInBlockAndNotInTrx() { + f.ensureInBlock(2) + + if f.transaction != nil { + f.panicInvalidState("caller expected to not be in transaction state but we were, this is a bug", 2) + } +} + +func (f *Firehose) ensureInBlockAndNotInTrxAndNotInCall() { + f.ensureInBlock(2) + + if f.transaction != nil { + f.panicInvalidState("caller expected to not be in transaction state but we were, this is a bug", 2) + } + + if f.callStack.HasActiveCall() { + f.panicInvalidState("caller expected to not be in call state but we were, this is a bug", 2) + } +} + +func (f *Firehose) ensureInBlockOrTrx() { + 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", 2) + } +} + +func (f *Firehose) ensureInBlockAndInTrxAndInCall() { + 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", 2) + } + + if !f.callStack.HasActiveCall() { + f.panicInvalidState("caller expected to be in call state but we were not, this is a bug", 2) + } +} + +func (f *Firehose) ensureInCall() { + if f.block == nil { + f.panicInvalidState("caller expected to be in call state but we were not, this is a bug", 2) + } +} + +func (f *Firehose) ensureInSystemCall() { + if !f.inSystemCall { + 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, callerSkip int) string { + 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 +// adds the "FIRE" prefix to the input and joins the input with spaces as well +// as adding a newline at the end. +// +// It flushes this through [flushToFirehose] to the `os.Stdout` writer. +func (f *Firehose) printBlockToFirehose(block *pbeth.Block, finalityStatus *FinalityStatus) { + marshalled, err := proto.Marshal(block) + if err != nil { + panic(fmt.Errorf("failed to marshal block: %w", err)) + } + + f.outputBuffer.Reset() + + previousHash := block.PreviousID() + previousNum := 0 + if block.Number > 0 { + previousNum = int(block.Number) - 1 + } + + libNum := finalityStatus.LastIrreversibleBlockNumber + if finalityStatus.IsEmpty() { + // FIXME: We should have access to the genesis block to perform this operation to ensure we never go below the + // the genesis block + if block.Number >= 200 { + libNum = block.Number - 200 + } else { + libNum = 0 + } + } + + // **Important* The final space in the Sprintf template is mandatory! + f.outputBuffer.WriteString(fmt.Sprintf("FIRE BLOCK %d %s %d %s %d %d ", block.Number, hex.EncodeToString(block.Hash), previousNum, previousHash, libNum, block.Time().UnixNano())) + + encoder := base64.NewEncoder(base64.StdEncoding, f.outputBuffer) + if _, err = encoder.Write(marshalled); err != nil { + panic(fmt.Errorf("write to encoder should have been infaillible: %w", err)) + } + + if err := encoder.Close(); err != nil { + panic(fmt.Errorf("closing encoder should have been infaillible: %w", err)) + } + + f.outputBuffer.WriteString("\n") + + flushToFirehose(f.outputBuffer.Bytes(), os.Stdout) +} + +// printToFirehose is an easy way to print to Firehose format, it essentially +// adds the "FIRE" prefix to the input and joins the input with spaces as well +// as adding a newline at the end. +// +// It flushes this through [flushToFirehose] to the `os.Stdout` writer. +func printToFirehose(input ...string) { + flushToFirehose([]byte("FIRE "+strings.Join(input, " ")+"\n"), os.Stdout) +} + +// flushToFirehose sends data to Firehose via `io.Writter` checking for errors +// and retrying if necessary. +// +// If error is still present after 10 retries, prints an error message to `writer` +// as well as writing file `/tmp/firehose_writer_failed_print.log` with the same +// error message. +func flushToFirehose(in []byte, writer io.Writer) { + var written int + var err error + loops := 10 + for i := 0; i < loops; i++ { + written, err = writer.Write(in) + + if len(in) == written { + return + } + + in = in[written:] + if i == loops-1 { + break + } + } + + errstr := fmt.Sprintf("\nFIREHOSE FAILED WRITING %dx: %s\n", loops, err) + if err := os.WriteFile("./firehose_writer_failed_print.log", []byte(errstr), 0600); err != nil { + fmt.Println(errstr) + } + + fmt.Fprint(writer, errstr) +} + +// FIXME: Create a unit test that is going to fail as soon as any header is added in +func newBlockHeaderFromChainHeader(h *types.Header, td *pbeth.BigInt) *pbeth.BlockHeader { + var withdrawalsHashBytes []byte + if hash := h.WithdrawalsHash; hash != nil { + withdrawalsHashBytes = hash.Bytes() + } + + var parentBeaconRootBytes []byte + if root := h.ParentBeaconRoot; root != nil { + parentBeaconRootBytes = root.Bytes() + } + + pbHead := &pbeth.BlockHeader{ + Hash: h.Hash().Bytes(), + Number: h.Number.Uint64(), + ParentHash: h.ParentHash.Bytes(), + UncleHash: h.UncleHash.Bytes(), + Coinbase: h.Coinbase.Bytes(), + StateRoot: h.Root.Bytes(), + TransactionsRoot: h.TxHash.Bytes(), + ReceiptRoot: h.ReceiptHash.Bytes(), + LogsBloom: h.Bloom.Bytes(), + Difficulty: firehoseBigIntFromNative(h.Difficulty), + TotalDifficulty: td, + GasLimit: h.GasLimit, + GasUsed: h.GasUsed, + Timestamp: timestamppb.New(time.Unix(int64(h.Time), 0)), + ExtraData: h.Extra, + MixHash: h.MixDigest.Bytes(), + Nonce: h.Nonce.Uint64(), + BaseFeePerGas: firehoseBigIntFromNative(h.BaseFee), + WithdrawalsRoot: withdrawalsHashBytes, + BlobGasUsed: h.BlobGasUsed, + ExcessBlobGas: h.ExcessBlobGas, + ParentBeaconRoot: parentBeaconRootBytes, + + // Only set on Polygon fork(s) + TxDependency: nil, + } + + if pbHead.Difficulty == nil { + pbHead.Difficulty = &pbeth.BigInt{Bytes: []byte{0}} + } + + return pbHead +} + +// FIXME: Bring back Firehose test that ensures no new tx type are missed +func transactionTypeFromChainTxType(txType uint8) pbeth.TransactionTrace_Type { + switch txType { + case types.AccessListTxType: + return pbeth.TransactionTrace_TRX_TYPE_ACCESS_LIST + case types.DynamicFeeTxType: + return pbeth.TransactionTrace_TRX_TYPE_DYNAMIC_FEE + case types.LegacyTxType: + return pbeth.TransactionTrace_TRX_TYPE_LEGACY + case types.BlobTxType: + return pbeth.TransactionTrace_TRX_TYPE_BLOB + default: + panic(fmt.Errorf("unknown transaction type %d", txType)) + } +} + +func transactionStatusFromChainTxReceipt(txStatus uint64) pbeth.TransactionTraceStatus { + switch txStatus { + case types.ReceiptStatusSuccessful: + return pbeth.TransactionTraceStatus_SUCCEEDED + case types.ReceiptStatusFailed: + return pbeth.TransactionTraceStatus_FAILED + default: + panic(fmt.Errorf("unknown transaction status %d", txStatus)) + } +} + +func rootCallType(create bool) pbeth.CallType { + if create { + return pbeth.CallType_CREATE + } + + return pbeth.CallType_CALL +} + +func callTypeFromOpCode(typ vm.OpCode) pbeth.CallType { + switch typ { + case vm.CALL: + return pbeth.CallType_CALL + case vm.STATICCALL: + return pbeth.CallType_STATIC + case vm.DELEGATECALL: + return pbeth.CallType_DELEGATE + case vm.CREATE, vm.CREATE2: + return pbeth.CallType_CREATE + case vm.CALLCODE: + return pbeth.CallType_CALLCODE + } + + return pbeth.CallType_UNSPECIFIED +} + +func newTxReceiptFromChain(receipt *types.Receipt, txType pbeth.TransactionTrace_Type) (out *pbeth.TransactionReceipt) { + out = &pbeth.TransactionReceipt{ + StateRoot: receipt.PostState, + CumulativeGasUsed: receipt.CumulativeGasUsed, + LogsBloom: receipt.Bloom[:], + } + + if txType == pbeth.TransactionTrace_TRX_TYPE_BLOB { + out.BlobGasUsed = &receipt.BlobGasUsed + out.BlobGasPrice = firehoseBigIntFromNative(receipt.BlobGasPrice) + } + + if len(receipt.Logs) > 0 { + out.Logs = make([]*pbeth.Log, len(receipt.Logs)) + for i, log := range receipt.Logs { + out.Logs[i] = &pbeth.Log{ + Address: log.Address.Bytes(), + Topics: func() [][]byte { + if len(log.Topics) == 0 { + return nil + } + + out := make([][]byte, len(log.Topics)) + for i, topic := range log.Topics { + out[i] = topic.Bytes() + } + return out + }(), + Data: log.Data, + Index: uint32(i), + BlockIndex: uint32(log.Index), + + // Ordinal on transaction receipt logs is populated at the very end, so pairing + // between call logs and receipt logs is made + } + } + } + + return out +} + +func newAccessListFromChain(accessList types.AccessList) (out []*pbeth.AccessTuple) { + if len(accessList) == 0 { + return nil + } + + out = make([]*pbeth.AccessTuple, len(accessList)) + for i, tuple := range accessList { + out[i] = &pbeth.AccessTuple{ + Address: tuple.Address.Bytes(), + StorageKeys: func() [][]byte { + out := make([][]byte, len(tuple.StorageKeys)) + for i, key := range tuple.StorageKeys { + out[i] = key.Bytes() + } + return out + }(), + } + } + + return +} + +func newBlobHashesFromChain(blobHashes []common.Hash) (out [][]byte) { + if len(blobHashes) == 0 { + return nil + } + + out = make([][]byte, len(blobHashes)) + for i, blobHash := range blobHashes { + out[i] = blobHash.Bytes() + } + + return +} + +var balanceChangeReasonToPb = map[tracing.BalanceChangeReason]pbeth.BalanceChange_Reason{ + tracing.BalanceIncreaseRewardMineUncle: pbeth.BalanceChange_REASON_REWARD_MINE_UNCLE, + tracing.BalanceIncreaseRewardMineBlock: pbeth.BalanceChange_REASON_REWARD_MINE_BLOCK, + tracing.BalanceIncreaseDaoContract: pbeth.BalanceChange_REASON_DAO_REFUND_CONTRACT, + tracing.BalanceDecreaseDaoAccount: pbeth.BalanceChange_REASON_DAO_ADJUST_BALANCE, + tracing.BalanceChangeTransfer: pbeth.BalanceChange_REASON_TRANSFER, + tracing.BalanceIncreaseGenesisBalance: pbeth.BalanceChange_REASON_GENESIS_BALANCE, + tracing.BalanceDecreaseGasBuy: pbeth.BalanceChange_REASON_GAS_BUY, + tracing.BalanceIncreaseRewardTransactionFee: pbeth.BalanceChange_REASON_REWARD_TRANSACTION_FEE, + tracing.BalanceIncreaseGasReturn: pbeth.BalanceChange_REASON_GAS_REFUND, + tracing.BalanceChangeTouchAccount: pbeth.BalanceChange_REASON_TOUCH_ACCOUNT, + tracing.BalanceIncreaseSelfdestruct: pbeth.BalanceChange_REASON_SUICIDE_REFUND, + tracing.BalanceDecreaseSelfdestruct: pbeth.BalanceChange_REASON_SUICIDE_WITHDRAW, + tracing.BalanceDecreaseSelfdestructBurn: pbeth.BalanceChange_REASON_BURN, + tracing.BalanceIncreaseWithdrawal: pbeth.BalanceChange_REASON_WITHDRAWAL, + + tracing.BalanceChangeUnspecified: pbeth.BalanceChange_REASON_UNKNOWN, +} + +func balanceChangeReasonFromChain(reason tracing.BalanceChangeReason) pbeth.BalanceChange_Reason { + if r, ok := balanceChangeReasonToPb[reason]; ok { + return r + } + + panic(fmt.Errorf("unknown tracer balance change reason value '%d', check state.BalanceChangeReason so see to which constant it refers to", reason)) +} + +var gasChangeReasonToPb = map[tracing.GasChangeReason]pbeth.GasChange_Reason{ + 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.GasChangeTxIntrinsicGas: pbeth.GasChange_REASON_INTRINSIC_GAS, + tracing.GasChangeCallContractCreation: pbeth.GasChange_REASON_CONTRACT_CREATION, + tracing.GasChangeCallContractCreation2: pbeth.GasChange_REASON_CONTRACT_CREATION2, + tracing.GasChangeCallCodeStorage: pbeth.GasChange_REASON_CODE_STORAGE, + tracing.GasChangeCallPrecompiledContract: pbeth.GasChange_REASON_PRECOMPILED_CONTRACT, + tracing.GasChangeCallStorageColdAccess: pbeth.GasChange_REASON_STATE_COLD_ACCESS, + tracing.GasChangeCallLeftOverRefunded: pbeth.GasChange_REASON_REFUND_AFTER_EXECUTION, + tracing.GasChangeCallFailedExecution: pbeth.GasChange_REASON_FAILED_EXECUTION, + + // Ignored, we track them manually, newGasChange ensure that we panic if we see Unknown + tracing.GasChangeCallOpCode: pbeth.GasChange_REASON_UNKNOWN, +} + +func gasChangeReasonFromChain(reason tracing.GasChangeReason) pbeth.GasChange_Reason { + if r, ok := gasChangeReasonToPb[reason]; ok { + if r == pbeth.GasChange_REASON_UNKNOWN { + panic(fmt.Errorf("tracer gas change reason value '%d' mapped to %s which is not accepted", reason, r)) + } + + return r + } + + panic(fmt.Errorf("unknown tracer gas change reason value '%d', check vm.GasChangeReason so see to which constant it refers to", reason)) +} + +func maxFeePerGas(tx *types.Transaction) *pbeth.BigInt { + switch tx.Type() { + case types.LegacyTxType, types.AccessListTxType: + return nil + + case types.DynamicFeeTxType, types.BlobTxType: + return firehoseBigIntFromNative(tx.GasFeeCap()) + + } + + panic(errUnhandledTransactionType("maxFeePerGas", tx.Type())) +} + +func maxPriorityFeePerGas(tx *types.Transaction) *pbeth.BigInt { + switch tx.Type() { + case types.LegacyTxType, types.AccessListTxType: + return nil + + case types.DynamicFeeTxType, types.BlobTxType: + return firehoseBigIntFromNative(tx.GasTipCap()) + } + + panic(errUnhandledTransactionType("maxPriorityFeePerGas", tx.Type())) +} + +func gasPrice(tx *types.Transaction, baseFee *big.Int) *pbeth.BigInt { + switch tx.Type() { + case types.LegacyTxType, types.AccessListTxType: + return firehoseBigIntFromNative(tx.GasPrice()) + + case types.DynamicFeeTxType, types.BlobTxType: + if baseFee == nil { + return firehoseBigIntFromNative(tx.GasPrice()) + } + + return firehoseBigIntFromNative(math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap())) + } + + panic(errUnhandledTransactionType("gasPrice", tx.Type())) +} + +func FirehoseDebug(msg string, args ...any) { + firehoseDebug(msg, args...) +} + +func firehoseInfo(msg string, args ...any) { + if isFirehoseInfoEnabled { + firehoseLog(msg, args) + } +} + +func firehoseDebug(msg string, args ...any) { + if isFirehoseDebugEnabled { + firehoseLog(msg, args) + } +} + +func firehoseTrace(msg string, args ...any) { + if isFirehoseTracerEnabled { + firehoseLog(msg, args) + } +} + +func firehoseLog(msg string, args []any) { + fmt.Fprintf(os.Stderr, "[Firehose] "+msg+"\n", args...) +} + +// Ignore unused, we keep it around for debugging purposes +var _ = firehoseDebugPrintStack + +func firehoseDebugPrintStack() { + if isFirehoseDebugEnabled { + fmt.Fprintf(os.Stderr, "[Firehose] Stacktrace\n") + + // PrintStack prints to Stderr + debug.PrintStack() + } +} + +func errUnhandledTransactionType(tag string, value uint8) error { + return fmt.Errorf("unhandled transaction type's %d for firehose.%s(), carefully review the patch, if this new transaction type add new fields, think about adding them to Firehose Block format, when you see this message, it means something changed in the chain model and great care and thinking most be put here to properly understand the changes and the consequences they bring for the instrumentation", value, tag) +} + +type Ordinal struct { + value uint64 +} + +// Reset resets the ordinal to zero. +func (o *Ordinal) Reset() { + o.value = 0 +} + +// Peek gives you the current ordinal value which is actually the last assigned +// value attributed, the next value that is going to be used is `Peek() + 1`. +func (o *Ordinal) Peek() (out uint64) { + return o.value +} + +// Next gives you the next sequential ordinal value that you should +// use to assign to your exeuction trace (block, transaction, call, etc). +func (o *Ordinal) Next() (out uint64) { + o.value++ + + return o.value +} + +type CallStack struct { + index uint32 + stack []*pbeth.Call + depth int +} + +func NewCallStack() *CallStack { + return &CallStack{} +} + +func (s *CallStack) Reset() { + s.index = 0 + s.stack = s.stack[:0] + s.depth = 0 +} + +func (s *CallStack) HasActiveCall() bool { + return len(s.stack) > 0 +} + +// Push a call onto the stack. The `Index` and `ParentIndex` of this call are +// assigned by this method which knowns how to find the parent call and deal with +// it. +func (s *CallStack) Push(call *pbeth.Call) { + s.index++ + call.Index = s.index + + call.Depth = uint32(s.depth) + s.depth++ + + // If a current call is active, it's the parent of this call + if parent := s.Peek(); parent != nil { + call.ParentIndex = parent.Index + } + + s.stack = append(s.stack, call) +} + +func (s *CallStack) ActiveIndex() uint32 { + if len(s.stack) == 0 { + return 0 + } + + return s.stack[len(s.stack)-1].Index +} + +func (s *CallStack) NextIndex() uint32 { + return s.index + 1 +} + +func (s *CallStack) Pop() (out *pbeth.Call) { + if len(s.stack) == 0 { + panic(fmt.Errorf("pop from empty call stack")) + } + + out = s.stack[len(s.stack)-1] + s.stack = s.stack[:len(s.stack)-1] + s.depth-- + + return +} + +// Peek returns the top of the stack without removing it, it's the +// activate call. +func (s *CallStack) Peek() *pbeth.Call { + if len(s.stack) == 0 { + return nil + } + + return s.stack[len(s.stack)-1] +} + +// DeferredCallState is a helper struct that can be used to accumulate call's state +// that is recorded before the Call has been started. This happens on the "starting" +// portion of the call/created. +type DeferredCallState struct { + accountCreations []*pbeth.AccountCreation + balanceChanges []*pbeth.BalanceChange + gasChanges []*pbeth.GasChange + nonceChanges []*pbeth.NonceChange +} + +func NewDeferredCallState() *DeferredCallState { + return &DeferredCallState{} +} + +func (d *DeferredCallState) MaybePopulateCallAndReset(source string, call *pbeth.Call) error { + if d.IsEmpty() { + return nil + } + + if source != "root" { + return fmt.Errorf("unexpected source for deferred call state, expected root but got %s, deferred call's state are always produced on the 'root' call", source) + } + + // We must happen because it's populated at beginning of the call as well as at the very end + call.AccountCreations = append(call.AccountCreations, d.accountCreations...) + call.BalanceChanges = append(call.BalanceChanges, d.balanceChanges...) + call.GasChanges = append(call.GasChanges, d.gasChanges...) + call.NonceChanges = append(call.NonceChanges, d.nonceChanges...) + + d.Reset() + + return nil +} + +func (d *DeferredCallState) IsEmpty() bool { + return len(d.accountCreations) == 0 && len(d.balanceChanges) == 0 && len(d.gasChanges) == 0 && len(d.nonceChanges) == 0 +} + +func (d *DeferredCallState) Reset() { + d.accountCreations = nil + d.balanceChanges = nil + d.gasChanges = nil + d.nonceChanges = nil +} + +type byteView []byte + +func (b byteView) String() string { + return hex.EncodeToString(b) +} + +func errorView(err error) _errorView { + return _errorView{err} +} + +type _errorView struct { + err error +} + +func (e _errorView) String() string { + if e.err == nil { + return "" + } + + return e.err.Error() +} + +type inputView []byte + +func (b inputView) String() string { + if len(b) == 0 { + return "" + } + + if len(b) < 4 { + return common.Bytes2Hex(b) + } + + method := b[:4] + rest := b[4:] + + if len(rest)%32 == 0 { + return fmt.Sprintf("%s (%d params)", common.Bytes2Hex(method), len(rest)/32) + } + + // Contract input starts with pre-defined chracters AFAIK, we could show them more nicely + + return fmt.Sprintf("%d bytes", len(rest)) +} + +type outputView []byte + +func (b outputView) String() string { + if len(b) == 0 { + return "" + } + + return fmt.Sprintf("%d bytes", len(b)) +} + +type receiptView types.Receipt + +func (r *receiptView) String() string { + if r == nil { + return "" + } + + status := "unknown" + switch r.Status { + case types.ReceiptStatusSuccessful: + status = "success" + case types.ReceiptStatusFailed: + status = "failed" + } + + return fmt.Sprintf("[status=%s, gasUsed=%d, logs=%d]", status, r.GasUsed, len(r.Logs)) +} + +func emptyBytesToNil(in []byte) []byte { + if len(in) == 0 { + return nil + } + + return in +} + +func normalizeSignaturePoint(value []byte) []byte { + if len(value) == 0 { + return nil + } + + if len(value) < 32 { + offset := 32 - len(value) + + out := make([]byte, 32) + copy(out[offset:32], value) + + return out + } + + return value[0:32] +} + +func firehoseBigIntFromNative(in *big.Int) *pbeth.BigInt { + if in == nil || in.Sign() == 0 { + return nil + } + + return &pbeth.BigInt{Bytes: in.Bytes()} +} + +type FinalityStatus struct { + LastIrreversibleBlockNumber uint64 + LastIrreversibleBlockHash []byte +} + +func (s *FinalityStatus) populate(finalNumber uint64, finalHash []byte) { + s.LastIrreversibleBlockNumber = finalNumber + s.LastIrreversibleBlockHash = finalHash +} + +func (s *FinalityStatus) populateFromChain(finalHeader *types.Header) { + if finalHeader == nil { + s.Reset() + return + } + + s.LastIrreversibleBlockNumber = finalHeader.Number.Uint64() + s.LastIrreversibleBlockHash = finalHeader.Hash().Bytes() +} + +func (s *FinalityStatus) Reset() { + s.LastIrreversibleBlockNumber = 0 + s.LastIrreversibleBlockHash = nil +} + +func (s *FinalityStatus) IsEmpty() bool { + return s.LastIrreversibleBlockNumber == 0 && len(s.LastIrreversibleBlockHash) == 0 +} + +var errFirehoseUnknownType = errors.New("firehose unknown tx type") +var sanitizeRegexp = regexp.MustCompile(`[\t( ){2,}]+`) + +func staticFirehoseChainValidationOnInit() { + firehoseKnownTxTypes := map[byte]bool{ + types.LegacyTxType: true, + types.AccessListTxType: true, + types.DynamicFeeTxType: true, + types.BlobTxType: true, + } + + for txType := byte(0); txType < 255; txType++ { + err := validateFirehoseKnownTransactionType(txType, firehoseKnownTxTypes[txType]) + if err != nil { + panic(fmt.Errorf(sanitizeRegexp.ReplaceAllString(` + If you see this panic message, it comes from a sanity check of Firehose instrumentation + around Ethereum transaction types. + + Over time, Ethereum added new transaction types but there is no easy way for Firehose to + report a compile time check that a new transaction's type must be handled. As such, we + have a runtime check at initialization of the process that encode/decode each possible + transaction's receipt and check proper handling. + + This panic means that a transaction that Firehose don't know about has most probably + been added and you must take **great care** to instrument it. One of the most important place + to look is in 'firehose.StartTransaction' where it should be properly handled. Think + carefully, read the EIP and ensure that any new "semantic" the transactions type's is + bringing is handled and instrumented (it might affect Block and other execution units also). + + For example, when London fork appeared, semantic of 'GasPrice' changed and it required + a different computation for 'GasPrice' when 'DynamicFeeTx' transaction were added. If you determined + it was indeed a new transaction's type, fix 'firehoseKnownTxTypes' variable above to include it + as a known Firehose type (after proper instrumentation of course). + + It's also possible the test itself is now flaky, we do 'receipt := types.Receipt{Type: }' + then 'buffer := receipt.EncodeRLP(...)' and then 'receipt.DecodeRLP(buffer)'. This should catch + new transaction types but could be now generate false positive. + + Received error: %w + `, " "), err)) + } + } +} + +func validateFirehoseKnownTransactionType(txType byte, isKnownFirehoseTxType bool) error { + writerBuffer := bytes.NewBuffer(nil) + + receipt := types.Receipt{Type: txType} + err := receipt.EncodeRLP(writerBuffer) + if err != nil { + if err == types.ErrTxTypeNotSupported { + if isKnownFirehoseTxType { + return fmt.Errorf("firehose known type but encoding RLP of receipt led to 'types.ErrTxTypeNotSupported'") + } + + // It's not a known type and encoding reported the same, so validation is OK + return nil + } + + // All other cases results in an error as we should have been able to encode it to RLP + return fmt.Errorf("encoding RLP: %w", err) + } + + readerBuffer := bytes.NewBuffer(writerBuffer.Bytes()) + err = receipt.DecodeRLP(rlp.NewStream(readerBuffer, 0)) + if err != nil { + if err == types.ErrTxTypeNotSupported { + if isKnownFirehoseTxType { + return fmt.Errorf("firehose known type but decoding of RLP of receipt led to 'types.ErrTxTypeNotSupported'") + } + + // It's not a known type and decoding reported the same, so validation is OK + return nil + } + + // All other cases results in an error as we should have been able to decode it from RLP + return fmt.Errorf("decoding RLP: %w", err) + } + + // If we reach here, encoding/decoding accepted the transaction's type, so let's ensure we expected the same + if !isKnownFirehoseTxType { + return fmt.Errorf("unknown tx type value %d: %w", txType, errFirehoseUnknownType) + } + + return nil +} + +type validationResult struct { + failures []string +} + +func (r *validationResult) panicOnAnyFailures(msg string, args ...any) { + if len(r.failures) > 0 { + panic(fmt.Errorf(fmt.Sprintf(msg, args...)+": validation failed:\n %s", strings.Join(r.failures, "\n"))) + } +} + +// We keep them around, planning in the future to use them (they existed in the previous Firehose patch) +var _, _, _, _ = validateAddressField, validateBigIntField, validateHashField, validateUint64Field + +func validateAddressField(into *validationResult, field string, a, b common.Address) { + validateField(into, field, a, b, a == b, common.Address.String) +} + +func validateBigIntField(into *validationResult, field string, a, b *big.Int) { + equal := false + if a == nil && b == nil { + equal = true + } else if a == nil || b == nil { + equal = false + } else { + equal = a.Cmp(b) == 0 + } + + validateField(into, field, a, b, equal, func(x *big.Int) string { + if x == nil { + return "" + } else { + return x.String() + } + }) +} + +func validateBytesField(into *validationResult, field string, a, b []byte) { + validateField(into, field, a, b, bytes.Equal(a, b), common.Bytes2Hex) +} + +func validateArrayOfBytesField(into *validationResult, field string, a, b [][]byte) { + if len(a) != len(b) { + into.failures = append(into.failures, fmt.Sprintf("%s [(actual element) %d != %d (expected element)]", field, len(a), len(b))) + return + } + + for i := range a { + validateBytesField(into, fmt.Sprintf("%s[%d]", field, i), a[i], b[i]) + } +} + +func validateHashField(into *validationResult, field string, a, b common.Hash) { + validateField(into, field, a, b, a == b, common.Hash.String) +} + +func validateUint32Field(into *validationResult, field string, a, b uint32) { + validateField(into, field, a, b, a == b, func(x uint32) string { return strconv.FormatUint(uint64(x), 10) }) +} + +func validateUint64Field(into *validationResult, field string, a, b uint64) { + validateField(into, field, a, b, a == b, func(x uint64) string { return strconv.FormatUint(x, 10) }) +} + +// validateField, pays the price for failure message construction only when field are not equal +func validateField[T any](into *validationResult, field string, a, b T, equal bool, toString func(x T) string) { + if !equal { + into.failures = append(into.failures, fmt.Sprintf("%s [(actual) %s %s %s (expected)]", field, toString(a), "!=", toString(b))) + } +} + +func ptr[T any](t T) *T { + return &t +} + +type Memory []byte + +func (m Memory) GetPtrUint256(offset, size *uint256.Int) []byte { + return m.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) +} + +func (m Memory) GetPtr(offset, size int64) []byte { + if size == 0 { + return nil + } + + if len(m) > int(offset) { + return m[offset : offset+size] + } + + return nil +} diff --git a/x/evm/tracers/firehose_test.go b/x/evm/tracers/firehose_test.go new file mode 100644 index 000000000..4b6e13238 --- /dev/null +++ b/x/evm/tracers/firehose_test.go @@ -0,0 +1,471 @@ +package tracers + +import ( + "encoding/json" + "fmt" + "math/big" + "os" + "reflect" + "slices" + "testing" + + "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/vm" + "github.com/ethereum/go-ethereum/params" + pbeth "github.com/sei-protocol/sei-chain/pb/sf/ethereum/type/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestFirehoseCallStack_Push(t *testing.T) { + type actionRunner func(t *testing.T, s *CallStack) + + push := func(call *pbeth.Call) actionRunner { return func(_ *testing.T, s *CallStack) { s.Push(call) } } + pop := func() actionRunner { return func(_ *testing.T, s *CallStack) { s.Pop() } } + check := func(r actionRunner) actionRunner { return func(t *testing.T, s *CallStack) { r(t, s) } } + + tests := []struct { + name string + actions []actionRunner + }{ + { + "push/pop emtpy", []actionRunner{ + push(&pbeth.Call{}), + pop(), + check(func(t *testing.T, s *CallStack) { + require.Len(t, s.stack, 0) + }), + }, + }, + { + "push/push/push", []actionRunner{ + push(&pbeth.Call{}), + push(&pbeth.Call{}), + push(&pbeth.Call{}), + check(func(t *testing.T, s *CallStack) { + require.Len(t, s.stack, 3) + + require.Equal(t, 1, int(s.stack[0].Index)) + require.Equal(t, 0, int(s.stack[0].ParentIndex)) + + require.Equal(t, 2, int(s.stack[1].Index)) + require.Equal(t, 1, int(s.stack[1].ParentIndex)) + + require.Equal(t, 3, int(s.stack[2].Index)) + require.Equal(t, 2, int(s.stack[2].ParentIndex)) + }), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := NewCallStack() + + for _, action := range tt.actions { + action(t, s) + } + }) + } +} + +func Test_validateKnownTransactionTypes(t *testing.T) { + tests := []struct { + name string + txType byte + knownType bool + want error + }{ + {"legacy", 0, true, nil}, + {"access_list", 1, true, nil}, + {"inexistant", 255, false, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateFirehoseKnownTransactionType(tt.txType, tt.knownType) + if tt.want == nil && err != nil { + t.Fatalf("Transaction of type %d expected to validate properly but received error %q", tt.txType, err) + } else if tt.want != nil && err == nil { + t.Fatalf("Transaction of type %d expected to validate improperly but generated no error", tt.txType) + } else if tt.want != nil && err != nil && tt.want.Error() != err.Error() { + t.Fatalf("Transaction of type %d expected to validate improperly but generated error %q does not match expected error %q", tt.txType, err, tt.want) + } + }) + } +} + +var ignorePbFieldNames = map[string]bool{ + "Hash": true, + "TotalDifficulty": true, + "state": true, + "unknownFields": true, + "sizeCache": true, + + // This was a Polygon specific field that existed for a while and has since been + // removed. It can be safely ignored in all protocols now. + "TxDependency": true, +} + +var pbFieldNameToGethMapping = map[string]string{ + "WithdrawalsRoot": "WithdrawalsHash", + "MixHash": "MixDigest", + "BaseFeePerGas": "BaseFee", + "StateRoot": "Root", + "ExtraData": "Extra", + "Timestamp": "Time", + "ReceiptRoot": "ReceiptHash", + "TransactionsRoot": "TxHash", + "LogsBloom": "Bloom", +} + +var ( + pbHeaderType = reflect.TypeOf((*pbeth.BlockHeader)(nil)).Elem() + gethHeaderType = reflect.TypeOf((*types.Header)(nil)).Elem() +) + +func Test_TypesHeader_AllConsensusFieldsAreKnown(t *testing.T) { + // This exact hash varies from protocol to protocol and also sometimes from one version to the other. + // When adding support for a new hard-fork that adds new block header fields, it's normal that this value + // changes. If you are sure the two struct are the same, then you can update the expected hash below + // to the new value. + expectedHash := common.HexToHash("5341947c531e5c9cf38202784b16ac66484fe1838aa6e825436b22321b927296") + + gethHeaderValue := reflect.New(gethHeaderType) + fillAllFieldsWithNonEmptyValues(t, gethHeaderValue, reflect.VisibleFields(gethHeaderType)) + gethHeader := gethHeaderValue.Interface().(*types.Header) + + // If you hit this assertion, it means that the fields `types.Header` of go-ethereum differs now + // versus last time this test was edited. + // + // It's important to understand that in Ethereum Block Header (e.g. `*types.Header`), the `Hash` is + // actually a computed value based on the other fields in the struct, so if you change any field, + // the hash will change also. + // + // On hard-fork, it happens that new fields are added, this test serves as a way to "detect" in codde + // that the expected fields of `types.Header` changed + require.Equal(t, expectedHash, gethHeader.Hash(), + "Geth Header Hash mistmatch, got %q but expecting %q on *types.Header:\n\nGeth Header (from fillNonDefault(new(*types.Header)))\n%s", + gethHeader.Hash().Hex(), + expectedHash, + asIndentedJSON(t, gethHeader), + ) +} + +func Test_FirehoseAndGethHeaderFieldMatches(t *testing.T) { + pbFields := filter(reflect.VisibleFields(pbHeaderType), func(f reflect.StructField) bool { + return !ignorePbFieldNames[f.Name] + }) + + gethFields := reflect.VisibleFields(gethHeaderType) + + pbFieldCount := len(pbFields) + gethFieldCount := len(gethFields) + + pbFieldNames := extractStructFieldNames(pbFields) + gethFieldNames := extractStructFieldNames(gethFields) + + // If you reach this assertion, it means that the fields count in the protobuf and go-ethereum are different. + // It is super important that you properly update the mapping from pbeth.BlockHeader to go-ethereum/core/types.Header + // that is done in `codecHeaderToGethHeader` function in `executor/provider_statedb.go`. + require.Equal( + t, + pbFieldCount, + gethFieldCount, + fieldsCountMistmatchMessage(t, pbFieldNames, gethFieldNames)) + + for pbFieldName := range pbFieldNames { + pbFieldRenamedName, found := pbFieldNameToGethMapping[pbFieldName] + if !found { + pbFieldRenamedName = pbFieldName + } + + assert.Contains(t, gethFieldNames, pbFieldRenamedName, "pbField.Name=%q (original %q) not found in gethFieldNames", pbFieldRenamedName, pbFieldName) + } +} + +func fillAllFieldsWithNonEmptyValues(t *testing.T, structValue reflect.Value, fields []reflect.StructField) { + t.Helper() + + for _, field := range fields { + fieldValue := structValue.Elem().FieldByName(field.Name) + require.True(t, fieldValue.IsValid(), "field %q not found", field.Name) + + switch fieldValue.Interface().(type) { + case []byte: + fieldValue.Set(reflect.ValueOf([]byte{1})) + case uint64: + fieldValue.Set(reflect.ValueOf(uint64(1))) + case *uint64: + var mockValue uint64 = 1 + fieldValue.Set(reflect.ValueOf(&mockValue)) + case *common.Hash: + var mockValue common.Hash = common.HexToHash("0x01") + fieldValue.Set(reflect.ValueOf(&mockValue)) + case common.Hash: + fieldValue.Set(reflect.ValueOf(common.HexToHash("0x01"))) + case common.Address: + fieldValue.Set(reflect.ValueOf(common.HexToAddress("0x01"))) + case types.Bloom: + fieldValue.Set(reflect.ValueOf(types.BytesToBloom([]byte{1}))) + case types.BlockNonce: + fieldValue.Set(reflect.ValueOf(types.EncodeNonce(1))) + case *big.Int: + fieldValue.Set(reflect.ValueOf(big.NewInt(1))) + case *pbeth.BigInt: + fieldValue.Set(reflect.ValueOf(&pbeth.BigInt{Bytes: []byte{1}})) + case *timestamppb.Timestamp: + fieldValue.Set(reflect.ValueOf(×tamppb.Timestamp{Seconds: 1})) + default: + // If you reach this panic in test, simply add a case above with a sane non-default + // value for the type in question. + t.Fatalf("unsupported type %T", fieldValue.Interface()) + } + } +} + +func fieldsCountMistmatchMessage(t *testing.T, pbFieldNames map[string]bool, gethFieldNames map[string]bool) string { + t.Helper() + + pbRemappedFieldNames := make(map[string]bool, len(pbFieldNames)) + for pbFieldName := range pbFieldNames { + pbFieldRenamedName, found := pbFieldNameToGethMapping[pbFieldName] + if !found { + pbFieldRenamedName = pbFieldName + } + + pbRemappedFieldNames[pbFieldRenamedName] = true + } + + return fmt.Sprintf( + "Field count mistmatch between `pbeth.BlockHeader` (has %d fields) and `*types.Header` (has %d fields)\n\n"+ + "Fields in `pbeth.Blockheader`:\n%s\n\n"+ + "Fields in `*types.Header`:\n%s\n\n"+ + "Missing in `pbeth.BlockHeader`:\n%s\n\n"+ + "Missing in `*types.Header`:\n%s", + len(pbRemappedFieldNames), + len(gethFieldNames), + asIndentedJSON(t, maps.Keys(pbRemappedFieldNames)), + asIndentedJSON(t, maps.Keys(gethFieldNames)), + asIndentedJSON(t, missingInSet(gethFieldNames, pbRemappedFieldNames)), + asIndentedJSON(t, missingInSet(pbRemappedFieldNames, gethFieldNames)), + ) +} + +func asIndentedJSON(t *testing.T, v any) string { + t.Helper() + out, err := json.MarshalIndent(v, "", " ") + require.NoError(t, err) + + return string(out) +} + +func missingInSet(a, b map[string]bool) []string { + missing := make([]string, 0) + for name := range a { + if !b[name] { + missing = append(missing, name) + } + } + + return missing +} + +func extractStructFieldNames(fields []reflect.StructField) map[string]bool { + result := make(map[string]bool, len(fields)) + for _, field := range fields { + result[field.Name] = true + } + return result +} + +func filter[S ~[]T, T any](s S, f func(T) bool) (out S) { + out = make(S, 0, len(s)/4) + for i, v := range s { + if f(v) { + out = append(out, s[i]) + } + } + + 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 emptyHash = common.Hash{} +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), + } +} diff --git a/x/evm/tracers/registry.go b/x/evm/tracers/registry.go new file mode 100644 index 000000000..71731343d --- /dev/null +++ b/x/evm/tracers/registry.go @@ -0,0 +1,31 @@ +package tracers + +var GlobalLiveTracerRegistry = NewLiveTracerRegistry() + +type LiveTracerRegistry interface { + GetFactoryByID(id string) (BlockchainTracerFactory, bool) + Register(id string, factory BlockchainTracerFactory) +} + +var _ LiveTracerRegistry = (*liveTracerRegistry)(nil) + +func NewLiveTracerRegistry() LiveTracerRegistry { + return &liveTracerRegistry{ + tracers: make(map[string]BlockchainTracerFactory), + } +} + +type liveTracerRegistry struct { + tracers map[string]BlockchainTracerFactory +} + +// Register implements LiveTracerRegistry. +func (r *liveTracerRegistry) Register(id string, factory BlockchainTracerFactory) { + r.tracers[id] = factory +} + +// GetFactoryByID implements LiveTracerRegistry. +func (r *liveTracerRegistry) GetFactoryByID(id string) (BlockchainTracerFactory, bool) { + v, found := r.tracers[id] + return v, found +} diff --git a/x/evm/tracers/testdata/firehose/reorder-ordinals-empty.golden.json b/x/evm/tracers/testdata/firehose/reorder-ordinals-empty.golden.json new file mode 100755 index 000000000..b2ac4a9e5 --- /dev/null +++ b/x/evm/tracers/testdata/firehose/reorder-ordinals-empty.golden.json @@ -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 +} \ No newline at end of file diff --git a/x/evm/tracers/tracing.go b/x/evm/tracers/tracing.go new file mode 100644 index 000000000..6bfb7be97 --- /dev/null +++ b/x/evm/tracers/tracing.go @@ -0,0 +1,112 @@ +package tracers + +import ( + "context" + "fmt" + "net/url" + + sdk "github.com/cosmos/cosmos-sdk/types" + ethtracing "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/params" + "github.com/sei-protocol/sei-chain/x/evm/tracing" +) + +// BlockchainTracerFactory is a function that creates a new [BlockchainTracer]. +// It's going to received the parsed URL from the `live-evm-tracer` flag. +// +// The scheme of the URL is going to be used to determine which tracer to use +// by the registry. +type BlockchainTracerFactory = func(tracerURL *url.URL) (*tracing.Hooks, error) + +// NewBlockchainTracer creates a new [tracing.Hooks] struct that is used to trace blocks and transactions +// for EVM needs. The tracer is instansiated by the provided URL and registered in the registry. +func NewBlockchainTracer(registry LiveTracerRegistry, tracerIdentifier string, chainConfig *params.ChainConfig) (*tracing.Hooks, error) { + tracerURL, err := url.Parse(tracerIdentifier) + if err != nil { + return nil, fmt.Errorf("tracer value %q should have been a valid URL: %w", tracerIdentifier, err) + } + + // We accept plain string like "firehose" and URL like "firehose://...". The former form parses as + // an URL correct with `scheme="", host="", path="firehose", so the logic below does that. Take + // the scheme is defined otherwise. + tracerID := tracerURL.Scheme + if tracerID == "" && tracerURL.Host == "" && tracerURL.EscapedPath() != "" { + tracerID = tracerURL.EscapedPath() + } + + if tracerID == "" { + return nil, fmt.Errorf("unable to extract tracer ID from %q", tracerID) + } + + factory, found := registry.GetFactoryByID(tracerID) + if !found { + return nil, fmt.Errorf("tracer %q is not registered", tracerID) + } + + tracer, err := factory(tracerURL) + if err != nil { + return nil, fmt.Errorf("failed to create tracer: %w", err) + } + + tracer.OnSeiBlockchainInit(chainConfig) + + return tracer, nil +} + +type CtxBlockchainTracerKeyType string + +const CtxBlockchainTracerKey = CtxBlockchainTracerKeyType("evm_and_state_logger") + +func SetCtxBlockchainTracer(ctx sdk.Context, logger *tracing.Hooks) sdk.Context { + return ctx.WithContext(context.WithValue(ctx.Context(), CtxBlockchainTracerKey, logger)) +} + +// GetCtxBlockchainTracer function to get the SEI specific [tracing.Hooks] struct +// used to trace EVM blocks and transactions. +func GetCtxBlockchainTracer(ctx sdk.Context) *tracing.Hooks { + rawVal := ctx.Context().Value(CtxBlockchainTracerKey) + if rawVal == nil { + return nil + } + logger, ok := rawVal.(*tracing.Hooks) + if !ok { + return nil + } + return logger +} + +// GetCtxEthTracingHooks is a convenience function to get the ethtracing.Hooks from the context +// avoiding nil pointer exceptions when trying to send the tracer to lower-level go-ethereum components +// that deals with *tracing.Hooks directly. +func GetCtxEthTracingHooks(ctx sdk.Context) *ethtracing.Hooks { + if logger := GetCtxBlockchainTracer(ctx); logger != nil { + return logger.Hooks + } + + return nil +} + +var _ sdk.TxTracer = (*TxTracerHooks)(nil) + +type TxTracerHooks struct { + Hooks *tracing.Hooks + + OnTxReset func() + OnTxCommit func() +} + +func (h TxTracerHooks) InjectInContext(ctx sdk.Context) sdk.Context { + return SetCtxBlockchainTracer(ctx, h.Hooks) +} + +func (h TxTracerHooks) Reset() { + if h.OnTxReset != nil { + h.OnTxReset() + } +} + +func (h TxTracerHooks) Commit() { + if h.OnTxCommit != nil { + h.OnTxCommit() + } +} diff --git a/x/evm/tracing/hooks.go b/x/evm/tracing/hooks.go new file mode 100644 index 000000000..351fd1dd2 --- /dev/null +++ b/x/evm/tracing/hooks.go @@ -0,0 +1,77 @@ +package tracing + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "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/params" +) + +// BlockEvent is emitted upon tracing an incoming block. +// It contains the block as well as consensus related information. +type BlockEvent struct { + Hash common.Hash + Header *types.Header + Size uint64 +} + +type ( + // OnSeiBlockchainInitHook is called when the blockchain is initialized + // once per process and receives the chain configuration. + OnSeiBlockchainInitHook = func(chainConfig *params.ChainConfig) + // OnSeiBlockStart is called before executing `block`. + // `td` is the total difficulty prior to `block`. + // `skip` indicates processing of this previously known block + // will be skipped. OnBlockStart and OnBlockEnd will be emitted to + // convey how chain is progressing. E.g. known blocks will be skipped + // when node is started after a crash. + OnSeiBlockStartHook = func(hash []byte, size uint64, b *types.Header) + // OnSeiBlockEnd is called after executing `block` and receives the error + // that occurred during processing. If an `err` is received in the callback, + // it means the block should be discarded (optimistic execution failed for example). + OnSeiBlockEnd = func(err error) + + // OnSeiSystemCallStart is called before executing a system call in Sei context. CoWasm + // contract execution that invokes the EVM from one ore another are examples of system calls. + // + // You can use this hook to route upcoming `OnEnter/OnExit` EVM tracing hook to be appended + // to a system call bucket for the purpose of tracing the system calls. + OnSeiSystemCallStart = func(err error) + + // OnSeiSystemCallStart is called after executing a system call in Sei context. CoWasm + // contract execution that invokes the EVM from one ore another are examples of system calls. + // + // You can use this hook to terminate special handling of `OnEnter/OnExit`. + OnSeiSystemCallEnd = func(err error) +) + +// Hooks is used to collect traces during chain processing. It's a similar +// interface as the go-ethereum's [tracing.Hooks] but adapted to Sei particularities. +// +// The method all starts with OnSei... to avoid confusion with the go-ethereum's [tracing.Hooks] +// interface and allow one to implement both interfaces in the same struct. +type Hooks struct { + *tracing.Hooks + + OnSeiBlockchainInit OnSeiBlockchainInitHook + OnSeiBlockStart OnSeiBlockStartHook + OnSeiBlockEnd OnSeiBlockEnd + + OnSeiSystemCallStart func() + OnSeiSystemCallEnd func() + + GetTxTracer func(txIndex int) sdk.TxTracer +} + +var _ sdk.TxTracer = (*Hooks)(nil) + +func (h *Hooks) InjectInContext(ctx sdk.Context) sdk.Context { + return ctx +} + +func (h *Hooks) Reset() { +} + +func (h *Hooks) Commit() { +}