From e469b41a450445c312aeec715a377f62bb52cb2f Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 18 Jan 2024 20:04:53 +0330 Subject: [PATCH] internal/ethapi: fill blob sidecar --- core/types/transaction.go | 20 ++++++ core/types/tx_blob.go | 10 ++- internal/ethapi/api.go | 2 +- internal/ethapi/transaction_args.go | 99 +++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 3 deletions(-) diff --git a/core/types/transaction.go b/core/types/transaction.go index 9ec0199a03..4e361bc81f 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -424,6 +424,26 @@ func (tx *Transaction) BlobGasFeeCapIntCmp(other *big.Int) int { return tx.BlobGasFeeCap().Cmp(other) } +// WithBlobTxSidecar returns a copy of tx with the blob sidecar. +func (tx *Transaction) WithBlobTxSidecar(sidecar *BlobTxSidecar) *Transaction { + blobtx, ok := tx.inner.(*BlobTx) + if !ok { + return tx + } + cpy := &Transaction{ + inner: blobtx.withSidecar(sidecar), + time: tx.time, + } + // Note: tx.size cache not carried over because the sidecar was not included in size! + if h := tx.hash.Load(); h != nil { + cpy.hash.Store(h) + } + if f := tx.from.Load(); f != nil { + cpy.from.Store(f) + } + return cpy +} + // WithoutBlobTxSidecar returns a copy of tx with the blob sidecar removed. func (tx *Transaction) WithoutBlobTxSidecar() *Transaction { blobtx, ok := tx.inner.(*BlobTx) diff --git a/core/types/tx_blob.go b/core/types/tx_blob.go index da4a9b72f1..c6c31d3488 100644 --- a/core/types/tx_blob.go +++ b/core/types/tx_blob.go @@ -63,7 +63,7 @@ type BlobTxSidecar struct { func (sc *BlobTxSidecar) BlobHashes() []common.Hash { h := make([]common.Hash, len(sc.Commitments)) for i := range sc.Blobs { - h[i] = blobHash(&sc.Commitments[i]) + h[i] = BlobHash(&sc.Commitments[i]) } return h } @@ -190,6 +190,12 @@ func (tx *BlobTx) withoutSidecar() *BlobTx { return &cpy } +func (tx *BlobTx) withSidecar(sidecar *BlobTxSidecar) *BlobTx { + cpy := *tx + tx.Sidecar = sidecar + return &cpy +} + func (tx *BlobTx) encode(b *bytes.Buffer) error { if tx.Sidecar == nil { return rlp.Encode(b, tx) @@ -236,7 +242,7 @@ func (tx *BlobTx) decode(input []byte) error { return nil } -func blobHash(commit *kzg4844.Commitment) common.Hash { +func BlobHash(commit *kzg4844.Commitment) common.Hash { hasher := sha256.New() hasher.Write(commit[:]) var vhash common.Hash diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ee479d7139..598d0406d7 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1839,7 +1839,7 @@ 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 TransactionArgs) (*SignTransactionResult, error) { +func (s *TransactionAPI) FillTransaction(ctx context.Context, args BlobTransactionArgs) (*SignTransactionResult, error) { // 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/transaction_args.go b/internal/ethapi/transaction_args.go index 75dbe38a59..3279d6c412 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -29,11 +29,107 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "github.com/holiman/uint256" ) +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) + for i, c := range args.Commitments { + hashes[i] = types.BlobHash(&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 { @@ -104,6 +200,9 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { if args.BlobHashes != nil && len(args.BlobHashes) == 0 { return errors.New(`need at least 1 blob for a blob transaction`) } + if args.BlobHashes != nil && len(args.BlobHashes) > maxBlobsPerTransaction { + return fmt.Errorf(`too many blobs in transaction (have=%d, max=%d)`, len(args.BlobHashes), maxBlobsPerTransaction) + } if args.To == nil && len(args.data()) == 0 { return errors.New(`contract creation without any data provided`) }