internal/ethapi: merge CallArgs and SendTxArgs (#22718)

There are two transaction parameter structures defined in
the codebase, although for different purposes. But most of
the parameters are shared. So it's nice to reduce the code
duplication by merging them together.

Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
gary rong 2021-05-26 04:30:21 +08:00 committed by GitHub
parent 836c647bdd
commit 51b32cc7e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 236 additions and 223 deletions

View File

@ -730,7 +730,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
// created during the execution of EVM if the given transaction was added on
// top of the provided block and returns them as a JSON object.
// You can provide -2 as a block number to trace on top of the pending block.
func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
// Try to retrieve the specified block
var (
err error

View File

@ -198,7 +198,7 @@ func TestTraceCall(t *testing.T) {
var testSuite = []struct {
blockNumber rpc.BlockNumber
call ethapi.CallArgs
call ethapi.TransactionArgs
config *TraceCallConfig
expectErr error
expect interface{}
@ -206,7 +206,7 @@ func TestTraceCall(t *testing.T) {
// Standard JSON trace upon the genesis, plain transfer.
{
blockNumber: rpc.BlockNumber(0),
call: ethapi.CallArgs{
call: ethapi.TransactionArgs{
From: &accounts[0].addr,
To: &accounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
@ -223,7 +223,7 @@ func TestTraceCall(t *testing.T) {
// Standard JSON trace upon the head, plain transfer.
{
blockNumber: rpc.BlockNumber(genBlocks),
call: ethapi.CallArgs{
call: ethapi.TransactionArgs{
From: &accounts[0].addr,
To: &accounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
@ -240,7 +240,7 @@ func TestTraceCall(t *testing.T) {
// Standard JSON trace upon the non-existent block, error expects
{
blockNumber: rpc.BlockNumber(genBlocks + 1),
call: ethapi.CallArgs{
call: ethapi.TransactionArgs{
From: &accounts[0].addr,
To: &accounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
@ -252,7 +252,7 @@ func TestTraceCall(t *testing.T) {
// Standard JSON trace upon the latest block
{
blockNumber: rpc.LatestBlockNumber,
call: ethapi.CallArgs{
call: ethapi.TransactionArgs{
From: &accounts[0].addr,
To: &accounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
@ -269,7 +269,7 @@ func TestTraceCall(t *testing.T) {
// Standard JSON trace upon the pending block
{
blockNumber: rpc.PendingBlockNumber,
call: ethapi.CallArgs{
call: ethapi.TransactionArgs{
From: &accounts[0].addr,
To: &accounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
@ -329,7 +329,7 @@ func TestOverridenTraceCall(t *testing.T) {
var testSuite = []struct {
blockNumber rpc.BlockNumber
call ethapi.CallArgs
call ethapi.TransactionArgs
config *TraceCallConfig
expectErr error
expect *callTrace
@ -337,7 +337,7 @@ func TestOverridenTraceCall(t *testing.T) {
// Succcessful call with state overriding
{
blockNumber: rpc.PendingBlockNumber,
call: ethapi.CallArgs{
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &randomAccounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
@ -361,7 +361,7 @@ func TestOverridenTraceCall(t *testing.T) {
// Invalid call without state overriding
{
blockNumber: rpc.PendingBlockNumber,
call: ethapi.CallArgs{
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &randomAccounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
@ -390,7 +390,7 @@ func TestOverridenTraceCall(t *testing.T) {
// }
{
blockNumber: rpc.PendingBlockNumber,
call: ethapi.CallArgs{
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &randomAccounts[2].addr,
Data: newRPCBytes(common.Hex2Bytes("8381f58a")), // call number()

View File

@ -862,7 +862,7 @@ func (c *CallResult) Status() Long {
}
func (b *Block) Call(ctx context.Context, args struct {
Data ethapi.CallArgs
Data ethapi.TransactionArgs
}) (*CallResult, error) {
if b.numberOrHash == nil {
_, err := b.resolve(ctx)
@ -887,7 +887,7 @@ func (b *Block) Call(ctx context.Context, args struct {
}
func (b *Block) EstimateGas(ctx context.Context, args struct {
Data ethapi.CallArgs
Data ethapi.TransactionArgs
}) (Long, error) {
if b.numberOrHash == nil {
_, err := b.resolveHeader(ctx)
@ -937,7 +937,7 @@ func (p *Pending) Account(ctx context.Context, args struct {
}
func (p *Pending) Call(ctx context.Context, args struct {
Data ethapi.CallArgs
Data ethapi.TransactionArgs
}) (*CallResult, error) {
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
@ -957,7 +957,7 @@ func (p *Pending) Call(ctx context.Context, args struct {
}
func (p *Pending) EstimateGas(ctx context.Context, args struct {
Data ethapi.CallArgs
Data ethapi.TransactionArgs
}) (Long, error) {
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
gas, err := ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap())

View File

@ -17,7 +17,6 @@
package ethapi
import (
"bytes"
"context"
"errors"
"fmt"
@ -351,9 +350,9 @@ func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
// signTransaction sets defaults and signs the given transaction
// NOTE: the caller needs to ensure that the nonceLock is held, if applicable,
// and release it after the transaction has been submitted to the tx pool
func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArgs, passwd string) (*types.Transaction, error) {
func (s *PrivateAccountAPI) 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}
account := accounts.Account{Address: args.from()}
wallet, err := s.am.Find(account)
if err != nil {
return nil, err
@ -369,18 +368,18 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArg
}
// SendTransaction will create a transaction from the given arguments and
// tries to sign it with the key associated with args.From. If the given passwd isn't
// able to decrypt the key it fails.
func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) {
// tries to sign it with the key associated with args.From. If the given
// passwd isn't able to decrypt the key it fails.
func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args TransactionArgs, passwd string) (common.Hash, error) {
if args.Nonce == nil {
// Hold the addresse's mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts.
s.nonceLock.LockAddr(args.From)
defer s.nonceLock.UnlockAddr(args.From)
s.nonceLock.LockAddr(args.from())
defer s.nonceLock.UnlockAddr(args.from())
}
signed, err := s.signTransaction(ctx, &args, passwd)
if err != nil {
log.Warn("Failed transaction send attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err)
log.Warn("Failed transaction send attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err)
return common.Hash{}, err
}
return SubmitTransaction(ctx, s.b, signed)
@ -390,9 +389,12 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs
// tries to sign it with the key associated with args.From. If the given passwd isn't
// able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast
// to other nodes
func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs, passwd string) (*SignTransactionResult, error) {
func (s *PrivateAccountAPI) 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, fmt.Errorf("sender not specified")
}
if args.Gas == nil {
return nil, fmt.Errorf("gas not specified")
}
@ -408,7 +410,7 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs
}
signed, err := s.signTransaction(ctx, &args, passwd)
if err != nil {
log.Warn("Failed transaction sign attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err)
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()
@ -473,7 +475,7 @@ func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Byt
// SignAndSendTransaction was renamed to SendTransaction. This method is deprecated
// and will be removed in the future. It primary goal is to give clients time to update.
func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) {
func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args TransactionArgs, passwd string) (common.Hash, error) {
return s.SendTransaction(ctx, args, passwd)
}
@ -566,6 +568,7 @@ type AccountResult struct {
StorageHash common.Hash `json:"storageHash"`
StorageProof []StorageResult `json:"storageProof"`
}
type StorageResult struct {
Key string `json:"key"`
Value *hexutil.Big `json:"value"`
@ -751,58 +754,6 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A
return res[:], state.Error()
}
// CallArgs represents the arguments for a call.
type CallArgs struct {
From *common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Data *hexutil.Bytes `json:"data"`
AccessList *types.AccessList `json:"accessList"`
}
// ToMessage converts CallArgs to the Message type used by the core evm
func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message {
// Set sender address or use zero address if none specified.
var addr common.Address
if args.From != nil {
addr = *args.From
}
// Set default gas & gas price if none were set
gas := globalGasCap
if gas == 0 {
gas = uint64(math.MaxUint64 / 2)
}
if args.Gas != nil {
gas = uint64(*args.Gas)
}
if globalGasCap != 0 && globalGasCap < gas {
log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
gas = globalGasCap
}
gasPrice := new(big.Int)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
}
value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
}
var data []byte
if args.Data != nil {
data = *args.Data
}
var accessList types.AccessList
if args.AccessList != nil {
accessList = *args.AccessList
}
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, nil, nil, data, accessList, false)
return msg
}
// 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
@ -855,7 +806,7 @@ func (diff *StateOverride) Apply(state *state.StateDB) error {
return nil
}
func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, 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)
@ -943,7 +894,7 @@ func (e *revertError) ErrorData() interface{} {
//
// Note, this function doesn't make and changes in the state/blockchain and is
// useful to execute and retrieve values.
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
if err != nil {
return nil, err
@ -955,7 +906,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr
return result.Return(), result.Err
}
func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) {
func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) {
// Binary search the gas requirement, as it may be higher than the amount used
var (
lo uint64 = params.TxGas - 1
@ -1066,7 +1017,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
// EstimateGas returns an estimate of the amount of gas needed to execute the
// given transaction against the current pending block.
func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) {
func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) {
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
if blockNrOrHash != nil {
bNrOrHash = *blockNrOrHash
@ -1324,7 +1275,7 @@ type accessListResult struct {
// CreateAccessList creates a EIP-2930 type AccessList for the given transaction.
// Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state.
func (s *PublicBlockChainAPI) CreateAccessList(ctx context.Context, args SendTxArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) {
func (s *PublicBlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) {
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
if blockNrOrHash != nil {
bNrOrHash = *blockNrOrHash
@ -1343,7 +1294,7 @@ func (s *PublicBlockChainAPI) CreateAccessList(ctx context.Context, args SendTxA
// 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 SendTxArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) {
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 {
@ -1361,21 +1312,15 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
if args.To != nil {
to = *args.To
} else {
to = crypto.CreateAddress(args.From, uint64(*args.Nonce))
}
var input []byte
if args.Input != nil {
input = *args.Input
} else if args.Data != nil {
input = *args.Data
to = crypto.CreateAddress(args.from(), uint64(*args.Nonce))
}
// Retrieve the precompiles since they don't need to be added to the access list
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number))
// Create an initial tracer
prevTracer := vm.NewAccessListTracer(nil, args.From, to, precompiles)
prevTracer := vm.NewAccessListTracer(nil, args.from(), to, precompiles)
if args.AccessList != nil {
prevTracer = vm.NewAccessListTracer(*args.AccessList, args.From, to, precompiles)
prevTracer = vm.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles)
}
for {
// Retrieve the current access list to expand
@ -1393,10 +1338,10 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
}
// Copy the original db so we don't modify it
statedb := db.Copy()
msg := types.NewMessage(args.From, args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), nil, nil, input, accessList, false)
msg := types.NewMessage(args.from(), args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), nil, nil, args.data(), accessList, false)
// Apply the transaction with the access list tracer
tracer := vm.NewAccessListTracer(accessList, args.From, to, precompiles)
tracer := vm.NewAccessListTracer(accessList, args.from(), to, precompiles)
config := vm.Config{Tracer: tracer, Debug: true}
vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config)
if err != nil {
@ -1597,123 +1542,6 @@ func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transacti
return wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
}
// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
type SendTxArgs struct {
From common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Nonce *hexutil.Uint64 `json:"nonce"`
// We accept "data" and "input" for backwards-compatibility reasons. "input" is the
// newer name and should be preferred by clients.
Data *hexutil.Bytes `json:"data"`
Input *hexutil.Bytes `json:"input"`
// For non-legacy transactions
AccessList *types.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"`
}
// setDefaults fills in default values for unspecified tx fields.
func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
if args.GasPrice == nil {
price, err := b.SuggestPrice(ctx)
if err != nil {
return err
}
args.GasPrice = (*hexutil.Big)(price)
}
if args.Value == nil {
args.Value = new(hexutil.Big)
}
if args.Nonce == nil {
nonce, err := b.GetPoolNonce(ctx, args.From)
if err != nil {
return err
}
args.Nonce = (*hexutil.Uint64)(&nonce)
}
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
}
if args.To == nil {
// Contract creation
var input []byte
if args.Data != nil {
input = *args.Data
} else if args.Input != nil {
input = *args.Input
}
if len(input) == 0 {
return errors.New(`contract creation without any data provided`)
}
}
// Estimate the gas usage if necessary.
if args.Gas == nil {
// For backwards-compatibility reason, we try both input and data
// but input is preferred.
input := args.Input
if input == nil {
input = args.Data
}
callArgs := CallArgs{
From: &args.From, // From shouldn't be nil
To: args.To,
GasPrice: args.GasPrice,
Value: args.Value,
Data: input,
AccessList: args.AccessList,
}
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap())
if err != nil {
return err
}
args.Gas = &estimated
log.Trace("Estimate gas usage automatically", "gas", args.Gas)
}
if args.ChainID == nil {
id := (*hexutil.Big)(b.ChainConfig().ChainID)
args.ChainID = id
}
return nil
}
// toTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called.
func (args *SendTxArgs) toTransaction() *types.Transaction {
var input []byte
if args.Input != nil {
input = *args.Input
} else if args.Data != nil {
input = *args.Data
}
var data types.TxData
if args.AccessList == nil {
data = &types.LegacyTx{
To: args.To,
Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas),
GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(args.Value),
Data: input,
}
} else {
data = &types.AccessListTx{
To: args.To,
ChainID: (*big.Int)(args.ChainID),
Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas),
GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(args.Value),
Data: input,
AccessList: *args.AccessList,
}
}
return types.NewTx(data)
}
// 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
@ -1746,9 +1574,9 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c
// SendTransaction creates a transaction for the given argument, sign it and submit it to the
// transaction pool.
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args TransactionArgs) (common.Hash, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: args.From}
account := accounts.Account{Address: args.from()}
wallet, err := s.b.AccountManager().Find(account)
if err != nil {
@ -1758,8 +1586,8 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
if args.Nonce == nil {
// Hold the addresse's mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts.
s.nonceLock.LockAddr(args.From)
defer s.nonceLock.UnlockAddr(args.From)
s.nonceLock.LockAddr(args.from())
defer s.nonceLock.UnlockAddr(args.from())
}
// Set some sanity defaults and terminate on failure
@ -1778,7 +1606,7 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
// FillTransaction fills the defaults (nonce, gas, gasPrice) on a given unsigned transaction,
// and returns it to the caller for further processing (signing + broadcast)
func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
return nil, err
@ -1836,7 +1664,7 @@ type SignTransactionResult struct {
// SignTransaction will sign the given transaction with the from account.
// The node needs to have the private key of the account corresponding with
// the given from address and it needs to be unlocked.
func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
if args.Gas == nil {
return nil, fmt.Errorf("gas not specified")
}
@ -1853,7 +1681,7 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil {
return nil, err
}
tx, err := s.sign(args.From, args.toTransaction())
tx, err := s.sign(args.from(), args.toTransaction())
if err != nil {
return nil, err
}
@ -1889,7 +1717,7 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err
// 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 (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {
func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {
if sendArgs.Nonce == nil {
return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec")
}
@ -1918,7 +1746,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
for _, p := range pending {
wantSigHash := s.signer.Hash(matchTx)
pFrom, err := types.Sender(s.signer, p)
if err == nil && pFrom == sendArgs.From && s.signer.Hash(p) == wantSigHash {
if err == nil && pFrom == sendArgs.from() && s.signer.Hash(p) == wantSigHash {
// Match. Re-sign and send the transaction.
if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 {
sendArgs.GasPrice = gasPrice
@ -1926,7 +1754,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit
}
signedTx, err := s.sign(sendArgs.From, sendArgs.toTransaction())
signedTx, err := s.sign(sendArgs.from(), sendArgs.toTransaction())
if err != nil {
return common.Hash{}, err
}

View File

@ -0,0 +1,185 @@
// Copyright 2021 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 (
"bytes"
"context"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
// TransactionArgs represents the arguments to construct a new transaction
// or a message call.
type TransactionArgs struct {
From *common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Nonce *hexutil.Uint64 `json:"nonce"`
// We accept "data" and "input" for backwards-compatibility reasons.
// "input" is the newer name and should be preferred by clients.
// Issue detail: https://github.com/ethereum/go-ethereum/issues/15628
Data *hexutil.Bytes `json:"data"`
Input *hexutil.Bytes `json:"input"`
// For non-legacy transactions
AccessList *types.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"`
}
// from retrieves the transaction sender address.
func (arg *TransactionArgs) from() common.Address {
if arg.From == nil {
return common.Address{}
}
return *arg.From
}
// data retrieves the transaction calldata. Input field is preferred.
func (arg *TransactionArgs) data() []byte {
if arg.Input != nil {
return *arg.Input
}
if arg.Data != nil {
return *arg.Data
}
return nil
}
// setDefaults fills in default values for unspecified tx fields.
func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
if args.GasPrice == nil {
price, err := b.SuggestPrice(ctx)
if err != nil {
return err
}
args.GasPrice = (*hexutil.Big)(price)
}
if args.Value == nil {
args.Value = new(hexutil.Big)
}
if args.Nonce == nil {
nonce, err := b.GetPoolNonce(ctx, args.from())
if err != nil {
return err
}
args.Nonce = (*hexutil.Uint64)(&nonce)
}
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
}
if args.To == nil && len(args.data()) == 0 {
return errors.New(`contract creation without any data provided`)
}
// Estimate the gas usage if necessary.
if args.Gas == nil {
// These fields are immutable during the estimation, safe to
// pass the pointer directly.
callArgs := TransactionArgs{
From: args.From,
To: args.To,
GasPrice: args.GasPrice,
Value: args.Value,
Data: args.Data,
AccessList: args.AccessList,
}
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap())
if err != nil {
return err
}
args.Gas = &estimated
log.Trace("Estimate gas usage automatically", "gas", args.Gas)
}
if args.ChainID == nil {
id := (*hexutil.Big)(b.ChainConfig().ChainID)
args.ChainID = id
}
return nil
}
// ToMessage converts TransactionArgs to the Message type used by the core evm
func (args *TransactionArgs) ToMessage(globalGasCap uint64) types.Message {
// Set sender address or use zero address if none specified.
addr := args.from()
// Set default gas & gas price if none were set
gas := globalGasCap
if gas == 0 {
gas = uint64(math.MaxUint64 / 2)
}
if args.Gas != nil {
gas = uint64(*args.Gas)
}
if globalGasCap != 0 && globalGasCap < gas {
log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
gas = globalGasCap
}
gasPrice := new(big.Int)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
}
value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
}
data := args.data()
var accessList types.AccessList
if args.AccessList != nil {
accessList = *args.AccessList
}
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, nil, nil, data, accessList, false)
return msg
}
// toTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called.
func (args *TransactionArgs) toTransaction() *types.Transaction {
var data types.TxData
if args.AccessList == nil {
data = &types.LegacyTx{
To: args.To,
Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas),
GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(args.Value),
Data: args.data(),
}
} else {
data = &types.AccessListTx{
To: args.To,
ChainID: (*big.Int)(args.ChainID),
Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas),
GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(args.Value),
Data: args.data(),
AccessList: *args.AccessList,
}
}
return types.NewTx(data)
}