go-ethereum/core/txpool/blobpool/lookup.go
Bosul Mun d91b71fb36
core/txpool/blobpool, eth: implement sparse blobpool (#34047)
This is the implementation of EIP-8070 Sparse Blobpool. It introduces
protocol version eth/72 which relays blob transaction cells instead of
full blobs.

The blobpool now store 'incomplete' transactions, where only some of
the cells are provided. The stored cell indexes are taken from the
custody bitmap, which is provided by the consensus layer in
forkchoiceUpdatedV4. This method will be called once Glamsterdam
activates, and the default custody is full custody, so for now there
is no change in the amount of stored cells for now.

The main entities added are the BlobBuffer and BlobFetcher, which work
together to track and fetch missing cells from connected peers. The
partial transactions become available for inclusion in blocks when
they are covered by enough peers that hold all cells.

This change also introduces engine_getBlobsV4, which allows for
cell-based responses (and partial blobs with only some of the cells).
We maintain backward compatibility with getBlobsV3 which expects full
blob responses by recovering the blob from available cells. Since this
process is resource-intensive, we proactively cache the conversion so
it is ready in time for getBlobsV3 calls. This mechanism will be
removed once support for getBlobsV4 is universal across all consensus
layer implementations.

devp2p tests for eth/72 are not part of this initial change. This is
to avoid breaking test success status for execution clients that do
not have eth/72 implemented yet. The tests will be added in a
subsequent change.

---------

Co-authored-by: Felix Lange <fjl@twurst.com>
2026-07-15 14:41:04 +02:00

128 lines
4.2 KiB
Go

// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package blobpool
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type txMetadata struct {
id uint64 // the billy id of transaction
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,
// transaction hashes to billy entries that include them, transaction hashes
// to the transaction size
type lookup struct {
blobIndex map[common.Hash]map[common.Hash]struct{}
txIndex map[common.Hash]*txMetadata
}
// newLookup creates a new index for tracking blob to tx; and tx to billy mappings.
func newLookup() *lookup {
return &lookup{
blobIndex: make(map[common.Hash]map[common.Hash]struct{}),
txIndex: make(map[common.Hash]*txMetadata),
}
}
// exists returns whether a transaction is already tracked or not.
func (l *lookup) exists(txhash common.Hash) bool {
_, exists := l.txIndex[txhash]
return exists
}
// storeidOfTx returns the datastore storage item id of a transaction.
func (l *lookup) storeidOfTx(txhash common.Hash) (uint64, bool) {
meta, ok := l.txIndex[txhash]
if !ok {
return 0, false
}
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
txs, ok := l.blobIndex[vhash]
if !ok {
return 0, false
}
// If the blob is known, return any tx for it
for tx := range txs {
return l.storeidOfTx(tx)
}
return 0, false // Weird, don't choke
}
// sizeOfTx returns the RLP-encoded size of transaction
func (l *lookup) sizeOfTx(txhash common.Hash) (uint64, bool) {
meta, ok := l.txIndex[txhash]
if !ok {
return 0, false
}
return meta.size, true
}
// track inserts a new set of mappings from blob versioned hashes to transaction
// hashes; and from transaction hashes to datastore storage item ids.
func (l *lookup) track(tx *blobTxMeta) {
// Map all the blobs to the transaction hash
for _, vhash := range tx.vhashes {
if _, ok := l.blobIndex[vhash]; !ok {
l.blobIndex[vhash] = make(map[common.Hash]struct{})
}
l.blobIndex[vhash][tx.hash] = struct{}{} // may be double mapped if a tx contains the same blob twice
}
// Map the transaction hash to the datastore id and RLP-encoded transaction size
l.txIndex[tx.hash] = &txMetadata{
id: tx.id,
size: tx.size,
sizeWithoutBlob: tx.sizeWithoutBlob,
custody: *tx.custody,
vhashes: tx.vhashes,
}
}
// untrack removes a set of mappings from blob versioned hashes to transaction
// hashes from the blob index.
func (l *lookup) untrack(tx *blobTxMeta) {
// Unmap the transaction hash from the datastore id
delete(l.txIndex, tx.hash)
// Unmap all the blobs from the transaction hash
for _, vhash := range tx.vhashes {
delete(l.blobIndex[vhash], tx.hash) // may be double deleted if a tx contains the same blob twice
if len(l.blobIndex[vhash]) == 0 {
delete(l.blobIndex, vhash)
}
}
}