diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 644f43913b..1359158817 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1671,6 +1671,17 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo return blobs, commitments, proofs, nil } +// GetBlobHashes returns the blob versioned hashes for a given transaction hash. +func (p *BlobPool) GetBlobHashes(txHash common.Hash) []common.Hash { + p.lock.RLock() + defer p.lock.RUnlock() + vhashes, ok := p.lookup.blobHashesOfTx(txHash) + if !ok { + return nil + } + return vhashes +} + // GetBlobCells returns cells for the given versioned blob hashes, // filtered by the requested cell indices(mask). // Each entry in the result corresponds to one vhash. Nil entries mean the blob @@ -2436,38 +2447,3 @@ func (p *BlobPool) GetCustody(hash common.Hash) *types.CustodyBitmap { } return nil } - -// GetCells returns the cells matching the given custody bitmap for a transaction. -func (p *BlobPool) GetCells(hash common.Hash, mask types.CustodyBitmap) ([]kzg4844.Cell, error) { - p.lock.RLock() - defer p.lock.RUnlock() - id, ok := p.lookup.storeidOfTx(hash) - if !ok { - return nil, errors.New("requested cells don't exist") - } - data, err := p.store.Get(id) - if err != nil { - return nil, errors.New("tracked blob transaction missing from store") - } - // Decode the blob transaction - var pooledTx PooledBlobTx - if err := rlp.DecodeBytes(data, &pooledTx); err != nil { - return nil, errors.New("blobs corrupted for traced transaction") - } - tx := pooledTx.Transaction - sidecar := pooledTx.Sidecar - // Return cells in blob-major order: [blob0_cell0, blob0_cell1, ..., blob1_cell0, ...] - cellsPerBlob := sidecar.Custody.OneCount() - cells := make([]kzg4844.Cell, 0, mask.OneCount()*len(tx.BlobHashes())) - for blobIdx := 0; blobIdx < len(tx.BlobHashes()); blobIdx++ { - for cellIdx, custodyIdx := range sidecar.Custody.Indices() { - if mask.IsSet(custodyIdx) { - cells = append(cells, sidecar.Cells[blobIdx*cellsPerBlob+cellIdx]) - } - } - } - if len(cells) != mask.OneCount()*len(tx.BlobHashes()) { - return nil, fmt.Errorf("not enough cells: tx %s, needed %d, have %d", tx.Hash(), len(tx.BlobHashes())*mask.OneCount(), len(cells)) - } - return cells, nil -} diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 35e9958bf3..cf072384c2 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -2227,33 +2227,32 @@ func TestGetCells(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cells, err := pool.GetCells(tt.hash, tt.mask) - - if err != nil && !tt.shouldFail { - t.Errorf("expected to success, got %v", err) + vhashes := pool.GetBlobHashes(tt.hash) + if tt.shouldFail { + if vhashes != nil { + t.Errorf("expected nil vhashes for non-existent tx") + } + return } - if err == nil && tt.shouldFail { - t.Errorf("expected to fail, got %v", err) + if vhashes == nil { + t.Fatalf("expected vhashes, got nil") } - - if len(cells) != tt.expectedLen { - t.Errorf("expected %d cells, got %d", tt.expectedLen, len(cells)) + blobCells, _, err := pool.GetBlobCells(vhashes, tt.mask) + if err != nil { + t.Fatalf("unexpected error: %v", err) } - - if tt.expectedLen > 0 && tt.expectedLen%3 == 0 { - blobCount := 3 - cellsPerBlob := tt.expectedLen / blobCount - - for blobIdx := 0; blobIdx < blobCount; blobIdx++ { - startIdx := blobIdx * cellsPerBlob - endIdx := startIdx + cellsPerBlob - - if endIdx > len(cells) { - t.Errorf("blob %d: expected cells up to index %d, but only have %d cells", - blobIdx, endIdx-1, len(cells)) + // Count total non-nil cells across all blobs + totalCells := 0 + for _, bc := range blobCells { + for _, c := range bc { + if c != nil { + totalCells++ } } } + if totalCells != tt.expectedLen { + t.Errorf("expected %d cells, got %d", tt.expectedLen, totalCells) + } }) } } diff --git a/core/txpool/blobpool/lookup.go b/core/txpool/blobpool/lookup.go index cbbeedde1b..78ddcf3089 100644 --- a/core/txpool/blobpool/lookup.go +++ b/core/txpool/blobpool/lookup.go @@ -26,6 +26,7 @@ type txMetadata struct { size uint64 // the RLP encoded size of transaction (blobs are included) sizeWithoutBlob uint64 // the RLP encoded size without blob data (for ETH/72 announcements) custody types.CustodyBitmap + vhashes []common.Hash // blob versioned hashes for the transaction } // lookup maps blob versioned hashes to transaction hashes that include them, @@ -59,6 +60,15 @@ func (l *lookup) storeidOfTx(txhash common.Hash) (uint64, bool) { return meta.id, true } +// blobHashesOfTx returns the blob versioned hashes for a transaction. +func (l *lookup) blobHashesOfTx(txhash common.Hash) ([]common.Hash, bool) { + meta, ok := l.txIndex[txhash] + if !ok { + return nil, false + } + return meta.vhashes, true +} + // storeidOfBlob returns the datastore storage item id of a blob. func (l *lookup) storeidOfBlob(vhash common.Hash) (uint64, bool) { // If the blob is unknown, return a miss @@ -98,6 +108,7 @@ func (l *lookup) track(tx *blobTxMeta) { size: tx.size, sizeWithoutBlob: tx.sizeWithoutBlob, custody: *tx.custody, + vhashes: tx.vhashes, } } diff --git a/eth/handler.go b/eth/handler.go index 2fb479a0ff..539c8a7c6d 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -103,7 +103,8 @@ type txPool interface { // support cell-based blob data availability. type blobPool interface { Has(hash common.Hash) bool - GetCells(hash common.Hash, mask types.CustodyBitmap) ([]kzg4844.Cell, error) + GetBlobHashes(hash common.Hash) []common.Hash + GetBlobCells(vhashes []common.Hash, mask types.CustodyBitmap) ([][]*kzg4844.Cell, [][]*kzg4844.Proof, error) HasPayload(hash common.Hash) bool GetCustody(hash common.Hash) *types.CustodyBitmap AddPooledTx(pooledTx *blobpool.PooledBlobTx) error diff --git a/eth/handler_test.go b/eth/handler_test.go index 75b8d12668..ca1a978c62 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -17,7 +17,6 @@ package eth import ( - "errors" "maps" "math/big" "math/rand" @@ -181,28 +180,59 @@ func (p *testTxPool) Pending(filter txpool.PendingFilter) (map[common.Address][] func (p *testTxPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription { return p.txFeed.Subscribe(ch) } -func (p *testTxPool) GetCells(hash common.Hash, mask types.CustodyBitmap) ([]kzg4844.Cell, error) { +func (p *testTxPool) GetBlobHashes(hash common.Hash) []common.Hash { p.lock.RLock() defer p.lock.RUnlock() - _, exists := p.txPool[hash] + tx, exists := p.txPool[hash] if !exists { - return nil, errors.New("Requested tx does not exist") + return nil } + return tx.BlobHashes() +} - var cells []kzg4844.Cell +func (p *testTxPool) GetBlobCells(vhashes []common.Hash, mask types.CustodyBitmap) ([][]*kzg4844.Cell, [][]*kzg4844.Proof, error) { + p.lock.RLock() + defer p.lock.RUnlock() - if cells, exists = p.cellPool[hash]; !exists { - return nil, errors.New("Requested cells do not exist") - } + requestedIndices := mask.Indices() + cells := make([][]*kzg4844.Cell, len(vhashes)) + proofs := make([][]*kzg4844.Proof, len(vhashes)) - result := make([]kzg4844.Cell, 0, mask.OneCount()) - for _, idx := range mask.Indices() { - if int(idx) < len(cells) { - result = append(result, cells[idx]) + for i, vhash := range vhashes { + // Find the tx containing this versioned hash + var foundTx *types.Transaction + var blobIdx int + for _, tx := range p.txPool { + for j, bh := range tx.BlobHashes() { + if bh == vhash { + foundTx = tx + blobIdx = j + break + } + } + if foundTx != nil { + break + } } + if foundTx == nil { + continue + } + txCells, ok := p.cellPool[foundTx.Hash()] + if !ok { + continue + } + _ = blobIdx // cells in the mock are stored flat by cell index + blobCells := make([]*kzg4844.Cell, len(requestedIndices)) + for j, idx := range requestedIndices { + if int(idx) < len(txCells) { + cell := txCells[idx] + blobCells[j] = &cell + } + } + cells[i] = blobCells } - return result, nil + return cells, proofs, nil } func (p *testTxPool) GetCustody(hash common.Hash) *types.CustodyBitmap { diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index ff90d6e328..26f4ddfda9 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -89,8 +89,10 @@ type Backend interface { // BlobPool defines the methods needed by the protocol handler to serve cell requests. type BlobPool interface { - // GetCells retrieves cells for a given transaction hash filtered by the custody bitmap. - GetCells(hash common.Hash, mask types.CustodyBitmap) ([]kzg4844.Cell, error) + // GetBlobHashes returns the blob versioned hashes for a given transaction hash. + GetBlobHashes(hash common.Hash) []common.Hash + // GetBlobCells retrieves cells and proofs for given versioned blob hashes filtered by the custody bitmap. + GetBlobCells(vhashes []common.Hash, mask types.CustodyBitmap) ([][]*kzg4844.Cell, [][]*kzg4844.Proof, error) // GetCustody returns the custody bitmap for a given transaction hash. GetCustody(hash common.Hash) *types.CustodyBitmap // Has returns whether the blob pool contains a transaction with the given hash. diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 0af04f87a0..68bf5ae787 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -623,14 +623,39 @@ func answerGetCells(backend Backend, query GetCellsRequest) ([]common.Hash, [][] if cellCounts >= maxCells { break } - cell, _ := backend.BlobPool().GetCells(hash, query.Mask) - if len(cell) == 0 { - // skip this tx + // Look up the blob versioned hashes for this transaction + vhashes := backend.BlobPool().GetBlobHashes(hash) + if len(vhashes) == 0 { + continue + } + blobCells, _, _ := backend.BlobPool().GetBlobCells(vhashes, query.Mask) + + // Flatten per-blob cells into a single slice. If any blob has a nil + // entry (unavailable cell), skip the entire transaction. + var flat []kzg4844.Cell + skip := false + for _, bc := range blobCells { + if bc == nil { + skip = true + break + } + for _, c := range bc { + if c == nil { + skip = true + break + } + flat = append(flat, *c) + } + if skip { + break + } + } + if skip || len(flat) == 0 { continue } hashes = append(hashes, hash) - cells = append(cells, cell) - cellCounts += len(cell) + cells = append(cells, flat) + cellCounts += len(flat) } return hashes, cells, query.Mask }