459bb4a647
This PR moves the logging/tracing-facilities out of `*state.StateDB`, in to a wrapping struct which implements `vm.StateDB` instead. In most places, it is a pretty straight-forward change: - First, hoisting the invocations from state objects up to the statedb. - Then making the mutation-methods simply return the previous value, so that the external logging layer could log everything. Some internal code uses the direct object-accessors to mutate the state, particularly in testing and in setting up state overrides, which means that these changes are unobservable for the hooked layer. Thus, configuring the overrides are not necessarily part of the API we want to publish. The trickiest part about the layering is that when the selfdestructs are finally deleted during `Finalise`, there's the possibility that someone sent some ether to it, which is burnt at that point, and thus needs to be logged. The hooked layer reaches into the inner layer to figure out these events. In package `vm`, the conversion from `state.StateDB + hooks` into a hooked `vm.StateDB` is performed where needed. --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
2307 lines
83 KiB
Go
2307 lines
83 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"
|
||
"maps"
|
||
gomath "math"
|
||
"math/big"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/davecgh/go-spew/spew"
|
||
"github.com/ethereum/go-ethereum/accounts"
|
||
"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/state"
|
||
"github.com/ethereum/go-ethereum/core/tracing"
|
||
"github.com/ethereum/go-ethereum/core/types"
|
||
"github.com/ethereum/go-ethereum/core/vm"
|
||
"github.com/ethereum/go-ethereum/crypto"
|
||
"github.com/ethereum/go-ethereum/eth/gasestimator"
|
||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||
"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"
|
||
"github.com/holiman/uint256"
|
||
"github.com/tyler-smith/go-bip39"
|
||
)
|
||
|
||
// estimateGasErrorRatio is the amount of overestimation eth_estimateGas is
|
||
// allowed to produce in order to speed up calculations.
|
||
const estimateGasErrorRatio = 0.015
|
||
|
||
var errBlobTxNotSupported = errors.New("signing blob transactions not supported")
|
||
|
||
// 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 (api *EthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
|
||
tipcap, err := api.b.SuggestGasTipCap(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if head := api.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 (api *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
|
||
tipcap, err := api.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"`
|
||
BlobBaseFee []*hexutil.Big `json:"baseFeePerBlobGas,omitempty"`
|
||
BlobGasUsedRatio []float64 `json:"blobGasUsedRatio,omitempty"`
|
||
}
|
||
|
||
// FeeHistory returns the fee market history.
|
||
func (api *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
|
||
oldest, reward, baseFee, gasUsed, blobBaseFee, blobGasUsed, err := api.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)
|
||
}
|
||
}
|
||
if blobBaseFee != nil {
|
||
results.BlobBaseFee = make([]*hexutil.Big, len(blobBaseFee))
|
||
for i, v := range blobBaseFee {
|
||
results.BlobBaseFee[i] = (*hexutil.Big)(v)
|
||
}
|
||
}
|
||
if blobGasUsed != nil {
|
||
results.BlobGasUsedRatio = blobGasUsed
|
||
}
|
||
return results, nil
|
||
}
|
||
|
||
// BlobBaseFee returns the base fee for blob gas at the current head.
|
||
func (api *EthereumAPI) BlobBaseFee(ctx context.Context) *hexutil.Big {
|
||
return (*hexutil.Big)(api.b.BlobBaseFee(ctx))
|
||
}
|
||
|
||
// 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 peers. 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 (api *EthereumAPI) Syncing() (interface{}, error) {
|
||
progress := api.b.SyncProgress()
|
||
|
||
// Return not syncing if the synchronisation already completed
|
||
if progress.Done() {
|
||
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),
|
||
"txIndexFinishedBlocks": hexutil.Uint64(progress.TxIndexFinishedBlocks),
|
||
"txIndexRemainingBlocks": hexutil.Uint64(progress.TxIndexRemainingBlocks),
|
||
}, 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 (api *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 := api.b.TxPoolContent()
|
||
curHeader := api.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, api.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, api.b.ChainConfig())
|
||
}
|
||
content["queued"][account.Hex()] = dump
|
||
}
|
||
return content
|
||
}
|
||
|
||
// ContentFrom returns the transactions contained within the transaction pool.
|
||
func (api *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCTransaction {
|
||
content := make(map[string]map[string]*RPCTransaction, 2)
|
||
pending, queue := api.b.TxPoolContentFrom(addr)
|
||
curHeader := api.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, api.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, api.b.ChainConfig())
|
||
}
|
||
content["queued"] = dump
|
||
|
||
return content
|
||
}
|
||
|
||
// Status returns the number of pending and queued transaction in the pool.
|
||
func (api *TxPoolAPI) Status() map[string]hexutil.Uint {
|
||
pending, queue := api.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 (api *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 := api.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 (api *EthereumAccountAPI) Accounts() []common.Address {
|
||
return api.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 creates 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 (api *PersonalAccountAPI) ListAccounts() []common.Address {
|
||
return api.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 (api *PersonalAccountAPI) ListWallets() []rawWallet {
|
||
wallets := make([]rawWallet, 0) // return [] instead of nil if empty
|
||
for _, wallet := range api.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 (api *PersonalAccountAPI) OpenWallet(url string, passphrase *string) error {
|
||
wallet, err := api.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 (api *PersonalAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
|
||
wallet, err := api.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 (api *PersonalAccountAPI) NewAccount(password string) (common.AddressEIP55, error) {
|
||
ks, err := fetchKeystore(api.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 (api *PersonalAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
|
||
key, err := crypto.HexToECDSA(privkey)
|
||
if err != nil {
|
||
return common.Address{}, err
|
||
}
|
||
ks, err := fetchKeystore(api.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 (api *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 api.b.ExtRPCEnabled() && !api.b.AccountManager().Config().InsecureUnlockAllowed {
|
||
return false, errors.New("account unlock with HTTP access is forbidden")
|
||
}
|
||
|
||
const max = uint64(time.Duration(gomath.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(api.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 (api *PersonalAccountAPI) LockAccount(addr common.Address) bool {
|
||
if ks, err := fetchKeystore(api.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 (api *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 := api.am.Find(account)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// Set some sanity defaults and terminate on failure
|
||
if err := args.setDefaults(ctx, api.b, false); err != nil {
|
||
return nil, err
|
||
}
|
||
// Assemble the transaction and sign with the wallet
|
||
tx := args.ToTransaction(types.LegacyTxType)
|
||
|
||
return wallet.SignTxWithPassphrase(account, passwd, tx, api.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 (api *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.
|
||
api.nonceLock.LockAddr(args.from())
|
||
defer api.nonceLock.UnlockAddr(args.from())
|
||
}
|
||
if args.IsEIP4844() {
|
||
return common.Hash{}, errBlobTxNotSupported
|
||
}
|
||
signed, err := api.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, api.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 (api *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.IsEIP4844() {
|
||
return nil, errBlobTxNotSupported
|
||
}
|
||
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(types.LegacyTxType)
|
||
if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
|
||
return nil, err
|
||
}
|
||
signed, err := api.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://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-personal#personal-sign
|
||
func (api *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 := api.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://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-personal#personal-ecrecover
|
||
func (api *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 (api *PersonalAccountAPI) InitializeWallet(ctx context.Context, url string) (string, error) {
|
||
wallet, err := api.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 (api *PersonalAccountAPI) Unpair(ctx context.Context, url string, pin string) error {
|
||
wallet, err := api.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}
|
||
}
|
||
|
||
// 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 (api *BlockChainAPI) BlockNumber() hexutil.Uint64 {
|
||
header, _ := api.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 (api *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
|
||
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
if state == nil || err != nil {
|
||
return nil, err
|
||
}
|
||
b := state.GetBalance(address).ToBig()
|
||
return (*hexutil.Big)(b), state.Error()
|
||
}
|
||
|
||
// AccountResult 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 (api *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 := api.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
|
||
}
|
||
balance := statedb.GetBalance(address).ToBig()
|
||
return &AccountResult{
|
||
Address: address,
|
||
AccountProof: accountProof,
|
||
Balance: (*hexutil.Big)(balance),
|
||
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 (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
|
||
header, err := api.b.HeaderByNumber(ctx, number)
|
||
if header != nil && err == nil {
|
||
response := RPCMarshalHeader(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 (api *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} {
|
||
header, _ := api.b.HeaderByHash(ctx, hash)
|
||
if header != nil {
|
||
return RPCMarshalHeader(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 (api *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
|
||
block, err := api.b.BlockByNumber(ctx, number)
|
||
if block != nil && err == nil {
|
||
response := RPCMarshalBlock(block, true, fullTx, api.b.ChainConfig())
|
||
if number == rpc.PendingBlockNumber {
|
||
// Pending blocks need to nil out a few fields
|
||
for _, field := range []string{"hash", "nonce", "miner"} {
|
||
response[field] = nil
|
||
}
|
||
}
|
||
return response, nil
|
||
}
|
||
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 (api *BlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) {
|
||
block, err := api.b.BlockByHash(ctx, hash)
|
||
if block != nil {
|
||
return RPCMarshalBlock(block, true, fullTx, api.b.ChainConfig()), nil
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
// GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index.
|
||
func (api *BlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) {
|
||
block, err := api.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 RPCMarshalBlock(block, false, false, api.b.ChainConfig()), nil
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
// GetUncleByBlockHashAndIndex returns the uncle block for the given block hash and index.
|
||
func (api *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (map[string]interface{}, error) {
|
||
block, err := api.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 RPCMarshalBlock(block, false, false, api.b.ChainConfig()), nil
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number
|
||
func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
|
||
if block, _ := api.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 (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
|
||
if block, _ := api.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 (api *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
|
||
state, _, err := api.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 (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, hexKey string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
|
||
state, _, err := api.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 (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
||
block, err := api.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 := api.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(api.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 stateDiff 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"`
|
||
MovePrecompileTo *common.Address `json:"movePrecompileToAddress"`
|
||
}
|
||
|
||
// StateOverride is the collection of overridden accounts.
|
||
type StateOverride map[common.Address]OverrideAccount
|
||
|
||
func (diff *StateOverride) has(address common.Address) bool {
|
||
_, ok := (*diff)[address]
|
||
return ok
|
||
}
|
||
|
||
// Apply overrides the fields of specified accounts into the given state.
|
||
func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.PrecompiledContracts) error {
|
||
if diff == nil {
|
||
return nil
|
||
}
|
||
// Tracks destinations of precompiles that were moved.
|
||
dirtyAddrs := make(map[common.Address]struct{})
|
||
for addr, account := range *diff {
|
||
// If a precompile was moved to this address already, it can't be overridden.
|
||
if _, ok := dirtyAddrs[addr]; ok {
|
||
return fmt.Errorf("account %s has already been overridden by a precompile", addr.Hex())
|
||
}
|
||
p, isPrecompile := precompiles[addr]
|
||
// The MoveTo feature makes it possible to move a precompile
|
||
// code to another address. If the target address is another precompile
|
||
// the code for the latter is lost for this session.
|
||
// Note the destination account is not cleared upon move.
|
||
if account.MovePrecompileTo != nil {
|
||
if !isPrecompile {
|
||
return fmt.Errorf("account %s is not a precompile", addr.Hex())
|
||
}
|
||
// Refuse to move a precompile to an address that has been
|
||
// or will be overridden.
|
||
if diff.has(*account.MovePrecompileTo) {
|
||
return fmt.Errorf("account %s is already overridden", account.MovePrecompileTo.Hex())
|
||
}
|
||
precompiles[*account.MovePrecompileTo] = p
|
||
dirtyAddrs[*account.MovePrecompileTo] = struct{}{}
|
||
}
|
||
if isPrecompile {
|
||
delete(precompiles, addr)
|
||
}
|
||
// Override account nonce.
|
||
if account.Nonce != nil {
|
||
statedb.SetNonce(addr, uint64(*account.Nonce))
|
||
}
|
||
// Override account(contract) code.
|
||
if account.Code != nil {
|
||
statedb.SetCode(addr, *account.Code)
|
||
}
|
||
// Override account balance.
|
||
if account.Balance != nil {
|
||
u256Balance, _ := uint256.FromBig((*big.Int)(account.Balance))
|
||
statedb.SetBalance(addr, u256Balance, tracing.BalanceChangeUnspecified)
|
||
}
|
||
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 {
|
||
statedb.SetStorage(addr, account.State)
|
||
}
|
||
// Apply state diff into specified accounts.
|
||
if account.StateDiff != nil {
|
||
for key, value := range account.StateDiff {
|
||
statedb.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.
|
||
statedb.Finalise(false)
|
||
return nil
|
||
}
|
||
|
||
// BlockOverrides is a set of header fields to override.
|
||
type BlockOverrides struct {
|
||
Number *hexutil.Big
|
||
Difficulty *hexutil.Big // No-op if we're simulating post-merge calls.
|
||
Time *hexutil.Uint64
|
||
GasLimit *hexutil.Uint64
|
||
FeeRecipient *common.Address
|
||
PrevRandao *common.Hash
|
||
BaseFeePerGas *hexutil.Big
|
||
BlobBaseFee *hexutil.Big
|
||
}
|
||
|
||
// Apply overrides the given header fields into the given block context.
|
||
func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
|
||
if o == nil {
|
||
return
|
||
}
|
||
if o.Number != nil {
|
||
blockCtx.BlockNumber = o.Number.ToInt()
|
||
}
|
||
if o.Difficulty != nil {
|
||
blockCtx.Difficulty = o.Difficulty.ToInt()
|
||
}
|
||
if o.Time != nil {
|
||
blockCtx.Time = uint64(*o.Time)
|
||
}
|
||
if o.GasLimit != nil {
|
||
blockCtx.GasLimit = uint64(*o.GasLimit)
|
||
}
|
||
if o.FeeRecipient != nil {
|
||
blockCtx.Coinbase = *o.FeeRecipient
|
||
}
|
||
if o.PrevRandao != nil {
|
||
blockCtx.Random = o.PrevRandao
|
||
}
|
||
if o.BaseFeePerGas != nil {
|
||
blockCtx.BaseFee = o.BaseFeePerGas.ToInt()
|
||
}
|
||
if o.BlobBaseFee != nil {
|
||
blockCtx.BlobBaseFee = o.BlobBaseFee.ToInt()
|
||
}
|
||
}
|
||
|
||
// MakeHeader returns a new header object with the overridden
|
||
// fields.
|
||
// Note: MakeHeader ignores BlobBaseFee if set. That's because
|
||
// header has no such field.
|
||
func (o *BlockOverrides) MakeHeader(header *types.Header) *types.Header {
|
||
if o == nil {
|
||
return header
|
||
}
|
||
h := types.CopyHeader(header)
|
||
if o.Number != nil {
|
||
h.Number = o.Number.ToInt()
|
||
}
|
||
if o.Difficulty != nil {
|
||
h.Difficulty = o.Difficulty.ToInt()
|
||
}
|
||
if o.Time != nil {
|
||
h.Time = uint64(*o.Time)
|
||
}
|
||
if o.GasLimit != nil {
|
||
h.GasLimit = uint64(*o.GasLimit)
|
||
}
|
||
if o.FeeRecipient != nil {
|
||
h.Coinbase = *o.FeeRecipient
|
||
}
|
||
if o.PrevRandao != nil {
|
||
h.MixDigest = *o.PrevRandao
|
||
}
|
||
if o.BaseFeePerGas != nil {
|
||
h.BaseFee = o.BaseFeePerGas.ToInt()
|
||
}
|
||
return h
|
||
}
|
||
|
||
// 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) {
|
||
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
|
||
if blockOverrides != nil {
|
||
blockOverrides.Apply(&blockCtx)
|
||
}
|
||
rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time)
|
||
precompiles := maps.Clone(vm.ActivePrecompiledContracts(rules))
|
||
if err := overrides.Apply(state, precompiles); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 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()
|
||
gp := new(core.GasPool)
|
||
if globalGasCap == 0 {
|
||
gp.AddGas(gomath.MaxUint64)
|
||
} else {
|
||
gp.AddGas(globalGasCap)
|
||
}
|
||
return applyMessage(ctx, b, args, state, header, timeout, gp, &blockCtx, &vm.Config{NoBaseFee: true}, precompiles, true)
|
||
}
|
||
|
||
func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, gp *core.GasPool, blockContext *vm.BlockContext, vmConfig *vm.Config, precompiles vm.PrecompiledContracts, skipChecks bool) (*core.ExecutionResult, error) {
|
||
// Get a new instance of the EVM.
|
||
if err := args.CallDefaults(gp.Gas(), blockContext.BaseFee, b.ChainConfig().ChainID); err != nil {
|
||
return nil, err
|
||
}
|
||
msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks)
|
||
// Lower the basefee to 0 to avoid breaking EVM
|
||
// invariants (basefee < feecap).
|
||
if msg.GasPrice.Sign() == 0 {
|
||
blockContext.BaseFee = new(big.Int)
|
||
}
|
||
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
|
||
blockContext.BlobBaseFee = new(big.Int)
|
||
}
|
||
evm := b.GetEVM(ctx, msg, state, header, vmConfig, blockContext)
|
||
if precompiles != nil {
|
||
evm.SetPrecompiles(precompiles)
|
||
}
|
||
res, err := applyMessageWithEVM(ctx, evm, msg, timeout, gp)
|
||
// If an internal state error occurred, let that have precedence. Otherwise,
|
||
// a "trie root missing" type of error will masquerade as e.g. "insufficient gas"
|
||
if err := state.Error(); err != nil {
|
||
return nil, err
|
||
}
|
||
return res, err
|
||
}
|
||
|
||
func applyMessageWithEVM(ctx context.Context, evm *vm.EVM, msg *core.Message, timeout time.Duration, gp *core.GasPool) (*core.ExecutionResult, error) {
|
||
// 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.
|
||
result, err := core.ApplyMessage(evm, msg, gp)
|
||
|
||
// 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, 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())
|
||
|
||
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||
if state == nil || err != nil {
|
||
return nil, err
|
||
}
|
||
return doCall(ctx, b, args, state, header, overrides, blockOverrides, timeout, globalGasCap)
|
||
}
|
||
|
||
// 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 (api *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) {
|
||
if blockNrOrHash == nil {
|
||
latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||
blockNrOrHash = &latest
|
||
}
|
||
result, err := DoCall(ctx, api.b, args, *blockNrOrHash, overrides, blockOverrides, api.b.RPCEVMTimeout(), api.b.RPCGasCap())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// 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
|
||
}
|
||
|
||
// SimulateV1 executes series of transactions on top of a base state.
|
||
// The transactions are packed into blocks. For each block, block header
|
||
// fields can be overridden. The state can also be overridden prior to
|
||
// execution of each block.
|
||
//
|
||
// Note, this function doesn't make any changes in the state/blockchain and is
|
||
// useful to execute and retrieve values.
|
||
func (api *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrOrHash *rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
||
if len(opts.BlockStateCalls) == 0 {
|
||
return nil, &invalidParamsError{message: "empty input"}
|
||
} else if len(opts.BlockStateCalls) > maxSimulateBlocks {
|
||
return nil, &clientLimitExceededError{message: "too many blocks"}
|
||
}
|
||
if blockNrOrHash == nil {
|
||
n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||
blockNrOrHash = &n
|
||
}
|
||
state, base, err := api.b.StateAndHeaderByNumberOrHash(ctx, *blockNrOrHash)
|
||
if state == nil || err != nil {
|
||
return nil, err
|
||
}
|
||
gasCap := api.b.RPCGasCap()
|
||
if gasCap == 0 {
|
||
gasCap = gomath.MaxUint64
|
||
}
|
||
sim := &simulator{
|
||
b: api.b,
|
||
state: state,
|
||
base: base,
|
||
chainConfig: api.b.ChainConfig(),
|
||
// Each tx and all the series of txes shouldn't consume more gas than cap
|
||
gp: new(core.GasPool).AddGas(gasCap),
|
||
traceTransfers: opts.TraceTransfers,
|
||
validate: opts.Validation,
|
||
fullTx: opts.ReturnFullTransactions,
|
||
}
|
||
return sim.execute(ctx, opts.BlockStateCalls)
|
||
}
|
||
|
||
// 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, nil); 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,
|
||
}
|
||
// Set any required transaction default, but make sure the gas cap itself is not messed with
|
||
// if it was not specified in the original argument list.
|
||
if args.Gas == nil {
|
||
args.Gas = new(hexutil.Uint64)
|
||
}
|
||
if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil {
|
||
return 0, err
|
||
}
|
||
call := args.ToMessage(header.BaseFee, true, true)
|
||
|
||
// Run the gas estimation and wrap any revertals into a custom return
|
||
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).
|
||
// Note: Required blob gas is not computed in this method.
|
||
func (api *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, api.b, args, bNrOrHash, overrides, api.b.RPCGasCap())
|
||
}
|
||
|
||
// 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
|
||
}
|
||
if head.RequestsHash != nil {
|
||
result["requestsRoot"] = head.RequestsHash
|
||
}
|
||
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) 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)
|
||
}
|
||
}
|
||
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.Withdrawals() != nil {
|
||
fields["withdrawals"] = block.Withdrawals()
|
||
}
|
||
return fields
|
||
}
|
||
|
||
// 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) *RPCTransaction {
|
||
txs := b.Transactions()
|
||
if index >= uint64(len(txs)) {
|
||
return nil
|
||
}
|
||
return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), b.Time(), index, b.BaseFee(), config)
|
||
}
|
||
|
||
// 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 (api *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) {
|
||
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||
if blockNrOrHash != nil {
|
||
bNrOrHash = *blockNrOrHash
|
||
}
|
||
acl, gasUsed, vmerr, err := AccessList(ctx, api.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
|
||
}
|
||
|
||
// Ensure any missing fields are filled, extract the recipient and input data
|
||
if err := args.setDefaults(ctx, b, true); 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.Sign() == 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 {
|
||
if err := ctx.Err(); err != nil {
|
||
return nil, 0, nil, err
|
||
}
|
||
// 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 := args.ToMessage(header.BaseFee, true, true)
|
||
|
||
// Apply the transaction with the access list tracer
|
||
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
|
||
config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true}
|
||
vmenv := b.GetEVM(ctx, msg, statedb, header, &config, nil)
|
||
// Lower the basefee to 0 to avoid breaking EVM
|
||
// invariants (basefee < feecap).
|
||
if msg.GasPrice.Sign() == 0 {
|
||
vmenv.Context.BaseFee = new(big.Int)
|
||
}
|
||
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
|
||
vmenv.Context.BlobBaseFee = new(big.Int)
|
||
}
|
||
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
||
if err != nil {
|
||
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction(types.LegacyTxType).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
|
||
}
|
||
|
||
// 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 (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
|
||
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
|
||
n := hexutil.Uint(len(block.Transactions()))
|
||
return &n
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
|
||
func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
|
||
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
|
||
n := hexutil.Uint(len(block.Transactions()))
|
||
return &n
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
|
||
func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction {
|
||
if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
|
||
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig())
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
|
||
func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction {
|
||
if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
|
||
return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig())
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index.
|
||
func (api *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) hexutil.Bytes {
|
||
if block, _ := api.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 (api *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes {
|
||
if block, _ := api.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 (api *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 := api.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 := api.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 (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
|
||
// Try to return an already finalized transaction
|
||
found, tx, blockHash, blockNumber, index, err := api.b.GetTransaction(ctx, hash)
|
||
if !found {
|
||
// No finalized transaction, try to retrieve it from the pool
|
||
if tx := api.b.GetPoolTransaction(hash); tx != nil {
|
||
return NewRPCPendingTransaction(tx, api.b.CurrentHeader(), api.b.ChainConfig()), nil
|
||
}
|
||
if err == nil {
|
||
return nil, nil
|
||
}
|
||
return nil, NewTxIndexingError()
|
||
}
|
||
header, err := api.b.HeaderByHash(ctx, blockHash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return newRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, api.b.ChainConfig()), nil
|
||
}
|
||
|
||
// GetRawTransactionByHash returns the bytes of the transaction for the given hash.
|
||
func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
||
// Retrieve a finalized transaction, or a pooled otherwise
|
||
found, tx, _, _, _, err := api.b.GetTransaction(ctx, hash)
|
||
if !found {
|
||
if tx = api.b.GetPoolTransaction(hash); tx != nil {
|
||
return tx.MarshalBinary()
|
||
}
|
||
if err == nil {
|
||
return nil, nil
|
||
}
|
||
return nil, NewTxIndexingError()
|
||
}
|
||
return tx.MarshalBinary()
|
||
}
|
||
|
||
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
|
||
func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
|
||
found, tx, blockHash, blockNumber, index, err := api.b.GetTransaction(ctx, hash)
|
||
if err != nil {
|
||
return nil, NewTxIndexingError() // transaction is not fully indexed
|
||
}
|
||
if !found {
|
||
return nil, nil // transaction is not existent or reachable
|
||
}
|
||
header, err := api.b.HeaderByHash(ctx, blockHash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
receipts, err := api.b.GetReceipts(ctx, blockHash)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if uint64(len(receipts)) <= index {
|
||
return nil, nil
|
||
}
|
||
receipt := receipts[index]
|
||
|
||
// Derive the sender.
|
||
signer := types.MakeSigner(api.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),
|
||
}
|
||
|
||
// 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 (api *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 := api.b.AccountManager().Find(account)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// Request the wallet to sign the transaction
|
||
return wallet.SignTx(account, tx, api.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 {
|
||
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 (api *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 := api.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.
|
||
api.nonceLock.LockAddr(args.from())
|
||
defer api.nonceLock.UnlockAddr(args.from())
|
||
}
|
||
if args.IsEIP4844() {
|
||
return common.Hash{}, errBlobTxNotSupported
|
||
}
|
||
|
||
// Set some sanity defaults and terminate on failure
|
||
if err := args.setDefaults(ctx, api.b, false); err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
// Assemble the transaction and sign with the wallet
|
||
tx := args.ToTransaction(types.LegacyTxType)
|
||
|
||
signed, err := wallet.SignTx(account, tx, api.b.ChainConfig().ChainID)
|
||
if err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
return SubmitTransaction(ctx, api.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 (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
||
args.blobSidecarAllowed = true
|
||
|
||
// Set some sanity defaults and terminate on failure
|
||
if err := args.setDefaults(ctx, api.b, false); err != nil {
|
||
return nil, err
|
||
}
|
||
// Assemble the transaction and obtain rlp
|
||
tx := args.ToTransaction(types.LegacyTxType)
|
||
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 (api *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, api.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 (api *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 := api.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 (api *TransactionAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
||
args.blobSidecarAllowed = true
|
||
|
||
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, api.b, false); err != nil {
|
||
return nil, err
|
||
}
|
||
// Before actually sign the transaction, ensure the transaction fee is reasonable.
|
||
tx := args.ToTransaction(types.LegacyTxType)
|
||
if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
|
||
return nil, err
|
||
}
|
||
signed, err := api.sign(args.from(), tx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// If the transaction-to-sign was a blob transaction, then the signed one
|
||
// no longer retains the blobs, only the blob hashes. In this step, we need
|
||
// to put back the blob(s).
|
||
if args.IsEIP4844() {
|
||
signed = signed.WithBlobTxSidecar(&types.BlobTxSidecar{
|
||
Blobs: args.Blobs,
|
||
Commitments: args.Commitments,
|
||
Proofs: args.Proofs,
|
||
})
|
||
}
|
||
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 (api *TransactionAPI) PendingTransactions() ([]*RPCTransaction, error) {
|
||
pending, err := api.b.GetPoolTransactions()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
accounts := make(map[common.Address]struct{})
|
||
for _, wallet := range api.b.AccountManager().Wallets() {
|
||
for _, account := range wallet.Accounts() {
|
||
accounts[account.Address] = struct{}{}
|
||
}
|
||
}
|
||
curHeader := api.b.CurrentHeader()
|
||
transactions := make([]*RPCTransaction, 0, len(pending))
|
||
for _, tx := range pending {
|
||
from, _ := types.Sender(api.signer, tx)
|
||
if _, exists := accounts[from]; exists {
|
||
transactions = append(transactions, NewRPCPendingTransaction(tx, curHeader, api.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.
|
||
func (api *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, api.b, false); err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
matchTx := sendArgs.ToTransaction(types.LegacyTxType)
|
||
|
||
// 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, api.b.RPCTxFeeCap()); err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
// Iterate the pending list for replacement
|
||
pending, err := api.b.GetPoolTransactions()
|
||
if err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
for _, p := range pending {
|
||
wantSigHash := api.signer.Hash(matchTx)
|
||
pFrom, err := types.Sender(api.signer, p)
|
||
if err == nil && pFrom == sendArgs.from() && api.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 := api.sign(sendArgs.from(), sendArgs.ToTransaction(types.LegacyTxType))
|
||
if err != nil {
|
||
return common.Hash{}, err
|
||
}
|
||
if err = api.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 (api *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
||
// Retrieve a finalized transaction, or a pooled otherwise
|
||
found, tx, _, _, _, err := api.b.GetTransaction(ctx, hash)
|
||
if !found {
|
||
if tx = api.b.GetPoolTransaction(hash); tx != nil {
|
||
return tx.MarshalBinary()
|
||
}
|
||
if err == nil {
|
||
return nil, nil
|
||
}
|
||
return nil, NewTxIndexingError()
|
||
}
|
||
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() (string, error) {
|
||
return api.b.ChainDb().Stat()
|
||
}
|
||
|
||
// 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))
|
||
}
|
||
|
||
// 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 (api *NetAPI) Listening() bool {
|
||
return true // always listening
|
||
}
|
||
|
||
// PeerCount returns the number of connected peers
|
||
func (api *NetAPI) PeerCount() hexutil.Uint {
|
||
return hexutil.Uint(api.net.PeerCount())
|
||
}
|
||
|
||
// Version returns the current ethereum protocol version.
|
||
func (api *NetAPI) Version() string {
|
||
return fmt.Sprintf("%d", api.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
|
||
}
|