diff --git a/tx-submitter/services/rollup.go b/tx-submitter/services/rollup.go index 6639fe2bb..77c206696 100644 --- a/tx-submitter/services/rollup.go +++ b/tx-submitter/services/rollup.go @@ -1747,6 +1747,39 @@ func (r *Rollup) InitFeeMetricsSum() error { return nil } +// buildCancelBlobSidecar builds the empty-blob sidecar for CancelTx, matching +// createBlobTx / ReSubmitTx blob-version selection. +func buildCancelBlobSidecar(head *ethtypes.Header, chainID uint64) (*ethtypes.BlobTxSidecar, common.Hash, error) { + var emptyBlob kzg4844.Blob + emptyCommitment, err := kzg4844.BlobToCommitment(&emptyBlob) + if err != nil { + return nil, common.Hash{}, fmt.Errorf("failed to create empty blob commitment: %w", err) + } + sidecar := ðtypes.BlobTxSidecar{ + Blobs: []kzg4844.Blob{emptyBlob}, + Commitments: []kzg4844.Commitment{emptyCommitment}, + } + switch blob.DetermineBlobVersion(head, chainID) { + case ethtypes.BlobSidecarVersion0: + sidecar.Version = ethtypes.BlobSidecarVersion0 + emptyProof, err := kzg4844.ComputeBlobProof(&emptyBlob, emptyCommitment) + if err != nil { + return nil, common.Hash{}, fmt.Errorf("failed to create empty blob proof: %w", err) + } + sidecar.Proofs = []kzg4844.Proof{emptyProof} + case ethtypes.BlobSidecarVersion1: + sidecar.Version = ethtypes.BlobSidecarVersion1 + proofs, err := blob.MakeCellProof(sidecar.Blobs) + if err != nil { + return nil, common.Hash{}, fmt.Errorf("failed to create empty cell proof: %w", err) + } + sidecar.Proofs = proofs + default: + return nil, common.Hash{}, fmt.Errorf("unsupported blob version") + } + return sidecar, kZGToVersionedHash(emptyCommitment), nil +} + // CancelTx creates a new transaction with empty calldata to cancel the original transaction func (r *Rollup) CancelTx(tx *ethtypes.Transaction) (*ethtypes.Transaction, error) { if tx == nil { @@ -1762,10 +1795,13 @@ func (r *Rollup) CancelTx(tx *ethtypes.Transaction) (*ethtypes.Transaction, erro "nonce", tx.Nonce(), ) - tip, gasFeeCap, blobFeeCap, _, err := r.GetGasTipAndCap() + tip, gasFeeCap, blobFeeCap, head, err := r.GetGasTipAndCap() if err != nil { return nil, fmt.Errorf("get gas tip and cap error:%w", err) } + if blobFeeCap == nil { + blobFeeCap = big.NewInt(0) + } // bump tip & feeCap bumpedFeeCap := calcThresholdValue(tx.GasFeeCap(), tx.Type() == ethtypes.BlobTxType) @@ -1801,17 +1837,10 @@ func (r *Rollup) CancelTx(tx *ethtypes.Transaction) (*ethtypes.Transaction, erro Data: []byte{}, // Empty calldata for cancellation }) case ethtypes.BlobTxType: - // For blob transactions, we need to keep one empty blob - var emptyBlob kzg4844.Blob - emptyCommitment, err := kzg4844.BlobToCommitment(&emptyBlob) + sidecar, versionedHash, err := buildCancelBlobSidecar(head, r.chainId.Uint64()) if err != nil { - return nil, fmt.Errorf("failed to create empty blob commitment: %w", err) - } - emptyProof, err := kzg4844.ComputeBlobProof(&emptyBlob, emptyCommitment) - if err != nil { - return nil, fmt.Errorf("failed to create empty blob proof: %w", err) + return nil, err } - newTx = ethtypes.NewTx(ðtypes.BlobTx{ ChainID: uint256.MustFromBig(tx.ChainId()), Nonce: tx.Nonce(), @@ -1822,12 +1851,8 @@ func (r *Rollup) CancelTx(tx *ethtypes.Transaction) (*ethtypes.Transaction, erro Value: uint256.MustFromBig(tx.Value()), Data: []byte{}, // Empty calldata for cancellation BlobFeeCap: uint256.MustFromBig(blobFeeCap), - BlobHashes: []common.Hash{kZGToVersionedHash(emptyCommitment)}, - Sidecar: ðtypes.BlobTxSidecar{ - Blobs: []kzg4844.Blob{emptyBlob}, - Commitments: []kzg4844.Commitment{emptyCommitment}, - Proofs: []kzg4844.Proof{emptyProof}, - }, + BlobHashes: []common.Hash{versionedHash}, + Sidecar: sidecar, }) default: return nil, fmt.Errorf("cancel unknown tx type:%v", tx.Type()) diff --git a/tx-submitter/services/rollup_test.go b/tx-submitter/services/rollup_test.go index 51a3f3cde..249eaee83 100644 --- a/tx-submitter/services/rollup_test.go +++ b/tx-submitter/services/rollup_test.go @@ -352,7 +352,33 @@ func TestCancelTx(t *testing.T) { cancelBlobFeeCap := cancelBlobTx.BlobGasFeeCap() require.True(t, cancelBlobFeeCap.Cmp(new(big.Int).Mul(originalBlobFeeCap, big.NewInt(2))) >= 0, "cancel blob tx blob fee cap should be at least 2x of original") require.Equal(t, 1, len(cancelBlobTx.BlobHashes())) + require.NotNil(t, cancelBlobTx.BlobTxSidecar()) require.Equal(t, 1, len(cancelBlobTx.BlobTxSidecar().Blobs)) + +} + +func TestBuildCancelBlobSidecarVersion(t *testing.T) { + t.Parallel() + baseFee := big.NewInt(1e9) + excess := uint64(0) + preOsakaHead := &types.Header{BaseFee: baseFee, Time: 1700000000, Number: big.NewInt(12_965_000), ExcessBlobGas: &excess} + osakaHead := &types.Header{BaseFee: baseFee, Time: 1764798551, Number: big.NewInt(12_965_000), ExcessBlobGas: &excess} + t.Run("v0 before osaka fork", func(t *testing.T) { + t.Parallel() + sidecar, hash, err := buildCancelBlobSidecar(preOsakaHead, 1) + require.NoError(t, err) + require.Equal(t, types.BlobSidecarVersion0, sidecar.Version) + require.Len(t, sidecar.Proofs, 1) + require.NotEqual(t, common.Hash{}, hash) + }) + t.Run("v1 at osaka fork", func(t *testing.T) { + t.Parallel() + sidecar, hash, err := buildCancelBlobSidecar(osakaHead, 1) + require.NoError(t, err) + require.Equal(t, types.BlobSidecarVersion1, sidecar.Version) + require.NotEmpty(t, sidecar.Proofs) + require.NotEqual(t, common.Hash{}, hash) + }) } func TestTxStateTransition(t *testing.T) {