From 4094359c473a5bfccdb462f049ecacca228cc2b3 Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Sun, 15 Oct 2023 10:47:01 -0700 Subject: [PATCH 01/18] migrate sentio tracer to geth --- cmd/geth/main.go | 462 ++++++++++++++++++ eth/tracers/internal/tracetest/util.go | 1 + eth/tracers/sentio/gen_account_json.go | 68 +++ eth/tracers/sentio/prestate.go | 316 ++++++++++++ eth/tracers/sentio/tracer.go | 634 +++++++++++++++++++++++++ plugin/evm/vm.go | 1 + 6 files changed, 1482 insertions(+) create mode 100644 cmd/geth/main.go create mode 100644 eth/tracers/sentio/gen_account_json.go create mode 100644 eth/tracers/sentio/prestate.go create mode 100644 eth/tracers/sentio/tracer.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go new file mode 100644 index 0000000000..5b14e09b1d --- /dev/null +++ b/cmd/geth/main.go @@ -0,0 +1,462 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +// geth is the official command-line client for Ethereum. +package main + +import ( + "fmt" + "os" + "sort" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/console/prompt" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/node" + + // Force-load the tracer engines to trigger registration + _ "github.com/ethereum/go-ethereum/eth/tracers/js" + _ "github.com/ethereum/go-ethereum/eth/tracers/native" + _ "github.com/ethereum/go-ethereum/eth/tracers/sentio" + + "github.com/urfave/cli/v2" +) + +const ( + clientIdentifier = "geth" // Client identifier to advertise over the network +) + +var ( + // flags that configure the node + nodeFlags = flags.Merge([]cli.Flag{ + utils.IdentityFlag, + utils.UnlockedAccountFlag, + utils.PasswordFileFlag, + utils.BootnodesFlag, + utils.MinFreeDiskSpaceFlag, + utils.KeyStoreDirFlag, + utils.ExternalSignerFlag, + utils.NoUSBFlag, + utils.USBFlag, + utils.SmartCardDaemonPathFlag, + utils.OverrideCancun, + utils.EnablePersonal, + utils.TxPoolLocalsFlag, + utils.TxPoolNoLocalsFlag, + utils.TxPoolJournalFlag, + utils.TxPoolRejournalFlag, + utils.TxPoolPriceLimitFlag, + utils.TxPoolPriceBumpFlag, + utils.TxPoolAccountSlotsFlag, + utils.TxPoolGlobalSlotsFlag, + utils.TxPoolAccountQueueFlag, + utils.TxPoolGlobalQueueFlag, + utils.TxPoolLifetimeFlag, + utils.SyncModeFlag, + utils.SyncTargetFlag, + utils.ExitWhenSyncedFlag, + utils.GCModeFlag, + utils.SnapshotFlag, + utils.TxLookupLimitFlag, + utils.LightServeFlag, + utils.LightIngressFlag, + utils.LightEgressFlag, + utils.LightMaxPeersFlag, + utils.LightNoPruneFlag, + utils.LightKDFFlag, + utils.UltraLightServersFlag, + utils.UltraLightFractionFlag, + utils.UltraLightOnlyAnnounceFlag, + utils.LightNoSyncServeFlag, + utils.EthRequiredBlocksFlag, + utils.LegacyWhitelistFlag, + utils.BloomFilterSizeFlag, + utils.CacheFlag, + utils.CacheDatabaseFlag, + utils.CacheTrieFlag, + utils.CacheTrieJournalFlag, + utils.CacheTrieRejournalFlag, + utils.CacheGCFlag, + utils.CacheSnapshotFlag, + utils.CacheNoPrefetchFlag, + utils.CachePreimagesFlag, + utils.CacheLogSizeFlag, + utils.FDLimitFlag, + utils.CryptoKZGFlag, + utils.ListenPortFlag, + utils.DiscoveryPortFlag, + utils.MaxPeersFlag, + utils.MaxPendingPeersFlag, + utils.MiningEnabledFlag, + utils.MinerGasLimitFlag, + utils.MinerGasPriceFlag, + utils.MinerEtherbaseFlag, + utils.MinerExtraDataFlag, + utils.MinerRecommitIntervalFlag, + utils.MinerNewPayloadTimeout, + utils.NATFlag, + utils.NoDiscoverFlag, + utils.DiscoveryV5Flag, + utils.NetrestrictFlag, + utils.NodeKeyFileFlag, + utils.NodeKeyHexFlag, + utils.DNSDiscoveryFlag, + utils.DeveloperFlag, + utils.DeveloperPeriodFlag, + utils.DeveloperGasLimitFlag, + utils.VMEnableDebugFlag, + utils.NetworkIdFlag, + utils.EthStatsURLFlag, + utils.NoCompactionFlag, + utils.GpoBlocksFlag, + utils.GpoPercentileFlag, + utils.GpoMaxGasPriceFlag, + utils.GpoIgnoreGasPriceFlag, + configFileFlag, + }, utils.NetworkFlags, utils.DatabasePathFlags) + + rpcFlags = []cli.Flag{ + utils.HTTPEnabledFlag, + utils.HTTPListenAddrFlag, + utils.HTTPPortFlag, + utils.HTTPCORSDomainFlag, + utils.AuthListenFlag, + utils.AuthPortFlag, + utils.AuthVirtualHostsFlag, + utils.JWTSecretFlag, + utils.HTTPVirtualHostsFlag, + utils.GraphQLEnabledFlag, + utils.GraphQLCORSDomainFlag, + utils.GraphQLVirtualHostsFlag, + utils.HTTPApiFlag, + utils.HTTPPathPrefixFlag, + utils.WSEnabledFlag, + utils.WSListenAddrFlag, + utils.WSPortFlag, + utils.WSApiFlag, + utils.WSAllowedOriginsFlag, + utils.WSPathPrefixFlag, + utils.IPCDisabledFlag, + utils.IPCPathFlag, + utils.InsecureUnlockAllowedFlag, + utils.RPCGlobalGasCapFlag, + utils.RPCGlobalEVMTimeoutFlag, + utils.RPCGlobalTxFeeCapFlag, + utils.AllowUnprotectedTxs, + } + + metricsFlags = []cli.Flag{ + utils.MetricsEnabledFlag, + utils.MetricsEnabledExpensiveFlag, + utils.MetricsHTTPFlag, + utils.MetricsPortFlag, + utils.MetricsEnableInfluxDBFlag, + utils.MetricsInfluxDBEndpointFlag, + utils.MetricsInfluxDBDatabaseFlag, + utils.MetricsInfluxDBUsernameFlag, + utils.MetricsInfluxDBPasswordFlag, + utils.MetricsInfluxDBTagsFlag, + utils.MetricsEnableInfluxDBV2Flag, + utils.MetricsInfluxDBTokenFlag, + utils.MetricsInfluxDBBucketFlag, + utils.MetricsInfluxDBOrganizationFlag, + } +) + +var app = flags.NewApp("the go-ethereum command line interface") + +func init() { + // Initialize the CLI app and start Geth + app.Action = geth + app.Copyright = "Copyright 2013-2023 The go-ethereum Authors" + app.Commands = []*cli.Command{ + // See chaincmd.go: + initCommand, + importCommand, + exportCommand, + importPreimagesCommand, + exportPreimagesCommand, + removedbCommand, + dumpCommand, + dumpGenesisCommand, + // See accountcmd.go: + accountCommand, + walletCommand, + // See consolecmd.go: + consoleCommand, + attachCommand, + javascriptCommand, + // See misccmd.go: + versionCommand, + versionCheckCommand, + licenseCommand, + // See config.go + dumpConfigCommand, + // see dbcmd.go + dbCommand, + // See cmd/utils/flags_legacy.go + utils.ShowDeprecated, + // See snapshot.go + snapshotCommand, + // See verkle.go + verkleCommand, + } + sort.Sort(cli.CommandsByName(app.Commands)) + + app.Flags = flags.Merge( + nodeFlags, + rpcFlags, + consoleFlags, + debug.Flags, + metricsFlags, + ) + + app.Before = func(ctx *cli.Context) error { + flags.MigrateGlobalFlags(ctx) + return debug.Setup(ctx) + } + app.After = func(ctx *cli.Context) error { + debug.Exit() + prompt.Stdin.Close() // Resets terminal mode. + return nil + } +} + +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +// prepare manipulates memory cache allowance and setups metric system. +// This function should be called before launching devp2p stack. +func prepare(ctx *cli.Context) { + // If we're running a known preset, log it for convenience. + switch { + case ctx.IsSet(utils.RinkebyFlag.Name): + log.Info("Starting Geth on Rinkeby testnet...") + + case ctx.IsSet(utils.GoerliFlag.Name): + log.Info("Starting Geth on Görli testnet...") + + case ctx.IsSet(utils.SepoliaFlag.Name): + log.Info("Starting Geth on Sepolia testnet...") + + case ctx.IsSet(utils.DeveloperFlag.Name): + log.Info("Starting Geth in ephemeral dev mode...") + log.Warn(`You are running Geth in --dev mode. Please note the following: + + 1. This mode is only intended for fast, iterative development without assumptions on + security or persistence. + 2. The database is created in memory unless specified otherwise. Therefore, shutting down + your computer or losing power will wipe your entire block data and chain state for + your dev environment. + 3. A random, pre-allocated developer account will be available and unlocked as + eth.coinbase, which can be used for testing. The random dev account is temporary, + stored on a ramdisk, and will be lost if your machine is restarted. + 4. Mining is enabled by default. However, the client will only seal blocks if transactions + are pending in the mempool. The miner's minimum accepted gas price is 1. + 5. Networking is disabled; there is no listen-address, the maximum number of peers is set + to 0, and discovery is disabled. +`) + + case !ctx.IsSet(utils.NetworkIdFlag.Name): + log.Info("Starting Geth on Ethereum mainnet...") + } + // If we're a full node on mainnet without --cache specified, bump default cache allowance + if ctx.String(utils.SyncModeFlag.Name) != "light" && !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) { + // Make sure we're not on any supported preconfigured testnet either + if !ctx.IsSet(utils.SepoliaFlag.Name) && + !ctx.IsSet(utils.RinkebyFlag.Name) && + !ctx.IsSet(utils.GoerliFlag.Name) && + !ctx.IsSet(utils.DeveloperFlag.Name) { + // Nope, we're really on mainnet. Bump that cache up! + log.Info("Bumping default cache on mainnet", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 4096) + ctx.Set(utils.CacheFlag.Name, strconv.Itoa(4096)) + } + } + // If we're running a light client on any network, drop the cache to some meaningfully low amount + if ctx.String(utils.SyncModeFlag.Name) == "light" && !ctx.IsSet(utils.CacheFlag.Name) { + log.Info("Dropping default light client cache", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 128) + ctx.Set(utils.CacheFlag.Name, strconv.Itoa(128)) + } + + // Start metrics export if enabled + utils.SetupMetrics(ctx) + + // Start system runtime metrics collection + go metrics.CollectProcessMetrics(3 * time.Second) +} + +// geth is the main entry point into the system if no special subcommand is run. +// It creates a default node based on the command line arguments and runs it in +// blocking mode, waiting for it to be shut down. +func geth(ctx *cli.Context) error { + if args := ctx.Args().Slice(); len(args) > 0 { + return fmt.Errorf("invalid command: %q", args[0]) + } + + prepare(ctx) + stack, backend := makeFullNode(ctx) + defer stack.Close() + + startNode(ctx, stack, backend, false) + stack.Wait() + return nil +} + +// startNode boots up the system node and all registered protocols, after which +// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the +// miner. +func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isConsole bool) { + debug.Memsize.Add("node", stack) + + // Start up the node itself + utils.StartNode(ctx, stack, isConsole) + + // Unlock any account specifically requested + unlockAccounts(ctx, stack) + + // Register wallet event handlers to open and auto-derive wallets + events := make(chan accounts.WalletEvent, 16) + stack.AccountManager().Subscribe(events) + + // Create a client to interact with local geth node. + rpcClient, err := stack.Attach() + if err != nil { + utils.Fatalf("Failed to attach to self: %v", err) + } + ethClient := ethclient.NewClient(rpcClient) + + go func() { + // Open any wallets already attached + for _, wallet := range stack.AccountManager().Wallets() { + if err := wallet.Open(""); err != nil { + log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) + } + } + // Listen for wallet event till termination + for event := range events { + switch event.Kind { + case accounts.WalletArrived: + if err := event.Wallet.Open(""); err != nil { + log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) + } + case accounts.WalletOpened: + status, _ := event.Wallet.Status() + log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) + + var derivationPaths []accounts.DerivationPath + if event.Wallet.URL().Scheme == "ledger" { + derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath) + } + derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath) + + event.Wallet.SelfDerive(derivationPaths, ethClient) + + case accounts.WalletDropped: + log.Info("Old wallet dropped", "url", event.Wallet.URL()) + event.Wallet.Close() + } + } + }() + + // Spawn a standalone goroutine for status synchronization monitoring, + // close the node when synchronization is complete if user required. + if ctx.Bool(utils.ExitWhenSyncedFlag.Name) { + go func() { + sub := stack.EventMux().Subscribe(downloader.DoneEvent{}) + defer sub.Unsubscribe() + for { + event := <-sub.Chan() + if event == nil { + continue + } + done, ok := event.Data.(downloader.DoneEvent) + if !ok { + continue + } + if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute { + log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(), + "age", common.PrettyAge(timestamp)) + stack.Close() + } + } + }() + } + + // Start auxiliary services if enabled + if ctx.Bool(utils.MiningEnabledFlag.Name) || ctx.Bool(utils.DeveloperFlag.Name) { + // Mining only makes sense if a full Ethereum node is running + if ctx.String(utils.SyncModeFlag.Name) == "light" { + utils.Fatalf("Light clients do not support mining") + } + ethBackend, ok := backend.(*eth.EthAPIBackend) + if !ok { + utils.Fatalf("Ethereum service not running") + } + // Set the gas price to the limits from the CLI and start mining + gasprice := flags.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) + ethBackend.TxPool().SetGasPrice(gasprice) + if err := ethBackend.StartMining(); err != nil { + utils.Fatalf("Failed to start mining: %v", err) + } + } +} + +// unlockAccounts unlocks any account specifically requested. +func unlockAccounts(ctx *cli.Context, stack *node.Node) { + var unlocks []string + inputs := strings.Split(ctx.String(utils.UnlockedAccountFlag.Name), ",") + for _, input := range inputs { + if trimmed := strings.TrimSpace(input); trimmed != "" { + unlocks = append(unlocks, trimmed) + } + } + // Short circuit if there is no account to unlock. + if len(unlocks) == 0 { + return + } + // If insecure account unlocking is not allowed if node's APIs are exposed to external. + // Print warning log to user and skip unlocking. + if !stack.Config().InsecureUnlockAllowed && stack.Config().ExtRPCEnabled() { + utils.Fatalf("Account unlock with HTTP access is forbidden!") + } + backends := stack.AccountManager().Backends(keystore.KeyStoreType) + if len(backends) == 0 { + log.Warn("Failed to unlock accounts, keystore is not available") + return + } + ks := backends[0].(*keystore.KeyStore) + passwords := utils.MakePasswordList(ctx) + for i, account := range unlocks { + unlockAccount(ks, account, i, passwords) + } +} diff --git a/eth/tracers/internal/tracetest/util.go b/eth/tracers/internal/tracetest/util.go index 3cecbf84ab..d36a40cae9 100644 --- a/eth/tracers/internal/tracetest/util.go +++ b/eth/tracers/internal/tracetest/util.go @@ -16,6 +16,7 @@ import ( // Force-load native and js packages, to trigger registration _ "github.com/ava-labs/coreth/eth/tracers/js" _ "github.com/ava-labs/coreth/eth/tracers/native" + _ "github.com/ava-labs/coreth/eth/tracers/sentio" ) // To generate a new callTracer test, copy paste the makeTest method below into diff --git a/eth/tracers/sentio/gen_account_json.go b/eth/tracers/sentio/gen_account_json.go new file mode 100644 index 0000000000..964b44ad27 --- /dev/null +++ b/eth/tracers/sentio/gen_account_json.go @@ -0,0 +1,68 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package sentio + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*accountMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (a account) MarshalJSON() ([]byte, error) { + type account struct { + Balance *hexutil.Big `json:"balance,omitempty"` + Code hexutil.Bytes `json:"code,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + CodeAddress *common.Address `json:"codeAddress,omitempty"` + MappingKeys map[string]string `json:"mappingKeys,omitempty"` + } + var enc account + enc.Balance = (*hexutil.Big)(a.Balance) + enc.Code = a.Code + enc.Nonce = a.Nonce + enc.Storage = a.Storage + enc.CodeAddress = a.CodeAddress + enc.MappingKeys = a.MappingKeys + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (a *account) UnmarshalJSON(input []byte) error { + type account struct { + Balance *hexutil.Big `json:"balance,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + Nonce *uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + CodeAddress *common.Address `json:"codeAddress,omitempty"` + MappingKeys map[string]string `json:"mappingKeys,omitempty"` + } + var dec account + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Balance != nil { + a.Balance = (*big.Int)(dec.Balance) + } + if dec.Code != nil { + a.Code = *dec.Code + } + if dec.Nonce != nil { + a.Nonce = *dec.Nonce + } + if dec.Storage != nil { + a.Storage = dec.Storage + } + if dec.CodeAddress != nil { + a.CodeAddress = dec.CodeAddress + } + if dec.MappingKeys != nil { + a.MappingKeys = dec.MappingKeys + } + return nil +} diff --git a/eth/tracers/sentio/prestate.go b/eth/tracers/sentio/prestate.go new file mode 100644 index 0000000000..c68e856e59 --- /dev/null +++ b/eth/tracers/sentio/prestate.go @@ -0,0 +1,316 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package sentio + +import ( + "bytes" + "encoding/json" + "math/big" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go + +func init() { + tracers.DefaultDirectory.Register("sentioPrestateTracer", newSentioPrestateTracer, false) +} + +type state = map[common.Address]*account + +type account struct { + Balance *big.Int `json:"balance,omitempty"` + Code []byte `json:"code,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + CodeAddress *common.Address `json:"codeAddress,omitempty"` + MappingKeys map[string]string `json:"mappingKeys,omitempty"` +} + +func (a *account) exists() bool { + return a.Nonce > 0 || len(a.Code) > 0 || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0) +} + +type accountMarshaling struct { + Balance *hexutil.Big + Code hexutil.Bytes +} + +type sentioPrestateTracer struct { + env *vm.EVM + pre state + post state + create bool + to common.Address + gasLimit uint64 // Amount of gas bought for the whole tx + config prestateTracerConfig + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption + created map[common.Address]bool + deleted map[common.Address]bool +} + +func (t *sentioPrestateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + //TODO implement me +} + +func (t *sentioPrestateTracer) CaptureExit(output []byte, usedGas uint64, err error) { + //TODO implement me +} + +func (t *sentioPrestateTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { + //TODO implement me +} + +type prestateTracerConfig struct { + DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications +} + +func newSentioPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + var config prestateTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + return &sentioPrestateTracer{ + pre: state{}, + post: state{}, + config: config, + created: make(map[common.Address]bool), + deleted: make(map[common.Address]bool), + }, nil +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *sentioPrestateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + t.env = env + t.create = create + t.to = to + + t.lookupAccount(from) + t.lookupAccount(to) + t.lookupAccount(env.Context.Coinbase) + + // The recipient balance includes the value transferred. + toBal := new(big.Int).Sub(t.pre[to].Balance, value) + t.pre[to].Balance = toBal + + // The sender balance is after reducing: value and gasLimit. + // We need to re-add them to get the pre-tx balance. + fromBal := new(big.Int).Set(t.pre[from].Balance) + gasPrice := env.TxContext.GasPrice + consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit)) + fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas)) + t.pre[from].Balance = fromBal + t.pre[from].Nonce-- + + if create && t.config.DiffMode { + t.created[to] = true + } +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *sentioPrestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { + if t.config.DiffMode { + return + } + + if t.create { + // Keep existing account prior to contract creation at that address + if s := t.pre[t.to]; s != nil && !s.exists() { + // Exclude newly created contract. + delete(t.pre, t.to) + } + } +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *sentioPrestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + stack := scope.Stack + stackData := stack.Data() + stackLen := len(stackData) + caller := scope.Contract.Address() + switch { + case stackLen >= 2 && op == vm.KECCAK256: + size := stackData[stackLen-2] + if size.Uint64() == 64 { + offset := stackData[stackLen-1] + rawkey := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + + // only cares 64 bytes for mapping key + hashOfKey := crypto.Keccak256(rawkey) + t.pre[caller].MappingKeys[common.Bytes2Hex(rawkey)] = "0x" + common.Bytes2Hex(hashOfKey) + } + case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): + slot := common.Hash(stackData[stackLen-1].Bytes32()) + t.pre[caller].CodeAddress = scope.Contract.CodeAddr + t.lookupStorage(caller, slot) + case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT): + addr := common.Address(stackData[stackLen-1].Bytes20()) + t.lookupAccount(addr) + if op == vm.SELFDESTRUCT { + t.deleted[caller] = true + } + case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): + addr := common.Address(stackData[stackLen-2].Bytes20()) + t.lookupAccount(addr) + case op == vm.CREATE: + nonce := t.env.StateDB.GetNonce(caller) + addr := crypto.CreateAddress(caller, nonce) + t.lookupAccount(addr) + t.created[addr] = true + case stackLen >= 4 && op == vm.CREATE2: + offset := stackData[stackLen-2] + size := stackData[stackLen-3] + init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + inithash := crypto.Keccak256(init) + salt := stackData[stackLen-4] + addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash) + t.lookupAccount(addr) + t.created[addr] = true + } +} + +func (t *sentioPrestateTracer) CaptureTxStart(gasLimit uint64) { + t.gasLimit = gasLimit +} + +func (t *sentioPrestateTracer) CaptureTxEnd(restGas uint64) { + if !t.config.DiffMode { + return + } + + for addr, state := range t.pre { + // The deleted account's state is pruned from `post` but kept in `pre` + if _, ok := t.deleted[addr]; ok { + continue + } + modified := false + postAccount := &account{Storage: make(map[common.Hash]common.Hash)} + newBalance := t.env.StateDB.GetBalance(addr) + newNonce := t.env.StateDB.GetNonce(addr) + newCode := t.env.StateDB.GetCode(addr) + postAccount.CodeAddress = state.CodeAddress + postAccount.MappingKeys = state.MappingKeys + + if newBalance.Cmp(t.pre[addr].Balance) != 0 { + modified = true + postAccount.Balance = newBalance + } + if newNonce != t.pre[addr].Nonce { + modified = true + postAccount.Nonce = newNonce + } + if !bytes.Equal(newCode, t.pre[addr].Code) { + modified = true + postAccount.Code = newCode + } + + for key, val := range state.Storage { + // don't include the empty slot + if val == (common.Hash{}) { + delete(t.pre[addr].Storage, key) + } + + newVal := t.env.StateDB.GetState(addr, key) + if val == newVal { + // Omit unchanged slots + delete(t.pre[addr].Storage, key) + } else { + modified = true + if newVal != (common.Hash{}) { + postAccount.Storage[key] = newVal + } + } + } + + if modified { + t.post[addr] = postAccount + } else { + // if state is not modified, then no need to include into the pre state + delete(t.pre, addr) + } + } + // the new created contracts' prestate were empty, so delete them + for a := range t.created { + // the created contract maybe exists in statedb before the creating tx + if s := t.pre[a]; s != nil && !s.exists() { + delete(t.pre, a) + } + } +} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *sentioPrestateTracer) GetResult() (json.RawMessage, error) { + var res []byte + var err error + if t.config.DiffMode { + res, err = json.Marshal(struct { + Post state `json:"post"` + Pre state `json:"pre"` + }{t.post, t.pre}) + } else { + res, err = json.Marshal(struct { + Pre state `json:"pre"` + }{t.pre}) + } + if err != nil { + return nil, err + } + return json.RawMessage(res), t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *sentioPrestateTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} + +// lookupAccount fetches details of an account and adds it to the prestate +// if it doesn't exist there. +func (t *sentioPrestateTracer) lookupAccount(addr common.Address) { + if _, ok := t.pre[addr]; ok { + return + } + + t.pre[addr] = &account{ + Balance: t.env.StateDB.GetBalance(addr), + Nonce: t.env.StateDB.GetNonce(addr), + Code: t.env.StateDB.GetCode(addr), + Storage: make(map[common.Hash]common.Hash), + MappingKeys: make(map[string]string), + } +} + +// lookupStorage fetches the requested storage slot and adds +// it to the prestate of the given contract. It assumes `lookupAccount` +// has been performed on the contract before. +func (t *sentioPrestateTracer) lookupStorage(addr common.Address, key common.Hash) { + if _, ok := t.pre[addr].Storage[key]; ok { + return + } + t.pre[addr].Storage[key] = t.env.StateDB.GetState(addr, key) +} + +func (*sentioPrestateTracer) CapturePreimage(pc uint64, hash common.Hash, preimage []byte) {} diff --git a/eth/tracers/sentio/tracer.go b/eth/tracers/sentio/tracer.go new file mode 100644 index 0000000000..74b0add790 --- /dev/null +++ b/eth/tracers/sentio/tracer.go @@ -0,0 +1,634 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package sentio + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "sync/atomic" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" +) + +type functionInfo struct { + address string + Name string `json:"name"` + SignatureHash string `json:"signatureHash"` + + Pc uint64 `json:"pc"` + InputSize int `json:"inputSize"` + InputMemory bool `json:"inputMemory"` + OutputSize int `json:"outputSize"` + OutputMemory bool `json:"outputMemory"` +} + +type sentioTracerConfig struct { + Functions map[string][]functionInfo `json:"functions"` + Calls map[string][]uint64 `json:"calls"` + Debug bool `json:"debug"` + WithInternalCalls bool `json:"withInternalCalls"` +} + +func init() { + tracers.DefaultDirectory.Register("sentioTracer", NewSentioTracer, false) +} + +type Trace struct { + // only in debug mode + Name string `json:"name,omitempty"` + + Type string `json:"type"` + Pc uint64 `json:"pc"` + // Global index of the trace + StartIndex int `json:"startIndex"` + EndIndex int `json:"endIndex"` + + // Gas remaining before the OP + Gas math.HexOrDecimal64 `json:"gas"` + // Gas for the entire call + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + + From *common.Address `json:"from,omitempty"` + // Used by call + To *common.Address `json:"to,omitempty"` + // Input + Input string `json:"input,omitempty"` // TODO better struct it and make it bytes + // Ether transfered + Value *hexutil.Big `json:"value,omitempty"` + // Return for calls + Output hexutil.Bytes `json:"output,omitempty"` + Error string `json:"error,omitempty"` + Revertal string `json:"revertReason,omitempty"` + + // Used by jump + InputStack []uint256.Int `json:"inputStack,omitempty"` + InputMemory *[]string `json:"inputMemory,omitempty"` + OutputStack []uint256.Int `json:"outputStack,omitempty"` + OutputMemory *[]string `json:"outputMemory,omitempty"` + FunctionPc uint64 `json:"functionPc,omitempty"` + + // Used by log + Address *common.Address `json:"address,omitempty"` + CodeAddress *common.Address `json:"codeAddress,omitempty"` + Data hexutil.Bytes `json:"data,omitempty"` + + Topics []common.Hash `json:"topics,omitempty"` + + // Only used by root + Traces []Trace `json:"traces,omitempty"` + + // Only set in debug mode + TracerConfig *sentioTracerConfig `json:"tracerConfig,omitempty"` + + // Use for internal call stack organization + // The jump to go into the function + //enterPc uint64 + exitPc uint64 + + // the function get called + function *functionInfo +} + +type sentioTracer struct { + config sentioTracerConfig + env *vm.EVM + activePrecompiles []common.Address // Updated on CaptureStart based on given rules + + functionMap map[string]map[uint64]functionInfo + callMap map[string]map[uint64]bool + + previousJump *Trace + index int + entryPc map[uint64]bool + + callstack []Trace + gasLimit uint64 + + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption +} + +func (t *sentioTracer) CaptureTxStart(gasLimit uint64) { + t.gasLimit = gasLimit +} + +func (t *sentioTracer) CaptureTxEnd(restGas uint64) { + t.callstack[0].EndIndex = t.index + t.callstack[0].GasUsed = math.HexOrDecimal64(t.gasLimit - restGas) + if t.callstack[0].StartIndex == -1 { + // It's possible that we can't correctly locate the PC that match the entry function (check why), in this case we need to 0 for the user + t.callstack[0].StartIndex = 0 + } +} + +func (t *sentioTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + t.env = env + rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) + t.activePrecompiles = vm.ActivePrecompiles(rules) + + root := Trace{ + StartIndex: -1, + Type: vm.CALL.String(), + From: &from, + To: &to, + Gas: math.HexOrDecimal64(gas), + Input: hexutil.Bytes(input).String(), + } + if value != nil { + root.Value = (*hexutil.Big)(value) + } + if create { + root.Type = vm.CREATE.String() + } + + if !create && !t.isPrecompiled(to) && len(input) >= 4 { + m, ok := t.functionMap[to.String()] + if ok { + sigHash := "0x" + common.Bytes2Hex(input[0:4]) + for pc, fn := range m { + if fn.SignatureHash == sigHash { + t.entryPc[pc] = true + } + } + log.Info(fmt.Sprintf("entry pc match %s (%d times) ", sigHash, len(t.entryPc))) + } + } + t.callstack = append(t.callstack, root) +} + +func (t *sentioTracer) CaptureEnd(output []byte, usedGas uint64, err error) { + t.callstack[0].EndIndex = t.index + t.callstack[0].GasUsed = math.HexOrDecimal64(usedGas) + t.callstack[0].Output = common.CopyBytes(output) + + stackSize := len(t.callstack) + t.popStack(1, output, uint64(t.callstack[stackSize-1].Gas)-usedGas, err) + + t.callstack[0].processError(output, err) +} + +func (t *sentioTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + // Skip if tracing was interrupted + if atomic.LoadUint32(&t.interrupt) > 0 { + return + } + size := len(t.callstack) + + t.callstack[size-1].From = &from + t.callstack[size-1].To = &to + t.callstack[size-1].Input = hexutil.Bytes(input).String() + t.callstack[size-1].Gas = math.HexOrDecimal64(gas) + + if value != nil { + t.callstack[size-1].Value = (*hexutil.Big)(value) + } +} + +func (t *sentioTracer) CaptureExit(output []byte, usedGas uint64, err error) { + size := len(t.callstack) + if size <= 1 { + return + } + + //log.Info(fmt.Sprintf("CaptureExit pop frame %s", t.callstack[size-1].Type)) + + stackSize := len(t.callstack) + for i := stackSize - 1; i >= 0; i-- { + if t.callstack[i].function != nil { + continue + } + + if stackSize-i > 1 { + log.Info(fmt.Sprintf("tail call optimization [external] size %d", stackSize-i)) + } + + call := &t.callstack[i] + //call.EndIndex = t.index + //call.GasUsed = math.HexOrDecimal64(usedGas) + call.processError(output, err) + + t.popStack(i, output, uint64(call.Gas)-usedGas, err) + return + } + + log.Error(fmt.Sprintf("failed to pop stack")) +} + +func (t *sentioTracer) popStack(to int, output []byte, currentGas uint64, err error) { // , scope *vm.ScopeContext + stackSize := len(t.callstack) + for j := stackSize - 1; j >= to; j-- { + t.callstack[j].Output = common.CopyBytes(output) + t.callstack[j].EndIndex = t.index + t.callstack[j].GasUsed = math.HexOrDecimal64(uint64(t.callstack[j].Gas) - currentGas) + + // TODO consider pass scopeContext so that popStack also record this + //if t.callstack[j].function != nil { + // t.callstack[j].OutputStack = copyStack(scope.Stack, t.callstack[j].function.OutputSize) + // if t.callstack[j].function.OutputMemory { + // t.callstack[j].OutputMemory = formatMemory(scope.Memory) + // } + //} + //if err != nil { + // t.callstack[j].Error = err.Error() + //} + t.callstack[j-1].Traces = append(t.callstack[j-1].Traces, t.callstack[j]) + } + + t.callstack = t.callstack[:to] +} + +func (t *sentioTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + // Skip if tracing was interrupted + if atomic.LoadUint32(&t.interrupt) > 0 { + return + } + t.index++ + + if t.callstack[0].StartIndex == -1 && t.entryPc[pc] { + //fillback the index and PC for root + t.callstack[0].Pc = pc + t.callstack[0].StartIndex = t.index - 1 + t.previousJump = nil + return + } + + var mergeBase = func(trace Trace) Trace { + trace.Pc = pc + trace.Type = op.String() + trace.Gas = math.HexOrDecimal64(gas) + trace.StartIndex = t.index - 1 + trace.EndIndex = t.index + + // Assume it's single instruction, adjust it for jump and call + trace.GasUsed = math.HexOrDecimal64(cost) + if err != nil { + // set error for instruction + trace.Error = err.Error() + } + return trace + } + + switch op { + case vm.CALL, vm.CALLCODE: + call := mergeBase(Trace{}) + value := scope.Stack.Back(2) + //log.Infof("sentio debug: %s %s", value, t.env) + if !value.IsZero() && !t.env.Context.CanTransfer(t.env.StateDB, scope.Contract.Address(), value.ToBig()) { + // TODO better understand why some call will not be process by captureEnter + // core/vm/evm.go:181 + call.Gas = math.HexOrDecimal64(scope.Stack.Back(0).Uint64()) + from := scope.Contract.Address() + call.From = &from + to := common.BigToAddress(scope.Stack.Back(1).ToBig()) + call.To = &to + call.Value = (*hexutil.Big)(value.ToBig()) + t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, call) + } else { + t.callstack = append(t.callstack, call) + } + case vm.CREATE, vm.CREATE2, vm.DELEGATECALL, vm.STATICCALL, vm.SELFDESTRUCT: + // more info to be add at CaptureEnter + call := mergeBase(Trace{}) + t.callstack = append(t.callstack, call) + case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4: + topicCount := int(op - vm.LOG0) + logOffset := scope.Stack.Back(0) + logSize := scope.Stack.Back(1) + data := copyMemory(scope.Memory, logOffset, logSize) + var topics []common.Hash + //stackLen := scope.Stack.Len() + for i := 0; i < topicCount; i++ { + topics = append(topics, scope.Stack.Back(2+i).Bytes32()) + } + addr := scope.Contract.Address() + l := mergeBase(Trace{ + Address: &addr, + CodeAddress: scope.Contract.CodeAddr, + Data: data, + Topics: topics, + }) + t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, l) + case vm.JUMP: + if !t.config.WithInternalCalls { + break + } + from := scope.Contract.CodeAddr + + jump := mergeBase(Trace{ + From: from, + //InputStack: append([]uint256.Int(nil), scope.Stack.Data...), // TODO only need partial + }) + if t.previousJump != nil { + log.Error("Unexpected previous jump", t.previousJump) + } + if err == nil { + t.previousJump = &jump + } else { + log.Error("error in jump", "err", err) + // error happend, attach to current frame + t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, jump) + } + case vm.JUMPDEST: + if !t.config.WithInternalCalls { + break + } + from := scope.Contract.CodeAddr + fromStr := from.String() + + if t.previousJump != nil { // vm.JumpDest and match with a previous jump (otherwise it's a jumpi) + defer func() { + t.previousJump = nil + }() + // Check if this is return + // TODO pontentially maintain a map for fast filtering + //log.Info("fromStr" + fromStr + ", callstack size" + fmt.Sprint(len(t.callStack))) + stackSize := len(t.callstack) + + // Part 1: try process the trace as function call exit + for i := stackSize - 1; i >= 0; i-- { + // process internal call within the same contract + // no function info means another external call + functionInfo := t.callstack[i].function + if functionInfo == nil { + break + } + + if functionInfo.address != fromStr { + break + } + + // find a match + if t.callstack[i].exitPc == pc { + // find a match, pop the stack, copy memory if needed + + if stackSize-i > 1 { + log.Info(fmt.Sprintf("tail call optimization size %d", stackSize-i)) + } + + // TODO maybe don't need return all + for j := stackSize - 1; j >= i; j-- { + call := &t.callstack[j] + functionJ := call.function + call.EndIndex = t.index - 1 // EndIndex should before the jumpdest + call.GasUsed = math.HexOrDecimal64(uint64(t.callstack[j].Gas) - gas) + if functionJ.OutputSize > len(scope.Stack.Data()) { + log.Error(fmt.Sprintf("stack size not enough (%d vs %d) for function %s %s. pc: %d", + len(scope.Stack.Data()), functionJ.OutputSize, functionJ.address, functionJ.Name, pc)) + if err == nil { + log.Error("stack size not enough has error", "err", err) + } + } else { + call.OutputStack = copyStack(scope.Stack, t.callstack[j].function.OutputSize) + } + if call.function.OutputMemory { + call.OutputMemory = formatMemory(scope.Memory) + } + //if err != nil { + // call.Error = err.Error() + //} + t.callstack[j-1].Traces = append(t.callstack[j-1].Traces, *call) + } + t.callstack = t.callstack[:i] + return + } + } + + // Part 2: try process the trace as function call entry + funcInfo := t.getFunctionInfo(fromStr, pc) + //log.Info("function info" + fmt.Sprint(funcInfo)) + + if funcInfo != nil { + // filter those jump are not call site + if !t.isCall(t.previousJump.From.String(), t.previousJump.Pc) { + return + } + + if funcInfo.InputSize >= len(scope.Stack.Data()) { + // TODO this check should not needed after frist check + log.Error("Unexpected stack size for function:" + fmt.Sprint(funcInfo) + ", stack" + fmt.Sprint(scope.Stack.Data)) + log.Error("previous jump" + fmt.Sprint(*t.previousJump)) + return + } + + // confirmed that we are in an internal call + //t.internalCallStack = append(t.internalCallStack, internalCallStack{ + // enterPc: t.previousJump.Pc, + // exitPc: scope.Stack.Back(funcInfo.InputSize).Uint64(), + // function: funcInfo, + //}) + //jump.enterPc = t.previousJump.Pc + t.previousJump.exitPc = scope.Stack.Back(funcInfo.InputSize).Uint64() + t.previousJump.function = funcInfo + t.previousJump.FunctionPc = pc + t.previousJump.InputStack = copyStack(scope.Stack, funcInfo.InputSize) + if t.config.Debug { + t.previousJump.Name = funcInfo.Name + } + if funcInfo.InputMemory { + t.previousJump.InputMemory = formatMemory(scope.Memory) + } + t.callstack = append(t.callstack, *t.previousJump) + //t.callstack = append(t.callstack, callStack{ + } + } + case vm.REVERT: + if !t.config.WithInternalCalls { + break + } + logOffset := scope.Stack.Back(0) + logSize := scope.Stack.Back(1) + output := scope.Memory.GetPtr(int64(logOffset.Uint64()), int64(logSize.Uint64())) + //data := copyMemory(logOffset, logSize) + + trace := mergeBase(Trace{ + Error: "execution reverted", + }) + if unpacked, err := abi.UnpackRevert(output); err == nil { + trace.Revertal = unpacked + } + t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, trace) + default: + if !t.config.WithInternalCalls { + break + } + if err != nil { + // Error happen, attach the error OP if not already processed + t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, mergeBase(Trace{})) + } + } +} +func (t *sentioTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +} + +// CapturePreimage records a SHA3 preimage discovered during execution. +func (t *sentioTracer) CapturePreimage(pc uint64, hash common.Hash, preimage []byte) {} + +func (t *sentioTracer) GetResult() (json.RawMessage, error) { + if t.config.Debug { + t.callstack[0].TracerConfig = &t.config + } + + if len(t.callstack) != 1 { + log.Error("callstack length is not 1, is " + fmt.Sprint(len(t.callstack))) + } + + res, err := json.Marshal(t.callstack[0]) + if err != nil { + return nil, err + } + return res, t.reason +} + +func (t *sentioTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} + +func NewSentioTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + //if name != "sentioTracer" { + // return nil, errors.New("no tracer found") + //} + + var config sentioTracerConfig + functionMap := map[string]map[uint64]functionInfo{} + callMap := map[string]map[uint64]bool{} + + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + + for address, functions := range config.Functions { + checkSumAddress := common.HexToAddress(address).String() + functionMap[checkSumAddress] = make(map[uint64]functionInfo) + + for _, function := range functions { + function.address = checkSumAddress + functionMap[checkSumAddress][function.Pc] = function + } + } + + for address, calls := range config.Calls { + checkSumAddress := common.HexToAddress(address).String() + callMap[checkSumAddress] = make(map[uint64]bool) + + for _, call := range calls { + callMap[checkSumAddress][call] = true + } + } + + log.Info(fmt.Sprintf("create sentioTracer config with %d functions, %d calls", len(functionMap), len(callMap))) + } + + return &sentioTracer{ + config: config, + functionMap: functionMap, + callMap: callMap, + entryPc: map[uint64]bool{}, + }, nil +} + +func (t *sentioTracer) isPrecompiled(addr common.Address) bool { + for _, p := range t.activePrecompiles { + if p == addr { + return true + } + } + return false +} + +func (t *sentioTracer) getFunctionInfo(address string, pc uint64) *functionInfo { + m, ok := t.functionMap[address] + if !ok || m == nil { + return nil + } + info, ok := m[pc] + if ok { + return &info + } + + return nil +} + +func (t *sentioTracer) isCall(address string, pc uint64) bool { + m, ok := t.callMap[address] + if !ok || m == nil { + return false + } + info, ok := m[pc] + if ok { + return info + } + return false +} + +// Only used in non detail mode +func (f *Trace) processError(output []byte, err error) { + //output = common.CopyBytes(output) + if err == nil { + //f.Output = output + return + } + f.Error = err.Error() + if f.Type == vm.CREATE.String() || f.Type == vm.CREATE2.String() { + f.To = &common.Address{} + } + if !errors.Is(err, vm.ErrExecutionReverted) || len(output) == 0 { + return + } + //f.Output = output + if len(output) < 4 { + return + } + if unpacked, err := abi.UnpackRevert(output); err == nil { + f.Revertal = unpacked + } +} + +func copyMemory(m *vm.Memory, offset *uint256.Int, size *uint256.Int) hexutil.Bytes { + // it's important to get copy + return m.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) +} + +func formatMemory(m *vm.Memory) *[]string { + res := make([]string, 0, (m.Len()+31)/32) + for i := 0; i+32 <= m.Len(); i += 32 { + res = append(res, fmt.Sprintf("%x", m.GetPtr(int64(i), 32))) + } + return &res +} + +func copyStack(s *vm.Stack, copySize int) []uint256.Int { + if copySize == 0 { + return nil + } + stackSize := len(s.Data()) + res := make([]uint256.Int, stackSize) + for i := stackSize - copySize; i < stackSize; i++ { + res[i] = s.Data()[i] + } + return res +} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 52e7bf125a..7a0c5022ea 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -56,6 +56,7 @@ import ( // inside of cmd/geth. _ "github.com/ava-labs/coreth/eth/tracers/js" _ "github.com/ava-labs/coreth/eth/tracers/native" + _ "github.com/ava-labs/coreth/eth/tracers/sentio" "github.com/ava-labs/coreth/precompile/precompileconfig" // Force-load precompiles to trigger registration From 42e9ecd58cfa736d72f363912a395d214b880dde Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Tue, 17 Oct 2023 20:12:16 -0700 Subject: [PATCH 02/18] support trace call many in geth --- eth/tracers/api.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index d835f7c945..125a38c851 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1097,3 +1097,99 @@ func overrideConfig(original *params.ChainConfig, override *params.ChainConfig) return copy, canon } + +type Bundle struct { + Transactions []*ethapi.TransactionArgs `json:"transactions"` + BlockOverride ethapi.BlockOverrides `json:"blockOverride"` +} + +type StateContext struct { + BlockNumber *rpc.BlockNumberOrHash `json:"blockNumber"` + TransactionIndex int `json:"transactionIndex"` +} + +func (api *API) TraceCallMany(ctx context.Context, bundles []*Bundle, simulateContext *StateContext, config *TraceCallConfig) (interface{}, error) { + if len(bundles) == 0 { + return nil, errors.New("empty bundles") + } + var result []interface{} + for _, bundle := range bundles { + r, err := api.traceBundle(ctx, bundle, simulateContext, config) + if err != nil { + return nil, err + } + result = append(result, r) + } + return result, nil +} + +func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext *StateContext, config *TraceCallConfig) (interface{}, error) { + var result []interface{} + // Try to retrieve the specified block + var ( + err error + block *types.Block + ) + if hash, ok := simulateContext.BlockNumber.Hash(); ok { + block, err = api.blockByHash(ctx, hash) + } else if number, ok := simulateContext.BlockNumber.Number(); ok { + if number == rpc.PendingBlockNumber { + // We don't have access to the miner here. For tracing 'future' transactions, + // it can be done with block- and state-overrides instead, which offers + // more flexibility and stability than trying to trace on 'pending', since + // the contents of 'pending' is unstable and probably not a true representation + // of what the next actual block is likely to contain. + return nil, errors.New("tracing on top of pending is not supported") + } + block, err = api.blockByNumber(ctx, number) + } else { + return nil, errors.New("invalid arguments; neither block nor hash specified") + } + if err != nil { + return nil, err + } + // try to recompute the state + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + + if err != nil { + return nil, err + } + _, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, simulateContext.TransactionIndex, reexec) + if err != nil { + return nil, err + } + defer release() + + // Apply the customization rules if required. + if config != nil { + if err := config.StateOverrides.Apply(statedb); err != nil { + return nil, err + } + config.BlockOverrides.Apply(&vmctx) + } + // Execute the trace + for idx, args := range bundle.Transactions { + if idx > 0 { + // TODO currently only support one tx in bundle + break + } + msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee()) + if err != nil { + return nil, err + } + + var traceConfig *TraceConfig + if config != nil { + traceConfig = &config.TraceConfig + } + r, err := api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) + if err != nil { + return nil, err + } + result = append(result, r) + } + return result, nil +} From 316269b61771759843bb4fc9d8c313db4fcd343a Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Tue, 7 Nov 2023 22:01:58 +0800 Subject: [PATCH 03/18] migrate override and gas changes (#1) --- core/state_transition.go | 16 +++++++++------- core/vm/evm.go | 26 +++++++++++++++++++++----- core/vm/instructions.go | 6 ++++-- core/vm/interpreter.go | 22 ++++++++++++++++++++++ eth/tracers/api.go | 29 +++++++++++++++++++++++++++-- 5 files changed, 83 insertions(+), 16 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 1599ba6ae4..b0cc334914 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -401,14 +401,16 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ) // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules) - if err != nil { - return nil, err - } - if st.gasRemaining < gas { - return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas) + if !st.evm.Config.IgnoreGas { + gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules) + if err != nil { + return nil, err + } + if st.gasRemaining < gas { + return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas) + } + st.gasRemaining -= gas } - st.gasRemaining -= gas // Check clause 6 if msg.Value.Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From, msg.Value) { diff --git a/core/vm/evm.go b/core/vm/evm.go index 4dbe316d9d..5967d012f2 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -590,6 +590,16 @@ func (c *codeAndHash) Hash() common.Hash { func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) { // Depth check execution. Fail if we're trying to execute above the // limit. + if evm.Config.CreateAddressOverride != nil { + address = *evm.Config.CreateAddressOverride + } + if evm.Config.CreationCodeOverrides != nil { + if code, ok := evm.Config.CreationCodeOverrides[address]; ok { + codeAndHash.code = code + codeAndHash.hash = common.Hash{} + _ = codeAndHash.Hash() + } + } if evm.depth > int(params.CallCreateDepth) { return nil, common.Address{}, gas, vmerrs.ErrDepth } @@ -614,10 +624,13 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.chainRules.IsApricotPhase2 { evm.StateDB.AddAddressToAccessList(address) } - // Ensure there's no existing contract already at the designated address - contractHash := evm.StateDB.GetCodeHash(address) - if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { - return nil, common.Address{}, 0, vmerrs.ErrContractAddressCollision + + if evm.Config.CreateAddressOverride == nil { + // Ensure there's no existing contract already at the designated address + contractHash := evm.StateDB.GetCodeHash(address) + if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { + return nil, common.Address{}, 0, vmerrs.ErrContractAddressCollision + } } // Create a new account on the state snapshot := evm.StateDB.Snapshot() @@ -643,7 +656,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ret, err := evm.interpreter.Run(contract, nil, false) // Check whether the max code size has been exceeded, assign err if the case. - if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { + if err == nil && !evm.Config.IgnoreCodeSizeLimit && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { err = vmerrs.ErrMaxCodeSizeExceeded } @@ -658,6 +671,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // by the error checking condition below. if err == nil { createDataGas := uint64(len(ret)) * params.CreateDataGas + if evm.Config.IgnoreGas { + createDataGas = 0 + } if contract.UseGas(createDataGas) { evm.StateDB.SetCode(address, ret) } else { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index fb549dc32f..fd055aa491 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -600,7 +600,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) gas = scope.Contract.Gas ) - if interpreter.evm.chainRules.IsEIP150 { + if !interpreter.evm.Config.IgnoreGas && interpreter.evm.chainRules.IsEIP150 { gas -= gas / 64 } // reuse size int for stackvalue @@ -648,7 +648,9 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] gas = scope.Contract.Gas ) // Apply EIP150 - gas -= gas / 64 + if !interpreter.evm.Config.IgnoreGas { + gas -= gas / 64 + } scope.Contract.UseGas(gas) // reuse size int for stackvalue stackvalue := size diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 9f04c32a93..c7b0d659f0 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -29,6 +29,7 @@ package vm import ( "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -45,6 +46,11 @@ type Config struct { NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages ExtraEips []int // Additional EIPS that are to be enabled + + CreationCodeOverrides map[common.Address]hexutil.Bytes + CreateAddressOverride *common.Address + IgnoreGas bool + IgnoreCodeSizeLimit bool } // ScopeContext contains the things that are per-call, such as stack and memory, @@ -109,6 +115,22 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { } } evm.Config.ExtraEips = extraEips + if evm.Config.IgnoreGas { + table = copyJumpTable(table) + for i, op := range table { + opCode := OpCode(i) + // retain call costs to prevent call stack from going too deep + // some contracts use a loop to burn gas + // if all codes in the loop have zero cost, it will run forever + if opCode == CALL || opCode == STATICCALL || opCode == CALLCODE || opCode == DELEGATECALL || opCode == GAS { + continue + } + op.constantGas = 0 + op.dynamicGas = func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) { + return 0, nil + } + } + } return &EVMInterpreter{evm: evm, table: table} } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 125a38c851..eba5dffe2e 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -185,13 +185,18 @@ type TraceConfig struct { // Config specific to given tracer. Note struct logger // config are historically embedded in main object. TracerConfig json.RawMessage + + StateOverrides *ethapi.StateOverride + IgnoreGas *bool + IgnoreCodeSizeLimit *bool + CreationCodeOverrides map[common.Address]hexutil.Bytes + CreateAddressOverride *common.Address } // TraceCallConfig is the config for traceCall API. It holds one more // field to override the state for tracing. type TraceCallConfig struct { TraceConfig - StateOverrides *ethapi.StateOverride BlockOverrides *ethapi.BlockOverrides } @@ -906,6 +911,12 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * TxIndex: int(index), TxHash: hash, } + + if config != nil { + if err := config.StateOverrides.Apply(statedb); err != nil { + return nil, err + } + } return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) } @@ -997,7 +1008,21 @@ func (api *baseAPI) traceTx(ctx context.Context, message *core.Message, txctx *C return nil, err } } - vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true}) + vmConfig := vm.Config{ + Tracer: tracer, + NoBaseFee: true, + } + if config != nil { + vmConfig.CreateAddressOverride = config.CreateAddressOverride + vmConfig.CreationCodeOverrides = config.CreationCodeOverrides + if config.IgnoreCodeSizeLimit != nil { + vmConfig.IgnoreCodeSizeLimit = *config.IgnoreCodeSizeLimit + } + if config.IgnoreGas != nil { + vmConfig.IgnoreGas = *config.IgnoreGas + } + } + vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vmConfig) // Define a meaningful timeout of a single transaction trace if config.Timeout != nil { From d025e679f08e4b8d888d6ed5dd8a060cd7597c72 Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:35:20 +0800 Subject: [PATCH 04/18] migrate memory compression (#2) --- eth/tracers/logger/logger.go | 51 +++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 2173db790f..f7c22b820d 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -17,6 +17,7 @@ package logger import ( + "bytes" "encoding/hex" "encoding/json" "fmt" @@ -49,12 +50,13 @@ func (s Storage) Copy() Storage { // Config are the configuration options for structured logger the EVM type Config struct { - EnableMemory bool // enable memory capture - DisableStack bool // disable stack capture - DisableStorage bool // disable storage capture - EnableReturnData bool // enable return data capture - Debug bool // print output during capture end - Limit int // maximum length of output, but zero means unlimited + EnableMemory bool // enable memory capture + DisableStack bool // disable stack capture + DisableStorage bool // disable storage capture + EnableReturnData bool // enable return data capture + Debug bool // print output during capture end + Limit int // maximum length of output, but zero means unlimited + MemoryCompressionWindow int // Chain overrides, can be used to execute a trace using future fork rules Overrides *params.ChainConfig `json:"overrides,omitempty"` } @@ -69,6 +71,7 @@ type StructLog struct { Gas uint64 `json:"gas"` GasCost uint64 `json:"gasCost"` Memory []byte `json:"memory,omitempty"` + Meq *int `json:"meq,omitempty"` MemorySize int `json:"memSize"` Stack []uint256.Int `json:"stack"` ReturnData []byte `json:"returnData,omitempty"` @@ -117,6 +120,10 @@ type StructLogger struct { gasLimit uint64 usedGas uint64 + prevMem [][]byte + prevMemWindow int + prevMemIdx int + interrupt atomic.Bool // Atomic flag to signal execution interruption reason error // Textual reason for the interruption } @@ -128,6 +135,9 @@ func NewStructLogger(cfg *Config) *StructLogger { } if cfg != nil { logger.cfg = *cfg + logger.prevMemWindow = cfg.MemoryCompressionWindow + logger.prevMemIdx = 0 + logger.prevMem = make([][]byte, cfg.MemoryCompressionWindow) } return logger } @@ -162,9 +172,36 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s contract := scope.Contract // Copy a snapshot of the current memory state to a new buffer var mem []byte + var meq *int if l.cfg.EnableMemory { mem = make([]byte, len(memory.Data())) copy(mem, memory.Data()) + + foundEq := false + if l.prevMemWindow > 0 { + i := l.prevMemIdx + for dist := 1; dist <= l.prevMemWindow; dist++ { + if i--; i < 0 { + i = l.prevMemWindow - 1 + } + if len(l.prevMem[i]) == len(mem) && bytes.Equal(l.prevMem[i], mem) { + foundEq = true + meq = new(int) + *meq = dist + mem = nil + break + } + } + if l.prevMemIdx++; l.prevMemIdx == l.prevMemWindow { + l.prevMemIdx = 0 + } + if foundEq { + l.prevMem[l.prevMemIdx] = l.prevMem[i] + } else { + l.prevMem[l.prevMemIdx] = make([]byte, len(mem)) + copy(l.prevMem[l.prevMemIdx], mem) + } + } } // Copy a snapshot of the current stack state to a new buffer var stck []uint256.Int @@ -208,7 +245,7 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s copy(rdata, rData) } // create a new snapshot of the EVM. - log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err} + log := StructLog{pc, op, gas, cost, mem, meq, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err} l.logs = append(l.logs, log) } From f1e7b8e3afa88c754a2b93c1799c159d1eef8ef0 Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:02:00 +0800 Subject: [PATCH 05/18] add more information for root trace (#3) --- eth/tracers/sentio/tracer.go | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/eth/tracers/sentio/tracer.go b/eth/tracers/sentio/tracer.go index 74b0add790..0e2e9b2609 100644 --- a/eth/tracers/sentio/tracer.go +++ b/eth/tracers/sentio/tracer.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" + corestate "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/log" @@ -100,9 +101,6 @@ type Trace struct { // Only used by root Traces []Trace `json:"traces,omitempty"` - // Only set in debug mode - TracerConfig *sentioTracerConfig `json:"tracerConfig,omitempty"` - // Use for internal call stack organization // The jump to go into the function //enterPc uint64 @@ -112,6 +110,14 @@ type Trace struct { function *functionInfo } +type Receipt struct { + Nonce uint64 `json:"nonce"` + TxHash *common.Hash `json:"transactionHash,omitempty"` + BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` + BlockHash *common.Hash `json:"blockHash,omitempty"` + TransactionIndex uint `json:"transactionIndex"` +} + type sentioTracer struct { config sentioTracerConfig env *vm.EVM @@ -119,6 +125,7 @@ type sentioTracer struct { functionMap map[string]map[uint64]functionInfo callMap map[string]map[uint64]bool + receipt Receipt previousJump *Trace index int @@ -146,6 +153,15 @@ func (t *sentioTracer) CaptureTxEnd(restGas uint64) { func (t *sentioTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { t.env = env + t.receipt.BlockNumber = (*hexutil.Big)(env.Context.BlockNumber) + // TODO this current will block the tracer + + // TODO bockHash & txHash + t.receipt.Nonce = env.StateDB.GetNonce(from) - 1 + if ibs, ok := env.StateDB.(*corestate.StateDB); ok { + t.receipt.TransactionIndex = uint(ibs.TxIndex()) + } + rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) t.activePrecompiles = vm.ActivePrecompiles(rules) @@ -487,15 +503,24 @@ func (t *sentioTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, s func (t *sentioTracer) CapturePreimage(pc uint64, hash common.Hash, preimage []byte) {} func (t *sentioTracer) GetResult() (json.RawMessage, error) { + type RootTrace struct { + Trace + TracerConfig *sentioTracerConfig `json:"tracerConfig,omitempty"` + Receipt Receipt `json:"receipt"` + } + root := RootTrace{ + Trace: t.callstack[0], + Receipt: t.receipt, + } if t.config.Debug { - t.callstack[0].TracerConfig = &t.config + root.TracerConfig = &t.config } if len(t.callstack) != 1 { log.Error("callstack length is not 1, is " + fmt.Sprint(len(t.callstack))) } - res, err := json.Marshal(t.callstack[0]) + res, err := json.Marshal(root) if err != nil { return nil, err } From d4fb86efee1693d627e1f8b11e811f018f18530d Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:35:35 +0800 Subject: [PATCH 06/18] correct handle call with value (#4) * correct handle call with value * set transfer value to zero if can't transfer --- eth/tracers/sentio/tracer.go | 39 +++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/eth/tracers/sentio/tracer.go b/eth/tracers/sentio/tracer.go index 0e2e9b2609..acfcfabe78 100644 --- a/eth/tracers/sentio/tracer.go +++ b/eth/tracers/sentio/tracer.go @@ -211,6 +211,16 @@ func (t *sentioTracer) CaptureEnter(typ vm.OpCode, from common.Address, to commo if atomic.LoadUint32(&t.interrupt) > 0 { return } + + if typ == vm.CALL || typ == vm.CALLCODE { + // After enter, make the assumped transfer as function call + topElementTraces := t.callstack[len(t.callstack)-1].Traces + call := topElementTraces[len(topElementTraces)-1] + topElementTraces = topElementTraces[:len(topElementTraces)-1] + t.callstack[len(t.callstack)-1].Traces = topElementTraces + t.callstack = append(t.callstack, call) + } + size := len(t.callstack) t.callstack[size-1].From = &from @@ -310,21 +320,22 @@ func (t *sentioTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s switch op { case vm.CALL, vm.CALLCODE: call := mergeBase(Trace{}) - value := scope.Stack.Back(2) - //log.Infof("sentio debug: %s %s", value, t.env) - if !value.IsZero() && !t.env.Context.CanTransfer(t.env.StateDB, scope.Contract.Address(), value.ToBig()) { - // TODO better understand why some call will not be process by captureEnter - // core/vm/evm.go:181 - call.Gas = math.HexOrDecimal64(scope.Stack.Back(0).Uint64()) - from := scope.Contract.Address() - call.From = &from - to := common.BigToAddress(scope.Stack.Back(1).ToBig()) - call.To = &to - call.Value = (*hexutil.Big)(value.ToBig()) - t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, call) - } else { - t.callstack = append(t.callstack, call) + call.Gas = math.HexOrDecimal64(scope.Stack.Back(0).Uint64()) + from := scope.Contract.Address() + call.From = &from + to := common.BigToAddress(scope.Stack.Back(1).ToBig()) + call.To = &to + call.Value = (*hexutil.Big)(scope.Stack.Back(2).ToBig()) + + v := call.Value.ToInt() + if v.BitLen() != 0 && !t.env.Context.CanTransfer(t.env.StateDB, from, v) { + if call.Error == "" { + call.Error = "insufficient funds for transfer" + } } + + // Treat this call as pure transfer until it enters the CaptureEnter + t.callstack[len(t.callstack)-1].Traces = append(t.callstack[len(t.callstack)-1].Traces, call) case vm.CREATE, vm.CREATE2, vm.DELEGATECALL, vm.STATICCALL, vm.SELFDESTRUCT: // more info to be add at CaptureEnter call := mergeBase(Trace{}) From 51a0b9c59ba4ca932c062e107458917b0eb941ea Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Mon, 18 Dec 2023 12:04:44 +0800 Subject: [PATCH 07/18] Add Mapping keys to post account (#5) --- eth/tracers/sentio/prestate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/tracers/sentio/prestate.go b/eth/tracers/sentio/prestate.go index c68e856e59..18a74f7d1d 100644 --- a/eth/tracers/sentio/prestate.go +++ b/eth/tracers/sentio/prestate.go @@ -211,7 +211,7 @@ func (t *sentioPrestateTracer) CaptureTxEnd(restGas uint64) { newNonce := t.env.StateDB.GetNonce(addr) newCode := t.env.StateDB.GetCode(addr) postAccount.CodeAddress = state.CodeAddress - postAccount.MappingKeys = state.MappingKeys + postAccount.MappingKeys = t.pre[addr].MappingKeys if newBalance.Cmp(t.pre[addr].Balance) != 0 { modified = true From 36a054f2c8d047f93ce2fcf76e9e8344f5f7226e Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:38:39 +0800 Subject: [PATCH 08/18] fix when tracer failed before start (#6) --- eth/tracers/sentio/tracer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eth/tracers/sentio/tracer.go b/eth/tracers/sentio/tracer.go index acfcfabe78..5408c0f4d5 100644 --- a/eth/tracers/sentio/tracer.go +++ b/eth/tracers/sentio/tracer.go @@ -143,6 +143,9 @@ func (t *sentioTracer) CaptureTxStart(gasLimit uint64) { } func (t *sentioTracer) CaptureTxEnd(restGas uint64) { + if len(t.callstack) == 0 { + return + } t.callstack[0].EndIndex = t.index t.callstack[0].GasUsed = math.HexOrDecimal64(t.gasLimit - restGas) if t.callstack[0].StartIndex == -1 { From 7dae089456415338b4e0dc720edcc839843d2c1b Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Wed, 27 Dec 2023 22:08:17 +0800 Subject: [PATCH 09/18] unlimited gas for debug_traceCallMany (#7) --- eth/tracers/api.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index eba5dffe2e..f6aa1d6139 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1201,6 +1201,10 @@ func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext // TODO currently only support one tx in bundle break } + if args.Gas == nil { + gasCap := api.backend.RPCGasCap() + args.Gas = (*hexutil.Uint64)(&gasCap) + } msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee()) if err != nil { return nil, err From b3947cec9b13b7fd03b4322769d8fbe36db58c02 Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Fri, 29 Dec 2023 13:50:24 +0800 Subject: [PATCH 10/18] support multiple txs in tracecallmany --- eth/tracers/api.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index f6aa1d6139..105babeff4 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1178,6 +1178,7 @@ func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext if config != nil && config.Reexec != nil { reexec = *config.Reexec } + is158 := api.backend.ChainConfig().IsEIP158(block.Number()) if err != nil { return nil, err @@ -1197,10 +1198,6 @@ func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext } // Execute the trace for idx, args := range bundle.Transactions { - if idx > 0 { - // TODO currently only support one tx in bundle - break - } if args.Gas == nil { gasCap := api.backend.RPCGasCap() args.Gas = (*hexutil.Uint64)(&gasCap) @@ -1214,11 +1211,17 @@ func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext if config != nil { traceConfig = &config.TraceConfig } - r, err := api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) + txctx := &Context{ + BlockHash: block.Hash(), + BlockNumber: block.Number(), + TxIndex: simulateContext.TransactionIndex + idx, + } + r, err := api.traceTx(ctx, msg, txctx, vmctx, statedb, traceConfig) if err != nil { return nil, err } result = append(result, r) + statedb.Finalise(is158) } return result, nil } From 3a32dda59190e9510f9fa64a83511434299a7ecb Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Fri, 29 Dec 2023 15:17:24 +0800 Subject: [PATCH 11/18] rpc client should keep result while return error --- rpc/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rpc/client.go b/rpc/client.go index 989441a6f8..602b97ada8 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -355,6 +355,7 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str case err != nil: return err case resp.Error != nil: + _ = json.Unmarshal(resp.Result, result) return resp.Error case len(resp.Result) == 0: return ErrNoResult From 2c50dcf108004aa894768ec0ffe553a1c0580dc5 Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Sun, 31 Dec 2023 14:07:35 +0800 Subject: [PATCH 12/18] be able to return partial results --- eth/tracers/api.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 105babeff4..43ca104272 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1133,6 +1133,10 @@ type StateContext struct { TransactionIndex int `json:"transactionIndex"` } +type FailedTrace struct { + Failed string `json:"failed,omitempty"` +} + func (api *API) TraceCallMany(ctx context.Context, bundles []*Bundle, simulateContext *StateContext, config *TraceCallConfig) (interface{}, error) { if len(bundles) == 0 { return nil, errors.New("empty bundles") @@ -1141,6 +1145,12 @@ func (api *API) TraceCallMany(ctx context.Context, bundles []*Bundle, simulateCo for _, bundle := range bundles { r, err := api.traceBundle(ctx, bundle, simulateContext, config) if err != nil { + if r != nil { + // return partial results + r = append(r, &FailedTrace{Failed: err.Error()}) + result = append(result, r) + return result, nil + } return nil, err } result = append(result, r) @@ -1148,7 +1158,7 @@ func (api *API) TraceCallMany(ctx context.Context, bundles []*Bundle, simulateCo return result, nil } -func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext *StateContext, config *TraceCallConfig) (interface{}, error) { +func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext *StateContext, config *TraceCallConfig) ([]interface{}, error) { var result []interface{} // Try to retrieve the specified block var ( @@ -1204,7 +1214,7 @@ func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext } msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee()) if err != nil { - return nil, err + return result, err } var traceConfig *TraceConfig @@ -1218,7 +1228,7 @@ func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext } r, err := api.traceTx(ctx, msg, txctx, vmctx, statedb, traceConfig) if err != nil { - return nil, err + return result, err } result = append(result, r) statedb.Finalise(is158) From 063ced2c586c6cbb422d9b8731b6b4c4013bedda Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Thu, 18 Jan 2024 10:50:57 +0800 Subject: [PATCH 13/18] migrate tracer changes (#8) --- eth/tracers/sentio/gen_account_json.go | 30 +++++++++++++++---------- eth/tracers/sentio/prestate.go | 31 ++++++++++++++++---------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/eth/tracers/sentio/gen_account_json.go b/eth/tracers/sentio/gen_account_json.go index 964b44ad27..e5498234a6 100644 --- a/eth/tracers/sentio/gen_account_json.go +++ b/eth/tracers/sentio/gen_account_json.go @@ -15,12 +15,13 @@ var _ = (*accountMarshaling)(nil) // MarshalJSON marshals as JSON. func (a account) MarshalJSON() ([]byte, error) { type account struct { - Balance *hexutil.Big `json:"balance,omitempty"` - Code hexutil.Bytes `json:"code,omitempty"` - Nonce uint64 `json:"nonce,omitempty"` - Storage map[common.Hash]common.Hash `json:"storage,omitempty"` - CodeAddress *common.Address `json:"codeAddress,omitempty"` - MappingKeys map[string]string `json:"mappingKeys,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + Code hexutil.Bytes `json:"code,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + CodeAddress *common.Address `json:"codeAddress,omitempty"` + CodeAddressBySlot map[common.Hash]*common.Address `json:"codeAddressBySlot,omitempty"` + MappingKeys map[string]string `json:"mappingKeys,omitempty"` } var enc account enc.Balance = (*hexutil.Big)(a.Balance) @@ -28,6 +29,7 @@ func (a account) MarshalJSON() ([]byte, error) { enc.Nonce = a.Nonce enc.Storage = a.Storage enc.CodeAddress = a.CodeAddress + enc.CodeAddressBySlot = a.CodeAddressBySlot enc.MappingKeys = a.MappingKeys return json.Marshal(&enc) } @@ -35,12 +37,13 @@ func (a account) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (a *account) UnmarshalJSON(input []byte) error { type account struct { - Balance *hexutil.Big `json:"balance,omitempty"` - Code *hexutil.Bytes `json:"code,omitempty"` - Nonce *uint64 `json:"nonce,omitempty"` - Storage map[common.Hash]common.Hash `json:"storage,omitempty"` - CodeAddress *common.Address `json:"codeAddress,omitempty"` - MappingKeys map[string]string `json:"mappingKeys,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + Nonce *uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + CodeAddress *common.Address `json:"codeAddress,omitempty"` + CodeAddressBySlot map[common.Hash]*common.Address `json:"codeAddressBySlot,omitempty"` + MappingKeys map[string]string `json:"mappingKeys,omitempty"` } var dec account if err := json.Unmarshal(input, &dec); err != nil { @@ -61,6 +64,9 @@ func (a *account) UnmarshalJSON(input []byte) error { if dec.CodeAddress != nil { a.CodeAddress = dec.CodeAddress } + if dec.CodeAddressBySlot != nil { + a.CodeAddressBySlot = dec.CodeAddressBySlot + } if dec.MappingKeys != nil { a.MappingKeys = dec.MappingKeys } diff --git a/eth/tracers/sentio/prestate.go b/eth/tracers/sentio/prestate.go index 18a74f7d1d..b36a3d55cd 100644 --- a/eth/tracers/sentio/prestate.go +++ b/eth/tracers/sentio/prestate.go @@ -38,12 +38,13 @@ func init() { type state = map[common.Address]*account type account struct { - Balance *big.Int `json:"balance,omitempty"` - Code []byte `json:"code,omitempty"` - Nonce uint64 `json:"nonce,omitempty"` - Storage map[common.Hash]common.Hash `json:"storage,omitempty"` - CodeAddress *common.Address `json:"codeAddress,omitempty"` - MappingKeys map[string]string `json:"mappingKeys,omitempty"` + Balance *big.Int `json:"balance,omitempty"` + Code []byte `json:"code,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + CodeAddress *common.Address `json:"codeAddress,omitempty"` + CodeAddressBySlot map[common.Hash]*common.Address `json:"codeAddressBySlot,omitempty"` + MappingKeys map[string]string `json:"mappingKeys,omitempty"` } func (a *account) exists() bool { @@ -155,15 +156,20 @@ func (t *sentioPrestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost u size := stackData[stackLen-2] if size.Uint64() == 64 { offset := stackData[stackLen-1] - rawkey := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + rawkey := scope.Memory.GetCopy(int64(offset.Uint64()), 64) // only cares 64 bytes for mapping key hashOfKey := crypto.Keccak256(rawkey) t.pre[caller].MappingKeys[common.Bytes2Hex(rawkey)] = "0x" + common.Bytes2Hex(hashOfKey) + + baseSlot := rawkey[32:] + t.pre[caller].CodeAddressBySlot[common.BytesToHash(baseSlot)] = scope.Contract.CodeAddr + t.pre[caller].CodeAddressBySlot[common.BytesToHash(hashOfKey)] = scope.Contract.CodeAddr } case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): slot := common.Hash(stackData[stackLen-1].Bytes32()) t.pre[caller].CodeAddress = scope.Contract.CodeAddr + t.pre[caller].CodeAddressBySlot[slot] = scope.Contract.CodeAddr t.lookupStorage(caller, slot) case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT): addr := common.Address(stackData[stackLen-1].Bytes20()) @@ -295,11 +301,12 @@ func (t *sentioPrestateTracer) lookupAccount(addr common.Address) { } t.pre[addr] = &account{ - Balance: t.env.StateDB.GetBalance(addr), - Nonce: t.env.StateDB.GetNonce(addr), - Code: t.env.StateDB.GetCode(addr), - Storage: make(map[common.Hash]common.Hash), - MappingKeys: make(map[string]string), + Balance: t.env.StateDB.GetBalance(addr), + Nonce: t.env.StateDB.GetNonce(addr), + Code: t.env.StateDB.GetCode(addr), + Storage: make(map[common.Hash]common.Hash), + MappingKeys: make(map[string]string), + CodeAddressBySlot: make(map[common.Hash]*common.Address), } } From 979c4ffb2af03dfb3374ed240d9b3e953b72ed1f Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:06:07 +0800 Subject: [PATCH 14/18] export meq field (#9) --- eth/tracers/logger/gen_structlog.go | 6 ++++++ eth/tracers/logger/logger.go | 3 +++ 2 files changed, 9 insertions(+) diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go index eda64987c4..26365e8f0a 100644 --- a/eth/tracers/logger/gen_structlog.go +++ b/eth/tracers/logger/gen_structlog.go @@ -22,6 +22,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { Gas math.HexOrDecimal64 `json:"gas"` GasCost math.HexOrDecimal64 `json:"gasCost"` Memory hexutil.Bytes `json:"memory,omitempty"` + Meq *int `json:"meq,omitempty"` MemorySize int `json:"memSize"` Stack []uint256.Int `json:"stack"` ReturnData hexutil.Bytes `json:"returnData,omitempty"` @@ -38,6 +39,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { enc.Gas = math.HexOrDecimal64(s.Gas) enc.GasCost = math.HexOrDecimal64(s.GasCost) enc.Memory = s.Memory + enc.Meq = s.Meq enc.MemorySize = s.MemorySize enc.Stack = s.Stack enc.ReturnData = s.ReturnData @@ -58,6 +60,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { Gas *math.HexOrDecimal64 `json:"gas"` GasCost *math.HexOrDecimal64 `json:"gasCost"` Memory *hexutil.Bytes `json:"memory,omitempty"` + Meq *int `json:"meq,omitempty"` MemorySize *int `json:"memSize"` Stack []uint256.Int `json:"stack"` ReturnData *hexutil.Bytes `json:"returnData,omitempty"` @@ -85,6 +88,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { if dec.Memory != nil { s.Memory = *dec.Memory } + if dec.Meq != nil { + s.Meq = dec.Meq + } if dec.MemorySize != nil { s.MemorySize = *dec.MemorySize } diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index f7c22b820d..67cc683161 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -86,6 +86,7 @@ type structLogMarshaling struct { Gas math.HexOrDecimal64 GasCost math.HexOrDecimal64 Memory hexutil.Bytes + Meq *int `json:"meq,omitempty"` ReturnData hexutil.Bytes OpName string `json:"opName"` // adds call to OpName() in MarshalJSON ErrorString string `json:"error,omitempty"` // adds call to ErrorString() in MarshalJSON @@ -456,6 +457,7 @@ type StructLogRes struct { Error string `json:"error,omitempty"` Stack *[]string `json:"stack,omitempty"` Memory *[]string `json:"memory,omitempty"` + Meq *int `json:"meq,omitempty"` Storage *map[string]string `json:"storage,omitempty"` RefundCounter uint64 `json:"refund,omitempty"` } @@ -472,6 +474,7 @@ func formatLogs(logs []StructLog) []StructLogRes { Depth: trace.Depth, Error: trace.ErrorString(), RefundCounter: trace.RefundCounter, + Meq: trace.Meq, } if trace.Stack != nil { stack := make([]string, len(trace.Stack)) From 673fe187497684f13d627eb5f8d09d36a65c5b61 Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Tue, 6 Feb 2024 18:50:03 +0800 Subject: [PATCH 15/18] ignore init code size limit (#10) --- core/state_transition.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state_transition.go b/core/state_transition.go index b0cc334914..6f981ad930 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -418,7 +418,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } // Check whether the init code size has been exceeded. - if rules.IsDurango && contractCreation && len(msg.Data) > params.MaxInitCodeSize { + if !st.evm.Config.IgnoreCodeSizeLimit && rules.IsDurango && contractCreation && len(msg.Data) > params.MaxInitCodeSize { return nil, fmt.Errorf("%w: code size %v limit %v", vmerrs.ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize) } From 67c0d753a846bc7446f226963d30712607e7cebe Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:29:21 +0800 Subject: [PATCH 16/18] code address field (#11) --- cmd/geth/main.go | 462 ----------------------------------- eth/tracers/sentio/tracer.go | 5 +- 2 files changed, 4 insertions(+), 463 deletions(-) delete mode 100644 cmd/geth/main.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go deleted file mode 100644 index 5b14e09b1d..0000000000 --- a/cmd/geth/main.go +++ /dev/null @@ -1,462 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// geth is the official command-line client for Ethereum. -package main - -import ( - "fmt" - "os" - "sort" - "strconv" - "strings" - "time" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/console/prompt" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/internal/debug" - "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/internal/flags" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/node" - - // Force-load the tracer engines to trigger registration - _ "github.com/ethereum/go-ethereum/eth/tracers/js" - _ "github.com/ethereum/go-ethereum/eth/tracers/native" - _ "github.com/ethereum/go-ethereum/eth/tracers/sentio" - - "github.com/urfave/cli/v2" -) - -const ( - clientIdentifier = "geth" // Client identifier to advertise over the network -) - -var ( - // flags that configure the node - nodeFlags = flags.Merge([]cli.Flag{ - utils.IdentityFlag, - utils.UnlockedAccountFlag, - utils.PasswordFileFlag, - utils.BootnodesFlag, - utils.MinFreeDiskSpaceFlag, - utils.KeyStoreDirFlag, - utils.ExternalSignerFlag, - utils.NoUSBFlag, - utils.USBFlag, - utils.SmartCardDaemonPathFlag, - utils.OverrideCancun, - utils.EnablePersonal, - utils.TxPoolLocalsFlag, - utils.TxPoolNoLocalsFlag, - utils.TxPoolJournalFlag, - utils.TxPoolRejournalFlag, - utils.TxPoolPriceLimitFlag, - utils.TxPoolPriceBumpFlag, - utils.TxPoolAccountSlotsFlag, - utils.TxPoolGlobalSlotsFlag, - utils.TxPoolAccountQueueFlag, - utils.TxPoolGlobalQueueFlag, - utils.TxPoolLifetimeFlag, - utils.SyncModeFlag, - utils.SyncTargetFlag, - utils.ExitWhenSyncedFlag, - utils.GCModeFlag, - utils.SnapshotFlag, - utils.TxLookupLimitFlag, - utils.LightServeFlag, - utils.LightIngressFlag, - utils.LightEgressFlag, - utils.LightMaxPeersFlag, - utils.LightNoPruneFlag, - utils.LightKDFFlag, - utils.UltraLightServersFlag, - utils.UltraLightFractionFlag, - utils.UltraLightOnlyAnnounceFlag, - utils.LightNoSyncServeFlag, - utils.EthRequiredBlocksFlag, - utils.LegacyWhitelistFlag, - utils.BloomFilterSizeFlag, - utils.CacheFlag, - utils.CacheDatabaseFlag, - utils.CacheTrieFlag, - utils.CacheTrieJournalFlag, - utils.CacheTrieRejournalFlag, - utils.CacheGCFlag, - utils.CacheSnapshotFlag, - utils.CacheNoPrefetchFlag, - utils.CachePreimagesFlag, - utils.CacheLogSizeFlag, - utils.FDLimitFlag, - utils.CryptoKZGFlag, - utils.ListenPortFlag, - utils.DiscoveryPortFlag, - utils.MaxPeersFlag, - utils.MaxPendingPeersFlag, - utils.MiningEnabledFlag, - utils.MinerGasLimitFlag, - utils.MinerGasPriceFlag, - utils.MinerEtherbaseFlag, - utils.MinerExtraDataFlag, - utils.MinerRecommitIntervalFlag, - utils.MinerNewPayloadTimeout, - utils.NATFlag, - utils.NoDiscoverFlag, - utils.DiscoveryV5Flag, - utils.NetrestrictFlag, - utils.NodeKeyFileFlag, - utils.NodeKeyHexFlag, - utils.DNSDiscoveryFlag, - utils.DeveloperFlag, - utils.DeveloperPeriodFlag, - utils.DeveloperGasLimitFlag, - utils.VMEnableDebugFlag, - utils.NetworkIdFlag, - utils.EthStatsURLFlag, - utils.NoCompactionFlag, - utils.GpoBlocksFlag, - utils.GpoPercentileFlag, - utils.GpoMaxGasPriceFlag, - utils.GpoIgnoreGasPriceFlag, - configFileFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags) - - rpcFlags = []cli.Flag{ - utils.HTTPEnabledFlag, - utils.HTTPListenAddrFlag, - utils.HTTPPortFlag, - utils.HTTPCORSDomainFlag, - utils.AuthListenFlag, - utils.AuthPortFlag, - utils.AuthVirtualHostsFlag, - utils.JWTSecretFlag, - utils.HTTPVirtualHostsFlag, - utils.GraphQLEnabledFlag, - utils.GraphQLCORSDomainFlag, - utils.GraphQLVirtualHostsFlag, - utils.HTTPApiFlag, - utils.HTTPPathPrefixFlag, - utils.WSEnabledFlag, - utils.WSListenAddrFlag, - utils.WSPortFlag, - utils.WSApiFlag, - utils.WSAllowedOriginsFlag, - utils.WSPathPrefixFlag, - utils.IPCDisabledFlag, - utils.IPCPathFlag, - utils.InsecureUnlockAllowedFlag, - utils.RPCGlobalGasCapFlag, - utils.RPCGlobalEVMTimeoutFlag, - utils.RPCGlobalTxFeeCapFlag, - utils.AllowUnprotectedTxs, - } - - metricsFlags = []cli.Flag{ - utils.MetricsEnabledFlag, - utils.MetricsEnabledExpensiveFlag, - utils.MetricsHTTPFlag, - utils.MetricsPortFlag, - utils.MetricsEnableInfluxDBFlag, - utils.MetricsInfluxDBEndpointFlag, - utils.MetricsInfluxDBDatabaseFlag, - utils.MetricsInfluxDBUsernameFlag, - utils.MetricsInfluxDBPasswordFlag, - utils.MetricsInfluxDBTagsFlag, - utils.MetricsEnableInfluxDBV2Flag, - utils.MetricsInfluxDBTokenFlag, - utils.MetricsInfluxDBBucketFlag, - utils.MetricsInfluxDBOrganizationFlag, - } -) - -var app = flags.NewApp("the go-ethereum command line interface") - -func init() { - // Initialize the CLI app and start Geth - app.Action = geth - app.Copyright = "Copyright 2013-2023 The go-ethereum Authors" - app.Commands = []*cli.Command{ - // See chaincmd.go: - initCommand, - importCommand, - exportCommand, - importPreimagesCommand, - exportPreimagesCommand, - removedbCommand, - dumpCommand, - dumpGenesisCommand, - // See accountcmd.go: - accountCommand, - walletCommand, - // See consolecmd.go: - consoleCommand, - attachCommand, - javascriptCommand, - // See misccmd.go: - versionCommand, - versionCheckCommand, - licenseCommand, - // See config.go - dumpConfigCommand, - // see dbcmd.go - dbCommand, - // See cmd/utils/flags_legacy.go - utils.ShowDeprecated, - // See snapshot.go - snapshotCommand, - // See verkle.go - verkleCommand, - } - sort.Sort(cli.CommandsByName(app.Commands)) - - app.Flags = flags.Merge( - nodeFlags, - rpcFlags, - consoleFlags, - debug.Flags, - metricsFlags, - ) - - app.Before = func(ctx *cli.Context) error { - flags.MigrateGlobalFlags(ctx) - return debug.Setup(ctx) - } - app.After = func(ctx *cli.Context) error { - debug.Exit() - prompt.Stdin.Close() // Resets terminal mode. - return nil - } -} - -func main() { - if err := app.Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} - -// prepare manipulates memory cache allowance and setups metric system. -// This function should be called before launching devp2p stack. -func prepare(ctx *cli.Context) { - // If we're running a known preset, log it for convenience. - switch { - case ctx.IsSet(utils.RinkebyFlag.Name): - log.Info("Starting Geth on Rinkeby testnet...") - - case ctx.IsSet(utils.GoerliFlag.Name): - log.Info("Starting Geth on Görli testnet...") - - case ctx.IsSet(utils.SepoliaFlag.Name): - log.Info("Starting Geth on Sepolia testnet...") - - case ctx.IsSet(utils.DeveloperFlag.Name): - log.Info("Starting Geth in ephemeral dev mode...") - log.Warn(`You are running Geth in --dev mode. Please note the following: - - 1. This mode is only intended for fast, iterative development without assumptions on - security or persistence. - 2. The database is created in memory unless specified otherwise. Therefore, shutting down - your computer or losing power will wipe your entire block data and chain state for - your dev environment. - 3. A random, pre-allocated developer account will be available and unlocked as - eth.coinbase, which can be used for testing. The random dev account is temporary, - stored on a ramdisk, and will be lost if your machine is restarted. - 4. Mining is enabled by default. However, the client will only seal blocks if transactions - are pending in the mempool. The miner's minimum accepted gas price is 1. - 5. Networking is disabled; there is no listen-address, the maximum number of peers is set - to 0, and discovery is disabled. -`) - - case !ctx.IsSet(utils.NetworkIdFlag.Name): - log.Info("Starting Geth on Ethereum mainnet...") - } - // If we're a full node on mainnet without --cache specified, bump default cache allowance - if ctx.String(utils.SyncModeFlag.Name) != "light" && !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) { - // Make sure we're not on any supported preconfigured testnet either - if !ctx.IsSet(utils.SepoliaFlag.Name) && - !ctx.IsSet(utils.RinkebyFlag.Name) && - !ctx.IsSet(utils.GoerliFlag.Name) && - !ctx.IsSet(utils.DeveloperFlag.Name) { - // Nope, we're really on mainnet. Bump that cache up! - log.Info("Bumping default cache on mainnet", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 4096) - ctx.Set(utils.CacheFlag.Name, strconv.Itoa(4096)) - } - } - // If we're running a light client on any network, drop the cache to some meaningfully low amount - if ctx.String(utils.SyncModeFlag.Name) == "light" && !ctx.IsSet(utils.CacheFlag.Name) { - log.Info("Dropping default light client cache", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 128) - ctx.Set(utils.CacheFlag.Name, strconv.Itoa(128)) - } - - // Start metrics export if enabled - utils.SetupMetrics(ctx) - - // Start system runtime metrics collection - go metrics.CollectProcessMetrics(3 * time.Second) -} - -// geth is the main entry point into the system if no special subcommand is run. -// It creates a default node based on the command line arguments and runs it in -// blocking mode, waiting for it to be shut down. -func geth(ctx *cli.Context) error { - if args := ctx.Args().Slice(); len(args) > 0 { - return fmt.Errorf("invalid command: %q", args[0]) - } - - prepare(ctx) - stack, backend := makeFullNode(ctx) - defer stack.Close() - - startNode(ctx, stack, backend, false) - stack.Wait() - return nil -} - -// startNode boots up the system node and all registered protocols, after which -// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the -// miner. -func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isConsole bool) { - debug.Memsize.Add("node", stack) - - // Start up the node itself - utils.StartNode(ctx, stack, isConsole) - - // Unlock any account specifically requested - unlockAccounts(ctx, stack) - - // Register wallet event handlers to open and auto-derive wallets - events := make(chan accounts.WalletEvent, 16) - stack.AccountManager().Subscribe(events) - - // Create a client to interact with local geth node. - rpcClient, err := stack.Attach() - if err != nil { - utils.Fatalf("Failed to attach to self: %v", err) - } - ethClient := ethclient.NewClient(rpcClient) - - go func() { - // Open any wallets already attached - for _, wallet := range stack.AccountManager().Wallets() { - if err := wallet.Open(""); err != nil { - log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) - } - } - // Listen for wallet event till termination - for event := range events { - switch event.Kind { - case accounts.WalletArrived: - if err := event.Wallet.Open(""); err != nil { - log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) - } - case accounts.WalletOpened: - status, _ := event.Wallet.Status() - log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) - - var derivationPaths []accounts.DerivationPath - if event.Wallet.URL().Scheme == "ledger" { - derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath) - } - derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath) - - event.Wallet.SelfDerive(derivationPaths, ethClient) - - case accounts.WalletDropped: - log.Info("Old wallet dropped", "url", event.Wallet.URL()) - event.Wallet.Close() - } - } - }() - - // Spawn a standalone goroutine for status synchronization monitoring, - // close the node when synchronization is complete if user required. - if ctx.Bool(utils.ExitWhenSyncedFlag.Name) { - go func() { - sub := stack.EventMux().Subscribe(downloader.DoneEvent{}) - defer sub.Unsubscribe() - for { - event := <-sub.Chan() - if event == nil { - continue - } - done, ok := event.Data.(downloader.DoneEvent) - if !ok { - continue - } - if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute { - log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(), - "age", common.PrettyAge(timestamp)) - stack.Close() - } - } - }() - } - - // Start auxiliary services if enabled - if ctx.Bool(utils.MiningEnabledFlag.Name) || ctx.Bool(utils.DeveloperFlag.Name) { - // Mining only makes sense if a full Ethereum node is running - if ctx.String(utils.SyncModeFlag.Name) == "light" { - utils.Fatalf("Light clients do not support mining") - } - ethBackend, ok := backend.(*eth.EthAPIBackend) - if !ok { - utils.Fatalf("Ethereum service not running") - } - // Set the gas price to the limits from the CLI and start mining - gasprice := flags.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) - ethBackend.TxPool().SetGasPrice(gasprice) - if err := ethBackend.StartMining(); err != nil { - utils.Fatalf("Failed to start mining: %v", err) - } - } -} - -// unlockAccounts unlocks any account specifically requested. -func unlockAccounts(ctx *cli.Context, stack *node.Node) { - var unlocks []string - inputs := strings.Split(ctx.String(utils.UnlockedAccountFlag.Name), ",") - for _, input := range inputs { - if trimmed := strings.TrimSpace(input); trimmed != "" { - unlocks = append(unlocks, trimmed) - } - } - // Short circuit if there is no account to unlock. - if len(unlocks) == 0 { - return - } - // If insecure account unlocking is not allowed if node's APIs are exposed to external. - // Print warning log to user and skip unlocking. - if !stack.Config().InsecureUnlockAllowed && stack.Config().ExtRPCEnabled() { - utils.Fatalf("Account unlock with HTTP access is forbidden!") - } - backends := stack.AccountManager().Backends(keystore.KeyStoreType) - if len(backends) == 0 { - log.Warn("Failed to unlock accounts, keystore is not available") - return - } - ks := backends[0].(*keystore.KeyStore) - passwords := utils.MakePasswordList(ctx) - for i, account := range unlocks { - unlockAccount(ks, account, i, passwords) - } -} diff --git a/eth/tracers/sentio/tracer.go b/eth/tracers/sentio/tracer.go index 5408c0f4d5..1be50edf22 100644 --- a/eth/tracers/sentio/tracer.go +++ b/eth/tracers/sentio/tracer.go @@ -326,6 +326,7 @@ func (t *sentioTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s call.Gas = math.HexOrDecimal64(scope.Stack.Back(0).Uint64()) from := scope.Contract.Address() call.From = &from + call.CodeAddress = scope.Contract.CodeAddr to := common.BigToAddress(scope.Stack.Back(1).ToBig()) call.To = &to call.Value = (*hexutil.Big)(scope.Stack.Back(2).ToBig()) @@ -366,9 +367,11 @@ func (t *sentioTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s break } from := scope.Contract.CodeAddr + codeAddress := scope.Contract.CodeAddr jump := mergeBase(Trace{ - From: from, + From: from, + CodeAddress: codeAddress, //InputStack: append([]uint256.Int(nil), scope.Stack.Data...), // TODO only need partial }) if t.previousJump != nil { From a7f1a78359b1f895a5c1ec5e56f11fa195088a11 Mon Sep 17 00:00:00 2001 From: Fuyao Zhao Date: Fri, 15 Mar 2024 03:15:36 -0700 Subject: [PATCH 17/18] patch with avacoreeth --- eth/tracers/sentio/prestate.go | 4 ++-- eth/tracers/sentio/tracer.go | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/eth/tracers/sentio/prestate.go b/eth/tracers/sentio/prestate.go index b36a3d55cd..9d159e0fd8 100644 --- a/eth/tracers/sentio/prestate.go +++ b/eth/tracers/sentio/prestate.go @@ -22,11 +22,11 @@ import ( "math/big" "sync/atomic" + "github.com/ava-labs/coreth/core/vm" + "github.com/ava-labs/coreth/eth/tracers" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/tracers" ) //go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go diff --git a/eth/tracers/sentio/tracer.go b/eth/tracers/sentio/tracer.go index 1be50edf22..b8b6badef8 100644 --- a/eth/tracers/sentio/tracer.go +++ b/eth/tracers/sentio/tracer.go @@ -23,13 +23,14 @@ import ( "math/big" "sync/atomic" - "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ava-labs/coreth/accounts/abi" + "github.com/ava-labs/coreth/core/vm" + "github.com/ava-labs/coreth/eth/tracers" + "github.com/ava-labs/coreth/vmerrs" + corestate "github.com/ava-labs/coreth/core/state" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" - corestate "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/log" "github.com/holiman/uint256" ) @@ -165,7 +166,7 @@ func (t *sentioTracer) CaptureStart(env *vm.EVM, from common.Address, to common. t.receipt.TransactionIndex = uint(ibs.TxIndex()) } - rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) + rules := env.ChainConfig().AvalancheRules(env.Context.BlockNumber, env.Context.Time) t.activePrecompiles = vm.ActivePrecompiles(rules) root := Trace{ @@ -638,7 +639,7 @@ func (f *Trace) processError(output []byte, err error) { if f.Type == vm.CREATE.String() || f.Type == vm.CREATE2.String() { f.To = &common.Address{} } - if !errors.Is(err, vm.ErrExecutionReverted) || len(output) == 0 { + if !errors.Is(err, vmerrs.ErrExecutionReverted) || len(output) == 0 { return } //f.Output = output From 312cba86bf206323455d27e8bb8e6f326e5cec6b Mon Sep 17 00:00:00 2001 From: lwedge99 <116623885+lwedge99@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:38:30 +0800 Subject: [PATCH 18/18] emit output for revert (#14) --- eth/tracers/sentio/tracer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eth/tracers/sentio/tracer.go b/eth/tracers/sentio/tracer.go index b8b6badef8..5b28d07603 100644 --- a/eth/tracers/sentio/tracer.go +++ b/eth/tracers/sentio/tracer.go @@ -498,7 +498,8 @@ func (t *sentioTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s //data := copyMemory(logOffset, logSize) trace := mergeBase(Trace{ - Error: "execution reverted", + Output: output, + Error: "execution reverted", }) if unpacked, err := abi.UnpackRevert(output); err == nil { trace.Revertal = unpacked