diff --git a/helper/common/common.go b/helper/common/common.go index 3ba8ffe337..6caf6b695e 100644 --- a/helper/common/common.go +++ b/helper/common/common.go @@ -79,6 +79,15 @@ func BigMin(x, y *big.Int) *big.Int { return x } +// BigMax returns the larger of x or y. +func BigMax(x, y *big.Int) *big.Int { + if x.Cmp(y) > 0 { + return x + } + + return y +} + func ConvertUnmarshalledUint(x interface{}) (uint64, error) { switch tx := x.(type) { case float64: diff --git a/jsonrpc/debug_endpoint_test.go b/jsonrpc/debug_endpoint_test.go index 0b90d06181..1644d86ec5 100644 --- a/jsonrpc/debug_endpoint_test.go +++ b/jsonrpc/debug_endpoint_test.go @@ -593,16 +593,16 @@ func TestTraceCall(t *testing.T) { blockNumber = BlockNumber(testBlock10.Number()) txArg = &txnArgs{ - From: &from, - To: &to, - Gas: &gas, - GasPrice: &gasPrice, - GasTipCap: &gasTipCap, - GasFeeCap: &gasFeeCap, - Value: &value, - Data: &data, - Input: &input, - Nonce: &nonce, + From: &from, + To: &to, + Gas: &gas, + GasPrice: &gasPrice, + MaxPriorityFeePerGas: &gasTipCap, + MaxFeePerGas: &gasFeeCap, + Value: &value, + Data: &data, + Input: &input, + Nonce: &nonce, } decodedTx = &types.Transaction{ Nonce: uint64(nonce), diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index 8d558ee6c1..b7ba7ccc25 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -251,6 +251,10 @@ func (e *Eth) SendRawTransaction(buf argBytes) (interface{}, error) { // SendTransaction rejects eth_sendTransaction json-rpc call as we don't support wallet management func (e *Eth) SendTransaction(args *txnArgs) (interface{}, error) { + if err := args.setDefaults(e.priceLimit, e); err != nil { + return nil, err + } + tx, err := DecodeTxn(args, e.store, true) if err != nil { return nil, err diff --git a/jsonrpc/eth_endpoint_test.go b/jsonrpc/eth_endpoint_test.go index 419ac9ce1b..2bb8f4a53d 100644 --- a/jsonrpc/eth_endpoint_test.go +++ b/jsonrpc/eth_endpoint_test.go @@ -36,15 +36,15 @@ func TestEth_DecodeTxn(t *testing.T) { { name: "should be successful", arg: &txnArgs{ - From: &addr1, - To: &addr2, - Gas: toArgUint64Ptr(21000), - GasPrice: toArgBytesPtr(big.NewInt(10000).Bytes()), - GasTipCap: toArgBytesPtr(big.NewInt(10000).Bytes()), - GasFeeCap: toArgBytesPtr(big.NewInt(10000).Bytes()), - Value: toArgBytesPtr(oneEther.Bytes()), - Data: nil, - Nonce: toArgUint64Ptr(0), + From: &addr1, + To: &addr2, + Gas: toArgUint64Ptr(21000), + GasPrice: toArgBytesPtr(big.NewInt(10000).Bytes()), + MaxPriorityFeePerGas: toArgBytesPtr(big.NewInt(10000).Bytes()), + MaxFeePerGas: toArgBytesPtr(big.NewInt(10000).Bytes()), + Value: toArgBytesPtr(oneEther.Bytes()), + Data: nil, + Nonce: toArgUint64Ptr(0), }, res: &types.Transaction{ From: addr1, @@ -266,16 +266,16 @@ func TestEth_TxnType(t *testing.T) { // Setup Txn args := &txnArgs{ - From: &addr1, - To: &addr2, - Gas: toArgUint64Ptr(21000), - GasPrice: toArgBytesPtr(big.NewInt(10000).Bytes()), - GasTipCap: toArgBytesPtr(big.NewInt(10000).Bytes()), - GasFeeCap: toArgBytesPtr(big.NewInt(10000).Bytes()), - Value: toArgBytesPtr(oneEther.Bytes()), - Data: nil, - Nonce: toArgUint64Ptr(0), - Type: toArgUint64Ptr(uint64(types.DynamicFeeTx)), + From: &addr1, + To: &addr2, + Gas: toArgUint64Ptr(21000), + GasPrice: toArgBytesPtr(big.NewInt(10000).Bytes()), + MaxPriorityFeePerGas: toArgBytesPtr(big.NewInt(10000).Bytes()), + MaxFeePerGas: toArgBytesPtr(big.NewInt(10000).Bytes()), + Value: toArgBytesPtr(oneEther.Bytes()), + Data: nil, + Nonce: toArgUint64Ptr(0), + Type: toArgUint64Ptr(uint64(types.DynamicFeeTx)), } expectedRes := &types.Transaction{ diff --git a/jsonrpc/helper.go b/jsonrpc/helper.go index eabfbbdcfb..1ddf0696fe 100644 --- a/jsonrpc/helper.go +++ b/jsonrpc/helper.go @@ -187,12 +187,12 @@ func DecodeTxn(arg *txnArgs, store nonceGetter, forceSetNonce bool) (*types.Tran arg.GasPrice = argBytesPtr([]byte{}) } - if arg.GasTipCap == nil { - arg.GasTipCap = argBytesPtr([]byte{}) + if arg.MaxPriorityFeePerGas == nil { + arg.MaxPriorityFeePerGas = argBytesPtr([]byte{}) } - if arg.GasFeeCap == nil { - arg.GasFeeCap = argBytesPtr([]byte{}) + if arg.MaxFeePerGas == nil { + arg.MaxFeePerGas = argBytesPtr([]byte{}) } var input []byte @@ -223,8 +223,8 @@ func DecodeTxn(arg *txnArgs, store nonceGetter, forceSetNonce bool) (*types.Tran From: *arg.From, Gas: uint64(*arg.Gas), GasPrice: new(big.Int).SetBytes(*arg.GasPrice), - GasTipCap: new(big.Int).SetBytes(*arg.GasTipCap), - GasFeeCap: new(big.Int).SetBytes(*arg.GasFeeCap), + GasTipCap: new(big.Int).SetBytes(*arg.MaxPriorityFeePerGas), + GasFeeCap: new(big.Int).SetBytes(*arg.MaxFeePerGas), Value: new(big.Int).SetBytes(*arg.Value), Input: input, Nonce: uint64(*arg.Nonce), diff --git a/jsonrpc/helper_test.go b/jsonrpc/helper_test.go index a9084bc9f1..23c3f62cee 100644 --- a/jsonrpc/helper_test.go +++ b/jsonrpc/helper_test.go @@ -696,15 +696,15 @@ func TestDecodeTxn(t *testing.T) { { name: "should return mapped transaction", arg: &txnArgs{ - From: &from, - To: &to, - Gas: &gas, - GasPrice: &gasPrice, - GasTipCap: &gasTipCap, - GasFeeCap: &gasFeeCap, - Value: &value, - Input: &input, - Nonce: &nonce, + From: &from, + To: &to, + Gas: &gas, + GasPrice: &gasPrice, + MaxPriorityFeePerGas: &gasTipCap, + MaxFeePerGas: &gasFeeCap, + Value: &value, + Input: &input, + Nonce: &nonce, }, store: &debugEndpointMockStore{}, expected: &types.Transaction{ diff --git a/jsonrpc/types.go b/jsonrpc/types.go index 3bc717ea3a..614b9a75fb 100644 --- a/jsonrpc/types.go +++ b/jsonrpc/types.go @@ -1,6 +1,8 @@ package jsonrpc import ( + "errors" + "fmt" "math/big" "strconv" "strings" @@ -376,17 +378,165 @@ func encodeToHex(b []byte) []byte { // txnArgs is the transaction argument for the rpc endpoints type txnArgs struct { - From *types.Address - To *types.Address - Gas *argUint64 - GasPrice *argBytes - GasTipCap *argBytes - GasFeeCap *argBytes - Value *argBytes - Data *argBytes - Input *argBytes - Nonce *argUint64 - Type *argUint64 + From *types.Address `json:"from"` + To *types.Address `json:"to"` + Gas *argUint64 `json:"gas"` + GasPrice *argBytes `json:"gasPrice"` + MaxFeePerGas *argBytes `json:"maxFeePerGas"` + MaxPriorityFeePerGas *argBytes `json:"maxPriorityFeePerGas"` + Value *argBytes `json:"value"` + Data *argBytes `json:"data"` + Input *argBytes `json:"input"` + Nonce *argUint64 `json:"nonce"` + Type *argUint64 `json:"type"` +} + +// data retrieves the transaction calldata. Input field is preferred. +func (args *txnArgs) data() []byte { + if args.Input != nil { + return *args.Input + } + + if args.Data != nil { + return *args.Data + } + + return nil +} + +func (args *txnArgs) setDefaults(priceLimit uint64, eth *Eth) error { + if err := args.setFeeDefaults(priceLimit, eth.store); err != nil { + return err + } + + if args.Nonce == nil { + args.Nonce = argUintPtr(eth.store.GetNonce(*args.From)) + } + + if args.Gas == nil { + // These fields are immutable during the estimation, safe to + // pass the pointer directly. + data := args.data() + callArgs := txnArgs{ + From: args.From, + To: args.To, + GasPrice: args.GasPrice, + MaxFeePerGas: args.MaxFeePerGas, + MaxPriorityFeePerGas: args.MaxPriorityFeePerGas, + Value: args.Value, + Data: argBytesPtr(data), + } + + estimatedGas, err := eth.EstimateGas(&callArgs, nil) + if err != nil { + return err + } + + estimatedGasUint64, ok := estimatedGas.(argUint64) + if !ok { + return errors.New("estimated gas not a uint64") + } + + args.Gas = &estimatedGasUint64 + } + + return nil +} + +// setFeeDefaults fills in default fee values for unspecified tx fields. +func (args *txnArgs) setFeeDefaults(priceLimit uint64, store ethStore) error { + // If both gasPrice and at least one of the EIP-1559 fee parameters are specified, error. + if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { + return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + } + + // If the tx has completely specified a fee mechanism, no default is needed. + // This allows users who are not yet synced past London to get defaults for + // other tx values. See https://github.com/ethereum/go-ethereum/pull/23274 + // for more information. + eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil + + // Sanity check the EIP-1559 fee parameters if present. + if args.GasPrice == nil && eip1559ParamsSet { + maxFeePerGas := new(big.Int).SetBytes(*args.MaxFeePerGas) + maxPriorityFeePerGas := new(big.Int).SetBytes(*args.MaxPriorityFeePerGas) + + if maxFeePerGas.Sign() == 0 { + return errors.New("maxFeePerGas must be non-zero") + } + + if maxFeePerGas.Cmp(maxPriorityFeePerGas) < 0 { + return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas) + } + + args.Type = argUintPtr(uint64(types.DynamicFeeTx)) + + return nil // No need to set anything, user already set MaxFeePerGas and MaxPriorityFeePerGas + } + + // Sanity check the non-EIP-1559 fee parameters. + head := store.Header() + isLondon := store.GetForksInTime(head.Number).London + + if args.GasPrice != nil && !eip1559ParamsSet { + // Zero gas-price is not allowed after London fork + if new(big.Int).SetBytes(*args.GasPrice).Sign() == 0 && isLondon { + return errors.New("gasPrice must be non-zero after london fork") + } + + return nil // No need to set anything, user already set GasPrice + } + + // Now attempt to fill in default value depending on whether London is active or not. + if isLondon { + // London is active, set maxPriorityFeePerGas and maxFeePerGas. + if err := args.setLondonFeeDefaults(head, store); err != nil { + return err + } + } else { + if args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil { + return errors.New("maxFeePerGas and maxPriorityFeePerGas are not valid before London is active") + } + + // London not active, set gas price. + avgGasPrice := store.GetAvgGasPrice() + + args.GasPrice = argBytesPtr(common.BigMax(new(big.Int).SetUint64(priceLimit), avgGasPrice).Bytes()) + } + return nil +} + +// setLondonFeeDefaults fills in reasonable default fee values for unspecified fields. +func (args *txnArgs) setLondonFeeDefaults(head *types.Header, store ethStore) error { + // Set maxPriorityFeePerGas if it is missing. + if args.MaxPriorityFeePerGas == nil { + tip, err := store.MaxPriorityFeePerGas() + if err != nil { + return err + } + args.MaxPriorityFeePerGas = argBytesPtr(tip.Bytes()) + } + + // Set maxFeePerGas if it is missing. + if args.MaxFeePerGas == nil { + // Set the max fee to be 2 times larger than the previous block's base fee. + // The additional slack allows the tx to not become invalidated if the base + // fee is rising. + val := new(big.Int).Add( + new(big.Int).SetBytes(*args.MaxPriorityFeePerGas), + new(big.Int).Mul(new(big.Int).SetUint64(head.BaseFee), big.NewInt(2)), + ) + args.MaxFeePerGas = argBytesPtr(val.Bytes()) + } + + // Both EIP-1559 fee parameters are now set; sanity check them. + if new(big.Int).SetBytes(*args.MaxFeePerGas).Cmp(new(big.Int).SetBytes(*args.MaxPriorityFeePerGas)) < 0 { + return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas) + } + + args.Type = argUintPtr(uint64(types.DynamicFeeTx)) + + return nil } type progression struct {