diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 5b9dfd5633..0b7a7958ba 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1101,7 +1101,7 @@ func (p *BlobPool) convertLegacySidecar(sender common.Address, hash common.Hash, // loudly if possible. If the blob transaction in this slot is corrupted, // leave it in the store, it will be dropped during the next pool // initialization. - var tx types.Transaction + var tx pooledBlobTx if err = rlp.DecodeBytes(data, &tx); err != nil { log.Error("Blob transaction is corrupted", "hash", hash, "id", id, "err", err) return false @@ -1110,11 +1110,11 @@ func (p *BlobPool) convertLegacySidecar(sender common.Address, hash common.Hash, // Skip conversion if the transaction does not match the expected hash, or if it was // already converted. This can occur if the original transaction was evicted from the // pool and the slot was reused by a new one. - if tx.Hash() != hash { - log.Warn("Blob transaction was replaced", "hash", hash, "id", id, "stored", tx.Hash()) + if tx.Transaction.Hash() != hash { + log.Warn("Blob transaction was replaced", "hash", hash, "id", id, "stored", tx.Transaction.Hash()) return false } - sc := tx.BlobTxSidecar() + sc := tx.Sidecar if sc.Version >= types.BlobSidecarVersion1 { log.Debug("Skipping conversion of blob tx", "hash", hash, "id", id) return false @@ -1122,7 +1122,7 @@ func (p *BlobPool) convertLegacySidecar(sender common.Address, hash common.Hash, // Perform the sidecar conversion, the failure is not expected and report the error // loudly if possible. - if err := tx.BlobTxSidecar().ToV1(); err != nil { + if err := tx.Sidecar.ToV1(); err != nil { log.Error("Failed to convert blob transaction", "hash", hash, "err", err) return false } @@ -1131,7 +1131,7 @@ func (p *BlobPool) convertLegacySidecar(sender common.Address, hash common.Hash, // the error loudly if possible. blob, err := rlp.EncodeToBytes(&tx) if err != nil { - log.Error("Failed to encode blob transaction", "hash", tx.Hash(), "err", err) + log.Error("Failed to encode blob transaction", "hash", tx.Transaction.Hash(), "err", err) return false } @@ -1327,16 +1327,15 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error { // this attack is financially inefficient to execute. head := p.head.Load() if p.chain.Config().IsOsaka(head.Number, head.Time) && pooledTx.Sidecar.Version == types.BlobSidecarVersion0 { - tx, err := pooledTx.convert() if err != nil { log.Error("Failed to convert the pooledTx", "err", err) return err } - if err := tx.BlobTxSidecar().ToV1(); err != nil { + if err := pooledTx.Sidecar.ToV1(); err != nil { log.Error("Failed to convert the legacy sidecar", "err", err) return err } - log.Info("Legacy blob transaction is reorged", "hash", tx.Hash()) + log.Info("Legacy blob transaction is reorged", "hash", pooledTx.Transaction.Hash()) } // Serialize the transaction back into the primary datastore. blob, err := rlp.EncodeToBytes(pooledTx) @@ -1806,7 +1805,7 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int { // the legacy sidecars if Osaka fork has been activated with a short time window. // // This function is pure static and lock free. -func (p *BlobPool) preCheck(tx *types.Transaction) error { +func (p *BlobPool) preCheck(tx *pooledBlobTx) error { var ( head = p.head.Load() isOsaka = p.chain.Config().IsOsaka(head.Number, head.Time) @@ -1817,12 +1816,12 @@ func (p *BlobPool) preCheck(tx *types.Transaction) error { } // Validate the transaction statically at first to avoid unnecessary // conversion. This step doesn't require lock protection. - if err := p.ValidateTxBasics(tx); err != nil { + if err := p.ValidateTxBasics(tx.Transaction); err != nil { return err } // Before the Osaka fork, reject the blob txs with cell proofs if !isOsaka { - if tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 { + if tx.Sidecar.Version == types.BlobSidecarVersion0 { return nil } else { return errors.New("cell proof is not supported yet") @@ -1830,7 +1829,7 @@ func (p *BlobPool) preCheck(tx *types.Transaction) error { } // After the Osaka fork, reject the legacy blob txs if the conversion // time window is passed. - if tx.BlobTxSidecar().Version == types.BlobSidecarVersion1 { + if tx.Sidecar.Version == types.BlobSidecarVersion1 { return nil } if head.Time > uint64(deadline.Unix()) { @@ -1851,13 +1850,13 @@ func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error { adds = make([]*types.Transaction, 0, len(txs)) ) for i, tx := range txs { - if errs[i] = p.preCheck(tx); errs[i] != nil { - continue - } pooledTx, err := newPooledBlobTx(tx) if err != nil { continue } + if errs[i] = p.preCheck(pooledTx); errs[i] != nil { + continue + } errs[i] = p.add(pooledTx) if errs[i] == nil { adds = append(adds, tx.WithoutBlobTxSidecar()) diff --git a/core/txpool/blobpool/conversion.go b/core/txpool/blobpool/conversion.go index 95828d83b2..a315016810 100644 --- a/core/txpool/blobpool/conversion.go +++ b/core/txpool/blobpool/conversion.go @@ -23,7 +23,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" ) @@ -34,8 +33,8 @@ const maxPendingConversionTasks = 2048 // txConvert represents a conversion task with an attached legacy blob transaction. type txConvert struct { - tx *types.Transaction // Legacy blob transaction - done chan error // Channel for signaling back if the conversion succeeds + tx *pooledBlobTx // Legacy blob transaction + done chan error // Channel for signaling back if the conversion succeeds } // conversionQueue is a dedicated queue for converting legacy blob transactions @@ -73,7 +72,7 @@ func newConversionQueue() *conversionQueue { // for conversion. // // This function may block for a long time until the transaction is processed. -func (q *conversionQueue) convert(tx *types.Transaction) error { +func (q *conversionQueue) convert(tx *pooledBlobTx) error { done := make(chan error, 1) select { case q.tasks <- &txConvert{tx: tx, done: done}: @@ -113,7 +112,7 @@ func (q *conversionQueue) run(tasks []*txConvert, done chan struct{}, interrupt t.done <- errors.New("conversion is interrupted") continue } - sidecar := t.tx.BlobTxSidecar() + sidecar := t.tx.Sidecar if sidecar == nil { t.done <- errors.New("tx without sidecar") continue @@ -122,7 +121,7 @@ func (q *conversionQueue) run(tasks []*txConvert, done chan struct{}, interrupt start := time.Now() err := sidecar.ToV1() t.done <- err - log.Trace("Converted legacy blob tx", "hash", t.tx.Hash(), "err", err, "elapsed", common.PrettyDuration(time.Since(start))) + log.Trace("Converted legacy blob tx", "hash", t.tx.Transaction.Hash(), "err", err, "elapsed", common.PrettyDuration(time.Since(start))) } } diff --git a/core/txpool/blobpool/conversion_test.go b/core/txpool/blobpool/conversion_test.go index a9fd26dbaf..27dcc6cb38 100644 --- a/core/txpool/blobpool/conversion_test.go +++ b/core/txpool/blobpool/conversion_test.go @@ -30,7 +30,7 @@ import ( ) // createV1BlobTx creates a blob transaction with version 1 sidecar for testing. -func createV1BlobTx(nonce uint64, key *ecdsa.PrivateKey) *types.Transaction { +func createV1BlobTx(nonce uint64, key *ecdsa.PrivateKey) *pooledBlobTx { blob := &kzg4844.Blob{byte(nonce)} commitment, _ := kzg4844.BlobToCommitment(blob) cellProofs, _ := kzg4844.ComputeCellProofs(blob) @@ -46,7 +46,9 @@ func createV1BlobTx(nonce uint64, key *ecdsa.PrivateKey) *types.Transaction { Value: uint256.NewInt(100), Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion1, []kzg4844.Blob{*blob}, []kzg4844.Commitment{commitment}, cellProofs), } - return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx) + tx := types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx) + pooledTx, _ := newPooledBlobTx(tx) + return pooledTx } func TestConversionQueueBasic(t *testing.T) { @@ -55,11 +57,12 @@ func TestConversionQueueBasic(t *testing.T) { key, _ := crypto.GenerateKey() tx := makeTx(0, 1, 1, 1, key) - if err := queue.convert(tx); err != nil { + pooledTx, _ := newPooledBlobTx(tx) + if err := queue.convert(pooledTx); err != nil { t.Fatalf("Expected successful conversion, got error: %v", err) } - if tx.BlobTxSidecar().Version != types.BlobSidecarVersion1 { - t.Errorf("Expected sidecar version to be %d, got %d", types.BlobSidecarVersion1, tx.BlobTxSidecar().Version) + if pooledTx.Sidecar.Version != types.BlobSidecarVersion1 { + t.Errorf("Expected sidecar version to be %d, got %d", types.BlobSidecarVersion1, pooledTx.Sidecar.Version) } } @@ -69,14 +72,14 @@ func TestConversionQueueV1BlobTx(t *testing.T) { key, _ := crypto.GenerateKey() tx := createV1BlobTx(0, key) - version := tx.BlobTxSidecar().Version + version := tx.Sidecar.Version err := queue.convert(tx) if err != nil { t.Fatalf("Expected successful conversion, got error: %v", err) } - if tx.BlobTxSidecar().Version != version { - t.Errorf("Expected sidecar version to remain %d, got %d", version, tx.BlobTxSidecar().Version) + if tx.Sidecar.Version != version { + t.Errorf("Expected sidecar version to remain %d, got %d", version, tx.Sidecar.Version) } } @@ -88,7 +91,8 @@ func TestConversionQueueClosed(t *testing.T) { key, _ := crypto.GenerateKey() tx := makeTx(0, 1, 1, 1, key) - err := queue.convert(tx) + pooledTx, _ := newPooledBlobTx(tx) + err := queue.convert(pooledTx) if err == nil { t.Fatal("Expected error when converting on closed queue, got nil") } diff --git a/core/types/tx_blob.go b/core/types/tx_blob.go index fb4e392274..ad4cc58719 100644 --- a/core/types/tx_blob.go +++ b/core/types/tx_blob.go @@ -207,6 +207,32 @@ func (c *BlobTxCellSidecar) ValidateBlobCommitmentHashes(hashes []common.Hash) e return sc.ValidateBlobCommitmentHashes(hashes) } +// ToV1 converts the BlobSidecar to version 1, attaching the cell proofs. +// This function only available when we can recover the original blob with given cells +// TODO: duplication +func (sc *BlobTxCellSidecar) ToV1() error { + if sc.Version == BlobSidecarVersion1 { + return nil + } + blobs, err := kzg4844.RecoverBlobs(sc.Cells, sc.Custody.Indices()) + if err != nil { + return err + } + if sc.Version == BlobSidecarVersion0 { + proofs := make([]kzg4844.Proof, 0, len(blobs)*kzg4844.CellProofsPerBlob) + for _, blob := range blobs { + cellProofs, err := kzg4844.ComputeCellProofs(&blob) + if err != nil { + return err + } + proofs = append(proofs, cellProofs...) + } + sc.Version = BlobSidecarVersion1 + sc.Proofs = proofs + } + return nil +} + // blobTxWithBlobs represents blob tx with its corresponding sidecar. // This is an interface because sidecars are versioned. type blobTxWithBlobs interface {