core: move blobTxForPool type to blobpool.go

This commit is contained in:
healthykim 2026-05-06 10:28:59 +02:00
parent bddf792428
commit e3f25c2fc9
5 changed files with 147 additions and 167 deletions

View file

@ -147,14 +147,57 @@ type blobTxMeta struct {
evictionBlobFeeJumps float64 // Worse blob fee (converted to fee jumps) across all previous nonces evictionBlobFeeJumps float64 // Worse blob fee (converted to fee jumps) across all previous nonces
} }
// newBlobTxForPool decomposes a blob transaction into BlobTxForPool // blobTxForPool is the storage representation of a blob transaction in the
// type. // blobpool.
func newBlobTxForPool(tx *types.Transaction) *types.BlobTxForPool { type blobTxForPool struct {
Tx *types.Transaction // tx without sidecar
Version byte
Commitments []kzg4844.Commitment
Proofs []kzg4844.Proof
Blobs []kzg4844.Blob
}
// Sidecar returns BlobTxSidecar of ptx.
func (ptx *blobTxForPool) Sidecar() *types.BlobTxSidecar {
return types.NewBlobTxSidecar(ptx.Version, ptx.Blobs, ptx.Commitments, ptx.Proofs)
}
// WithSidecar copies the sidecar's fields into the flat fields.
func (ptx *blobTxForPool) WithSidecar(sc *types.BlobTxSidecar) {
ptx.Version = sc.Version
ptx.Commitments = sc.Commitments
ptx.Proofs = sc.Proofs
ptx.Blobs = sc.Blobs
}
// TxSize returns the transaction size on the network without
// reconstructing the transaction.
func (ptx *blobTxForPool) TxSize() uint64 {
var blobs, commitments, proofs uint64
for i := range ptx.Blobs {
blobs += rlp.BytesSize(ptx.Blobs[i][:])
}
for i := range ptx.Commitments {
commitments += rlp.BytesSize(ptx.Commitments[i][:])
}
for i := range ptx.Proofs {
proofs += rlp.BytesSize(ptx.Proofs[i][:])
}
return ptx.Tx.Size() + rlp.ListSize(rlp.ListSize(blobs)+rlp.ListSize(commitments)+rlp.ListSize(proofs))
}
// ToTx reconstructs a full Transaction with the sidecar attached.
func (ptx *blobTxForPool) ToTx() *types.Transaction {
return ptx.Tx.WithBlobTxSidecar(ptx.Sidecar())
}
// newBlobTxForPool decomposes a blob transaction into blobTxForPool type.
func newBlobTxForPool(tx *types.Transaction) *blobTxForPool {
sc := tx.BlobTxSidecar() sc := tx.BlobTxSidecar()
if sc == nil { if sc == nil {
panic("missing blob tx sidecar") panic("missing blob tx sidecar")
} }
return &types.BlobTxForPool{ return &blobTxForPool{
Tx: tx.WithoutBlobTxSidecar(), Tx: tx.WithoutBlobTxSidecar(),
Version: sc.Version, Version: sc.Version,
Commitments: sc.Commitments, Commitments: sc.Commitments,
@ -163,9 +206,72 @@ func newBlobTxForPool(tx *types.Transaction) *types.BlobTxForPool {
} }
} }
// encodeForNetwork transforms stored blobTxForPool RLP into the standard
// network transaction encoding. This is used for getRLP.
//
// Stored RLP: [type_byte || tx_fields, version, [comms], [proofs], [blobs]]
// V0: type_byte || rlp([tx_fields, [blobs], [comms], [proofs]])
// V1: type_byte || rlp([tx_fields, version, [blobs], [comms], [proofs]])
func encodeForNetwork(storedRLP []byte) ([]byte, error) {
elems, err := rlp.SplitListValues(storedRLP)
if err != nil {
return nil, fmt.Errorf("invalid blobTxForPool RLP: %w", err)
}
if len(elems) < 5 {
return nil, fmt.Errorf("blobTxForPool has %d elements, need at least 5", len(elems))
}
// 1. Extract tx byte and other tx fields
txBytes, _, err := rlp.SplitString(elems[0])
if err != nil {
return nil, fmt.Errorf("invalid tx bytes: %w", err)
}
if len(txBytes) < 2 {
return nil, errors.New("tx bytes too short")
}
typeByte := txBytes[0]
txRLP := txBytes[1:]
// 2. Find the version of sidecar.
version, _, err := rlp.SplitString(elems[1])
if err != nil {
return nil, fmt.Errorf("invalid version: %w", err)
}
var versionByte byte
switch len(version) {
case 0:
versionByte = 0
case 1:
versionByte = version[0]
default:
return nil, fmt.Errorf("invalid version length: %d", len(version))
}
// 3. Extract sidecar elements.
commitmentsRLP := elems[2]
proofsRLP := elems[3]
blobsRLP := elems[4]
// 4. Reconstruct into the network format.
var outer [][]byte
if versionByte == types.BlobSidecarVersion0 {
outer = [][]byte{txRLP, blobsRLP, commitmentsRLP, proofsRLP}
} else {
outer = [][]byte{txRLP, elems[1], blobsRLP, commitmentsRLP, proofsRLP}
}
body, err := rlp.MergeListValues(outer)
if err != nil {
return nil, err
}
// Prepend type byte and wrap as an RLP string.
inner := make([]byte, 1+len(body))
inner[0] = typeByte
copy(inner[1:], body)
return rlp.EncodeToBytes(inner)
}
// newBlobTxMeta retrieves the indexed metadata fields from a pooled blob // newBlobTxMeta retrieves the indexed metadata fields from a pooled blob
// transaction and assembles a helper struct to track in memory. // transaction and assembles a helper struct to track in memory.
func newBlobTxMeta(id uint64, size uint64, storageSize uint32, ptx *types.BlobTxForPool) *blobTxMeta { func newBlobTxMeta(id uint64, size uint64, storageSize uint32, ptx *blobTxForPool) *blobTxMeta {
meta := &blobTxMeta{ meta := &blobTxMeta{
hash: ptx.Tx.Hash(), hash: ptx.Tx.Hash(),
vhashes: ptx.Tx.BlobHashes(), vhashes: ptx.Tx.BlobHashes(),
@ -608,7 +714,7 @@ func (p *BlobPool) Close() error {
// each transaction on disk to create the in-memory metadata index. // each transaction on disk to create the in-memory metadata index.
// Return value `bool` is set to true when the entry has old Transaction type. // Return value `bool` is set to true when the entry has old Transaction type.
func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) (bool, error) { func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) (bool, error) {
ptx := new(types.BlobTxForPool) ptx := new(blobTxForPool)
if err := rlp.DecodeBytes(blob, ptx); err != nil { if err := rlp.DecodeBytes(blob, ptx); err != nil {
tx := new(types.Transaction) tx := new(types.Transaction)
if err := rlp.DecodeBytes(blob, tx); err != nil { if err := rlp.DecodeBytes(blob, tx); err != nil {
@ -916,7 +1022,7 @@ func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusi
log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err) log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
return return
} }
ptx := new(types.BlobTxForPool) ptx := new(blobTxForPool)
if err := rlp.DecodeBytes(data, ptx); err != nil { if err := rlp.DecodeBytes(data, ptx); err != nil {
log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err) log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
return return
@ -1456,7 +1562,7 @@ func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
if len(data) == 0 { if len(data) == 0 {
return nil return nil
} }
ptx := new(types.BlobTxForPool) ptx := new(blobTxForPool)
if err := rlp.DecodeBytes(data, ptx); err != nil { if err := rlp.DecodeBytes(data, ptx); err != nil {
id, _ := p.lookup.storeidOfTx(hash) id, _ := p.lookup.storeidOfTx(hash)
@ -1470,7 +1576,7 @@ func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
// GetRLP returns a RLP-encoded transaction for network if it is contained in the pool. // GetRLP returns a RLP-encoded transaction for network if it is contained in the pool.
func (p *BlobPool) GetRLP(hash common.Hash) []byte { func (p *BlobPool) GetRLP(hash common.Hash) []byte {
data := p.getRLP(hash) data := p.getRLP(hash)
rlp, err := types.EncodeForNetwork(data) rlp, err := encodeForNetwork(data)
if err != nil { if err != nil {
log.Error("Failed to encode pooled tx into the network type", "hash", hash, "err", err) log.Error("Failed to encode pooled tx into the network type", "hash", hash, "err", err)
return nil return nil
@ -1545,7 +1651,7 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
} }
// Decode the blob transaction // Decode the blob transaction
ptx := new(types.BlobTxForPool) ptx := new(blobTxForPool)
if err := rlp.DecodeBytes(data, ptx); err != nil { if err := rlp.DecodeBytes(data, ptx); err != nil {
log.Error("Blobs corrupted for traced transaction", "id", txID, "err", err) log.Error("Blobs corrupted for traced transaction", "id", txID, "err", err)
continue continue

View file

@ -235,7 +235,7 @@ func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64,
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx) return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
} }
// encodeForPool encodes a blob transaction in the BlobTxForPool storage format. // encodeForPool encodes a blob transaction in the blobTxForPool storage format.
func encodeForPool(tx *types.Transaction) []byte { func encodeForPool(tx *types.Transaction) []byte {
blob, _ := rlp.EncodeToBytes(newBlobTxForPool(tx)) blob, _ := rlp.EncodeToBytes(newBlobTxForPool(tx))
return blob return blob
@ -2061,6 +2061,32 @@ func TestGetBlobs(t *testing.T) {
pool.Close() pool.Close()
} }
// TestEncodeForNetwork verifies that encodeForNetwork produces output identical
// to rlp.EncodeToBytes on the original transaction, for both V0 and V1 sidecars.
func TestEncodeForNetwork(t *testing.T) {
t.Run("v0", func(t *testing.T) { testEncodeForNetwork(t, types.BlobSidecarVersion0) })
t.Run("v1", func(t *testing.T) { testEncodeForNetwork(t, types.BlobSidecarVersion1) })
}
func testEncodeForNetwork(t *testing.T, version byte) {
key, _ := crypto.GenerateKey()
tx := makeMultiBlobTx(0, 1, 1, 1, 1, 0, key, version)
wantRLP, err := rlp.EncodeToBytes(tx)
if err != nil {
t.Fatalf("failed to encode tx: %v", err)
}
storedRLP := encodeForPool(tx)
gotRLP, err := encodeForNetwork(storedRLP)
if err != nil {
t.Fatalf("encodeForNetwork failed: %v", err)
}
if !bytes.Equal(gotRLP, wantRLP) {
t.Fatalf("network encoding mismatch (version %d): got %d bytes, want %d bytes", version, len(gotRLP), len(wantRLP))
}
}
// fakeBilly is a billy.Database implementation which just drops data on the floor. // fakeBilly is a billy.Database implementation which just drops data on the floor.
type fakeBilly struct { type fakeBilly struct {
billy.Database billy.Database

View file

@ -33,7 +33,7 @@ import (
type limboBlob struct { type limboBlob struct {
TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs
Block uint64 // Block in which the blob transaction was included Block uint64 // Block in which the blob transaction was included
Ptx *types.BlobTxForPool Ptx *blobTxForPool
} }
// limbo is a light, indexed database to temporarily store recently included // limbo is a light, indexed database to temporarily store recently included
@ -146,7 +146,7 @@ func (l *limbo) finalize(final *types.Header) {
// push stores a new blob transaction into the limbo, waiting until finality for // push stores a new blob transaction into the limbo, waiting until finality for
// it to be automatically evicted. // it to be automatically evicted.
func (l *limbo) push(ptx *types.BlobTxForPool, block uint64) error { func (l *limbo) push(ptx *blobTxForPool, block uint64) error {
hash := ptx.Tx.Hash() hash := ptx.Tx.Hash()
if _, ok := l.index[hash]; ok { if _, ok := l.index[hash]; ok {
log.Error("Limbo cannot push already tracked blobs", "tx", hash) log.Error("Limbo cannot push already tracked blobs", "tx", hash)
@ -162,7 +162,7 @@ func (l *limbo) push(ptx *types.BlobTxForPool, block uint64) error {
// pull retrieves a previously pushed set of blobs back from the limbo, removing // pull retrieves a previously pushed set of blobs back from the limbo, removing
// it at the same time. This method should be used when a previously included blob // it at the same time. This method should be used when a previously included blob
// transaction gets reorged out. // transaction gets reorged out.
func (l *limbo) pull(tx common.Hash) (*types.BlobTxForPool, error) { func (l *limbo) pull(tx common.Hash) (*blobTxForPool, error) {
// If the blobs are not tracked by the limbo, there's not much to do. This // If the blobs are not tracked by the limbo, there's not much to do. This
// can happen for example if a blob transaction is mined without pushing it // can happen for example if a blob transaction is mined without pushing it
// into the network first. // into the network first.
@ -239,7 +239,7 @@ func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) {
// setAndIndex assembles a limbo blob database entry and stores it, also updating // setAndIndex assembles a limbo blob database entry and stores it, also updating
// the in-memory indices. // the in-memory indices.
func (l *limbo) setAndIndex(ptx *types.BlobTxForPool, block uint64) error { func (l *limbo) setAndIndex(ptx *blobTxForPool, block uint64) error {
txhash := ptx.Tx.Hash() txhash := ptx.Tx.Hash()
item := &limboBlob{ item := &limboBlob{
TxHash: txhash, TxHash: txhash,

View file

@ -176,112 +176,6 @@ func (sc *BlobTxSidecar) Copy() *BlobTxSidecar {
} }
} }
// BlobTxForPool is a type used for blob transaction in the blobpool.
type BlobTxForPool struct {
Tx *Transaction // tx without sidecar
Version byte
Commitments []kzg4844.Commitment
Proofs []kzg4844.Proof
Blobs []kzg4844.Blob
}
// Sidecar returns BlobTxSidecar of ptx.
func (ptx *BlobTxForPool) Sidecar() *BlobTxSidecar {
return NewBlobTxSidecar(ptx.Version, ptx.Blobs, ptx.Commitments, ptx.Proofs)
}
// WithSidecar copies the sidecar's fields into the flat fields.
func (ptx *BlobTxForPool) WithSidecar(sc *BlobTxSidecar) {
ptx.Version = sc.Version
ptx.Commitments = sc.Commitments
ptx.Proofs = sc.Proofs
ptx.Blobs = sc.Blobs
}
// TxSize returns the transaction size on the network without
// reconstructing the transaction.
func (ptx *BlobTxForPool) TxSize() uint64 {
var blobs, commitments, proofs uint64
for i := range ptx.Blobs {
blobs += rlp.BytesSize(ptx.Blobs[i][:])
}
for i := range ptx.Commitments {
commitments += rlp.BytesSize(ptx.Commitments[i][:])
}
for i := range ptx.Proofs {
proofs += rlp.BytesSize(ptx.Proofs[i][:])
}
return ptx.Tx.Size() + rlp.ListSize(rlp.ListSize(blobs)+rlp.ListSize(commitments)+rlp.ListSize(proofs))
}
// ToTx reconstructs a full Transaction with the sidecar attached.
func (ptx *BlobTxForPool) ToTx() *Transaction {
return ptx.Tx.WithBlobTxSidecar(ptx.Sidecar())
}
// EncodeForNetwork transforms stored BlobTxForPool RLP into the standard
// network transaction encoding.
//
// Stored RLP: [type_byte || tx_fields, version, [comms], [proofs], [blobs]]
// V0: type_byte || rlp([tx_fields, [blobs], [comms], [proofs]])
// V1: type_byte || rlp([tx_fields, version, [blobs], [comms], [proofs]])
func EncodeForNetwork(storedRLP []byte) ([]byte, error) {
elems, err := rlp.SplitListValues(storedRLP)
if err != nil {
return nil, fmt.Errorf("invalid BlobTxForPool RLP: %w", err)
}
if len(elems) < 5 {
return nil, fmt.Errorf("BlobTxForPool has %d elements, need at least 5", len(elems))
}
// 1. Extract tx byte and other tx fields
txBytes, _, err := rlp.SplitString(elems[0])
if err != nil {
return nil, fmt.Errorf("invalid tx bytes: %w", err)
}
if len(txBytes) < 2 {
return nil, errors.New("tx bytes too short")
}
typeByte := txBytes[0]
txRLP := txBytes[1:]
// 2. Find the version of sidecar.
version, _, err := rlp.SplitString(elems[1])
if err != nil {
return nil, fmt.Errorf("invalid version: %w", err)
}
var versionByte byte
switch len(version) {
case 0:
versionByte = 0
case 1:
versionByte = version[0]
default:
return nil, fmt.Errorf("invalid version length: %d", len(version))
}
// 3. Extract sidecar elements.
commitmentsRLP := elems[2]
proofsRLP := elems[3]
blobsRLP := elems[4]
// 4. Reconstruct into the network format.
var outer [][]byte
if versionByte == BlobSidecarVersion0 {
outer = [][]byte{txRLP, blobsRLP, commitmentsRLP, proofsRLP}
} else {
outer = [][]byte{txRLP, elems[1], blobsRLP, commitmentsRLP, proofsRLP}
}
body, err := rlp.MergeListValues(outer)
if err != nil {
return nil, err
}
// Prepend type byte and wrap as an RLP string.
inner := make([]byte, 1+len(body))
inner[0] = typeByte
copy(inner[1:], body)
return rlp.EncodeToBytes(inner)
}
// blobTxWithBlobs represents blob tx with its corresponding sidecar. // blobTxWithBlobs represents blob tx with its corresponding sidecar.
// This is an interface because sidecars are versioned. // This is an interface because sidecars are versioned.
type blobTxWithBlobs interface { type blobTxWithBlobs interface {

View file

@ -17,14 +17,12 @@
package types package types
import ( import (
"bytes"
"crypto/ecdsa" "crypto/ecdsa"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -88,50 +86,6 @@ func createEmptyBlobTx(key *ecdsa.PrivateKey, withSidecar bool) *Transaction {
return MustSignNewTx(key, signer, blobtx) return MustSignNewTx(key, signer, blobtx)
} }
// TestEncodeForNetwork verifies that EncodeForNetwork produces output identical
// to rlp.EncodeToBytes on the original transaction, for both V0 and V1 sidecars.
func TestEncodeForNetwork(t *testing.T) {
t.Run("v0", func(t *testing.T) { testEncodeForNetwork(t, BlobSidecarVersion0) })
t.Run("v1", func(t *testing.T) { testEncodeForNetwork(t, BlobSidecarVersion1) })
}
func testEncodeForNetwork(t *testing.T, version byte) {
key, _ := crypto.GenerateKey()
tx := createEmptyBlobTx(key, true)
if version == BlobSidecarVersion1 {
if err := tx.BlobTxSidecar().ToV1(); err != nil {
t.Fatalf("failed to convert sidecar to v1: %v", err)
}
}
wantRLP, err := rlp.EncodeToBytes(tx)
if err != nil {
t.Fatalf("failed to encode tx: %v", err)
}
sc := tx.BlobTxSidecar()
ptx := &BlobTxForPool{
Tx: tx.WithoutBlobTxSidecar(),
Version: sc.Version,
Commitments: sc.Commitments,
Proofs: sc.Proofs,
Blobs: sc.Blobs,
}
storedRLP, err := rlp.EncodeToBytes(ptx)
if err != nil {
t.Fatalf("failed to encode BlobTxForPool: %v", err)
}
gotRLP, err := EncodeForNetwork(storedRLP)
if err != nil {
t.Fatalf("EncodeForNetwork failed: %v", err)
}
if !bytes.Equal(gotRLP, wantRLP) {
t.Fatalf("network encoding mismatch (version %d): got %d bytes, want %d bytes", version, len(gotRLP), len(wantRLP))
}
}
func createEmptyBlobTxInner(withSidecar bool) *BlobTx { func createEmptyBlobTxInner(withSidecar bool) *BlobTx {
sidecar := NewBlobTxSidecar(BlobSidecarVersion0, []kzg4844.Blob{*emptyBlob}, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}) sidecar := NewBlobTxSidecar(BlobSidecarVersion0, []kzg4844.Blob{*emptyBlob}, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof})
blobtx := &BlobTx{ blobtx := &BlobTx{