diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index c4322fcbf8..3ecc384210 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -102,7 +102,7 @@ type sidecarConfig struct { // setDefaults fills in default values for unspecified tx fields. func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, config sidecarConfig) error { - if err := args.validateTxType(); err != nil { + if err := args.validateTxTypeSupported(); err != nil { return err } if err := args.setBlobTxSidecar(ctx, config); err != nil { @@ -180,6 +180,9 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, config } else { args.ChainID = (*hexutil.Big)(want) } + if err := args.validateTxTypeMatch(types.LegacyTxType); err != nil { + return err + } return nil } @@ -394,7 +397,7 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config sideca // CallDefaults sanitizes the transaction arguments, often filling in zero values, // for the purpose of eth_call class of RPC methods. func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, chainID *big.Int) error { - if err := args.validateTxType(); err != nil { + if err := args.validateTxTypeSupported(); err != nil { return err } // Reject invalid combinations of pre- and post-1559 fee styles @@ -443,11 +446,14 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, if args.BlobFeeCap == nil && args.BlobHashes != nil { args.BlobFeeCap = new(hexutil.Big) } + if err := args.validateTxTypeMatch(types.LegacyTxType); err != nil { + return err + } return nil } -func (args *TransactionArgs) validateTxType() error { +func (args *TransactionArgs) validateTxTypeSupported() error { if args.Type == nil { return nil } @@ -459,6 +465,21 @@ func (args *TransactionArgs) validateTxType() error { } } +func (args *TransactionArgs) validateTxTypeMatch(defaultType int) error { + if args.Type == nil { + return nil + } + // Blob txs cannot be contract creations. ToTransaction assumes this invariant. + if args.BlobHashes != nil && args.To == nil { + return errors.New(`missing "to" in blob transaction`) + } + inferred := args.ToTransaction(defaultType).Type() + if uint64(*args.Type) != uint64(inferred) { + return fmt.Errorf("transaction type mismatch (requested=%d inferred=%d)", uint64(*args.Type), inferred) + } + return nil +} + // ToMessage converts the transaction arguments to the Message type used by the // core evm. This method is used in calls and traces that do not require a real // live transaction. diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 310fddcabb..d13daa3738 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -290,6 +290,45 @@ func TestTransactionArgsRejectUnsupportedTypeInSetDefaults(t *testing.T) { } } +func TestTransactionArgsRejectTypeMismatchInCallDefaults(t *testing.T) { + t.Parallel() + + requestedLegacy := hexutil.Uint64(types.LegacyTxType) + args := &TransactionArgs{ + Type: &requestedLegacy, + MaxFeePerGas: (*hexutil.Big)(big.NewInt(2)), + } + err := args.CallDefaults(0, big.NewInt(1), big.NewInt(1)) + if err == nil { + t.Fatal("expected type mismatch error") + } + if err.Error() != "transaction type mismatch (requested=0 inferred=2)" { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestTransactionArgsRejectTypeMismatchInSetDefaults(t *testing.T) { + t.Parallel() + + requestedDynamic := hexutil.Uint64(types.DynamicFeeTxType) + gas := hexutil.Uint64(21000) + gasPrice := (*hexutil.Big)(big.NewInt(1)) + to := common.Address{0x1} + args := &TransactionArgs{ + To: &to, + Gas: &gas, + GasPrice: gasPrice, + Type: &requestedDynamic, + } + err := args.setDefaults(context.Background(), newBackendMock(), sidecarConfig{}) + if err == nil { + t.Fatal("expected type mismatch error") + } + if err.Error() != "transaction type mismatch (requested=2 inferred=0)" { + t.Fatalf("unexpected error: %v", err) + } +} + type backendMock struct { current *types.Header config *params.ChainConfig