From d75e28ee323f5c3e57ac95456107c35bba63f611 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 5 Aug 2025 11:21:25 +0800 Subject: [PATCH] core, internal, miner, signer: convert legacy sidecar in Osaka fork --- core/txpool/blobpool/blobpool.go | 31 +++++++- core/txpool/validation.go | 6 +- internal/ethapi/api.go | 50 ++++++++++--- internal/ethapi/transaction_args.go | 106 +++++++++++++++++++--------- miner/worker.go | 16 ----- signer/core/apitypes/types.go | 7 +- 6 files changed, 152 insertions(+), 64 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 948ecd14c3..382cf96d75 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1397,6 +1397,31 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int { return available } +// convertSidecar converts the legacy sidecar in the submitted transactions +// if osaka fork has been activated. +func (p *BlobPool) convertSidecar(txs []*types.Transaction) ([]*types.Transaction, []error) { + head := p.chain.CurrentBlock() + if !p.chain.Config().IsOsaka(head.Number, head.Time) { + return txs, make([]error, len(txs)) + } + var errs []error + for _, tx := range txs { + sidecar := tx.BlobTxSidecar() + if sidecar == nil { + errs = append(errs, errors.New("missing sidecar in blob transaction")) + continue + } + if sidecar.Version == types.BlobSidecarVersion0 { + if err := sidecar.ToV1(); err != nil { + errs = append(errs, err) + continue + } + } + errs = append(errs, nil) + } + return txs, errs +} + // Add inserts a set of blob transactions into the pool if they pass validation (both // consensus validity and pool restrictions). // @@ -1404,10 +1429,14 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int { // related to the add is finished. Only use this during tests for determinism. func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error { var ( + errs []error adds = make([]*types.Transaction, 0, len(txs)) - errs = make([]error, len(txs)) ) + txs, errs = p.convertSidecar(txs) for i, tx := range txs { + if errs[i] != nil { + continue + } errs[i] = p.add(tx) if errs[i] == nil { adds = append(adds, tx.WithoutBlobTxSidecar()) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index d4f3401086..46974fad3c 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -22,7 +22,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -167,9 +166,8 @@ func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationO if len(hashes) == 0 { return errors.New("blobless blob transaction") } - maxBlobs := eip4844.MaxBlobsPerBlock(opts.Config, head.Time) - if len(hashes) > maxBlobs { - return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs) + if len(hashes) > params.BlobTxMaxBlobs { + return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.BlobTxMaxBlobs) } if len(sidecar.Blobs) != len(hashes) { return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes)) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 51b6ca3c44..3eb4003f5a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1479,6 +1479,8 @@ 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. +// +// This API is not capable for submitting blob transaction with sidecar. func (api *TransactionAPI) SendTransaction(ctx context.Context, args TransactionArgs) (common.Hash, error) { // Look up the wallet containing the requested signer account := accounts.Account{Address: args.from()} @@ -1499,7 +1501,10 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction } // Set some sanity defaults and terminate on failure - if err := args.setDefaults(ctx, api.b, false); err != nil { + config := setDefaultConfig{ + skipGasEstimation: false, + } + if err := args.setDefaults(ctx, api.b, config); err != nil { return common.Hash{}, err } // Assemble the transaction and sign with the wallet @@ -1516,10 +1521,20 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction // on a given unsigned transaction, and returns it to the caller for further // processing (signing + broadcast). func (api *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, api.b, false); err != nil { + config := setDefaultConfig{ + skipGasEstimation: false, + blobSidecarAllowed: true, + blobSidecarVersion: types.BlobSidecarVersion0, + } + if len(args.Blobs) > 0 { + chainHead := api.b.CurrentHeader() + isOsaka := api.b.ChainConfig().IsOsaka(chainHead.Number, chainHead.Time) + if isOsaka { + config.blobSidecarVersion = types.BlobSidecarVersion1 + } + } + if err := args.setDefaults(ctx, api.b, config); err != nil { return nil, err } // Assemble the transaction and obtain rlp @@ -1576,8 +1591,6 @@ type SignTransactionResult struct { // The node needs to have the private key of the account corresponding with // the given from address and it needs to be unlocked. func (api *TransactionAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { - args.blobSidecarAllowed = true - if args.Gas == nil { return nil, errors.New("gas not specified") } @@ -1587,7 +1600,21 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction if args.Nonce == nil { return nil, errors.New("nonce not specified") } - if err := args.setDefaults(ctx, api.b, false); err != nil { + config := setDefaultConfig{ + skipGasEstimation: false, + blobSidecarAllowed: true, + } + sidecarVersion := types.BlobSidecarVersion0 + if len(args.Blobs) > 0 { + chainHead := api.b.CurrentHeader() + isOsaka := api.b.ChainConfig().IsOsaka(chainHead.Number, chainHead.Time) + if isOsaka { + sidecarVersion = types.BlobSidecarVersion1 + } + } + config.blobSidecarVersion = sidecarVersion + + if err := args.setDefaults(ctx, api.b, config); err != nil { return nil, err } // Before actually sign the transaction, ensure the transaction fee is reasonable. @@ -1603,7 +1630,7 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction // no longer retains the blobs, only the blob hashes. In this step, we need // to put back the blob(s). if args.IsEIP4844() { - signed = signed.WithBlobTxSidecar(types.NewBlobTxSidecar(types.BlobSidecarVersion0, args.Blobs, args.Commitments, args.Proofs)) + signed = signed.WithBlobTxSidecar(types.NewBlobTxSidecar(sidecarVersion, args.Blobs, args.Commitments, args.Proofs)) } data, err := signed.MarshalBinary() if err != nil { @@ -1638,11 +1665,16 @@ func (api *TransactionAPI) PendingTransactions() ([]*RPCTransaction, error) { // 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. +// +// This API is not capable for submitting blob transaction with sidecar. func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) { if sendArgs.Nonce == nil { return common.Hash{}, errors.New("missing transaction nonce in transaction spec") } - if err := sendArgs.setDefaults(ctx, api.b, false); err != nil { + config := setDefaultConfig{ + skipGasEstimation: false, + } + if err := sendArgs.setDefaults(ctx, api.b, config); err != nil { return common.Hash{}, err } matchTx := sendArgs.ToTransaction(types.LegacyTxType) diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 6b094721e4..bb460c3f94 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -70,9 +70,6 @@ type TransactionArgs struct { // For SetCodeTxType AuthorizationList []types.SetCodeAuthorization `json:"authorizationList"` - - // This configures whether blobs are allowed to be passed. - blobSidecarAllowed bool } // from retrieves the transaction sender address. @@ -94,9 +91,22 @@ func (args *TransactionArgs) data() []byte { return nil } +// setDefaultConfig defines the options for deriving missing fields of transactions. +type setDefaultConfig struct { + // This configures whether blobs are allowed to be passed and + // the associated sidecar version should be attached. + blobSidecarAllowed bool + blobSidecarVersion byte + + // skipGasEstimation is the flag whether the gas estimation is skipped. + // this flag should only be used in the scenarios that a precise gas limit + // is not critical, e.g., in non-transaction calls. + skipGasEstimation bool +} + // setDefaults fills in default values for unspecified tx fields. -func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, skipGasEstimation bool) error { - if err := args.setBlobTxSidecar(ctx); err != nil { +func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, config setDefaultConfig) error { + if err := args.setBlobTxSidecar(ctx, config); err != nil { return err } if err := args.setFeeDefaults(ctx, b, b.CurrentHeader()); err != nil { @@ -119,11 +129,10 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, skipGas // BlobTx fields if args.BlobHashes != nil && len(args.BlobHashes) == 0 { - return errors.New(`need at least 1 blob for a blob transaction`) + return errors.New("need at least 1 blob for a blob transaction") } - maxBlobs := eip4844.MaxBlobsPerBlock(b.ChainConfig(), b.CurrentHeader().Time) - if args.BlobHashes != nil && len(args.BlobHashes) > maxBlobs { - return fmt.Errorf(`too many blobs in transaction (have=%d, max=%d)`, len(args.BlobHashes), maxBlobs) + if args.BlobHashes != nil && len(args.BlobHashes) > params.BlobTxMaxBlobs { + return fmt.Errorf("too many blobs in transaction (have=%d, max=%d)", len(args.BlobHashes), params.BlobTxMaxBlobs) } // create check @@ -137,13 +146,13 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, skipGas } if args.Gas == nil { - if skipGasEstimation { // Skip gas usage estimation if a precise gas limit is not critical, e.g., in non-transaction calls. + if config.skipGasEstimation { gas := hexutil.Uint64(b.RPCGasCap()) if gas == 0 { gas = hexutil.Uint64(math.MaxUint64 / 2) } args.Gas = &gas - } else { // Estimate the gas usage otherwise. + } else { // These fields are immutable during the estimation, safe to // pass the pointer directly. data := args.data() @@ -283,18 +292,17 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ } // setBlobTxSidecar adds the blob tx -func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context) error { +func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config setDefaultConfig) 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 { + if !config.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 { @@ -304,42 +312,73 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context) error { } // 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) - } + n := len(args.Blobs) 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 && len(args.Commitments) != n { + return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n) + } + proofLen := n + if config.blobSidecarVersion == types.BlobSidecarVersion1 { + proofLen = n * kzg4844.CellProofsPerBlob + } + if args.Proofs != nil && len(args.Proofs) != proofLen { + if len(args.Proofs) != n { + return fmt.Errorf("number of blobs and proofs mismatch (have=%d, want=%d)", len(args.Proofs), proofLen) + } + // Unset the commitments and proofs, as they may be submitted in the legacy format + log.Debug("Unset legacy commitments and proofs", "blobs", n, "proofs", len(args.Proofs)) + args.Commitments, args.Proofs = nil, nil + } + // Generate commitments and proofs if they are missing, or validate them if they + // are provided. if args.Commitments == nil { - // Generate commitment and proof. - commitments := make([]kzg4844.Commitment, n) - proofs := make([]kzg4844.Proof, n) + var ( + commitments = make([]kzg4844.Commitment, n) + proofs = make([]kzg4844.Proof, proofLen) + ) for i, b := range args.Blobs { c, err := kzg4844.BlobToCommitment(&b) if err != nil { return fmt.Errorf("blobs[%d]: error computing commitment: %v", i, err) } commitments[i] = c - p, err := kzg4844.ComputeBlobProof(&b, c) - if err != nil { - return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err) + + switch config.blobSidecarVersion { + case types.BlobSidecarVersion0: + p, err := kzg4844.ComputeBlobProof(&b, c) + if err != nil { + return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err) + } + proofs[i] = p + case types.BlobSidecarVersion1: + ps, err := kzg4844.ComputeCellProofs(&b) + if err != nil { + return fmt.Errorf("blobs[%d]: error computing cell proof: %v", i, err) + } + proofs = append(proofs, ps...) } - 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) + switch config.blobSidecarVersion { + case types.BlobSidecarVersion0: + 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) + } + } + case types.BlobSidecarVersion1: + if err := kzg4844.VerifyCellProofs(args.Blobs, args.Commitments, args.Proofs); err != nil { + return fmt.Errorf("failed to verify blob cell proof: %v", err) } } } + // Generate blob hashes if they are missing, or validate them if they are provided. hashes := make([]common.Hash, n) hasher := sha256.New() for i, c := range args.Commitments { @@ -527,8 +566,11 @@ func (args *TransactionArgs) ToTransaction(defaultType int) *types.Transaction { BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)), } if args.Blobs != nil { - // TODO(rjl493456442, marius) support V1 - data.(*types.BlobTx).Sidecar = types.NewBlobTxSidecar(types.BlobSidecarVersion0, args.Blobs, args.Commitments, args.Proofs) + version := types.BlobSidecarVersion0 + if len(args.Proofs) == len(args.Blobs)*kzg4844.CellProofsPerBlob { + version = types.BlobSidecarVersion1 + } + data.(*types.BlobTx).Sidecar = types.NewBlobTxSidecar(version, args.Blobs, args.Commitments, args.Proofs) } case types.DynamicFeeTxType: diff --git a/miner/worker.go b/miner/worker.go index ee31a65359..b54b337962 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -344,7 +344,6 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (* func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error { var ( - isOsaka = miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) isCancun = miner.chainConfig.IsCancun(env.header.Number, env.header.Time) gasLimit = env.header.GasLimit ) @@ -425,21 +424,6 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran if !env.txFitsSize(tx) { break } - - // Make sure all transactions after osaka have cell proofs - if isOsaka { - if sidecar := tx.BlobTxSidecar(); sidecar != nil { - if sidecar.Version == types.BlobSidecarVersion0 { - log.Info("Including blob tx with v0 sidecar, recomputing proofs", "hash", ltx.Hash) - if err := sidecar.ToV1(); err != nil { - txs.Pop() - log.Warn("Failed to recompute cell proofs", "hash", ltx.Hash, "err", err) - continue - } - } - } - } - // Error may be ignored here. The error has already been checked // during transaction acceptance in the transaction pool. from, _ := types.Sender(env.signer, tx) diff --git a/signer/core/apitypes/types.go b/signer/core/apitypes/types.go index b5fd5a2854..781f480a29 100644 --- a/signer/core/apitypes/types.go +++ b/signer/core/apitypes/types.go @@ -167,8 +167,11 @@ func (args *SendTxArgs) ToTransaction() (*types.Transaction, error) { BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)), } if args.Blobs != nil { - // TODO(rjl493456442, marius) support V1 - data.(*types.BlobTx).Sidecar = types.NewBlobTxSidecar(types.BlobSidecarVersion0, args.Blobs, args.Commitments, args.Proofs) + version := types.BlobSidecarVersion0 + if len(args.Proofs) == len(args.Blobs)*kzg4844.CellProofsPerBlob { + version = types.BlobSidecarVersion1 + } + data.(*types.BlobTx).Sidecar = types.NewBlobTxSidecar(version, args.Blobs, args.Commitments, args.Proofs) } case args.MaxFeePerGas != nil: