mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-28 15:46:43 +00:00
* cmd, core, trie: verkle-capable `geth init` (#28270) This change allows the creation of a genesis block for verkle testnets. This makes for a chunk of code that is easier to review and still touches many discussion points. * eth/tracers/js: fix isPush for push0 (#28520) Fixes so that `push0` opcode is correctly reported as `true` by the `IsPush` function --------- Co-authored-by: Martin Holst Swende <martin@swende.se> * trie: spelling - fix comments in hasher (#28507) Co-authored-by: VM <arimas@foxmail.com> * tests/fuzzers: move fuzzers into native packages (#28467) This PR moves our fuzzers from tests/fuzzers into whatever their respective 'native' package is. The historical reason why they were placed in an external location, is that when they were based on go-fuzz, they could not be "hidden" via the _test.go prefix. So in order to shove them away from the go-ethereum "production code", they were put aside. But now we've rewritten them to be based on golang testing, and thus can be brought back. I've left (in tests/) the ones that are not production (bls128381), require non-standard imports (secp requires btcec, bn256 requires gnark/google/cloudflare deps). This PR also adds a fuzzer for precompiled contracts, because why not. This PR utilizes a newly rewritten replacement for go-118-fuzz-build, namely gofuzz-shim, which utilises the inputs from the fuzzing engine better. * tests: skip tests on windows 32bit CI (#28521) tests: skip half the blockchain- and state-tests on windows 32bit CI-tests * cmd/geth: more special cases logging tests (#28527) adds logging tests for errors and custom fmt.Stringer-types which output strings that needs to be quoted/escaped. * accounts,cmd,console,les,metrics: refactor some errors checked by (ST1005) go-staticcheck (#28532) fix: fix some (ST1005)go-staticcheck * miner: run tests in parallel (#28506) Changes many of the tests in the miner package to run in parallel * internal/jsre/deps: fix typo in jsdoc (#28511) minor typo fix * accounts/abi: improve readability of method-to-string conversion (#28530) refactor: improve readability of NewMethod print * all: replace some cases of strings.SplitN with strings.Cut (#28446) * ethdb/memorydb, trie: reduced allocations (#28473) * trie: use pooling of iterator states in iterator The node iterator burns through a lot of memory while iterating a trie, and a lot of that can be avoided by using a fairly small pool (max 40 items). name old time/op new time/op delta Iterator-8 6.22ms ± 3% 5.40ms ± 6% -13.18% (p=0.008 n=5+5) name old alloc/op new alloc/op delta Iterator-8 2.36MB ± 0% 1.67MB ± 0% -29.23% (p=0.008 n=5+5) name old allocs/op new allocs/op delta Iterator-8 37.0k ± 0% 29.8k ± 0% ~ (p=0.079 n=4+5) * ethdb/memorydb: avoid one copying of key By making the transformation from []byte to string at an earlier point, we save an allocation which otherwise happens later on. name old time/op new time/op delta BatchAllocs-8 412µs ± 6% 382µs ± 2% -7.18% (p=0.016 n=5+4) name old alloc/op new alloc/op delta BatchAllocs-8 480kB ± 0% 490kB ± 0% +1.93% (p=0.008 n=5+5) name old allocs/op new allocs/op delta BatchAllocs-8 3.03k ± 0% 2.03k ± 0% -32.98% (p=0.008 n=5+5) * Dockerfile: update Go to 1.21 (#28538) * cmd/evm: validate blockchain tests poststate account storage (#28443) This PR verifies the accounts' storage as specified in a blockchain test's postState field The expect-section, it does really only check that the test works. It's meant for the test-author to verify that "If the test does what it's supposed to, then the nonce of X should be 2, and the slot Y at Z should be 0x123. This expect-section is not exhaustive (not full post-state) It is also not auto-generated, but put there manually by the author. We can still check it, as a test-sanity-check, in geth * signer: run tests in parallel (#28536) marks tests as parallel-safe in package signer * accounts, cmd: fix typos (#28526) * core/txpool/legacypool: respect nolocals-setting (#28435) This change adds a check to ensure that transactions added to the legacy pool are not treated as 'locals' if the global locals-management has been disabled. This change makes the pool enforce the --txpool.pricelimit setting. * cmd: run tests in parallel (#28546) * core/state/snapshot: print correct error from trie iterator (#28560) * cmd/evm: capitalize evm commands (#28569) * standard:fix for a unified standard * standard:fix more as a complements --------- Co-authored-by: haotian <haotian@haotiandeMacBook-Air.local> * accounts/abi: context info on unpack-errors (#28529) adds contextual information to errors returned by unpack * core, trie, rpc: speed up tests (#28461) * rpc: make subscription test faster reduces time for TestClientSubscriptionChannelClose from 25 sec to < 1 sec. * trie: cache trie nodes for faster sanity check This reduces the time spent on TestIncompleteSyncHash from ~25s to ~16s. * core/forkid: speed up validation test This takes the validation test from > 5s to sub 1 sec * core/state: improve snapshot test run brings the time for TestSnapshotRandom from 13s down to 6s * accounts/keystore: improve keyfile test This removes some unnecessary waits and reduces the runtime of TestUpdatedKeyfileContents from 5 to 3 seconds * trie: remove resolver * trie: only check ~5% of all trie nodes * ethdb/pebble: don't double-close iterator inside pebbleIterator (#28566) Adds 'released' flag to pebbleIterator to avoid double closing cockroachdb/pebble.Iterator as it is an invalid operation. Fixes #28565 * eth/filters: reuse error msg for invalid block range (#28479) * core/types: make 'v' optional for DynamicFeeTx and BlobTx (#28564) This fixes an issue where transactions would not be accepted when they have only 'yParity' and not 'v'. * rpc: improve performance of subscription notification encoding (#28328) It turns out that encoding json.RawMessage is slow because package json basically parses the message again to ensure it is valid. We can avoid the slowdown by encoding the entire RPC notification once, which yields a 30% speedup. * cmd/utils: validate pre-existing genesis in --dev mode (#28468) geth --dev can be used with an existing data directory and genesis block. Since dev mode only works with PoS, we need to verify that the merge has happened. Co-authored-by: Felix Lange <fjl@twurst.com> * cmd/geth: add support for --dev flag in dumpgenesis (#28463) Co-authored-by: Felix Lange <fjl@twurst.com> Co-authored-by: lightclient <lightclient@protonmail.com> * les/vflux: run tests in parallel (#28524) * cmd/{geth,utils}: add cmd to export preimages in snap enumeration order (#28256) Adds a subcommand: `geth snapshot export-preimages`, to export preimages of every hash found during a snapshot enumeration: that is, it exports _only the active state_, and not _all_ preimages that have been used but are no longer part of the state. This tool is needed for the verkle transition, in order to distribute the preimages needed for the conversion. Since only the 'active' preimages are exported, the output is shrunk from ~70GB to ~4GB. The order of the output is the order used by the snapshot enumeration, which avoids database thrashing. However, it also means that storage-slot preimages are not deduplicated. * cmd/geth: fix build error (#28585) * cmd/devp2p/internal/ethtest: undo debug-hack (#28588) cmd/devp2p/internal/ethtest: remove a debug-hack flaw which prevented certain tests from running * params: update discV5 bootnodes (#28562) update discV5 bootnodes from https://github.com/eth-clients/eth2-networks/blob/master/shared/mainnet/bootstrap_nodes.txt * cmd, les, tests: remove light client code (#28586) * cmd, les, tests: remove light client code This commit removes the light client (LES) code. Since the merge the light client has been broken and it is hard to maintain it alongside the normal client. We decided it would be best to remove it for now and maybe rework and reintroduce it in the future. * cmd, eth: remove some more mentions of light mode * cmd: re-add flags and mark as deprecated * cmd: warn the user about deprecated flags * eth: better error message * eth, internal/ethapi: drop some weird indirection (#28597) * trie: fix random test generator early terminate (#28590) This change fixes a minor bug in the `randTest.Generate` function, which caused the `quick.Check` to be a no-op. * eth/gasestimator, internal/ethapi: move gas estimator out of rpc (#28600) * go.mod: update uint256 to v1.2.4 (#28612) * eth/catalyst, eth/downloader: expose more sync information (#28584) This change exposes more information from sync module internally * light: remove package light(#28614) This changes removes the package 'light', which is currently unused. * cmd/evm, core/state: fix post-exec dump of state (statetests, blockchaintests) (#28504) There were several problems related to dumping state. - If a preimage was missing, even if we had set the `OnlyWithAddresses` to `false`, to export them anyway, the way the mapping was constructed (using `common.Address` as key) made the entries get lost anyway. Concerns both state- and blockchain tests. - Blockchain test execution was not configured to store preimages. This changes makes it so that the block test executor takes a callback, just like the state test executor already does. This callback can be used to examine the post-execution state, e.g. to aid debugging of test failures. * ethereum: remove TODO comment about subscription (#28609) * eth/tracers/js: fix type inconsistencies (#28488) This change fixes two type-inconsistencies in the JS tracer: - In most places we return byte arrays as a `Uint8Array` to the tracer. However it seems we missed doing the conversion for `ctx` fields which are passed to the tracer during `result`. They are passed as simple arrays. I think Uint8Arrays are more suitable and we should change this inconsistency. Note: this will be a breaking-change. But I believe the effect is small. If we look at our tracers we see that these fields (`ctx.from`, `ctx.to`, etc.) are used in 2 ways. Passed to `toHex` which takes both array or buffer. Or the length was measured which is the same for both types. - The `slice` taking in `int, int` params versus `memory.slice` taking `int64, int64` params. I suggest changing `slice` types to `int64`. This should have no effect almost in any case. * crypto/secp256k1: fix 32-bit tests when CGO_ENABLED=0 (#28602) * consensus: verify the nonexistence of shanghai- and cancun-specific header fields (#28605) * eth/gasestimator: allow slight estimation error in favor of less iterations (#28618) * eth/gasestimator: early exit for plain transfer and error allowance * core, eth/gasestimator: hard guess at a possible required gas * internal/ethapi: update estimation tests with the error ratio * eth/gasestimator: I hate you linter * graphql: fix gas estimation test --------- Co-authored-by: Oren <orenyomtov@users.noreply.github.com> * all: replace log15 with slog (#28187) This PR replaces Geth's logger package (a fork of [log15](https://github.com/inconshreveable/log15)) with an implementation using slog, a logging library included as part of the Go standard library as of Go1.21. Main changes are as follows: * removes any log handlers that were unused in the Geth codebase. * Json, logfmt, and terminal formatters are now slog handlers. * Verbosity level constants are changed to match slog constant values. Internal translation is done to make this opaque to the user and backwards compatible with existing `--verbosity` and `--vmodule` options. * `--log.backtraceat` and `--log.debug` are removed. The external-facing API is largely the same as the existing Geth logger. Logger method signatures remain unchanged. A small semantic difference is that a `Handler` can only be set once per `Logger` and not changed dynamically. This just means that a new logger must be instantiated every time the handler of the root logger is changed. ---- For users of the `go-ethereum/log` module. If you were using this module for your own project, you will need to change the initialization. If you previously did ```golang log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) ``` You now instead need to do ```golang log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) ``` See more about reasoning here: https://github.com/ethereum/go-ethereum/issues/28558#issuecomment-1820606613 * core/state: make stateobject.create selfcontain (#28459) * trie/triedb/hashdb: take lock around access to dirties cache (#28542) Add read locking of db lock around access to dirties cache in hashdb.Database to prevent data race versus hashdb.Database.dereference which can modify the dirities map by deleting an item. Fixes #28541 --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com> * accounts/abi/bind: fix typo (#28630) * slog: faster and less memory-consumption (#28621) These changes improves the performance of the non-coloured terminal formatting, _quite a lot_. ``` name old time/op new time/op delta TerminalHandler-8 10.2µs ±15% 5.4µs ± 9% -47.02% (p=0.008 n=5+5) name old alloc/op new alloc/op delta TerminalHandler-8 2.17kB ± 0% 0.40kB ± 0% -81.46% (p=0.008 n=5+5) name old allocs/op new allocs/op delta TerminalHandler-8 33.0 ± 0% 5.0 ± 0% -84.85% (p=0.008 n=5+5) ``` I tried to _somewhat_ organize the commits, but the it might still be a bit chaotic. Some core insights: - The function `terminalHandler.Handl` uses a mutex, and writes all output immediately to 'upstream'. Thus, it can reuse a scratch-buffer every time. - This buffer can be propagated internally, making all the internal formatters either write directly to it, - OR, make use of the `tmp := buf.AvailableBuffer()` in some cases, where a byte buffer "extra capacity" can be temporarily used. - The `slog` package uses `Attr` by value. It makes sense to minimize operating on them, since iterating / collecting into a new slice, iterating again etc causes copy-on-heap. Better to operate on them only once. - If we want to do padding, it's better to copy from a constant `space`-buffer than to invoke `bytes.Repeat` every single time. * eth/tracers: tx-level state in debug_traceCall (#28460) * cmd/evm: fix Env struct json tag (#28635) * accounts/abi/bind: fixed typos (#28634) * Update auth.go * Update backend.go * Update bind.go * Update bind_test.go * eth/fetcher: fix invalid tracking of received at time for block (#28637) eth/fetcher: fix invalid tracking of received at time * accounts: run tests in parallel (#28544) * eth/tracers/logger: make structlog/json-log stack hex again (#28628) * common/hexutil: define hex wrappers for uint256.Int * eth/tracers/logger: make structlog/json-log stack hex again * common/hexutil: goimports * log: remove lazy, remove unused interfaces, unexport methods (#28622) This change - Removes interface `log.Format`, - Removes method `log.FormatFunc`, - unexports `TerminalHandler.TerminalFormat` formatting methods (renamed to `TerminalHandler.format`) - removes the notion of `log.Lazy` values The lazy handler was useful in the old log package, since it could defer the evaluation of costly attributes until later in the log pipeline: thus, if the logging was done at 'Trace', we could skip evaluation if logging only was set to 'Info'. With the move to slog, this way of deferring evaluation is no longer needed, since slog introduced 'Enabled': the caller can thus do the evaluate-or-not decision at the callsite, which is much more straight-forward than dealing with lazy reflect-based evaluation. Also, lazy evaluation would not work with 'native' slog, as in, these two statements would be evaluated differently: ```golang log.Info("foo", "my lazy", lazyObj) slog.Info("foo", "my lazy", lazyObj) ``` * .github: use github actions to run 32-bit linux tests (#28549) use github actions to run 32-bit linux tests * ethdb/pebble: remove a dependency (#28627) The dependency was not really used anyway, so we can get rid of it. Co-authored-by: Felix Lange <fjl@twurst.com> * tests/fuzzers/bls12381: deactivate BLS fuzzer when CGO_ENABLED=0 (#28653) tests/fuzzers/bls12381: deactivate fuzzer when CGO_ENABLED=0 * build: upgrade -dlgo version to Go 1.21.5 (#28648) * rpc: fix ns/µs mismatch in metrics (#28649) The rpc/duration/all meter was in nanoseconds, the individual meter in microseconds. This PR changes it so both of them use nanoseconds. * cmd/evm: fix dump after state-test exec (#28650) The dump after state-test didn't work, the problem was an error, "Already committed", which was silently ignored. This change re-initialises the state, so the dumping works again. * beacon/light: add CommitteeChain (#27766) This change implements CommitteeChain which is a key component of the beacon light client. It is a passive data structure that can validate, hold and update a chain of beacon light sync committees and updates, starting from a checkpoint that proves the starting committee through a beacon block hash, header and corresponding state. Once synced to the current sync period, CommitteeChain can also validate signed beacon headers. * cmd/utils, eth: disallow invalid snap sync / snapshot flag combos (#28657) * eth: prevent startup in snap mode without snapshots * cmd/utils: try to fix bad flag combos wrt snap sync and snapshot generation * trie: remove inconsistent trie nodes during sync in path mode (#28595) This fixes a database corruption issue that could occur during state healing. When sync is aborted while certain modifications were already committed, and a reorg occurs, the database would contain incorrect trie nodes stored by path. These nodes need to detected/deleted in order to obtain a complete and fully correct state after state healing. --------- Co-authored-by: Felix Lange <fjl@twurst.com> * cmd/utils: fix HTTPHost, WSHost flag priority (#28669) Co-authored-by: Felix Lange <fjl@twurst.com> * eth/protocols/eth: fix typos in comments (#28652) * core/txpool : small cleanup refactors (#28654) * eth/fetcher, eth/gasestimator: fix typos in comments (#28675) * all: fix typos in comments (#28662) Co-authored-by: Felix Lange <fjl@twurst.com> * miner: eliminate the dead loop possibility for `newWorkLoop` and `mainLoop` (#28677) discard the intervalAdjust message if the channel is full * all: fix typos in comments (#28682) chore(core,eth):fix a couple of typos * p2p/discover: add liveness check in collectTableNodes (#28686) * p2p/discover: add liveness check in collectTableNodes * p2p/discover: fix test * p2p/discover: rename to appendLiveNodes * p2p/discover: add dedup logic back * p2p/discover: simplify * p2p/discover: fix issue found by test * internal/flags: add missing flag types for auto-env-var generation (#28692) Certain flags, such as `--rpc.txfeecap` currently do not have an env-var auto-generated for them. This change adds three missing cli flag types to the auto env-var helper function to fix this. * cmd/evm: default to mirror mainnet forks enabled (#28691) cmd/evm: default to using dev chain config (all mainnet HFs activated at block/timestamp 0 * cmd/evm, cmd/clef, cmd/bootnode: fix / unify logging (#28696) This change fixes a problem with our non-core binaries: evm, clef, bootnode. First of all, they failed to convert from legacy loglevels 1 to 5, to the new slog loglevels -4 to 4. Secondly, the logging was actually setup in the init phase, and then overridden in the main. This is not needed for evm, since it used the same flag name as the main geth verbosity. Better to let the flags/internal handle the logging init. * cmd/evm: t8n support custom tracers (#28557) This change implements ability for the `evm t8n` tool to use custom tracers; either 'native' golang tracers or javascript tracers. * params: release go-ethereum v1.13.6 stable * Fix build errors * Fix test-integration --------- Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Co-authored-by: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Co-authored-by: Martin Holst Swende <martin@swende.se> Co-authored-by: VM <112189277+sysvm@users.noreply.github.com> Co-authored-by: VM <arimas@foxmail.com> Co-authored-by: jwasinger <j-wasinger@hotmail.com> Co-authored-by: Zoro <40222601+BabyHalimao@users.noreply.github.com> Co-authored-by: Håvard Anda Estensen <haavard.ae@gmail.com> Co-authored-by: aliening <128203330+aliening@users.noreply.github.com> Co-authored-by: Halimao <1065621723@qq.com> Co-authored-by: danceratopz <danceratopz@gmail.com> Co-authored-by: levisyin <150114626+levisyin@users.noreply.github.com> Co-authored-by: jp-imx <109574657+jp-imx@users.noreply.github.com> Co-authored-by: rjl493456442 <garyrong0905@gmail.com> Co-authored-by: Haotian <51777534+tmelhao@users.noreply.github.com> Co-authored-by: haotian <haotian@haotiandeMacBook-Air.local> Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de> Co-authored-by: Maciej Kulawik <10907694+magicxyyz@users.noreply.github.com> Co-authored-by: ucwong <ucwong@126.com> Co-authored-by: Mario Vega <marioevz@gmail.com> Co-authored-by: Delweng <delweng@gmail.com> Co-authored-by: Felix Lange <fjl@twurst.com> Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: Mikel Cortes <45786396+cortze@users.noreply.github.com> Co-authored-by: Péter Szilágyi <peterke@gmail.com> Co-authored-by: Ng Wei Han <47109095+weiihann@users.noreply.github.com> Co-authored-by: lightclient <14004106+lightclient@users.noreply.github.com> Co-authored-by: Shivam Sandbhor <shivam.sandbhor@gmail.com> Co-authored-by: Jakub Freebit <49676311+jakub-freebit@users.noreply.github.com> Co-authored-by: Oren <orenyomtov@users.noreply.github.com> Co-authored-by: BorkBorked <107079055+BorkBorked@users.noreply.github.com> Co-authored-by: ddl <dengdiliang@gmail.com> Co-authored-by: Manav Darji <manavdarji.india@gmail.com> Co-authored-by: Marius Kjærstad <sandakersmann@users.noreply.github.com> Co-authored-by: Felföldi Zsolt <zsfelfoldi@gmail.com> Co-authored-by: Ford <153042616+guerrierindien@users.noreply.github.com> Co-authored-by: Ursulafe <152976968+Ursulafe@users.noreply.github.com> Co-authored-by: Elias Rad <146735585+nnsW3@users.noreply.github.com> Co-authored-by: FletcherMan <fanciture@163.com> Co-authored-by: alex <152680487+bodhi-crypo@users.noreply.github.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
2654 lines
88 KiB
Go
2654 lines
88 KiB
Go
// Copyright 2015 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 <http://www.gnu.org/licenses/>.
|
||
|
||
package ethapi
|
||
|
||
import (
|
||
"context"
|
||
"encoding/hex"
|
||
"errors"
|
||
"fmt"
|
||
"math/big"
|
||
"runtime"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/davecgh/go-spew/spew"
|
||
"github.com/tyler-smith/go-bip39"
|
||
|
||
"github.com/ethereum/go-ethereum/accounts"
|
||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
||
"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/consensus"
|
||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||
"github.com/ethereum/go-ethereum/core"
|
||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||
"github.com/ethereum/go-ethereum/core/state"
|
||
"github.com/ethereum/go-ethereum/core/types"
|
||
"github.com/ethereum/go-ethereum/core/vm"
|
||
"github.com/ethereum/go-ethereum/crypto"
|
||
"github.com/ethereum/go-ethereum/eth/gasestimator"
|
||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||
"github.com/ethereum/go-ethereum/ethdb"
|
||
"github.com/ethereum/go-ethereum/log"
|
||
"github.com/ethereum/go-ethereum/p2p"
|
||
"github.com/ethereum/go-ethereum/params"
|
||
"github.com/ethereum/go-ethereum/rlp"
|
||
"github.com/ethereum/go-ethereum/rpc"
|
||
"github.com/ethereum/go-ethereum/trie"
|
||
)
|
||
|
||
// estimateGasErrorRatio is the amount of overestimation eth_estimateGas is
|
||
// allowed to produce in order to speed up calculations.
|
||
const estimateGasErrorRatio = 0.015
|
||
|
||
// EthereumAPI provides an API to access Ethereum related information.
|
||
type EthereumAPI struct {
|
||
b Backend
|
||
}
|
||
|
||
// NewEthereumAPI creates a new Ethereum protocol API.
|
||
func NewEthereumAPI(b Backend) *EthereumAPI {
|
||
return &EthereumAPI{b}
|
||
}
|
||
|
||
// GasPrice returns a suggestion for a gas price for legacy transactions.
|
||
func (s *EthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
|
||
tipcap, err := s.b.SuggestGasTipCap(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if head := s.b.CurrentHeader(); head.BaseFee != nil {
|
||
tipcap.Add(tipcap, head.BaseFee)
|
||
}
|
||
|
||
return (*hexutil.Big)(tipcap), err
|
||
}
|
||
|
||
// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions.
|
||
func (s *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
|
||
tipcap, err := s.b.SuggestGasTipCap(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return (*hexutil.Big)(tipcap), err
|
||
}
|
||
|
||
type feeHistoryResult struct {
|
||
OldestBlock *hexutil.Big `json:"oldestBlock"`
|
||
Reward [][]*hexutil.Big `json:"reward,omitempty"`
|
||
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
|
||
GasUsedRatio []float64 `json:"gasUsedRatio"`
|
||
}
|
||
|
||
// FeeHistory returns the fee market history.
|
||
func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
|
||
oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, uint64(blockCount), lastBlock, rewardPercentiles)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
results := &feeHistoryResult{
|
||
OldestBlock: (*hexutil.Big)(oldest),
|
||
GasUsedRatio: gasUsed,
|
||
}
|
||
if reward != nil {
|
||
results.Reward = make([][]*hexutil.Big, len(reward))
|
||
for i, w := range reward {
|
||
results.Reward[i] = make([]*hexutil.Big, len(w))
|
||
for j, v := range w {
|
||
results.Reward[i][j] = (*hexutil.Big)(v)
|
||
}
|
||
}
|
||
}
|
||
|
||
if baseFee != nil {
|
||
results.BaseFee = make([]*hexutil.Big, len(baseFee))
|
||
for i, v := range baseFee {
|
||
results.BaseFee[i] = (*hexutil.Big)(v)
|
||
}
|
||
}
|
||
|
||
return results, nil
|
||
}
|
||
|
||
// Syncing returns false in case the node is currently not syncing with the network. It can be up-to-date or has not
|
||
// yet received the latest block headers from its pears. In case it is synchronizing:
|
||
// - startingBlock: block number this node started to synchronize from
|
||
// - currentBlock: block number this node is currently importing
|
||
// - highestBlock: block number of the highest block header this node has received from peers
|
||
// - pulledStates: number of state entries processed until now
|
||
// - knownStates: number of known state entries that still need to be pulled
|
||
func (s *EthereumAPI) Syncing() (interface{}, error) {
|
||
progress := s.b.SyncProgress()
|
||
|
||
// Return not syncing if the synchronisation already completed
|
||
if progress.CurrentBlock >= progress.HighestBlock {
|
||
return false, nil
|
||
}
|
||
// Otherwise gather the block sync stats
|
||
return map[string]interface{}{
|
||
"startingBlock": hexutil.Uint64(progress.StartingBlock),
|
||
"currentBlock": hexutil.Uint64(progress.CurrentBlock),
|
||
"highestBlock": hexutil.Uint64(progress.HighestBlock),
|
||
"syncedAccounts": hexutil.Uint64(progress.SyncedAccounts),
|
||
"syncedAccountBytes": hexutil.Uint64(progress.SyncedAccountBytes),
|
||
"syncedBytecodes": hexutil.Uint64(progress.SyncedBytecodes),
|
||
"syncedBytecodeBytes": hexutil.Uint64(progress.SyncedBytecodeBytes),
|
||
"syncedStorage": hexutil.Uint64(progress.SyncedStorage),
|
||
"syncedStorageBytes": hexutil.Uint64(progress.SyncedStorageBytes),
|
||
"healedTrienodes": hexutil.Uint64(progress.HealedTrienodes),
|
||
"healedTrienodeBytes": hexutil.Uint64(progress.HealedTrienodeBytes),
|
||
"healedBytecodes": hexutil.Uint64(progress.HealedBytecodes),
|
||
"healedBytecodeBytes": hexutil.Uint64(progress.HealedBytecodeBytes),
|
||
"healingTrienodes": hexutil.Uint64(progress.HealingTrienodes),
|
||
"healingBytecode": hexutil.Uint64(progress.HealingBytecode),
|
||
}, nil
|
||
}
|
||
|
||
// TxPoolAPI offers and API for the transaction pool. It only operates on data that is non-confidential.
|
||
type TxPoolAPI struct {
|
||
b Backend
|
||
}
|
||
|
||
// NewTxPoolAPI creates a new tx pool service that gives information about the transaction pool.
|
||
func NewTxPoolAPI(b Backend) *TxPoolAPI {
|
||
return &TxPoolAPI{b}
|
||
}
|
||
|
||
// Content returns the transactions contained within the transaction pool.
|
||
func (s *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction {
|
||
content := map[string]map[string]map[string]*RPCTransaction{
|
||
"pending": make(map[string]map[string]*RPCTransaction),
|
||
"queued": make(map[string]map[string]*RPCTransaction),
|
||
}
|
||
pending, queue := s.b.TxPoolContent()
|
||
curHeader := s.b.CurrentHeader()
|
||
// Flatten the pending transactions
|
||
for account, txs := range pending {
|
||
dump := make(map[string]*RPCTransaction)
|
||
for _, tx := range txs {
|
||
dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
|
||
}
|
||
|
||
content["pending"][account.Hex()] = dump
|
||
}
|
||
// Flatten the queued transactions
|
||
for account, txs := range queue {
|
||
dump := make(map[string]*RPCTransaction)
|
||
for _, tx := range txs {
|
||
dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
|
||
}
|
||
|
||
content["queued"][account.Hex()] = dump
|
||
}
|
||
|
||
return content
|
||
}
|
||
|
||
// ContentFrom returns the transactions contained within the transaction pool.
|
||
func (s *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCTransaction {
|
||
content := make(map[string]map[string]*RPCTransaction, 2)
|
||
pending, queue := s.b.TxPoolContentFrom(addr)
|
||
curHeader := s.b.CurrentHeader()
|
||
|
||
// Build the pending transactions
|
||
dump := make(map[string]*RPCTransaction, len(pending))
|
||
for _, tx := range pending {
|
||
dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
|
||
}
|
||
|
||
content["pending"] = dump
|
||
|
||
// Build the queued transactions
|
||
dump = make(map[string]*RPCTransaction, len(queue))
|
||
for _, tx := range queue {
|
||
dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
|
||
}
|
||
|
||
content["queued"] = dump
|
||
|
||
return content
|
||
}
|
||
|
||
// Status returns the number of pending and queued transaction in the pool.
|
||
func (s *TxPoolAPI) Status() map[string]hexutil.Uint {
|
||
pending, queue := s.b.Stats()
|
||
|
||
return map[string]hexutil.Uint{
|
||
"pending": hexutil.Uint(pending),
|
||
"queued": hexutil.Uint(queue),
|
||
}
|
||
}
|
||
|
||
// Inspect retrieves the content of the transaction pool and flattens it into an
|
||
// easily inspectable list.
|
||
func (s *TxPoolAPI) Inspect() map[string]map[string]map[string]string {
|
||
content := map[string]map[string]map[string]string{
|
||
"pending": make(map[string]map[string]string),
|
||
"queued": make(map[string]map[string]string),
|
||
}
|
||
pending, queue := s.b.TxPoolContent()
|
||
|
||
// Define a formatter to flatten a transaction into a string
|
||
var format = func(tx *types.Transaction) string {
|
||
if to := tx.To(); to != nil {
|
||
return fmt.Sprintf("%s: %v wei + %v gas × %v wei", tx.To().Hex(), tx.Value(), tx.Gas(), tx.GasPrice())
|
||
}
|
||
return fmt.Sprintf("contract creation: %v wei + %v gas × %v wei", tx.Value(), tx.Gas(), tx.GasPrice())
|
||
}
|
||
// Flatten the pending transactions
|
||
for account, txs := range pending {
|
||
dump := make(map[string]string)
|
||
for _, tx := range txs {
|
||
dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx)
|
||
}
|
||
|
||
content["pending"][account.Hex()] = dump
|
||
}
|
||
// Flatten the queued transactions
|
||
for account, txs := range queue {
|
||
dump := make(map[string]string)
|
||
for _, tx := range txs {
|
||
dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx)
|
||
}
|
||
|
||
content["queued"][account.Hex()] = dump
|
||
}
|
||
|
||
return content
|
||
}
|
||
|
||
// EthereumAccountAPI provides an API to access accounts managed by this node.
|
||
// It offers only methods that can retrieve accounts.
|
||
type EthereumAccountAPI struct {
|
||
am *accounts.Manager
|
||
}
|
||
|
||
// NewEthereumAccountAPI creates a new EthereumAccountAPI.
|
||
func NewEthereumAccountAPI(am *accounts.Manager) *EthereumAccountAPI {
|
||
return &EthereumAccountAPI{am: am}
|
||
}
|
||
|
||
// Accounts returns the collection of accounts this node manages.
|
||
func (s *EthereumAccountAPI) Accounts() []common.Address {
|
||
return s.am.Accounts()
|
||
}
|
||
|
||
// PersonalAccountAPI provides an API to access accounts managed by this node.
|
||
// It offers methods to create, (un)lock en list accounts. Some methods accept
|
||
// passwords and are therefore considered private by default.
|
||
type PersonalAccountAPI struct {
|
||
am *accounts.Manager
|
||
nonceLock *AddrLocker
|
||
b Backend
|
||
}
|
||
|
||
// NewPersonalAccountAPI create a new PersonalAccountAPI.
|
||
func NewPersonalAccountAPI(b Backend, nonceLock *AddrLocker) *PersonalAccountAPI {
|
||
return &PersonalAccountAPI{
|
||
am: b.AccountManager(),
|
||
nonceLock: nonceLock,
|
||
b: b,
|
||
}
|
||
}
|
||
|
||
// ListAccounts will return a list of addresses for accounts this node manages.
|
||
func (s *PersonalAccountAPI) ListAccounts() []common.Address {
|
||
return s.am.Accounts()
|
||
}
|
||
|
||
// rawWallet is a JSON representation of an accounts.Wallet interface, with its
|
||
// data contents extracted into plain fields.
|
||
type rawWallet struct {
|
||
URL string `json:"url"`
|
||
Status string `json:"status"`
|
||
Failure string `json:"failure,omitempty"`
|
||
Accounts []accounts.Account `json:"accounts,omitempty"`
|
||
}
|
||
|
||
// ListWallets will return a list of wallets this node manages.
|
||
func (s *PersonalAccountAPI) ListWallets() []rawWallet {
|
||
wallets := make([]rawWallet, 0) // return [] instead of nil if empty
|
||
|
||
for _, wallet := range s.am.Wallets() {
|
||
status, failure := wallet.Status()
|
||
|
||
raw := rawWallet{
|
||
URL: wallet.URL().String(),
|
||
Status: status,
|
||
Accounts: wallet.Accounts(),
|
||
}
|
||
if failure != nil {
|
||
raw.Failure = failure.Error()
|
||
}
|
||
|
||
wallets = append(wallets, raw)
|
||
}
|
||
|
||
return wallets
|
||
}
|
||
|
||
// OpenWallet initiates a hardware wallet opening procedure, establishing a USB
|
||
// connection and attempting to authenticate via the provided passphrase. Note,
|
||
// the method may return an extra challenge requiring a second open (e.g. the
|
||
// Trezor PIN matrix challenge).
|
||
func (s *PersonalAccountAPI) OpenWallet(url string, passphrase *string) error {
|
||
wallet, err := s.am.Wallet(url)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
pass := ""
|
||
if passphrase != nil {
|
||
pass = *passphrase
|
||
}
|
||
|
||
return wallet.Open(pass)
|
||
}
|
||
|
||
// DeriveAccount requests an HD wallet to derive a new account, optionally pinning
|
||
// it for later reuse.
|
||
func (s *PersonalAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
|
||
wallet, err := s.am.Wallet(url)
|
||
if err != nil {
|
||
return accounts.Account{}, err
|
||
}
|
||
|
||
derivPath, err := accounts.ParseDerivationPath(path)
|
||
if err != nil {
|
||
return accounts.Account{}, err
|
||
}
|
||
|
||
if pin == nil {
|
||
pin = new(bool)
|
||
}
|
||
|
||
return wallet.Derive(derivPath, *pin)
|
||
}
|
||
|
||
// NewAccount will create a new account and returns the address for the new account.
|
||
func (s *PersonalAccountAPI) NewAccount(password string) (common.AddressEIP55, error) {
|
||
ks, err := fetchKeystore(s.am)
|
||
if err != nil {
|
||
return common.AddressEIP55{}, err
|
||
}
|
||
|
||
acc, err := ks.NewAccount(password)
|
||
if err == nil {
|
||
addrEIP55 := common.AddressEIP55(acc.Address)
|
||
log.Info("Your new key was generated", "address", addrEIP55.String())
|
||
log.Warn("Please backup your key file!", "path", acc.URL.Path)
|
||
log.Warn("Please remember your password!")
|
||
return addrEIP55, nil
|
||
}
|
||
return common.AddressEIP55{}, err
|
||
}
|
||
|
||
// fetchKeystore retrieves the encrypted keystore from the account manager.
|
||
func fetchKeystore(am *accounts.Manager) (*keystore.KeyStore, error) {
|
||
if ks := am.Backends(keystore.KeyStoreType); len(ks) > 0 {
|
||
return ks[0].(*keystore.KeyStore), nil
|
||
}
|
||
|
||
return nil, errors.New("local keystore not used")
|
||
}
|
||
|
||
// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
|
||
// encrypting it with the passphrase.
|
||
func (s *PersonalAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
|
||
key, err := crypto.HexToECDSA(privkey)
|
||
if err != nil {
|
||
return common.Address{}, err
|
||
}
|
||
|
||
ks, err := fetchKeystore(s.am)
|
||
if err != nil {
|
||
return common.Address{}, err
|
||
}
|
||
|
||
acc, err := ks.ImportECDSA(key, password)
|
||
|
||
return acc.Address, err
|
||
}
|
||
|
||
// UnlockAccount will unlock the account associated with the given address with
|
||
// the given password for duration seconds. If duration is nil it will use a
|
||
// default of 300 seconds. It returns an indication if the account was unlocked.
|
||
func (s *PersonalAccountAPI) UnlockAccount(ctx context.Context, addr common.Address, password string, duration *uint64) (bool, error) {
|
||
// When the API is exposed by external RPC(http, ws etc), unless the user
|
||
// explicitly specifies to allow the insecure account unlocking, otherwise
|
||
// it is disabled.
|
||
if s.b.ExtRPCEnabled() && !s.b.AccountManager().Config().InsecureUnlockAllowed {
|
||
return false, errors.New("account unlock with HTTP access is forbidden")
|
||
}
|
||
|
||
const max = uint64(time.Duration(math.MaxInt64) / time.Second)
|
||
|
||
var d time.Duration
|
||
if duration == nil {
|
||
d = 300 * time.Second
|
||
} else if *duration > max {
|
||
return false, errors.New("unlock duration too large")
|
||
} else {
|
||
d = time.Duration(*duration) * time.Second
|
||
}
|
||
|
||
ks, err := fetchKeystore(s.am)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
err = ks.TimedUnlock(accounts.Account{Address: addr}, password, d)
|
||
if err != nil {
|
||
log.Warn("Failed account unlock attempt", "address", addr, "err", err)
|
||
}
|
||
|
||
return err == nil, err
|
||
}
|
||
|
||
// LockAccount will lock the account associated with the given address when it's unlocked.
|
||
func (s *PersonalAccountAPI) LockAccount(addr common.Address) bool {
|
||
if ks, err := fetchKeystore(s.am); err == nil {
|
||
return ks.Lock(addr) == nil
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// signTransaction sets defaults and signs the given transaction
|
||
// NOTE: the caller needs to ensure that the nonceLock is held, if applicable,
|
||
// and release it after the transaction has been submitted to the tx pool
|
||
func (s *PersonalAccountAPI) signTransaction(ctx context.Context, args *TransactionArgs, passwd string) (*types.Transaction, error) {
|
||
// Look up the wallet containing the requested signer
|
||
account := accounts.Account{Address: args.from()}
|
||
|
||
wallet, err := s.am.Find(account)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// Set some sanity defaults and terminate on failure
|
||
if err := args.setDefaults(ctx, s.b); err != nil {
|
||
return nil, err
|
||
}
|
||
// Assemble the transaction and sign with the wallet
|
||
tx := args.toTransaction()
|
||
|
||
return wallet.SignTxWithPassphrase(account, passwd, tx, s.b.ChainConfig().ChainID)
|
||
}
|
||
|
||
// SendTransaction will create a transaction from the given arguments and
|
||
// tries to sign it with the key associated with args.From. If the given
|
||
// passwd isn't able to decrypt the key it fails.
|
||
func (s *PersonalAccountAPI) SendTransaction(ctx context.Context, args TransactionArgs, passwd string) (common.Hash, error) {
|
||
if args.Nonce == nil {
|
||
// Hold the mutex around signing to prevent concurrent assignment of
|
||
// the same nonce to multiple accounts.
|
||
s.nonceLock.LockAddr(args.from())
|
||
defer s.nonceLock.UnlockAddr(args.from())
|
||
}
|
||
|
||
signed, err := s.signTransaction(ctx, &args, passwd)
|
||
if err != nil {
|
||
log.Warn("Failed transaction send attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err)
|
||
return common.Hash{}, err
|
||
}
|
||
|
||
return SubmitTransaction(ctx, s.b, signed)
|
||
}
|
||
|
||
// SignTransaction will create a transaction from the given arguments and
|
||
// tries to sign it with the key associated with args.From. If the given passwd isn't
|
||
// able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast
|
||
// to other nodes
|
||
func (s *PersonalAccountAPI) SignTransaction(ctx context.Context, args TransactionArgs, passwd string) (*SignTransactionResult, error) {
|
||
// No need to obtain the noncelock mutex, since we won't be sending this
|
||
// tx into the transaction pool, but right back to the user
|
||
if args.From == nil {
|
||
return nil, errors.New("sender not specified")
|
||
}
|
||
|
||
if args.Gas == nil {
|
||
return nil, errors.New("gas not specified")
|
||
}
|
||
|
||
if args.GasPrice == nil && (args.MaxFeePerGas == nil || args.MaxPriorityFeePerGas == nil) {
|
||
return nil, errors.New("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas")
|
||
}
|
||
|
||
if args.Nonce == nil {
|
||
return nil, errors.New("nonce not specified")
|
||
}
|
||
// Before actually signing the transaction, ensure the transaction fee is reasonable.
|
||
tx := args.toTransaction()
|
||
if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
signed, err := s.signTransaction(ctx, &args, passwd)
|
||
if err != nil {
|
||
log.Warn("Failed transaction sign attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err)
|
||
return nil, err
|
||
}
|
||
|
||
data, err := signed.MarshalBinary()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &SignTransactionResult{data, signed}, nil
|
||
}
|
||
|
||
// Sign calculates an Ethereum ECDSA signature for:
|
||
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))
|
||
//
|
||
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
|
||
// where the V value will be 27 or 28 for legacy reasons.
|
||
//
|
||
// The key used to calculate the signature is decrypted with the given password.
|
||
//
|
||
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
|
||
func (s *PersonalAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
|
||
// Look up the wallet containing the requested signer
|
||
account := accounts.Account{Address: addr}
|
||
|
||
wallet, err := s.b.AccountManager().Find(account)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// Assemble sign the data with the wallet
|
||
signature, err := wallet.SignTextWithPassphrase(account, passwd, data)
|
||
if err != nil {
|
||
log.Warn("Failed data sign attempt", "address", addr, "err", err)
|
||
return nil, err
|
||
}
|
||
|
||
signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||
|
||
return signature, nil
|
||
}
|
||
|
||
// EcRecover returns the address for the account that was used to create the signature.
|
||
// Note, this function is compatible with eth_sign and personal_sign. As such it recovers
|
||
// the address of:
|
||
// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
|
||
// addr = ecrecover(hash, signature)
|
||
//
|
||
// Note, the signature must conform to the secp256k1 curve R, S and V values, where
|
||
// the V value must be 27 or 28 for legacy reasons.
|
||
//
|
||
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover
|
||
func (s *PersonalAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
|
||
if len(sig) != crypto.SignatureLength {
|
||
return common.Address{}, fmt.Errorf("signature must be %d bytes long", crypto.SignatureLength)
|
||
}
|
||
|
||
if sig[crypto.RecoveryIDOffset] != 27 && sig[crypto.RecoveryIDOffset] != 28 {
|
||
return common.Address{}, errors.New("invalid Ethereum signature (V is not 27 or 28)")
|
||
}
|
||
|
||
sig[crypto.RecoveryIDOffset] -= 27 // Transform yellow paper V from 27/28 to 0/1
|
||
|
||
rpk, err := crypto.SigToPub(accounts.TextHash(data), sig)
|
||
if err != nil {
|
||
return common.Address{}, err
|
||
}
|
||
|
||
return crypto.PubkeyToAddress(*rpk), nil
|
||
}
|
||
|
||
// InitializeWallet initializes a new wallet at the provided URL, by generating and returning a new private key.
|
||
func (s *PersonalAccountAPI) InitializeWallet(ctx context.Context, url string) (string, error) {
|
||
wallet, err := s.am.Wallet(url)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
entropy, err := bip39.NewEntropy(256)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
mnemonic, err := bip39.NewMnemonic(entropy)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
seed := bip39.NewSeed(mnemonic, "")
|
||
|
||
switch wallet := wallet.(type) {
|
||
case *scwallet.Wallet:
|
||
return mnemonic, wallet.Initialize(seed)
|
||
default:
|
||
return "", errors.New("specified wallet does not support initialization")
|
||
}
|
||
}
|
||
|
||
// Unpair deletes a pairing between wallet and geth.
|
||
func (s *PersonalAccountAPI) Unpair(ctx context.Context, url string, pin string) error {
|
||
wallet, err := s.am.Wallet(url)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
switch wallet := wallet.(type) {
|
||
case *scwallet.Wallet:
|
||
return wallet.Unpair([]byte(pin))
|
||
default:
|
||
return errors.New("specified wallet does not support pairing")
|
||
}
|
||
}
|
||
|
||
// BlockChainAPI provides an API to access Ethereum blockchain data.
|
||
type BlockChainAPI struct {
|
||
b Backend
|
||
}
|
||
|
||
// NewBlockChainAPI creates a new Ethereum blockchain API.
|
||
func NewBlockChainAPI(b Backend) *BlockChainAPI {
|
||
return &BlockChainAPI{b}
|
||
}
|
||
|
||
// GetTransactionReceiptsByBlock returns the transaction receipts for the given block number or hash.
|
||
func (s *BlockChainAPI) GetTransactionReceiptsByBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
||
block, err := s.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if block == nil {
|
||
return nil, errors.New("block not found")
|
||
}
|
||
|
||
receipts, err := s.b.GetReceipts(ctx, block.Hash())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
txs := block.Transactions()
|
||
|
||
var txHash common.Hash
|
||
|
||
borReceipt := rawdb.ReadBorReceipt(s.b.ChainDb(), block.Hash(), block.NumberU64(), s.b.ChainConfig())
|
||
if borReceipt != nil {
|
||
receipts = append(receipts, borReceipt)
|
||
|
||
txHash = types.GetDerivedBorTxHash(types.BorReceiptKey(block.Number().Uint64(), block.Hash()))
|
||
if txHash != (common.Hash{}) {
|
||
borTx, _, _, _, _ := s.b.GetBorBlockTransactionWithBlockHash(ctx, txHash, block.Hash())
|
||
txs = append(txs, borTx)
|
||
}
|
||
}
|
||
|
||
if len(txs) != len(receipts) {
|
||
return nil, fmt.Errorf("txs length %d doesn't equal to receipts' length %d", len(txs), len(receipts))
|
||
}
|
||
|
||
txReceipts := make([]map[string]interface{}, 0, len(txs))
|
||
|
||
for idx, receipt := range receipts {
|
||
tx := txs[idx]
|
||
|
||
signer := types.MakeSigner(s.b.ChainConfig(), block.Number(), block.Time())
|
||
from, _ := types.Sender(signer, tx)
|
||
|
||
fields := map[string]interface{}{
|
||
"blockHash": block.Hash(),
|
||
"blockNumber": hexutil.Uint64(block.NumberU64()),
|
||
"transactionHash": tx.Hash(),
|
||
"transactionIndex": hexutil.Uint64(idx),
|
||
"from": from,
|
||
"to": tx.To(),
|
||
"gasUsed": hexutil.Uint64(receipt.GasUsed),
|
||
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
|
||
"contractAddress": nil,
|
||
"logs": receipt.Logs,
|
||
"logsBloom": receipt.Bloom,
|
||
"type": hexutil.Uint(tx.Type()),
|
||
"effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
|
||
}
|
||
|
||
if receipt.EffectiveGasPrice == nil {
|
||
fields["effectiveGasPrice"] = new(hexutil.Big)
|
||
}
|
||
|
||
// Assign receipt status or post state.
|
||
if len(receipt.PostState) > 0 {
|
||
fields["root"] = hexutil.Bytes(receipt.PostState)
|
||
} else {
|
||
fields["status"] = hexutil.Uint(receipt.Status)
|
||
}
|
||
|
||
if receipt.Logs == nil {
|
||
fields["logs"] = []*types.Log{}
|
||
}
|
||
|
||
if borReceipt != nil && idx == len(receipts)-1 {
|
||
fields["transactionHash"] = txHash
|
||
}
|
||
|
||
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
|
||
if receipt.ContractAddress != (common.Address{}) {
|
||
fields["contractAddress"] = receipt.ContractAddress
|
||
}
|
||
|
||
txReceipts = append(txReceipts, fields)
|
||
}
|
||
|
||
return txReceipts, nil
|
||
}
|
||
|
||
// ChainId is the EIP-155 replay-protection chain id for the current Ethereum chain config.
|
||
//
|
||
// Note, this method does not conform to EIP-695 because the configured chain ID is always
|
||
// returned, regardless of the current head block. We used to return an error when the chain
|
||
// wasn't synced up to a block where EIP-155 is enabled, but this behavior caused issues
|
||
// in CL clients.
|
||
func (api *BlockChainAPI) ChainId() *hexutil.Big {
|
||
return (*hexutil.Big)(api.b.ChainConfig().ChainID)
|
||
}
|
||
|
||
// BlockNumber returns the block number of the chain head.
|
||
func (s *BlockChainAPI) BlockNumber() hexutil.Uint64 {
|
||
header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available
|
||
return hexutil.Uint64(header.Number.Uint64())
|
||
}
|
||
|
||
// GetBalance returns the amount of wei for the given address in the state of the
|
||
// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
|
||
// block numbers are also allowed.
|
||
func (s *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
|
||
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
if state == nil || err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return (*hexutil.Big)(state.GetBalance(address)), state.Error()
|
||
}
|
||
|
||
// Result structs for GetProof
|
||
type AccountResult struct {
|
||
Address common.Address `json:"address"`
|
||
AccountProof []string `json:"accountProof"`
|
||
Balance *hexutil.Big `json:"balance"`
|
||
CodeHash common.Hash `json:"codeHash"`
|
||
Nonce hexutil.Uint64 `json:"nonce"`
|
||
StorageHash common.Hash `json:"storageHash"`
|
||
StorageProof []StorageResult `json:"storageProof"`
|
||
}
|
||
|
||
type StorageResult struct {
|
||
Key string `json:"key"`
|
||
Value *hexutil.Big `json:"value"`
|
||
Proof []string `json:"proof"`
|
||
}
|
||
|
||
// proofList implements ethdb.KeyValueWriter and collects the proofs as
|
||
// hex-strings for delivery to rpc-caller.
|
||
type proofList []string
|
||
|
||
func (n *proofList) Put(key []byte, value []byte) error {
|
||
*n = append(*n, hexutil.Encode(value))
|
||
return nil
|
||
}
|
||
|
||
func (n *proofList) Delete(key []byte) error {
|
||
panic("not supported")
|
||
}
|
||
|
||
// GetProof returns the Merkle-proof for a given account and optionally some storage keys.
|
||
func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
|
||
var (
|
||
keys = make([]common.Hash, len(storageKeys))
|
||
keyLengths = make([]int, len(storageKeys))
|
||
storageProof = make([]StorageResult, len(storageKeys))
|
||
)
|
||
// Deserialize all keys. This prevents state access on invalid input.
|
||
for i, hexKey := range storageKeys {
|
||
var err error
|
||
keys[i], keyLengths[i], err = decodeHash(hexKey)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
statedb, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
if statedb == nil || err != nil {
|
||
return nil, err
|
||
}
|
||
codeHash := statedb.GetCodeHash(address)
|
||
storageRoot := statedb.GetStorageRoot(address)
|
||
|
||
if len(keys) > 0 {
|
||
var storageTrie state.Trie
|
||
if storageRoot != types.EmptyRootHash && storageRoot != (common.Hash{}) {
|
||
id := trie.StorageTrieID(header.Root, crypto.Keccak256Hash(address.Bytes()), storageRoot)
|
||
st, err := trie.NewStateTrie(id, statedb.Database().TrieDB())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
storageTrie = st
|
||
}
|
||
// Create the proofs for the storageKeys.
|
||
for i, key := range keys {
|
||
// Output key encoding is a bit special: if the input was a 32-byte hash, it is
|
||
// returned as such. Otherwise, we apply the QUANTITY encoding mandated by the
|
||
// JSON-RPC spec for getProof. This behavior exists to preserve backwards
|
||
// compatibility with older client versions.
|
||
var outputKey string
|
||
if keyLengths[i] != 32 {
|
||
outputKey = hexutil.EncodeBig(key.Big())
|
||
} else {
|
||
outputKey = hexutil.Encode(key[:])
|
||
}
|
||
if storageTrie == nil {
|
||
storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}}
|
||
continue
|
||
}
|
||
var proof proofList
|
||
if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), &proof); err != nil {
|
||
return nil, err
|
||
}
|
||
value := (*hexutil.Big)(statedb.GetState(address, key).Big())
|
||
storageProof[i] = StorageResult{outputKey, value, proof}
|
||
}
|
||
}
|
||
// Create the accountProof.
|
||
tr, err := trie.NewStateTrie(trie.StateTrieID(header.Root), statedb.Database().TrieDB())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var accountProof proofList
|
||
if err := tr.Prove(crypto.Keccak256(address.Bytes()), &accountProof); err != nil {
|
||
return nil, err
|
||
}
|
||
return &AccountResult{
|
||
Address: address,
|
||
AccountProof: accountProof,
|
||
Balance: (*hexutil.Big)(statedb.GetBalance(address)),
|
||
CodeHash: codeHash,
|
||
Nonce: hexutil.Uint64(statedb.GetNonce(address)),
|
||
StorageHash: storageRoot,
|
||
StorageProof: storageProof,
|
||
}, statedb.Error()
|
||
}
|
||
|
||
// decodeHash parses a hex-encoded 32-byte hash. The input may optionally
|
||
// be prefixed by 0x and can have a byte length up to 32.
|
||
func decodeHash(s string) (h common.Hash, inputLength int, err error) {
|
||
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
|
||
s = s[2:]
|
||
}
|
||
|
||
if (len(s) & 1) > 0 {
|
||
s = "0" + s
|
||
}
|
||
|
||
b, err := hex.DecodeString(s)
|
||
if err != nil {
|
||
return common.Hash{}, 0, errors.New("hex string invalid")
|
||
}
|
||
|
||
if len(b) > 32 {
|
||
return common.Hash{}, len(b), errors.New("hex string too long, want at most 32 bytes")
|
||
}
|
||
return common.BytesToHash(b), len(b), nil
|
||
}
|
||
|
||
// GetHeaderByNumber returns the requested canonical block header.
|
||
// - When blockNr is -1 the chain pending header is returned.
|
||
// - When blockNr is -2 the chain latest header is returned.
|
||
// - When blockNr is -3 the chain finalized header is returned.
|
||
// - When blockNr is -4 the chain safe header is returned.
|
||
func (s *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
|
||
header, err := s.b.HeaderByNumber(ctx, number)
|
||
if header != nil && err == nil {
|
||
response := s.rpcMarshalHeader(ctx, header)
|
||
if number == rpc.PendingBlockNumber {
|
||
// Pending header need to nil out a few fields
|
||
for _, field := range []string{"hash", "nonce", "miner"} {
|
||
response[field] = nil
|
||
}
|
||
}
|
||
return response, err
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
// GetHeaderByHash returns the requested header by hash.
|
||
func (s *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} {
|
||
header, _ := s.b.HeaderByHash(ctx, hash)
|
||
if header != nil {
|
||
return s.rpcMarshalHeader(ctx, header)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetBlockByNumber returns the requested canonical block.
|
||
// - When blockNr is -1 the chain pending block is returned.
|
||
// - When blockNr is -2 the chain latest block is returned.
|
||
// - When blockNr is -3 the chain finalized block is returned.
|
||
// - When blockNr is -4 the chain safe block is returned.
|
||
// - When fullTx is true all transactions in the block are returned, otherwise
|
||
// only the transaction hash is returned.
|
||
func (s *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
|
||
block, err := s.b.BlockByNumber(ctx, number)
|
||
if block != nil && err == nil {
|
||
response, err := s.rpcMarshalBlock(ctx, block, true, fullTx)
|
||
if err == nil && number == rpc.PendingBlockNumber {
|
||
// Pending blocks need to nil out a few fields
|
||
for _, field := range []string{"hash", "nonce", "miner"} {
|
||
response[field] = nil
|
||
}
|
||
}
|
||
|
||
// append marshalled bor transaction
|
||
if err == nil && response != nil {
|
||
response = s.appendRPCMarshalBorTransaction(ctx, block, response, fullTx)
|
||
}
|
||
|
||
return response, err
|
||
}
|
||
|
||
return nil, err
|
||
}
|
||
|
||
// GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full
|
||
// detail, otherwise only the transaction hash is returned.
|
||
func (s *BlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) {
|
||
block, err := s.b.BlockByHash(ctx, hash)
|
||
if block != nil {
|
||
response, err := s.rpcMarshalBlock(ctx, block, true, fullTx)
|
||
// append marshalled bor transaction
|
||
if err == nil && response != nil {
|
||
return s.appendRPCMarshalBorTransaction(ctx, block, response, fullTx), err
|
||
}
|
||
|
||
return response, err
|
||
}
|
||
|
||
return nil, err
|
||
}
|
||
|
||
// GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index.
|
||
func (s *BlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) {
|
||
block, err := s.b.BlockByNumber(ctx, blockNr)
|
||
if block != nil {
|
||
uncles := block.Uncles()
|
||
if index >= hexutil.Uint(len(uncles)) {
|
||
log.Debug("Requested uncle not found", "number", blockNr, "hash", block.Hash(), "index", index)
|
||
return nil, nil
|
||
}
|
||
|
||
block = types.NewBlockWithHeader(uncles[index])
|
||
|
||
return s.rpcMarshalBlock(ctx, block, false, false)
|
||
}
|
||
|
||
return nil, err
|
||
}
|
||
|
||
// GetUncleByBlockHashAndIndex returns the uncle block for the given block hash and index.
|
||
func (s *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (map[string]interface{}, error) {
|
||
block, err := s.b.BlockByHash(ctx, blockHash)
|
||
if block != nil {
|
||
uncles := block.Uncles()
|
||
if index >= hexutil.Uint(len(uncles)) {
|
||
log.Debug("Requested uncle not found", "number", block.Number(), "hash", blockHash, "index", index)
|
||
return nil, nil
|
||
}
|
||
|
||
block = types.NewBlockWithHeader(uncles[index])
|
||
|
||
return s.rpcMarshalBlock(ctx, block, false, false)
|
||
}
|
||
|
||
return nil, err
|
||
}
|
||
|
||
// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number
|
||
func (s *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
|
||
if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
|
||
n := hexutil.Uint(len(block.Uncles()))
|
||
return &n
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash
|
||
func (s *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
|
||
if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
|
||
n := hexutil.Uint(len(block.Uncles()))
|
||
return &n
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetCode returns the code stored at the given address in the state for the given block number.
|
||
func (s *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
|
||
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
if state == nil || err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
code := state.GetCode(address)
|
||
|
||
return code, state.Error()
|
||
}
|
||
|
||
// GetStorageAt returns the storage from the state at the given address, key and
|
||
// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block
|
||
// numbers are also allowed.
|
||
func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, hexKey string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
|
||
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
if state == nil || err != nil {
|
||
return nil, err
|
||
}
|
||
key, _, err := decodeHash(hexKey)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("unable to decode storage key: %s", err)
|
||
}
|
||
|
||
res := state.GetState(address, key)
|
||
|
||
return res[:], state.Error()
|
||
}
|
||
|
||
// GetBlockReceipts returns the block receipts for the given block hash or number or tag.
|
||
func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
||
block, err := s.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||
if block == nil || err != nil {
|
||
// When the block doesn't exist, the RPC method should return JSON null
|
||
// as per specification.
|
||
return nil, nil
|
||
}
|
||
receipts, err := s.b.GetReceipts(ctx, block.Hash())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
txs := block.Transactions()
|
||
if len(txs) != len(receipts) {
|
||
return nil, fmt.Errorf("receipts length mismatch: %d vs %d", len(txs), len(receipts))
|
||
}
|
||
|
||
// Derive the sender.
|
||
signer := types.MakeSigner(s.b.ChainConfig(), block.Number(), block.Time())
|
||
|
||
result := make([]map[string]interface{}, len(receipts))
|
||
for i, receipt := range receipts {
|
||
result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i)
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// OverrideAccount indicates the overriding fields of account during the execution
|
||
// of a message call.
|
||
// Note, state and stateDiff can't be specified at the same time. If state is
|
||
// set, message execution will only use the data in the given state. Otherwise
|
||
// if statDiff is set, all diff will be applied first and then execute the call
|
||
// message.
|
||
type OverrideAccount struct {
|
||
Nonce *hexutil.Uint64 `json:"nonce"`
|
||
Code *hexutil.Bytes `json:"code"`
|
||
Balance **hexutil.Big `json:"balance"`
|
||
State *map[common.Hash]common.Hash `json:"state"`
|
||
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
|
||
}
|
||
|
||
// StateOverride is the collection of overridden accounts.
|
||
type StateOverride map[common.Address]OverrideAccount
|
||
|
||
// Apply overrides the fields of specified accounts into the given state.
|
||
func (diff *StateOverride) Apply(state *state.StateDB) error {
|
||
if diff == nil {
|
||
return nil
|
||
}
|
||
|
||
for addr, account := range *diff {
|
||
// Override account nonce.
|
||
if account.Nonce != nil {
|
||
state.SetNonce(addr, uint64(*account.Nonce))
|
||
}
|
||
// Override account(contract) code.
|
||
if account.Code != nil {
|
||
state.SetCode(addr, *account.Code)
|
||
}
|
||
// Override account balance.
|
||
if account.Balance != nil {
|
||
state.SetBalance(addr, (*big.Int)(*account.Balance))
|
||
}
|
||
|
||
if account.State != nil && account.StateDiff != nil {
|
||
return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
|
||
}
|
||
// Replace entire state if caller requires.
|
||
if account.State != nil {
|
||
state.SetStorage(addr, *account.State)
|
||
}
|
||
// Apply state diff into specified accounts.
|
||
if account.StateDiff != nil {
|
||
for key, value := range *account.StateDiff {
|
||
state.SetState(addr, key, value)
|
||
}
|
||
}
|
||
}
|
||
// Now finalize the changes. Finalize is normally performed between transactions.
|
||
// By using finalize, the overrides are semantically behaving as
|
||
// if they were created in a transaction just before the tracing occur.
|
||
state.Finalise(false)
|
||
|
||
return nil
|
||
}
|
||
|
||
// BlockOverrides is a set of header fields to override.
|
||
type BlockOverrides struct {
|
||
Number *hexutil.Big
|
||
Difficulty *hexutil.Big
|
||
Time *hexutil.Uint64
|
||
GasLimit *hexutil.Uint64
|
||
Coinbase *common.Address
|
||
Random *common.Hash
|
||
BaseFee *hexutil.Big
|
||
BlobBaseFee *hexutil.Big
|
||
}
|
||
|
||
// Apply overrides the given header fields into the given block context.
|
||
func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
|
||
if diff == nil {
|
||
return
|
||
}
|
||
|
||
if diff.Number != nil {
|
||
blockCtx.BlockNumber = diff.Number.ToInt()
|
||
}
|
||
|
||
if diff.Difficulty != nil {
|
||
blockCtx.Difficulty = diff.Difficulty.ToInt()
|
||
}
|
||
|
||
if diff.Time != nil {
|
||
blockCtx.Time = uint64(*diff.Time)
|
||
}
|
||
|
||
if diff.GasLimit != nil {
|
||
blockCtx.GasLimit = uint64(*diff.GasLimit)
|
||
}
|
||
|
||
if diff.Coinbase != nil {
|
||
blockCtx.Coinbase = *diff.Coinbase
|
||
}
|
||
|
||
if diff.Random != nil {
|
||
blockCtx.Random = diff.Random
|
||
}
|
||
|
||
if diff.BaseFee != nil {
|
||
blockCtx.BaseFee = diff.BaseFee.ToInt()
|
||
}
|
||
if diff.BlobBaseFee != nil {
|
||
blockCtx.BlobBaseFee = diff.BlobBaseFee.ToInt()
|
||
}
|
||
}
|
||
|
||
// ChainContextBackend provides methods required to implement ChainContext.
|
||
type ChainContextBackend interface {
|
||
Engine() consensus.Engine
|
||
HeaderByNumber(context.Context, rpc.BlockNumber) (*types.Header, error)
|
||
}
|
||
|
||
// ChainContext is an implementation of core.ChainContext. It's main use-case
|
||
// is instantiating a vm.BlockContext without having access to the BlockChain object.
|
||
type ChainContext struct {
|
||
b ChainContextBackend
|
||
ctx context.Context
|
||
}
|
||
|
||
// NewChainContext creates a new ChainContext object.
|
||
func NewChainContext(ctx context.Context, backend ChainContextBackend) *ChainContext {
|
||
return &ChainContext{ctx: ctx, b: backend}
|
||
}
|
||
|
||
func (context *ChainContext) Engine() consensus.Engine {
|
||
return context.b.Engine()
|
||
}
|
||
|
||
func (context *ChainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
|
||
// This method is called to get the hash for a block number when executing the BLOCKHASH
|
||
// opcode. Hence no need to search for non-canonical blocks.
|
||
header, err := context.b.HeaderByNumber(context.ctx, rpc.BlockNumber(number))
|
||
if err != nil || header.Hash() != hash {
|
||
return nil
|
||
}
|
||
return header
|
||
}
|
||
|
||
func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
|
||
if err := overrides.Apply(state); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return doCallWithState(ctx, b, args, header, state, timeout, globalGasCap, blockOverrides)
|
||
}
|
||
|
||
func doCallWithState(ctx context.Context, b Backend, args TransactionArgs, header *types.Header, state *state.StateDB, timeout time.Duration, globalGasCap uint64, blockOverrides *BlockOverrides) (*core.ExecutionResult, error) {
|
||
// Setup context so it may be cancelled the call has completed
|
||
// or, in case of unmetered gas, setup a context with a timeout.
|
||
var cancel context.CancelFunc
|
||
if timeout > 0 {
|
||
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||
} else {
|
||
ctx, cancel = context.WithCancel(ctx)
|
||
}
|
||
// Make sure the context is cancelled when the call has completed
|
||
// this makes sure resources are cleaned up.
|
||
defer cancel()
|
||
|
||
// Get a new instance of the EVM.
|
||
msg, err := args.ToMessage(globalGasCap, header.BaseFee)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
|
||
if blockOverrides != nil {
|
||
blockOverrides.Apply(&blockCtx)
|
||
}
|
||
evm := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx)
|
||
|
||
// Wait for the context to be done and cancel the evm. Even if the
|
||
// EVM has finished, cancelling may be done (repeatedly)
|
||
go func() {
|
||
<-ctx.Done()
|
||
evm.Cancel()
|
||
}()
|
||
|
||
// Execute the message.
|
||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
||
result, err := core.ApplyMessage(evm, msg, gp, context.Background())
|
||
if err := state.Error(); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// If the timer caused an abort, return an appropriate error message
|
||
if evm.Cancelled() {
|
||
return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
|
||
}
|
||
|
||
if err != nil {
|
||
return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.GasLimit)
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, state *state.StateDB, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
|
||
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
|
||
|
||
var (
|
||
header *types.Header
|
||
err error
|
||
)
|
||
|
||
// BOR: This is used by bor consensus to fetch data from genesis contracts for state-sync
|
||
// Fetch the state and header from blockNumberOrHash if it's coming from normal eth_call path.
|
||
if state == nil {
|
||
state, header, err = b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
if state == nil || err != nil {
|
||
return nil, err
|
||
}
|
||
} else {
|
||
// Fetch the header from the given blockNumberOrHash. Note that this path is only taken
|
||
// when we're doing a call from bor consensus to fetch data from genesis contracts. It's
|
||
// necessary to fetch header using header hash as we might be experiencing a reorg and there
|
||
// can be multiple headers with same number.
|
||
header, err = b.HeaderByHash(ctx, *blockNrOrHash.BlockHash)
|
||
if header == nil || err != nil {
|
||
log.Warn("Error fetching header on CallWithState", "err", err)
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
return doCall(ctx, b, args, state, header, overrides, blockOverrides, timeout, globalGasCap)
|
||
}
|
||
|
||
func newRevertError(revert []byte) *revertError {
|
||
err := vm.ErrExecutionReverted
|
||
|
||
reason, errUnpack := abi.UnpackRevert(revert)
|
||
if errUnpack == nil {
|
||
err = fmt.Errorf("%w: %v", vm.ErrExecutionReverted, reason)
|
||
}
|
||
|
||
return &revertError{
|
||
error: err,
|
||
reason: hexutil.Encode(revert),
|
||
}
|
||
}
|
||
|
||
// revertError is an API error that encompasses an EVM revertal with JSON error
|
||
// code and a binary data blob.
|
||
type revertError struct {
|
||
error
|
||
reason string // revert reason hex encoded
|
||
}
|
||
|
||
// ErrorCode returns the JSON error code for a revertal.
|
||
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
|
||
func (e *revertError) ErrorCode() int {
|
||
return 3
|
||
}
|
||
|
||
// ErrorData returns the hex encoded revert reason.
|
||
func (e *revertError) ErrorData() interface{} {
|
||
return e.reason
|
||
}
|
||
|
||
// Call executes the given transaction on the state for the given block number.
|
||
//
|
||
// Additionally, the caller can specify a batch of contract for fields overriding.
|
||
//
|
||
// Note, this function doesn't make and changes in the state/blockchain and is
|
||
// useful to execute and retrieve values.
|
||
func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) {
|
||
return s.CallWithState(ctx, args, *blockNrOrHash, nil, overrides, blockOverrides)
|
||
}
|
||
|
||
// CallWithState executes the given transaction on the given state for
|
||
// the given block number. Note that as it does an EVM call, fields in
|
||
// the underlying state will change. Make sure to handle it outside of
|
||
// this function (ideally by sending a copy of state).
|
||
//
|
||
// Additionally, the caller can specify a batch of contract for fields overriding.
|
||
//
|
||
// Note, this function doesn't make and changes in the state/blockchain and is
|
||
// useful to execute and retrieve values.
|
||
func (s *BlockChainAPI) CallWithState(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, state *state.StateDB, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) {
|
||
result, err := DoCall(ctx, s.b, args, blockNrOrHash, state, overrides, blockOverrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if int(s.b.RPCRpcReturnDataLimit()) > 0 && len(result.ReturnData) > int(s.b.RPCRpcReturnDataLimit()) {
|
||
return nil, fmt.Errorf("call returned result of length %d exceeding limit %d", len(result.ReturnData), int(s.b.RPCRpcReturnDataLimit()))
|
||
}
|
||
|
||
// If the result contains a revert reason, try to unpack and return it.
|
||
if len(result.Revert()) > 0 {
|
||
return nil, newRevertError(result.Revert())
|
||
}
|
||
|
||
return result.Return(), result.Err
|
||
}
|
||
|
||
// DoEstimateGas returns the lowest possible gas limit that allows the transaction to run
|
||
// successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if
|
||
// there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil &
|
||
// non-zero) and `gasCap` (if non-zero).
|
||
func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, gasCap uint64) (hexutil.Uint64, error) {
|
||
// Retrieve the base state and mutate it with any overrides
|
||
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
if state == nil || err != nil {
|
||
return 0, err
|
||
}
|
||
if err = overrides.Apply(state); err != nil {
|
||
return 0, err
|
||
}
|
||
// Construct the gas estimator option from the user input
|
||
opts := &gasestimator.Options{
|
||
Config: b.ChainConfig(),
|
||
Chain: NewChainContext(ctx, b),
|
||
Header: header,
|
||
State: state,
|
||
ErrorRatio: estimateGasErrorRatio,
|
||
}
|
||
// Run the gas estimation andwrap any revertals into a custom return
|
||
call, err := args.ToMessage(gasCap, header.BaseFee)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
|
||
if err != nil {
|
||
if len(revert) > 0 {
|
||
return 0, newRevertError(revert)
|
||
}
|
||
return 0, err
|
||
}
|
||
return hexutil.Uint64(estimate), nil
|
||
}
|
||
|
||
// EstimateGas returns the lowest possible gas limit that allows the transaction to run
|
||
// successfully at block `blockNrOrHash`, or the latest block if `blockNrOrHash` is unspecified. It
|
||
// returns error if the transaction would revert or if there are unexpected failures. The returned
|
||
// value is capped by both `args.Gas` (if non-nil & non-zero) and the backend's RPCGasCap
|
||
// configuration (if non-zero).
|
||
func (s *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Uint64, error) {
|
||
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||
if blockNrOrHash != nil {
|
||
bNrOrHash = *blockNrOrHash
|
||
}
|
||
return DoEstimateGas(ctx, s.b, args, bNrOrHash, overrides, s.b.RPCGasCap())
|
||
}
|
||
|
||
// ExecutionResult groups all structured logs emitted by the EVM
|
||
// while replaying a transaction in debug mode as well as transaction
|
||
// execution status, the amount of gas used and the return value
|
||
type ExecutionResult struct {
|
||
Gas uint64 `json:"gas"`
|
||
Failed bool `json:"failed"`
|
||
ReturnValue string `json:"returnValue"`
|
||
StructLogs []StructLogRes `json:"structLogs"`
|
||
}
|
||
|
||
// StructLogRes stores a structured log emitted by the EVM while replaying a
|
||
// transaction in debug mode
|
||
type StructLogRes struct {
|
||
Pc uint64 `json:"pc"`
|
||
Op string `json:"op"`
|
||
Gas uint64 `json:"gas"`
|
||
GasCost uint64 `json:"gasCost"`
|
||
Depth int `json:"depth"`
|
||
Error string `json:"error,omitempty"`
|
||
Stack *[]string `json:"stack,omitempty"`
|
||
Memory *[]string `json:"memory,omitempty"`
|
||
Storage *map[string]string `json:"storage,omitempty"`
|
||
}
|
||
|
||
// FormatLogs formats EVM returned structured logs for json output
|
||
func FormatLogs(logs []logger.StructLog) []StructLogRes {
|
||
formatted := make([]StructLogRes, len(logs))
|
||
for index, trace := range logs {
|
||
formatted[index] = StructLogRes{
|
||
Pc: trace.Pc,
|
||
Op: trace.Op.String(),
|
||
Gas: trace.Gas,
|
||
GasCost: trace.GasCost,
|
||
Depth: trace.Depth,
|
||
Error: trace.ErrorString(),
|
||
}
|
||
|
||
if trace.Stack != nil {
|
||
stack := make([]string, len(trace.Stack))
|
||
for i, stackValue := range trace.Stack {
|
||
stack[i] = stackValue.Hex()
|
||
}
|
||
|
||
formatted[index].Stack = &stack
|
||
}
|
||
|
||
if trace.Memory != nil {
|
||
memory := make([]string, 0, (len(trace.Memory)+31)/32)
|
||
for i := 0; i+32 <= len(trace.Memory); i += 32 {
|
||
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
|
||
}
|
||
|
||
formatted[index].Memory = &memory
|
||
}
|
||
|
||
if trace.Storage != nil {
|
||
storage := make(map[string]string)
|
||
for i, storageValue := range trace.Storage {
|
||
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
|
||
}
|
||
|
||
formatted[index].Storage = &storage
|
||
}
|
||
}
|
||
|
||
return formatted
|
||
}
|
||
|
||
// RPCMarshalHeader converts the given header to the RPC output .
|
||
func RPCMarshalHeader(head *types.Header) map[string]interface{} {
|
||
result := map[string]interface{}{
|
||
"number": (*hexutil.Big)(head.Number),
|
||
"hash": head.Hash(),
|
||
"parentHash": head.ParentHash,
|
||
"nonce": head.Nonce,
|
||
"mixHash": head.MixDigest,
|
||
"sha3Uncles": head.UncleHash,
|
||
"logsBloom": head.Bloom,
|
||
"stateRoot": head.Root,
|
||
"miner": head.Coinbase,
|
||
"difficulty": (*hexutil.Big)(head.Difficulty),
|
||
"extraData": hexutil.Bytes(head.Extra),
|
||
"gasLimit": hexutil.Uint64(head.GasLimit),
|
||
"gasUsed": hexutil.Uint64(head.GasUsed),
|
||
"timestamp": hexutil.Uint64(head.Time),
|
||
"transactionsRoot": head.TxHash,
|
||
"receiptsRoot": head.ReceiptHash,
|
||
}
|
||
if head.BaseFee != nil {
|
||
result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee)
|
||
}
|
||
if head.WithdrawalsHash != nil {
|
||
result["withdrawalsRoot"] = head.WithdrawalsHash
|
||
}
|
||
if head.BlobGasUsed != nil {
|
||
result["blobGasUsed"] = hexutil.Uint64(*head.BlobGasUsed)
|
||
}
|
||
if head.ExcessBlobGas != nil {
|
||
result["excessBlobGas"] = hexutil.Uint64(*head.ExcessBlobGas)
|
||
}
|
||
if head.ParentBeaconRoot != nil {
|
||
result["parentBeaconBlockRoot"] = head.ParentBeaconRoot
|
||
}
|
||
return result
|
||
}
|
||
|
||
// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are
|
||
// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain
|
||
// transaction hashes.
|
||
func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *params.ChainConfig, db ethdb.Database) map[string]interface{} {
|
||
fields := RPCMarshalHeader(block.Header())
|
||
fields["size"] = hexutil.Uint64(block.Size())
|
||
|
||
if inclTx {
|
||
formatTx := func(idx int, tx *types.Transaction) interface{} {
|
||
return tx.Hash()
|
||
}
|
||
if fullTx {
|
||
formatTx = func(idx int, tx *types.Transaction) interface{} {
|
||
return newRPCTransactionFromBlockIndex(block, uint64(idx), config, db)
|
||
}
|
||
}
|
||
|
||
txs := block.Transactions()
|
||
transactions := make([]interface{}, len(txs))
|
||
for i, tx := range txs {
|
||
transactions[i] = formatTx(i, tx)
|
||
}
|
||
|
||
fields["transactions"] = transactions
|
||
}
|
||
|
||
uncles := block.Uncles()
|
||
uncleHashes := make([]common.Hash, len(uncles))
|
||
|
||
for i, uncle := range uncles {
|
||
uncleHashes[i] = uncle.Hash()
|
||
}
|
||
|
||
fields["uncles"] = uncleHashes
|
||
if block.Header().WithdrawalsHash != nil {
|
||
fields["withdrawals"] = block.Withdrawals()
|
||
}
|
||
return fields
|
||
}
|
||
|
||
// rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires
|
||
// a `BlockchainAPI`.
|
||
func (s *BlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Header) map[string]interface{} {
|
||
fields := RPCMarshalHeader(header)
|
||
fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, header.Hash()))
|
||
return fields
|
||
}
|
||
|
||
// rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires
|
||
// a `BlockchainAPI`.
|
||
func (s *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
|
||
fields := RPCMarshalBlock(b, inclTx, fullTx, s.b.ChainConfig(), s.b.ChainDb())
|
||
if inclTx {
|
||
fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, b.Hash()))
|
||
}
|
||
return fields, nil
|
||
}
|
||
|
||
// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
|
||
type RPCTransaction struct {
|
||
BlockHash *common.Hash `json:"blockHash"`
|
||
BlockNumber *hexutil.Big `json:"blockNumber"`
|
||
From common.Address `json:"from"`
|
||
Gas hexutil.Uint64 `json:"gas"`
|
||
GasPrice *hexutil.Big `json:"gasPrice"`
|
||
GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
|
||
GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
|
||
MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
|
||
Hash common.Hash `json:"hash"`
|
||
Input hexutil.Bytes `json:"input"`
|
||
Nonce hexutil.Uint64 `json:"nonce"`
|
||
To *common.Address `json:"to"`
|
||
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
|
||
Value *hexutil.Big `json:"value"`
|
||
Type hexutil.Uint64 `json:"type"`
|
||
Accesses *types.AccessList `json:"accessList,omitempty"`
|
||
ChainID *hexutil.Big `json:"chainId,omitempty"`
|
||
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
|
||
V *hexutil.Big `json:"v"`
|
||
R *hexutil.Big `json:"r"`
|
||
S *hexutil.Big `json:"s"`
|
||
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
|
||
}
|
||
|
||
// newRPCTransaction returns a transaction that will serialize to the RPC
|
||
// representation, with the given location metadata set (if available).
|
||
func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, blockTime uint64, index uint64, baseFee *big.Int, config *params.ChainConfig) *RPCTransaction {
|
||
signer := types.MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
|
||
from, _ := types.Sender(signer, tx)
|
||
v, r, s := tx.RawSignatureValues()
|
||
|
||
result := &RPCTransaction{
|
||
Type: hexutil.Uint64(tx.Type()),
|
||
From: from,
|
||
Gas: hexutil.Uint64(tx.Gas()),
|
||
GasPrice: (*hexutil.Big)(tx.GasPrice()),
|
||
Hash: tx.Hash(),
|
||
Input: hexutil.Bytes(tx.Data()),
|
||
Nonce: hexutil.Uint64(tx.Nonce()),
|
||
To: tx.To(),
|
||
Value: (*hexutil.Big)(tx.Value()),
|
||
V: (*hexutil.Big)(v),
|
||
R: (*hexutil.Big)(r),
|
||
S: (*hexutil.Big)(s),
|
||
}
|
||
if blockHash != (common.Hash{}) {
|
||
result.BlockHash = &blockHash
|
||
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
|
||
result.TransactionIndex = (*hexutil.Uint64)(&index)
|
||
}
|
||
|
||
switch tx.Type() {
|
||
case types.LegacyTxType:
|
||
// if a legacy transaction has an EIP-155 chain id, include it explicitly
|
||
if id := tx.ChainId(); id.Sign() != 0 {
|
||
result.ChainID = (*hexutil.Big)(id)
|
||
}
|
||
|
||
case types.AccessListTxType:
|
||
al := tx.AccessList()
|
||
yparity := hexutil.Uint64(v.Sign())
|
||
result.Accesses = &al
|
||
result.ChainID = (*hexutil.Big)(tx.ChainId())
|
||
result.YParity = &yparity
|
||
|
||
case types.DynamicFeeTxType:
|
||
al := tx.AccessList()
|
||
yparity := hexutil.Uint64(v.Sign())
|
||
result.Accesses = &al
|
||
result.ChainID = (*hexutil.Big)(tx.ChainId())
|
||
result.YParity = &yparity
|
||
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
|
||
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
|
||
// if the transaction has been mined, compute the effective gas price
|
||
if baseFee != nil && blockHash != (common.Hash{}) {
|
||
// price = min(gasTipCap + baseFee, gasFeeCap)
|
||
result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee))
|
||
} else {
|
||
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
|
||
}
|
||
|
||
case types.BlobTxType:
|
||
al := tx.AccessList()
|
||
yparity := hexutil.Uint64(v.Sign())
|
||
result.Accesses = &al
|
||
result.ChainID = (*hexutil.Big)(tx.ChainId())
|
||
result.YParity = &yparity
|
||
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
|
||
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
|
||
// if the transaction has been mined, compute the effective gas price
|
||
if baseFee != nil && blockHash != (common.Hash{}) {
|
||
result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee))
|
||
} else {
|
||
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
|
||
}
|
||
result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap())
|
||
result.BlobVersionedHashes = tx.BlobHashes()
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// effectiveGasPrice computes the transaction gas fee, based on the given basefee value.
|
||
//
|
||
// price = min(gasTipCap + baseFee, gasFeeCap)
|
||
func effectiveGasPrice(tx *types.Transaction, baseFee *big.Int) *big.Int {
|
||
fee := tx.GasTipCap()
|
||
fee = fee.Add(fee, baseFee)
|
||
if tx.GasFeeCapIntCmp(fee) < 0 {
|
||
return tx.GasFeeCap()
|
||
}
|
||
return fee
|
||
}
|
||
|
||
// NewRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation
|
||
func NewRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig) *RPCTransaction {
|
||
var (
|
||
baseFee *big.Int
|
||
blockNumber = uint64(0)
|
||
blockTime = uint64(0)
|
||
)
|
||
if current != nil {
|
||
baseFee = eip1559.CalcBaseFee(config, current)
|
||
blockNumber = current.Number.Uint64()
|
||
blockTime = current.Time
|
||
}
|
||
return newRPCTransaction(tx, common.Hash{}, blockNumber, blockTime, 0, baseFee, config)
|
||
}
|
||
|
||
// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation.
|
||
func newRPCTransactionFromBlockIndex(b *types.Block, index uint64, config *params.ChainConfig, db ethdb.Database) *RPCTransaction {
|
||
txs := b.Transactions()
|
||
|
||
if index >= uint64(len(txs)+1) {
|
||
return nil
|
||
}
|
||
|
||
var borReceipt *types.Receipt
|
||
|
||
// Read bor receipts if a state-sync transaction is requested
|
||
if index == uint64(len(txs)) {
|
||
borReceipt = rawdb.ReadBorReceipt(db, b.Hash(), b.NumberU64(), config)
|
||
if borReceipt != nil {
|
||
if borReceipt.TxHash != (common.Hash{}) {
|
||
borTx, _, _, _ := rawdb.ReadBorTransactionWithBlockHash(db, borReceipt.TxHash, b.Hash())
|
||
if borTx != nil {
|
||
txs = append(txs, borTx)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// If the index is still out of the range after checking bor state sync transaction, it means that the transaction index is invalid
|
||
if index >= uint64(len(txs)) {
|
||
return nil
|
||
}
|
||
|
||
rpcTx := newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time(), index, b.BaseFee(), config)
|
||
|
||
// If the transaction is a bor transaction, we need to set the hash to the derived bor tx hash. BorTx is always the last index.
|
||
if borReceipt != nil && index == uint64(len(txs)-1) {
|
||
rpcTx.Hash = borReceipt.TxHash
|
||
rpcTx.ChainID = nil
|
||
}
|
||
|
||
return rpcTx
|
||
}
|
||
|
||
// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index.
|
||
func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) hexutil.Bytes {
|
||
txs := b.Transactions()
|
||
if index >= uint64(len(txs)) {
|
||
return nil
|
||
}
|
||
|
||
blob, _ := txs[index].MarshalBinary()
|
||
|
||
return blob
|
||
}
|
||
|
||
// accessListResult returns an optional accesslist
|
||
// It's the result of the `debug_createAccessList` RPC call.
|
||
// It contains an error if the transaction itself failed.
|
||
type accessListResult struct {
|
||
Accesslist *types.AccessList `json:"accessList"`
|
||
Error string `json:"error,omitempty"`
|
||
GasUsed hexutil.Uint64 `json:"gasUsed"`
|
||
}
|
||
|
||
// CreateAccessList creates an EIP-2930 type AccessList for the given transaction.
|
||
// Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state.
|
||
func (s *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) {
|
||
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
|
||
if blockNrOrHash != nil {
|
||
bNrOrHash = *blockNrOrHash
|
||
}
|
||
|
||
acl, gasUsed, vmerr, err := AccessList(ctx, s.b, bNrOrHash, args)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
result := &accessListResult{Accesslist: &acl, GasUsed: hexutil.Uint64(gasUsed)}
|
||
if vmerr != nil {
|
||
result.Error = vmerr.Error()
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// AccessList creates an access list for the given transaction.
|
||
// If the accesslist creation fails an error is returned.
|
||
// If the transaction itself fails, an vmErr is returned.
|
||
func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args TransactionArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) {
|
||
// Retrieve the execution context
|
||
db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
if db == nil || err != nil {
|
||
return nil, 0, nil, err
|
||
}
|
||
// If the gas amount is not set, default to RPC gas cap.
|
||
if args.Gas == nil {
|
||
tmp := hexutil.Uint64(b.RPCGasCap())
|
||
args.Gas = &tmp
|
||
}
|
||
|
||
// Ensure any missing fields are filled, extract the recipient and input data
|
||
if err := args.setDefaults(ctx, b); err != nil {
|
||
return nil, 0, nil, err
|
||
}
|
||
|
||
var to common.Address
|
||
if args.To != nil {
|
||
to = *args.To
|
||
} else {
|
||
to = crypto.CreateAddress(args.from(), uint64(*args.Nonce))
|
||
}
|
||
|
||
isPostMerge := header.Difficulty.Cmp(common.Big0) == 0
|
||
// Retrieve the precompiles since they don't need to be added to the access list
|
||
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time))
|
||
|
||
// Create an initial tracer
|
||
prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles)
|
||
if args.AccessList != nil {
|
||
prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles)
|
||
}
|
||
|
||
for {
|
||
// Retrieve the current access list to expand
|
||
accessList := prevTracer.AccessList()
|
||
log.Trace("Creating access list", "input", accessList)
|
||
|
||
// Copy the original db so we don't modify it
|
||
statedb := db.Copy()
|
||
// Set the accesslist to the last al
|
||
args.AccessList = &accessList
|
||
|
||
msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee)
|
||
if err != nil {
|
||
return nil, 0, nil, err
|
||
}
|
||
|
||
// Apply the transaction with the access list tracer
|
||
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
|
||
config := vm.Config{Tracer: tracer, NoBaseFee: true}
|
||
vmenv := b.GetEVM(ctx, msg, statedb, header, &config, nil)
|
||
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), context.Background())
|
||
if err != nil {
|
||
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err)
|
||
}
|
||
|
||
if tracer.Equal(prevTracer) {
|
||
return accessList, res.UsedGas, res.Err, nil
|
||
}
|
||
|
||
prevTracer = tracer
|
||
}
|
||
}
|
||
|
||
// TransactionAPI exposes methods for reading and creating transaction data.
|
||
type TransactionAPI struct {
|
||
b Backend
|
||
nonceLock *AddrLocker
|
||
signer types.Signer
|
||
}
|
||
|
||
// returns block transactions along with state-sync transaction if present
|
||
// nolint: unparam
|
||
func (api *TransactionAPI) getAllBlockTransactions(ctx context.Context, block *types.Block) (types.Transactions, bool) {
|
||
txs := block.Transactions()
|
||
|
||
stateSyncPresent := false
|
||
|
||
borReceipt := rawdb.ReadBorReceipt(api.b.ChainDb(), block.Hash(), block.NumberU64(), api.b.ChainConfig())
|
||
if borReceipt != nil {
|
||
txHash := types.GetDerivedBorTxHash(types.BorReceiptKey(block.Number().Uint64(), block.Hash()))
|
||
if txHash != (common.Hash{}) {
|
||
borTx, _, _, _, _ := api.b.GetBorBlockTransactionWithBlockHash(ctx, txHash, block.Hash())
|
||
txs = append(txs, borTx)
|
||
stateSyncPresent = true
|
||
}
|
||
}
|
||
|
||
return txs, stateSyncPresent
|
||
}
|
||
|
||
// NewTransactionAPI creates a new RPC service with methods for interacting with transactions.
|
||
func NewTransactionAPI(b Backend, nonceLock *AddrLocker) *TransactionAPI {
|
||
// The signer used by the API should always be the 'latest' known one because we expect
|
||
// signers to be backwards-compatible with old transactions.
|
||
signer := types.LatestSigner(b.ChainConfig())
|
||
return &TransactionAPI{b, nonceLock, signer}
|
||
}
|
||
|
||
// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
|
||
func (s *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
|
||
if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
|
||
txs, _ := s.getAllBlockTransactions(ctx, block)
|
||
n := hexutil.Uint(len(txs))
|
||
|
||
return &n
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
|
||
func (s *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
|
||
if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
|
||
txs, _ := s.getAllBlockTransactions(ctx, block)
|
||
n := hexutil.Uint(len(txs))
|
||
|
||
return &n
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
|
||
func (s *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction {
|
||
if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
|
||
return newRPCTransactionFromBlockIndex(block, uint64(index), s.b.ChainConfig(), s.b.ChainDb())
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
|
||
func (s *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction {
|
||
if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
|
||
return newRPCTransactionFromBlockIndex(block, uint64(index), s.b.ChainConfig(), s.b.ChainDb())
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index.
|
||
func (s *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) hexutil.Bytes {
|
||
if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
|
||
return newRPCRawTransactionFromBlockIndex(block, uint64(index))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetRawTransactionByBlockHashAndIndex returns the bytes of the transaction for the given block hash and index.
|
||
func (s *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes {
|
||
if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
|
||
return newRPCRawTransactionFromBlockIndex(block, uint64(index))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetTransactionCount returns the number of transactions the given address has sent for the given block number
|
||
func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
|
||
// Ask transaction pool for the nonce which includes pending transactions
|
||
if blockNr, ok := blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber {
|
||
nonce, err := s.b.GetPoolNonce(ctx, address)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return (*hexutil.Uint64)(&nonce), nil
|
||
}
|
||
// Resolve block number and use its state to ask for the nonce
|
||
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
if state == nil || err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
nonce := state.GetNonce(address)
|
||
|
||
return (*hexutil.Uint64)(&nonce), state.Error()
|
||
}
|
||
|
||
// GetTransactionByHash returns the transaction for the given hash
|
||
func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
|
||
borTx := false
|
||
|
||
// Try to return an already finalized transaction
|
||
tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// fetch bor block tx if necessary
|
||
if tx == nil {
|
||
if tx, blockHash, blockNumber, index, err = s.b.GetBorBlockTransaction(ctx, hash); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
borTx = true
|
||
}
|
||
|
||
if tx != nil {
|
||
header, err := s.b.HeaderByHash(ctx, blockHash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
resultTx := newRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, s.b.ChainConfig())
|
||
|
||
if borTx {
|
||
// newRPCTransaction calculates hash based on RLP of the transaction data.
|
||
// In case of bor block tx, we need simple derived tx hash (same as function argument) instead of RLP hash
|
||
resultTx.Hash = hash
|
||
resultTx.ChainID = nil
|
||
}
|
||
|
||
return resultTx, nil
|
||
}
|
||
// No finalized transaction, try to retrieve it from the pool
|
||
if tx := s.b.GetPoolTransaction(hash); tx != nil {
|
||
return NewRPCPendingTransaction(tx, s.b.CurrentHeader(), s.b.ChainConfig()), nil
|
||
}
|
||
|
||
// Transaction unknown, return as such
|
||
return nil, nil
|
||
}
|
||
|
||
// GetRawTransactionByHash returns the bytes of the transaction for the given hash.
|
||
func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
||
// Retrieve a finalized transaction, or a pooled otherwise
|
||
tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if tx == nil {
|
||
if tx = s.b.GetPoolTransaction(hash); tx == nil {
|
||
// Transaction not found anywhere, abort
|
||
return nil, nil
|
||
}
|
||
}
|
||
// Serialize to RLP and return
|
||
return tx.MarshalBinary()
|
||
}
|
||
|
||
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
|
||
func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
|
||
borTx := false
|
||
|
||
tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
|
||
if err != nil {
|
||
// When the transaction doesn't exist, the RPC method should return JSON null
|
||
// as per specification.
|
||
return nil, nil
|
||
}
|
||
if tx == nil {
|
||
tx, blockHash, blockNumber, index = rawdb.ReadBorTransaction(s.b.ChainDb(), hash)
|
||
borTx = true
|
||
}
|
||
|
||
if tx == nil {
|
||
return nil, nil
|
||
}
|
||
|
||
var receipt *types.Receipt
|
||
|
||
if borTx {
|
||
// Fetch bor block receipt
|
||
receipt = rawdb.ReadBorReceipt(s.b.ChainDb(), blockHash, blockNumber, s.b.ChainConfig())
|
||
} else {
|
||
receipts, err := s.b.GetReceipts(ctx, blockHash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if uint64(len(receipts)) <= index {
|
||
return nil, nil
|
||
}
|
||
|
||
receipt = receipts[index]
|
||
}
|
||
|
||
header, err := s.b.HeaderByHash(ctx, blockHash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Derive the sender.
|
||
signer := types.MakeSigner(s.b.ChainConfig(), header.Number, header.Time)
|
||
return marshalReceipt(receipt, blockHash, blockNumber, signer, tx, int(index)), nil
|
||
}
|
||
|
||
// marshalReceipt marshals a transaction receipt into a JSON object.
|
||
func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} {
|
||
from, _ := types.Sender(signer, tx)
|
||
|
||
fields := map[string]interface{}{
|
||
"blockHash": blockHash,
|
||
"blockNumber": hexutil.Uint64(blockNumber),
|
||
"transactionHash": tx.Hash(),
|
||
"transactionIndex": hexutil.Uint64(txIndex),
|
||
"from": from,
|
||
"to": tx.To(),
|
||
"gasUsed": hexutil.Uint64(receipt.GasUsed),
|
||
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
|
||
"contractAddress": nil,
|
||
"logs": receipt.Logs,
|
||
"logsBloom": receipt.Bloom,
|
||
"type": hexutil.Uint(tx.Type()),
|
||
"effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
|
||
}
|
||
|
||
if receipt.EffectiveGasPrice == nil {
|
||
fields["effectiveGasPrice"] = new(hexutil.Big)
|
||
}
|
||
|
||
// Assign receipt status or post state.
|
||
if len(receipt.PostState) > 0 {
|
||
fields["root"] = hexutil.Bytes(receipt.PostState)
|
||
} else {
|
||
fields["status"] = hexutil.Uint(receipt.Status)
|
||
}
|
||
|
||
if receipt.Logs == nil {
|
||
fields["logs"] = []*types.Log{}
|
||
}
|
||
|
||
if tx.Type() == types.BlobTxType {
|
||
fields["blobGasUsed"] = hexutil.Uint64(receipt.BlobGasUsed)
|
||
fields["blobGasPrice"] = (*hexutil.Big)(receipt.BlobGasPrice)
|
||
}
|
||
|
||
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
|
||
if receipt.ContractAddress != (common.Address{}) {
|
||
fields["contractAddress"] = receipt.ContractAddress
|
||
}
|
||
return fields
|
||
}
|
||
|
||
// sign is a helper function that signs a transaction with the private key of the given address.
|
||
func (s *TransactionAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||
// Look up the wallet containing the requested signer
|
||
account := accounts.Account{Address: addr}
|
||
|
||
wallet, err := s.b.AccountManager().Find(account)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// Request the wallet to sign the transaction
|
||
return wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
|
||
}
|
||
|
||
// SubmitTransaction is a helper function that submits tx to txPool and logs a message.
|
||
func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
|
||
// If the transaction fee cap is already specified, ensure the
|
||
// fee of the given transaction is _reasonable_.
|
||
if err := checkTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
|
||
if !b.UnprotectedAllowed() && !tx.Protected() {
|
||
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
|
||
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
|
||
}
|
||
|
||
if err := b.SendTx(ctx, tx); err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
// Print a log with full tx details for manual investigations and interventions
|
||
head := b.CurrentBlock()
|
||
signer := types.MakeSigner(b.ChainConfig(), head.Number, head.Time)
|
||
from, err := types.Sender(signer, tx)
|
||
|
||
if err != nil && (!b.UnprotectedAllowed() || (b.UnprotectedAllowed() && err != types.ErrInvalidChainId)) {
|
||
return common.Hash{}, err
|
||
}
|
||
|
||
if tx.To() == nil {
|
||
addr := crypto.CreateAddress(from, tx.Nonce())
|
||
log.Info("Submitted contract creation", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "contract", addr.Hex(), "value", tx.Value())
|
||
} else {
|
||
log.Info("Submitted transaction", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "recipient", tx.To(), "value", tx.Value())
|
||
}
|
||
|
||
return tx.Hash(), nil
|
||
}
|
||
|
||
// SendTransaction creates a transaction for the given argument, sign it and submit it to the
|
||
// transaction pool.
|
||
func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionArgs) (common.Hash, error) {
|
||
// Look up the wallet containing the requested signer
|
||
account := accounts.Account{Address: args.from()}
|
||
|
||
wallet, err := s.b.AccountManager().Find(account)
|
||
if err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
|
||
if args.Nonce == nil {
|
||
// Hold the mutex around signing to prevent concurrent assignment of
|
||
// the same nonce to multiple accounts.
|
||
s.nonceLock.LockAddr(args.from())
|
||
defer s.nonceLock.UnlockAddr(args.from())
|
||
}
|
||
|
||
// Set some sanity defaults and terminate on failure
|
||
if err := args.setDefaults(ctx, s.b); err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
// Assemble the transaction and sign with the wallet
|
||
tx := args.toTransaction()
|
||
|
||
signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
|
||
if err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
|
||
return SubmitTransaction(ctx, s.b, signed)
|
||
}
|
||
|
||
// FillTransaction fills the defaults (nonce, gas, gasPrice or 1559 fields)
|
||
// on a given unsigned transaction, and returns it to the caller for further
|
||
// processing (signing + broadcast).
|
||
func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
||
// Set some sanity defaults and terminate on failure
|
||
if err := args.setDefaults(ctx, s.b); err != nil {
|
||
return nil, err
|
||
}
|
||
// Assemble the transaction and obtain rlp
|
||
tx := args.toTransaction()
|
||
|
||
data, err := tx.MarshalBinary()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &SignTransactionResult{data, tx}, nil
|
||
}
|
||
|
||
// SendRawTransaction will add the signed transaction to the transaction pool.
|
||
// The sender is responsible for signing the transaction and using the correct nonce.
|
||
func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
|
||
tx := new(types.Transaction)
|
||
if err := tx.UnmarshalBinary(input); err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
|
||
return SubmitTransaction(ctx, s.b, tx)
|
||
}
|
||
|
||
// Sign calculates an ECDSA signature for:
|
||
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message).
|
||
//
|
||
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
|
||
// where the V value will be 27 or 28 for legacy reasons.
|
||
//
|
||
// The account associated with addr must be unlocked.
|
||
//
|
||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
|
||
func (s *TransactionAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||
// Look up the wallet containing the requested signer
|
||
account := accounts.Account{Address: addr}
|
||
|
||
wallet, err := s.b.AccountManager().Find(account)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// Sign the requested hash with the wallet
|
||
signature, err := wallet.SignText(account, data)
|
||
if err == nil {
|
||
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||
}
|
||
|
||
return signature, err
|
||
}
|
||
|
||
// SignTransactionResult represents a RLP encoded signed transaction.
|
||
type SignTransactionResult struct {
|
||
Raw hexutil.Bytes `json:"raw"`
|
||
Tx *types.Transaction `json:"tx"`
|
||
}
|
||
|
||
// SignTransaction will sign the given transaction with the from account.
|
||
// The node needs to have the private key of the account corresponding with
|
||
// the given from address and it needs to be unlocked.
|
||
func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
||
if args.Gas == nil {
|
||
return nil, errors.New("gas not specified")
|
||
}
|
||
|
||
if args.GasPrice == nil && (args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil) {
|
||
return nil, errors.New("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas")
|
||
}
|
||
|
||
if args.Nonce == nil {
|
||
return nil, errors.New("nonce not specified")
|
||
}
|
||
|
||
if err := args.setDefaults(ctx, s.b); err != nil {
|
||
return nil, err
|
||
}
|
||
// Before actually sign the transaction, ensure the transaction fee is reasonable.
|
||
tx := args.toTransaction()
|
||
if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
signed, err := s.sign(args.from(), tx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
data, err := signed.MarshalBinary()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &SignTransactionResult{data, signed}, nil
|
||
}
|
||
|
||
// PendingTransactions returns the transactions that are in the transaction pool
|
||
// and have a from address that is one of the accounts this node manages.
|
||
func (s *TransactionAPI) PendingTransactions() ([]*RPCTransaction, error) {
|
||
pending, err := s.b.GetPoolTransactions()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
accounts := make(map[common.Address]struct{})
|
||
|
||
for _, wallet := range s.b.AccountManager().Wallets() {
|
||
for _, account := range wallet.Accounts() {
|
||
accounts[account.Address] = struct{}{}
|
||
}
|
||
}
|
||
|
||
curHeader := s.b.CurrentHeader()
|
||
transactions := make([]*RPCTransaction, 0, len(pending))
|
||
|
||
for _, tx := range pending {
|
||
from, _ := types.Sender(s.signer, tx)
|
||
if _, exists := accounts[from]; exists {
|
||
transactions = append(transactions, NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()))
|
||
}
|
||
}
|
||
|
||
return transactions, nil
|
||
}
|
||
|
||
// Resend accepts an existing transaction and a new gas price and limit. It will remove
|
||
// the given transaction from the pool and reinsert it with the new gas price and limit.
|
||
// nolint:gocognit
|
||
func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {
|
||
if sendArgs.Nonce == nil {
|
||
return common.Hash{}, errors.New("missing transaction nonce in transaction spec")
|
||
}
|
||
|
||
if err := sendArgs.setDefaults(ctx, s.b); err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
|
||
matchTx := sendArgs.toTransaction()
|
||
|
||
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
|
||
var price = matchTx.GasPrice()
|
||
if gasPrice != nil {
|
||
price = gasPrice.ToInt()
|
||
}
|
||
|
||
var gas = matchTx.Gas()
|
||
if gasLimit != nil {
|
||
gas = uint64(*gasLimit)
|
||
}
|
||
|
||
if err := checkTxFee(price, gas, s.b.RPCTxFeeCap()); err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
// Iterate the pending list for replacement
|
||
pending, err := s.b.GetPoolTransactions()
|
||
if err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
|
||
for _, p := range pending {
|
||
wantSigHash := s.signer.Hash(matchTx)
|
||
pFrom, err := types.Sender(s.signer, p)
|
||
|
||
if err != nil && (s.b.UnprotectedAllowed() && err == types.ErrInvalidChainId) {
|
||
err = nil
|
||
}
|
||
|
||
if err == nil && pFrom == sendArgs.from() && s.signer.Hash(p) == wantSigHash {
|
||
// Match. Re-sign and send the transaction.
|
||
if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 {
|
||
sendArgs.GasPrice = gasPrice
|
||
}
|
||
|
||
if gasLimit != nil && *gasLimit != 0 {
|
||
sendArgs.Gas = gasLimit
|
||
}
|
||
|
||
signedTx, err := s.sign(sendArgs.from(), sendArgs.toTransaction())
|
||
if err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
|
||
if err = s.b.SendTx(ctx, signedTx); err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
|
||
return signedTx.Hash(), nil
|
||
}
|
||
}
|
||
|
||
return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash())
|
||
}
|
||
|
||
// DebugAPI is the collection of Ethereum APIs exposed over the debugging
|
||
// namespace.
|
||
type DebugAPI struct {
|
||
b Backend
|
||
}
|
||
|
||
// NewDebugAPI creates a new instance of DebugAPI.
|
||
func NewDebugAPI(b Backend) *DebugAPI {
|
||
return &DebugAPI{b: b}
|
||
}
|
||
|
||
// GetRawHeader retrieves the RLP encoding for a single header.
|
||
func (api *DebugAPI) GetRawHeader(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
|
||
var hash common.Hash
|
||
if h, ok := blockNrOrHash.Hash(); ok {
|
||
hash = h
|
||
} else {
|
||
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
hash = block.Hash()
|
||
}
|
||
|
||
header, _ := api.b.HeaderByHash(ctx, hash)
|
||
if header == nil {
|
||
return nil, fmt.Errorf("header #%d not found", hash)
|
||
}
|
||
|
||
return rlp.EncodeToBytes(header)
|
||
}
|
||
|
||
// GetRawBlock retrieves the RLP encoded for a single block.
|
||
func (api *DebugAPI) GetRawBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
|
||
var hash common.Hash
|
||
if h, ok := blockNrOrHash.Hash(); ok {
|
||
hash = h
|
||
} else {
|
||
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
hash = block.Hash()
|
||
}
|
||
|
||
block, _ := api.b.BlockByHash(ctx, hash)
|
||
if block == nil {
|
||
return nil, fmt.Errorf("block #%d not found", hash)
|
||
}
|
||
|
||
return rlp.EncodeToBytes(block)
|
||
}
|
||
|
||
// GetRawReceipts retrieves the binary-encoded receipts of a single block.
|
||
func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]hexutil.Bytes, error) {
|
||
var hash common.Hash
|
||
if h, ok := blockNrOrHash.Hash(); ok {
|
||
hash = h
|
||
} else {
|
||
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
hash = block.Hash()
|
||
}
|
||
|
||
receipts, err := api.b.GetReceipts(ctx, hash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
result := make([]hexutil.Bytes, len(receipts))
|
||
|
||
for i, receipt := range receipts {
|
||
b, err := receipt.MarshalBinary()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
result[i] = b
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// GetRawTransaction returns the bytes of the transaction for the given hash.
|
||
func (s *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
||
// Retrieve a finalized transaction, or a pooled otherwise
|
||
tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if tx == nil {
|
||
if tx = s.b.GetPoolTransaction(hash); tx == nil {
|
||
// Transaction not found anywhere, abort
|
||
return nil, nil
|
||
}
|
||
}
|
||
|
||
return tx.MarshalBinary()
|
||
}
|
||
|
||
// PrintBlock retrieves a block and returns its pretty printed form.
|
||
func (api *DebugAPI) PrintBlock(ctx context.Context, number uint64) (string, error) {
|
||
block, _ := api.b.BlockByNumber(ctx, rpc.BlockNumber(number))
|
||
if block == nil {
|
||
return "", fmt.Errorf("block #%d not found", number)
|
||
}
|
||
|
||
return spew.Sdump(block), nil
|
||
}
|
||
|
||
// ChaindbProperty returns leveldb properties of the key-value database.
|
||
func (api *DebugAPI) ChaindbProperty(property string) (string, error) {
|
||
return api.b.ChainDb().Stat(property)
|
||
}
|
||
|
||
// ChaindbCompact flattens the entire key-value database into a single level,
|
||
// removing all unused slots and merging all keys.
|
||
func (api *DebugAPI) ChaindbCompact() error {
|
||
cstart := time.Now()
|
||
for b := 0; b <= 255; b++ {
|
||
var (
|
||
start = []byte{byte(b)}
|
||
end = []byte{byte(b + 1)}
|
||
)
|
||
if b == 255 {
|
||
end = nil
|
||
}
|
||
log.Info("Compacting database", "range", fmt.Sprintf("%#X-%#X", start, end), "elapsed", common.PrettyDuration(time.Since(cstart)))
|
||
if err := api.b.ChainDb().Compact(start, end); err != nil {
|
||
log.Error("Database compaction failed", "err", err)
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// SetHead rewinds the head of the blockchain to a previous block.
|
||
func (api *DebugAPI) SetHead(number hexutil.Uint64) {
|
||
api.b.SetHead(uint64(number))
|
||
}
|
||
|
||
// GetTraceStack returns the current trace stack
|
||
func (api *DebugAPI) GetTraceStack() string {
|
||
buf := make([]byte, 1024)
|
||
|
||
for {
|
||
n := runtime.Stack(buf, true)
|
||
|
||
if n < len(buf) {
|
||
return string(buf)
|
||
}
|
||
|
||
buf = make([]byte, 2*len(buf))
|
||
}
|
||
}
|
||
|
||
// NetAPI offers network related RPC methods
|
||
type NetAPI struct {
|
||
net *p2p.Server
|
||
networkVersion uint64
|
||
}
|
||
|
||
// NewNetAPI creates a new net API instance.
|
||
func NewNetAPI(net *p2p.Server, networkVersion uint64) *NetAPI {
|
||
return &NetAPI{net, networkVersion}
|
||
}
|
||
|
||
// Listening returns an indication if the node is listening for network connections.
|
||
func (s *NetAPI) Listening() bool {
|
||
return true // always listening
|
||
}
|
||
|
||
// PeerCount returns the number of connected peers
|
||
func (s *NetAPI) PeerCount() hexutil.Uint {
|
||
return hexutil.Uint(s.net.PeerCount())
|
||
}
|
||
|
||
// Version returns the current ethereum protocol version.
|
||
func (s *NetAPI) Version() string {
|
||
return fmt.Sprintf("%d", s.networkVersion)
|
||
}
|
||
|
||
// checkTxFee is an internal function used to check whether the fee of
|
||
// the given transaction is _reasonable_(under the cap).
|
||
func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
|
||
// Short circuit if there is no cap for transaction fee at all.
|
||
if cap == 0 {
|
||
return nil
|
||
}
|
||
|
||
feeEth := new(big.Float).Quo(new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas))), new(big.Float).SetInt(big.NewInt(params.Ether)))
|
||
|
||
feeFloat, _ := feeEth.Float64()
|
||
if feeFloat > cap {
|
||
return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap)
|
||
}
|
||
|
||
return nil
|
||
}
|