Skip to content

core/txpool/blobpool: add BlobTxForPool type#34882

Merged
fjl merged 15 commits into
ethereum:masterfrom
healthykim:blobpool-tx-type
May 12, 2026
Merged

core/txpool/blobpool: add BlobTxForPool type#34882
fjl merged 15 commits into
ethereum:masterfrom
healthykim:blobpool-tx-type

Conversation

@healthykim
Copy link
Copy Markdown
Contributor

This PR introduces a separate transaction pool type for sparse blobpool.

In sparse blobpool, PooledTransactions message delivers transactions without
blobs, partial or full cells are downloaded by Cells message. Blobpool no longer
stores transactions with complete sidecars, and it stores transactions without
blobs, along with the corresponding cells. Because of this, a dedicated type
distinct from types.Transaction is required.

This PR introduces a type called BlobTxForPool and stores each sidecar field
independently, in order to bypass the assumption that a sidecar always exists as
a complete unit.

Reintroducing the conversion queue was considered, but was ultimately omitted
because type conversion should be sufficiently fast. With sparse blobpool, blob
-> cell computation would take about ~13ms per blob. Not sure whether this is
fast enough, but otherwise we can add the conversion queue later on the sparse
blobpool branch.

@healthykim healthykim requested a review from rjl493456442 as a code owner May 5, 2026 21:35
@fjl
Copy link
Copy Markdown
Contributor

fjl commented May 6, 2026

If it is at all possible, we should move this type into core/txpool/blobpool. The EncodeForNetwork function can be moved into the eth protocol package later, or it can stay in blobpool package. Either way, for GetRLP on the blobpool, we should document the returned RLP format.

Comment thread core/txpool/blobpool/blobpool.go Outdated

// GetRLP returns a RLP-encoded transaction for network if it is contained in the pool.
// GetRLP returns a RLP-encoded transaction for p2p messages, converted from
// the pool's internal type, if it is contained in the pool.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to make a small decision here. GetRLP is supposed to be an optimization that makes the pool return objects which are directly sendable in a p2p message. With the new format, this is not easily possible anymore. So the question now becomes, if we have to transform the format anyway, should we transform it here or in the p2p protocol implementation?

I think it's fine to have knowledge of the p2p encoding in the blobpool. But we should document the format returned by this method, i.e. say

// This method returns the RLP format used by the eth protocol:
//      txType || [..., version, blobs, commitments, proofs]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the eth/72 protocol, the blobs in this encoding is always empty !! 😭
But @healthykim confirmed that we will later change this method to pass in the protocol version for which it should be encoded.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can leave it like this here

I looked a bit more into where the GET operations are actually used.

  • Get is only used by the resolver, at least in blobpool. (legacypool also uses it at the P2P level for broadcasting. It is a bit tricky since they share the same interface.)
  • GetRLP is only used for P2P serving.

So I think the possible approach for sparse blobpool would be:

  • keep GetRLP as the P2P path and take a version number,
  • make Get return everything except sidecars,
  • split sidecar/cell queries into separate functions

Comment thread core/txpool/blobpool/blobpool.go Outdated
txRLP := txBytes[1:]

// 2. Find the version of sidecar.
version, _, err := rlp.SplitString(elems[1])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it use rlp.SplitUint64? The version is an integer.

Comment thread core/txpool/blobpool/blobpool.go Outdated
}

// recheck verifies the pool's content for a specific account and drops anything
// that does not
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment was duplicated accidentally

Comment thread core/txpool/blobpool/blobpool.go Outdated
}
var tx types.Transaction
if err = rlp.DecodeBytes(data, &tx); err != nil {
ptx := new(blobTxForPool)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style thing, why use new(T) instead of var ptx T

Comment thread core/txpool/blobpool/blobpool.go Outdated
Comment on lines +719 to +722
tx := new(types.Transaction)
if err := rlp.DecodeBytes(blob, tx); err != nil {
return false, err
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really have to do this? It is quite an expensive way to check. We could also just check if blob[0] == 3 or something.

Comment thread core/txpool/blobpool/blobpool.go Outdated
Comment on lines +729 to +731
if p.lookup.exists(meta.hash) {
return false, errors.New("duplicate blob entry")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated in trackTransaction

// blobTxForPool is the storage representation of a blob transaction in the
// blobpool.
type blobTxForPool struct {
Tx *types.Transaction // tx without sidecar
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could embed this field. Then you can just call ptx.Hash().

TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs
Block uint64 // Block in which the blob transaction was included
Tx *types.Transaction
Ptx *blobTxForPool
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename is good 🌼

Comment thread core/txpool/blobpool/blobpool.go Outdated
}

// WithSidecar copies the sidecar's fields into the flat fields.
func (ptx *blobTxForPool) WithSidecar(sc *types.BlobTxSidecar) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically use the WithX naming for methods that copy the receiver and return it modified. This method just modifies the receiver so it should be called ApplySidecar.

Comment thread core/txpool/blobpool/blobpool.go Outdated
var fails []uint64
var (
fails []uint64
convertTxs []*types.Transaction
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note here, if the blobpool is at full capacity, this slice could potentially hold multiple gigabytes worth of transactions. I don't think it will occur in practice, but might be better to store the ids, then pull up the transactions from billy one-by-one for the conversion.

Comment thread core/txpool/blobpool/blobpool.go Outdated
// Index all transactions on disk and delete anything unprocessable
var fails []uint64
var (
fails []uint64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also fails here is a bit strange name. The idea is that it contains the ids that will be deleted. So perhaps it should be called toDelete.

Comment thread core/txpool/blobpool/blobpool.go Outdated
var ptx blobTxForPool
if err := rlp.DecodeBytes(blob, &ptx); err != nil {
kind, _, _, splitErr := rlp.Split(blob)
if splitErr == nil && kind == rlp.String {
Copy link
Copy Markdown
Contributor

@fjl fjl May 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could still check for the first byte being 3 here, like this

func isLegacyBlobTx(b []byte) bool {
    k, content, _, err := rlp.Split(b)
    return err == nil && k == rlp.String && len(content) > 1 && content[0] == 3
}

It's basically free to check this.

Comment thread core/txpool/blobpool/blobpool.go Outdated
Comment on lines +720 to +722
return true, nil
}
return false, err
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it's a bit strange to return a boolean. You could also return a special error errLegacyTx and then match on it like err == errLegacyTx in the caller.

@fjl fjl added this to the 1.17.4 milestone May 12, 2026
@fjl fjl changed the title core: add BlobTxForPool type core/txpool/blobpool: add BlobTxForPool type May 12, 2026
@fjl fjl merged commit 726d657 into ethereum:master May 12, 2026
10 checks passed
@cuiweixie
Copy link
Copy Markdown
Contributor

cuiweixie commented May 13, 2026

#34960. May you check this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants