From d75e28ee323f5c3e57ac95456107c35bba63f611 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 5 Aug 2025 11:21:25 +0800 Subject: [PATCH 1/6] 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 | 102 ++++++++++++++++++++-------- miner/worker.go | 16 ----- signer/core/apitypes/types.go | 7 +- 6 files changed, 150 insertions(+), 62 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 948ecd14c3a..382cf96d757 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 d4f34010866..46974fad3cb 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 51b6ca3c442..3eb4003f5a7 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 6b094721e46..bb460c3f94e 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) + 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) } - 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) + proofLen := n + if config.blobSidecarVersion == types.BlobSidecarVersion1 { + proofLen = n * kzg4844.CellProofsPerBlob } - 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.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 ee31a653591..b54b337962c 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 b5fd5a28540..781f480a293 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: From 76e3a3686f1e76f13820a7391e6584cdd8769112 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 5 Aug 2025 14:17:35 +0800 Subject: [PATCH 2/6] core/txpool/blobpool: add test --- core/txpool/blobpool/blobpool_test.go | 101 +++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 55eed86cff1..cc7dda6c034 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -27,6 +27,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "sync" "testing" @@ -47,11 +48,12 @@ import ( ) var ( - testBlobs []*kzg4844.Blob - testBlobCommits []kzg4844.Commitment - testBlobProofs []kzg4844.Proof - testBlobVHashes [][32]byte - testBlobIndices = make(map[[32]byte]int) + testBlobs []*kzg4844.Blob + testBlobCommits []kzg4844.Commitment + testBlobProofs []kzg4844.Proof + testBlobCellProofs [][]kzg4844.Proof + testBlobVHashes [][32]byte + testBlobIndices = make(map[[32]byte]int) ) const testMaxBlobsPerBlock = 6 @@ -67,6 +69,9 @@ func init() { testBlobProof, _ := kzg4844.ComputeBlobProof(testBlob, testBlobCommit) testBlobProofs = append(testBlobProofs, testBlobProof) + testBlobCellProof, _ := kzg4844.ComputeCellProofs(testBlob) + testBlobCellProofs = append(testBlobCellProofs, testBlobCellProof) + testBlobVHash := kzg4844.CalcBlobHashV1(sha256.New(), &testBlobCommit) testBlobIndices[testBlobVHash] = len(testBlobVHashes) testBlobVHashes = append(testBlobVHashes, testBlobVHash) @@ -416,24 +421,40 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) { hashes = append(hashes, tx.vhashes...) } } - blobs, _, proofs, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0) + blobs1, _, proofs1, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0) + if err != nil { + t.Fatal(err) + } + blobs2, _, proofs2, err := pool.GetBlobs(hashes, types.BlobSidecarVersion1) if err != nil { t.Fatal(err) } // Cross validate what we received vs what we wanted - if len(blobs) != len(hashes) || len(proofs) != len(hashes) { - t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), len(hashes)) + if len(blobs1) != len(hashes) || len(proofs1) != len(hashes) { + t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs1), len(proofs1), len(hashes)) + return + } + if len(blobs2) != len(hashes) || len(proofs2) != len(hashes) { + t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want blobs %d, want proofs: %d", len(blobs2), len(proofs2), len(hashes), len(hashes)) return } for i, hash := range hashes { // If an item is missing, but shouldn't, error - if blobs[i] == nil || proofs[i] == nil { + if blobs1[i] == nil || proofs1[i] == nil { + t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash) + continue + } + if blobs2[i] == nil || proofs2[i] == nil { t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash) continue } // Item retrieved, make sure it matches the expectation index := testBlobIndices[hash] - if *blobs[i] != *testBlobs[index] || proofs[i][0] != testBlobProofs[index] { + if *blobs1[i] != *testBlobs[index] || proofs1[i][0] != testBlobProofs[index] { + t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash) + continue + } + if *blobs2[i] != *testBlobs[index] || !slices.Equal(proofs2[i], testBlobCellProofs[index]) { t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash) continue } @@ -1668,6 +1689,66 @@ func TestAdd(t *testing.T) { } } +// Tests that adding the transactions with legacy sidecar and expect them to +// be converted to new format correctly. +func TestAddLegacyBlobTx(t *testing.T) { + var ( + key1, _ = crypto.GenerateKey() + key2, _ = crypto.GenerateKey() + + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + ) + + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.Commit(0, true, false) + + // Make Prague-enabled custom chain config. + cancunTime := uint64(0) + pragueTime := uint64(0) + osakaTime := uint64(0) + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + LondonBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + CancunTime: &cancunTime, + PragueTime: &pragueTime, + OsakaTime: &osakaTime, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + }, + } + chain := &testBlockChain{ + config: config, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + pool := New(Config{Datadir: t.TempDir()}, chain, nil) + if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + + // Attempt to add legacy blob transactions. + var ( + tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0) + tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion0) + tx3 = makeMultiBlobTx(1, 1, 800, 70, 6, 12, key2, types.BlobSidecarVersion1) + ) + errs := pool.Add([]*types.Transaction{tx1, tx2, tx3}, true) + for _, err := range errs { + if err != nil { + t.Fatalf("failed to add tx: %v", err) + } + } + verifyPoolInternals(t, pool) + pool.Close() +} + func TestGetBlobs(t *testing.T) { //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) From 38d1d128d9319ba25228c63eaf2ec987cd29db47 Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Fri, 22 Aug 2025 10:28:54 +0200 Subject: [PATCH 3/6] internal/ethapi: minor cleanups --- internal/ethapi/api.go | 22 ++++------ internal/ethapi/transaction_args.go | 65 ++++++++++++----------------- 2 files changed, 33 insertions(+), 54 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 3eb4003f5a7..891b0d0b521 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1501,10 +1501,7 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction } // Set some sanity defaults and terminate on failure - config := setDefaultConfig{ - skipGasEstimation: false, - } - if err := args.setDefaults(ctx, api.b, config); err != nil { + if err := args.setDefaults(ctx, api.b, sidecarConfig{}); err != nil { return common.Hash{}, err } // Assemble the transaction and sign with the wallet @@ -1522,8 +1519,7 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction // processing (signing + broadcast). func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { // Set some sanity defaults and terminate on failure - config := setDefaultConfig{ - skipGasEstimation: false, + config := sidecarConfig{ blobSidecarAllowed: true, blobSidecarVersion: types.BlobSidecarVersion0, } @@ -1600,10 +1596,6 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction if args.Nonce == nil { return nil, errors.New("nonce not specified") } - config := setDefaultConfig{ - skipGasEstimation: false, - blobSidecarAllowed: true, - } sidecarVersion := types.BlobSidecarVersion0 if len(args.Blobs) > 0 { chainHead := api.b.CurrentHeader() @@ -1612,8 +1604,11 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction sidecarVersion = types.BlobSidecarVersion1 } } - config.blobSidecarVersion = sidecarVersion + config := sidecarConfig{ + blobSidecarAllowed: true, + blobSidecarVersion: sidecarVersion, + } if err := args.setDefaults(ctx, api.b, config); err != nil { return nil, err } @@ -1671,10 +1666,7 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, if sendArgs.Nonce == nil { return common.Hash{}, errors.New("missing transaction nonce in transaction spec") } - config := setDefaultConfig{ - skipGasEstimation: false, - } - if err := sendArgs.setDefaults(ctx, api.b, config); err != nil { + if err := sendArgs.setDefaults(ctx, api.b, sidecarConfig{}); 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 bb460c3f94e..372e7001bec 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -91,21 +91,16 @@ func (args *TransactionArgs) data() []byte { return nil } -// setDefaultConfig defines the options for deriving missing fields of transactions. -type setDefaultConfig struct { +// sidecarConfig defines the options for deriving missing fields of transactions. +type sidecarConfig 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, config setDefaultConfig) error { +func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, config sidecarConfig) error { if err := args.setBlobTxSidecar(ctx, config); err != nil { return err } @@ -146,36 +141,28 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, config } if args.Gas == nil { - if config.skipGasEstimation { - gas := hexutil.Uint64(b.RPCGasCap()) - if gas == 0 { - gas = hexutil.Uint64(math.MaxUint64 / 2) - } - args.Gas = &gas - } else { - // These fields are immutable during the estimation, safe to - // pass the pointer directly. - data := args.data() - callArgs := TransactionArgs{ - From: args.From, - To: args.To, - GasPrice: args.GasPrice, - MaxFeePerGas: args.MaxFeePerGas, - MaxPriorityFeePerGas: args.MaxPriorityFeePerGas, - Value: args.Value, - Data: (*hexutil.Bytes)(&data), - AccessList: args.AccessList, - BlobFeeCap: args.BlobFeeCap, - BlobHashes: args.BlobHashes, - } - latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) - estimated, err := DoEstimateGas(ctx, b, callArgs, latestBlockNr, nil, nil, b.RPCGasCap()) - if err != nil { - return err - } - args.Gas = &estimated - log.Trace("Estimate gas usage automatically", "gas", args.Gas) + // These fields are immutable during the estimation, safe to + // pass the pointer directly. + data := args.data() + callArgs := TransactionArgs{ + From: args.From, + To: args.To, + GasPrice: args.GasPrice, + MaxFeePerGas: args.MaxFeePerGas, + MaxPriorityFeePerGas: args.MaxPriorityFeePerGas, + Value: args.Value, + Data: (*hexutil.Bytes)(&data), + AccessList: args.AccessList, + BlobFeeCap: args.BlobFeeCap, + BlobHashes: args.BlobHashes, + } + latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + estimated, err := DoEstimateGas(ctx, b, callArgs, latestBlockNr, nil, nil, b.RPCGasCap()) + if err != nil { + return err } + 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 @@ -292,7 +279,7 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ } // setBlobTxSidecar adds the blob tx -func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config setDefaultConfig) error { +func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config sidecarConfig) error { // No blobs, we're done. if args.Blobs == nil { return nil @@ -311,7 +298,7 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config setDef return errors.New(`blob commitments provided while proofs were not`) } - // len(blobs) == len(commitments) == len(proofs) == len(hashes) + // len(blobs) == len(commitments) == len(hashes) 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) From b4bf2ae70825b14b9a2d3dcce75969c3b078cd24 Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Fri, 22 Aug 2025 10:32:31 +0200 Subject: [PATCH 4/6] internal/ethapi: comment --- internal/ethapi/transaction_args.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 372e7001bec..48a526f34f9 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -307,6 +307,8 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config sideca return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n) } proofLen := n + // if V0: len(blobs) == len(proofs) + // if V1: len(blobs) == len(proofs) * 128 if config.blobSidecarVersion == types.BlobSidecarVersion1 { proofLen = n * kzg4844.CellProofsPerBlob } From 3e4ed1cdc0df07b2ba300ac3854e26241ef31371 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Mon, 25 Aug 2025 15:15:13 +0800 Subject: [PATCH 5/6] internal/ethapi: fix test --- internal/ethapi/api.go | 11 +++--- internal/ethapi/api_test.go | 61 ++++++++++++++++++++++------- internal/ethapi/transaction_args.go | 11 +++--- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 891b0d0b521..c75cca6852d 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1519,17 +1519,18 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction // processing (signing + broadcast). func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { // Set some sanity defaults and terminate on failure - config := sidecarConfig{ - blobSidecarAllowed: true, - blobSidecarVersion: types.BlobSidecarVersion0, - } + sidecarVersion := 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 + sidecarVersion = types.BlobSidecarVersion1 } } + config := sidecarConfig{ + blobSidecarAllowed: true, + blobSidecarVersion: sidecarVersion, + } if err := args.setDefaults(ctx, api.b, config); err != nil { return nil, err } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index de6d1d5e068..4d3ad03bd43 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -34,11 +34,9 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/internal/ethapi/override" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -56,6 +54,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/blocktest" + "github.com/ethereum/go-ethereum/internal/ethapi/override" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "github.com/holiman/uint256" @@ -2661,19 +2660,53 @@ func TestSendBlobTransaction(t *testing.T) { func TestFillBlobTransaction(t *testing.T) { t.Parallel() + + testFillBlobTransaction(t, false) + testFillBlobTransaction(t, true) +} + +func testFillBlobTransaction(t *testing.T, osaka bool) { // Initialize test accounts + config := *params.MergedTestChainConfig + if !osaka { + config.OsakaTime = nil + } var ( key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") to = crypto.PubkeyToAddress(key.PublicKey) genesis = &core.Genesis{ - Config: params.MergedTestChainConfig, + Config: &config, Alloc: types.GenesisAlloc{}, } - emptyBlob = new(kzg4844.Blob) - emptyBlobs = []kzg4844.Blob{*emptyBlob} - emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob) - emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit) - emptyBlobHash common.Hash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit) + emptyBlob = new(kzg4844.Blob) + emptyBlobs = []kzg4844.Blob{*emptyBlob} + emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob) + emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit) + emptyBlobCellProofs, _ = kzg4844.ComputeCellProofs(emptyBlob) + emptyBlobHash common.Hash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit) + + fillEmptyKZGProofs = func(blobs int) []kzg4844.Proof { + if osaka { + return make([]kzg4844.Proof, blobs*kzg4844.CellProofsPerBlob) + } + return make([]kzg4844.Proof, blobs) + } + expectSidecar = func() *types.BlobTxSidecar { + if osaka { + return types.NewBlobTxSidecar( + types.BlobSidecarVersion1, + emptyBlobs, + []kzg4844.Commitment{emptyBlobCommit}, + emptyBlobCellProofs, + ) + } + return types.NewBlobTxSidecar( + types.BlobSidecarVersion0, + emptyBlobs, + []kzg4844.Commitment{emptyBlobCommit}, + []kzg4844.Proof{emptyBlobProof}, + ) + } ) b := newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) { b.SetPoS() @@ -2733,7 +2766,7 @@ func TestFillBlobTransaction(t *testing.T) { Commitments: []kzg4844.Commitment{{}, {}}, Proofs: []kzg4844.Proof{{}}, }, - err: `number of blobs and proofs mismatch (have=1, want=2)`, + err: fmt.Sprintf(`number of blobs and proofs mismatch (have=1, want=%d)`, len(fillEmptyKZGProofs(2))), }, { name: "TestInvalidProofVerification", @@ -2743,7 +2776,7 @@ func TestFillBlobTransaction(t *testing.T) { Value: (*hexutil.Big)(big.NewInt(1)), Blobs: []kzg4844.Blob{{}, {}}, Commitments: []kzg4844.Commitment{{}, {}}, - Proofs: []kzg4844.Proof{{}, {}}, + Proofs: fillEmptyKZGProofs(2), }, err: `failed to verify blob proof: short buffer`, }, @@ -2759,7 +2792,7 @@ func TestFillBlobTransaction(t *testing.T) { }, want: &result{ Hashes: []common.Hash{emptyBlobHash}, - Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, emptyBlobs, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}), + Sidecar: expectSidecar(), }, }, { @@ -2775,7 +2808,7 @@ func TestFillBlobTransaction(t *testing.T) { }, want: &result{ Hashes: []common.Hash{emptyBlobHash}, - Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, emptyBlobs, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}), + Sidecar: expectSidecar(), }, }, { @@ -2801,7 +2834,7 @@ func TestFillBlobTransaction(t *testing.T) { }, want: &result{ Hashes: []common.Hash{emptyBlobHash}, - Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, emptyBlobs, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}), + Sidecar: expectSidecar(), }, }, } diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 48a526f34f9..d671d69a2c4 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -306,9 +306,10 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config sideca 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 V0: len(blobs) == len(proofs) // if V1: len(blobs) == len(proofs) * 128 + proofLen := n if config.blobSidecarVersion == types.BlobSidecarVersion1 { proofLen = n * kzg4844.CellProofsPerBlob } @@ -326,7 +327,7 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config sideca if args.Commitments == nil { var ( commitments = make([]kzg4844.Commitment, n) - proofs = make([]kzg4844.Proof, proofLen) + proofs = make([]kzg4844.Proof, 0, proofLen) ) for i, b := range args.Blobs { c, err := kzg4844.BlobToCommitment(&b) @@ -341,11 +342,11 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config sideca if err != nil { return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err) } - proofs[i] = p + proofs = append(proofs, p) case types.BlobSidecarVersion1: ps, err := kzg4844.ComputeCellProofs(&b) if err != nil { - return fmt.Errorf("blobs[%d]: error computing cell proof: %v", i, err) + return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err) } proofs = append(proofs, ps...) } @@ -362,7 +363,7 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config sideca } 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) + return fmt.Errorf("failed to verify blob proof: %v", err) } } } From 1a887a2155e5232051f11a79424b6c2838d62a7b Mon Sep 17 00:00:00 2001 From: lightclient Date: Mon, 25 Aug 2025 13:45:47 -0600 Subject: [PATCH 6/6] core/txpool,internal/ethapi: fixup a few nits --- core/txpool/blobpool/blobpool.go | 2 +- core/txpool/blobpool/blobpool_test.go | 19 +------------------ internal/ethapi/api.go | 10 ++++------ internal/ethapi/transaction_args.go | 2 +- 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 382cf96d757..64ee3fcd9a6 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1398,7 +1398,7 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int { } // convertSidecar converts the legacy sidecar in the submitted transactions -// if osaka fork has been activated. +// 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) { diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index cc7dda6c034..51ab27eb014 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -1705,25 +1705,8 @@ func TestAddLegacyBlobTx(t *testing.T) { statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) statedb.Commit(0, true, false) - // Make Prague-enabled custom chain config. - cancunTime := uint64(0) - pragueTime := uint64(0) - osakaTime := uint64(0) - config := ¶ms.ChainConfig{ - ChainID: big.NewInt(1), - LondonBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - CancunTime: &cancunTime, - PragueTime: &pragueTime, - OsakaTime: &osakaTime, - BlobScheduleConfig: ¶ms.BlobScheduleConfig{ - Cancun: params.DefaultCancunBlobConfig, - Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, - }, - } chain := &testBlockChain{ - config: config, + config: params.MergedTestChainConfig, basefee: uint256.NewInt(1050), blobfee: uint256.NewInt(105), statedb: statedb, diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c75cca6852d..693edfbd783 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1521,9 +1521,8 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction // Set some sanity defaults and terminate on failure sidecarVersion := types.BlobSidecarVersion0 if len(args.Blobs) > 0 { - chainHead := api.b.CurrentHeader() - isOsaka := api.b.ChainConfig().IsOsaka(chainHead.Number, chainHead.Time) - if isOsaka { + h := api.b.CurrentHeader() + if api.b.ChainConfig().IsOsaka(h.Number, h.Time) { sidecarVersion = types.BlobSidecarVersion1 } } @@ -1599,9 +1598,8 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction } sidecarVersion := types.BlobSidecarVersion0 if len(args.Blobs) > 0 { - chainHead := api.b.CurrentHeader() - isOsaka := api.b.ChainConfig().IsOsaka(chainHead.Number, chainHead.Time) - if isOsaka { + h := api.b.CurrentHeader() + if api.b.ChainConfig().IsOsaka(h.Number, h.Time) { sidecarVersion = types.BlobSidecarVersion1 } } diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index d671d69a2c4..f80ef6d0803 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -162,7 +162,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, config return err } args.Gas = &estimated - log.Trace("Estimate gas usage automatically", "gas", args.Gas) + log.Trace("Estimated gas usage automatically", "gas", args.Gas) } // If chain id is provided, ensure it matches the local chain id. Otherwise, set the local