diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index b2b7e5c4b0..359f51acc2 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1839,7 +1839,9 @@ func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionAr // FillTransaction fills the defaults (nonce, gas, gasPrice or 1559 fields) // on a given unsigned transaction, and returns it to the caller for further // processing (signing + broadcast). -func (s *TransactionAPI) FillTransaction(ctx context.Context, args BlobTransactionArgs) (*SignTransactionResult, error) { +func (s *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, s.b); err != nil { return nil, err diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 8f3b96856a..cdeeace5e0 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -991,12 +991,10 @@ func TestSignTransaction(t *testing.T) { b.SetPoS() }) api := NewTransactionAPI(b, nil) - res, err := api.FillTransaction(context.Background(), BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - }, + res, err := api.FillTransaction(context.Background(), TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), }) if err != nil { t.Fatalf("failed to fill tx defaults: %v\n", err) @@ -1031,13 +1029,11 @@ func TestSignBlobTransaction(t *testing.T) { b.SetPoS() }) api := NewTransactionAPI(b, nil) - res, err := api.FillTransaction(context.Background(), BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - BlobHashes: []common.Hash{{0x01, 0x22}}, - }, + res, err := api.FillTransaction(context.Background(), TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), + BlobHashes: []common.Hash{{0x01, 0x22}}, }) if err != nil { t.Fatalf("failed to fill tx defaults: %v\n", err) @@ -1067,13 +1063,11 @@ func TestSendBlobTransaction(t *testing.T) { b.SetPoS() }) api := NewTransactionAPI(b, nil) - res, err := api.FillTransaction(context.Background(), BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - BlobHashes: []common.Hash{common.Hash{0x01, 0x22}}, - }, + res, err := api.FillTransaction(context.Background(), TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), + BlobHashes: []common.Hash{common.Hash{0x01, 0x22}}, }) if err != nil { t.Fatalf("failed to fill tx defaults: %v\n", err) @@ -1112,18 +1106,16 @@ func TestFillBlobTransaction(t *testing.T) { } suite := []struct { name string - args BlobTransactionArgs + args TransactionArgs err string want *result }{ { name: "TestInvalidParamsCombination1", - args: BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - }, + args: TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), Blobs: []kzg4844.Blob{{}}, Proofs: []kzg4844.Proof{{}}, }, @@ -1131,12 +1123,10 @@ func TestFillBlobTransaction(t *testing.T) { }, { name: "TestInvalidParamsCombination2", - args: BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - }, + args: TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), Blobs: []kzg4844.Blob{{}}, Commitments: []kzg4844.Commitment{{}}, }, @@ -1144,12 +1134,10 @@ func TestFillBlobTransaction(t *testing.T) { }, { name: "TestInvalidParamsCount1", - args: BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - }, + args: TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), Blobs: []kzg4844.Blob{{}}, Commitments: []kzg4844.Commitment{{}, {}}, Proofs: []kzg4844.Proof{{}, {}}, @@ -1158,12 +1146,10 @@ func TestFillBlobTransaction(t *testing.T) { }, { name: "TestInvalidParamsCount2", - args: BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - }, + args: TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), Blobs: []kzg4844.Blob{{}, {}}, Commitments: []kzg4844.Commitment{{}, {}}, Proofs: []kzg4844.Proof{{}}, @@ -1172,12 +1158,10 @@ func TestFillBlobTransaction(t *testing.T) { }, { name: "TestInvalidProofVerification", - args: BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - }, + args: TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), Blobs: []kzg4844.Blob{{}, {}}, Commitments: []kzg4844.Commitment{{}, {}}, Proofs: []kzg4844.Proof{{}, {}}, @@ -1186,12 +1170,10 @@ func TestFillBlobTransaction(t *testing.T) { }, { name: "TestGenerateBlobHashes", - args: BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - }, + args: TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), Blobs: []kzg4844.Blob{emptyBlob}, Commitments: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}, @@ -1207,13 +1189,11 @@ func TestFillBlobTransaction(t *testing.T) { }, { name: "TestValidBlobHashes", - args: BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - BlobHashes: []common.Hash{emptyBlobHash}, - }, + args: TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), + BlobHashes: []common.Hash{emptyBlobHash}, Blobs: []kzg4844.Blob{emptyBlob}, Commitments: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}, @@ -1229,13 +1209,11 @@ func TestFillBlobTransaction(t *testing.T) { }, { name: "TestInvalidBlobHashes", - args: BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - BlobHashes: []common.Hash{{0x01, 0x22}}, - }, + args: TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), + BlobHashes: []common.Hash{{0x01, 0x22}}, Blobs: []kzg4844.Blob{emptyBlob}, Commitments: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}, @@ -1244,12 +1222,10 @@ func TestFillBlobTransaction(t *testing.T) { }, { name: "TestGenerateBlobProofs", - args: BlobTransactionArgs{ - TransactionArgs: TransactionArgs{ - From: &b.acc.Address, - To: &to, - Value: (*hexutil.Big)(big.NewInt(1)), - }, + args: TransactionArgs{ + From: &b.acc.Address, + To: &to, + Value: (*hexutil.Big)(big.NewInt(1)), Blobs: []kzg4844.Blob{emptyBlob}, }, want: &result{ diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 6b41e0a1c2..6a9f4d6c16 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -41,97 +41,6 @@ var ( maxBlobsPerTransaction = params.MaxBlobGasPerBlock / params.BlobTxBlobGasPerBlob ) -// BlobTransactionArgs represents the arguments to construct a new -// blob transaction. It includes the blob side-car. -type BlobTransactionArgs struct { - TransactionArgs - Blobs []kzg4844.Blob `json:"blobs"` - Commitments []kzg4844.Commitment `json:"commitments"` - Proofs []kzg4844.Proof `json:"proofs"` -} - -func (args *BlobTransactionArgs) setDefaults(ctx context.Context, b Backend) error { - // First validate and fill in other fields to avoid - // the expensive blob proof computation for an invalid transaction. - if err := args.TransactionArgs.setDefaults(ctx, b); err != nil { - return err - } - // No blobs, we're done. - if args.Blobs == nil { - return nil - } - n := len(args.Blobs) - // Assume user provides either only blobs (w/o hashes), or - // blobs together with commitments and proofs. - if args.Commitments == nil && args.Proofs != nil { - return errors.New(`blob proofs provided while commitments were not`) - } else if args.Commitments != nil && args.Proofs == nil { - return errors.New(`blob commitments provided while proofs were not`) - } - // len(blobs) == len(commitments) == len(proofs) == len(hashes) - if args.Commitments != nil && len(args.Commitments) != n { - return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n) - } - if args.Proofs != nil && len(args.Proofs) != n { - return fmt.Errorf("number of blobs and proofs mismatch (have=%d, want=%d)", len(args.Proofs), n) - } - if args.BlobHashes != nil && len(args.BlobHashes) != n { - return fmt.Errorf("number of blobs and hashes mismatch (have=%d, want=%d)", len(args.BlobHashes), n) - } - if args.Commitments == nil { - // Generate commitment and proof. - commitments := make([]kzg4844.Commitment, n) - proofs := make([]kzg4844.Proof, n) - for i, b := range args.Blobs { - c, err := kzg4844.BlobToCommitment(b) - if err != nil { - return err - } - commitments[i] = c - p, err := kzg4844.ComputeBlobProof(b, c) - if err != nil { - return err - } - proofs[i] = p - } - args.Commitments = commitments - args.Proofs = proofs - } else { - for i, b := range args.Blobs { - if err := kzg4844.VerifyBlobProof(b, args.Commitments[i], args.Proofs[i]); err != nil { - return fmt.Errorf("failed to verify blob proof: %v", err) - } - } - } - hashes := make([]common.Hash, n) - hasher := sha256.New() - for i, c := range args.Commitments { - hashes[i] = kzg4844.CalcBlobHashV1(hasher, &c) - } - if args.BlobHashes != nil { - for i, h := range hashes { - if h != args.BlobHashes[i] { - return fmt.Errorf("blob hash verification failed (have=%s, want=%s)", args.BlobHashes[i], h) - } - } - } else { - args.BlobHashes = hashes - } - return nil -} - -func (args *BlobTransactionArgs) toTransaction() *types.Transaction { - tx := args.TransactionArgs.toTransaction() - if args.Blobs != nil && tx.Type() == types.BlobTxType { - return tx.WithBlobTxSidecar(&types.BlobTxSidecar{ - Blobs: args.Blobs, - Commitments: args.Commitments, - Proofs: args.Proofs, - }) - } - return tx -} - // TransactionArgs represents the arguments to construct a new transaction // or a message call. type TransactionArgs struct { @@ -154,9 +63,17 @@ type TransactionArgs struct { AccessList *types.AccessList `json:"accessList,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"` - // Introduced by EIP-4844. + // For BlobTxType BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas"` BlobHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + + // For BlobTxType transactions with blob sidecar + Blobs []kzg4844.Blob `json:"blobs"` + Commitments []kzg4844.Commitment `json:"commitments"` + Proofs []kzg4844.Proof `json:"proofs"` + + // This configures whether blobs are allowed to be passed. + blobSidecarAllowed bool } // from retrieves the transaction sender address. @@ -180,9 +97,13 @@ func (args *TransactionArgs) data() []byte { // setDefaults fills in default values for unspecified tx fields. func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { + if err := args.setBlobTxSidecar(ctx, b); err != nil { + return err + } if err := args.setFeeDefaults(ctx, b); err != nil { return err } + if args.Value == nil { args.Value = new(hexutil.Big) } @@ -196,6 +117,8 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { 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`) } + + // BlobTx fields if args.BlobHashes != nil && args.To == nil { return errors.New(`blob transactions cannot have the form of a create transaction`) } @@ -205,9 +128,12 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { if args.BlobHashes != nil && len(args.BlobHashes) > maxBlobsPerTransaction { return fmt.Errorf(`too many blobs in transaction (have=%d, max=%d)`, len(args.BlobHashes), maxBlobsPerTransaction) } + + // create check 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 @@ -231,6 +157,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { args.Gas = &estimated log.Trace("Estimate gas usage automatically", "gas", args.Gas) } + // If chain id is provided, ensure it matches the local chain id. Otherwise, set the local // chain id as the default. want := b.ChainConfig().ChainID @@ -266,10 +193,12 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) erro } return nil // No need to set anything, user already set MaxFeePerGas and MaxPriorityFeePerGas } + // Sanity check the EIP-4844 fee parameters. if args.BlobFeeCap != nil && args.BlobFeeCap.ToInt().Sign() == 0 { return errors.New("maxFeePerBlobGas must be non-zero") } + // Sanity check the non-EIP-1559 fee parameters. head := b.CurrentHeader() isLondon := b.ChainConfig().IsLondon(head.Number) @@ -351,6 +280,81 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ return nil } +// setBlobTxSidecar adds the blob tx +func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, b Backend) error { + // No blobs, we're done. + if args.Blobs == nil { + return nil + } + + // Passing blobs is not allowed in all contexts, only in specific methods. + if !args.blobSidecarAllowed { + return errors.New("'blobs' is not supported for this RPC method") + } + + n := len(args.Blobs) + // Assume user provides either only blobs (w/o hashes), or + // blobs together with commitments and proofs. + if args.Commitments == nil && args.Proofs != nil { + return errors.New(`blob proofs provided while commitments were not`) + } else if args.Commitments != nil && args.Proofs == nil { + return errors.New(`blob commitments provided while proofs were not`) + } + + // len(blobs) == len(commitments) == len(proofs) == len(hashes) + if args.Commitments != nil && len(args.Commitments) != n { + return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n) + } + if args.Proofs != nil && len(args.Proofs) != n { + return fmt.Errorf("number of blobs and proofs mismatch (have=%d, want=%d)", len(args.Proofs), n) + } + if args.BlobHashes != nil && len(args.BlobHashes) != n { + return fmt.Errorf("number of blobs and hashes mismatch (have=%d, want=%d)", len(args.BlobHashes), n) + } + + if args.Commitments == nil { + // Generate commitment and proof. + commitments := make([]kzg4844.Commitment, n) + proofs := make([]kzg4844.Proof, n) + for i, b := range args.Blobs { + c, err := kzg4844.BlobToCommitment(b) + if err != nil { + return err + } + commitments[i] = c + p, err := kzg4844.ComputeBlobProof(b, c) + if err != nil { + return err + } + proofs[i] = p + } + args.Commitments = commitments + args.Proofs = proofs + } else { + for i, b := range args.Blobs { + if err := kzg4844.VerifyBlobProof(b, args.Commitments[i], args.Proofs[i]); err != nil { + return fmt.Errorf("failed to verify blob proof: %v", err) + } + } + } + + hashes := make([]common.Hash, n) + hasher := sha256.New() + for i, c := range args.Commitments { + hashes[i] = kzg4844.CalcBlobHashV1(hasher, &c) + } + if args.BlobHashes != nil { + for i, h := range hashes { + if h != args.BlobHashes[i] { + return fmt.Errorf("blob hash verification failed (have=%s, want=%s)", args.BlobHashes[i], h) + } + } + } else { + args.BlobHashes = hashes + } + 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. @@ -464,6 +468,14 @@ func (args *TransactionArgs) toTransaction() *types.Transaction { BlobHashes: args.BlobHashes, BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)), } + if args.Blobs != nil { + data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{ + Blobs: args.Blobs, + Commitments: args.Commitments, + Proofs: args.Proofs, + } + } + case args.MaxFeePerGas != nil: al := types.AccessList{} if args.AccessList != nil { @@ -480,6 +492,7 @@ func (args *TransactionArgs) toTransaction() *types.Transaction { Data: args.data(), AccessList: al, } + case args.AccessList != nil: data = &types.AccessListTx{ To: args.To, @@ -491,6 +504,7 @@ func (args *TransactionArgs) toTransaction() *types.Transaction { Data: args.data(), AccessList: *args.AccessList, } + default: data = &types.LegacyTx{ To: args.To, @@ -504,12 +518,6 @@ func (args *TransactionArgs) toTransaction() *types.Transaction { return types.NewTx(data) } -// ToTransaction converts the arguments to a transaction. -// This assumes that setDefaults has been called. -func (args *TransactionArgs) ToTransaction() *types.Transaction { - return args.toTransaction() -} - // IsEIP4844 returns an indicator if the args contains EIP4844 fields. func (args *TransactionArgs) IsEIP4844() bool { return args.BlobHashes != nil || args.BlobFeeCap != nil