Skip to content

Commit 2a6beb6

Browse files
authored
core/types: support for optional blob sidecar in BlobTx (#27841)
This PR removes the newly added txpool.Transaction wrapper type, and instead adds a way of keeping the blob sidecar within types.Transaction. It's better this way because most code in go-ethereum does not care about blob transactions, and probably never will. This will start mattering especially on the client side of RPC, where all APIs are based on types.Transaction. Users need to be able to use the same signing flows they already have. However, since blobs are only allowed in some places but not others, we will now need to add checks to avoid creating invalid blocks. I'm still trying to figure out the best place to do some of these. The way I have it currently is as follows: - In block validation (import), txs are verified not to have a blob sidecar. - In miner, we strip off the sidecar when committing the transaction into the block. - In TxPool validation, txs must have a sidecar to be added into the blobpool. - Note there is a special case here: when transactions are re-added because of a chain reorg, we cannot use the transactions gathered from the old chain blocks as-is, because they will be missing their blobs. This was previously handled by storing the blobs into the 'blobpool limbo'. The code has now changed to store the full transaction in the limbo instead, but it might be confusing for code readers why we're not simply adding the types.Transaction we already have. Code changes summary: - txpool.Transaction removed and all uses replaced by types.Transaction again - blobpool now stores types.Transaction instead of defining its own blobTx format for storage - the blobpool limbo now stores types.Transaction instead of storing only the blobs - checks to validate the presence/absence of the blob sidecar added in certain critical places
1 parent 6886006 commit 2a6beb6

32 files changed

+626
-373
lines changed

core/block_validator.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
6868
if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash {
6969
return fmt.Errorf("transaction root hash mismatch (header value %x, calculated %x)", header.TxHash, hash)
7070
}
71+
7172
// Withdrawals are present after the Shanghai fork.
7273
if header.WithdrawalsHash != nil {
7374
// Withdrawals list must be present in body after Shanghai.
@@ -81,14 +82,23 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
8182
// Withdrawals are not allowed prior to Shanghai fork
8283
return errors.New("withdrawals present in block body")
8384
}
85+
8486
// Blob transactions may be present after the Cancun fork.
8587
var blobs int
86-
for _, tx := range block.Transactions() {
88+
for i, tx := range block.Transactions() {
8789
// Count the number of blobs to validate against the header's blobGasUsed
8890
blobs += len(tx.BlobHashes())
91+
92+
// If the tx is a blob tx, it must NOT have a sidecar attached to be valid in a block.
93+
if tx.BlobTxSidecar() != nil {
94+
return fmt.Errorf("unexpected blob sidecar in transaction at index %d", i)
95+
}
96+
8997
// The individual checks for blob validity (version-check + not empty)
90-
// happens in the state_transition check.
98+
// happens in StateTransition.
9199
}
100+
101+
// Check blob gas usage.
92102
if header.BlobGasUsed != nil {
93103
if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated
94104
return fmt.Errorf("blob gas used mismatch (header %v, calculated %v)", *header.BlobGasUsed, blobs*params.BlobTxBlobGasPerBlob)
@@ -98,6 +108,8 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
98108
return errors.New("data blobs present in block body")
99109
}
100110
}
111+
112+
// Ancestor block must be known.
101113
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
102114
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
103115
return consensus.ErrUnknownAncestor

core/blockchain.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,19 +1085,30 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
10851085
ancientReceipts, liveReceipts []types.Receipts
10861086
)
10871087
// Do a sanity check that the provided chain is actually ordered and linked
1088-
for i := 0; i < len(blockChain); i++ {
1088+
for i, block := range blockChain {
10891089
if i != 0 {
1090-
if blockChain[i].NumberU64() != blockChain[i-1].NumberU64()+1 || blockChain[i].ParentHash() != blockChain[i-1].Hash() {
1091-
log.Error("Non contiguous receipt insert", "number", blockChain[i].Number(), "hash", blockChain[i].Hash(), "parent", blockChain[i].ParentHash(),
1092-
"prevnumber", blockChain[i-1].Number(), "prevhash", blockChain[i-1].Hash())
1093-
return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, blockChain[i-1].NumberU64(),
1094-
blockChain[i-1].Hash().Bytes()[:4], i, blockChain[i].NumberU64(), blockChain[i].Hash().Bytes()[:4], blockChain[i].ParentHash().Bytes()[:4])
1090+
prev := blockChain[i-1]
1091+
if block.NumberU64() != prev.NumberU64()+1 || block.ParentHash() != prev.Hash() {
1092+
log.Error("Non contiguous receipt insert",
1093+
"number", block.Number(), "hash", block.Hash(), "parent", block.ParentHash(),
1094+
"prevnumber", prev.Number(), "prevhash", prev.Hash())
1095+
return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])",
1096+
i-1, prev.NumberU64(), prev.Hash().Bytes()[:4],
1097+
i, block.NumberU64(), block.Hash().Bytes()[:4], block.ParentHash().Bytes()[:4])
10951098
}
10961099
}
1097-
if blockChain[i].NumberU64() <= ancientLimit {
1098-
ancientBlocks, ancientReceipts = append(ancientBlocks, blockChain[i]), append(ancientReceipts, receiptChain[i])
1100+
if block.NumberU64() <= ancientLimit {
1101+
ancientBlocks, ancientReceipts = append(ancientBlocks, block), append(ancientReceipts, receiptChain[i])
10991102
} else {
1100-
liveBlocks, liveReceipts = append(liveBlocks, blockChain[i]), append(liveReceipts, receiptChain[i])
1103+
liveBlocks, liveReceipts = append(liveBlocks, block), append(liveReceipts, receiptChain[i])
1104+
}
1105+
1106+
// Here we also validate that blob transactions in the block do not contain a sidecar.
1107+
// While the sidecar does not affect the block hash / tx hash, sending blobs within a block is not allowed.
1108+
for txIndex, tx := range block.Transactions() {
1109+
if tx.Type() == types.BlobTxType && tx.BlobTxSidecar() != nil {
1110+
return 0, fmt.Errorf("block #%d contains unexpected blob sidecar in tx at index %d", block.NumberU64(), txIndex)
1111+
}
11011112
}
11021113
}
11031114

core/txpool/blobpool/blobpool.go

Lines changed: 34 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package blobpool
1919

2020
import (
2121
"container/heap"
22+
"errors"
2223
"fmt"
2324
"math"
2425
"math/big"
@@ -35,7 +36,6 @@ import (
3536
"github.com/ethereum/go-ethereum/core/state"
3637
"github.com/ethereum/go-ethereum/core/txpool"
3738
"github.com/ethereum/go-ethereum/core/types"
38-
"github.com/ethereum/go-ethereum/crypto/kzg4844"
3939
"github.com/ethereum/go-ethereum/event"
4040
"github.com/ethereum/go-ethereum/log"
4141
"github.com/ethereum/go-ethereum/metrics"
@@ -83,16 +83,6 @@ const (
8383
limboedTransactionStore = "limbo"
8484
)
8585

86-
// blobTx is a wrapper around types.BlobTx which also contains the literal blob
87-
// data along with all the transaction metadata.
88-
type blobTx struct {
89-
Tx *types.Transaction
90-
91-
Blobs []kzg4844.Blob
92-
Commits []kzg4844.Commitment
93-
Proofs []kzg4844.Proof
94-
}
95-
9686
// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and
9787
// schedule the blob transactions into the following blocks. Only ever add the
9888
// bare minimum needed fields to keep the size down (and thus number of entries
@@ -455,22 +445,27 @@ func (p *BlobPool) Close() error {
455445
// parseTransaction is a callback method on pool creation that gets called for
456446
// each transaction on disk to create the in-memory metadata index.
457447
func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
458-
item := new(blobTx)
459-
if err := rlp.DecodeBytes(blob, item); err != nil {
448+
tx := new(types.Transaction)
449+
if err := rlp.DecodeBytes(blob, tx); err != nil {
460450
// This path is impossible unless the disk data representation changes
461451
// across restarts. For that ever unprobable case, recover gracefully
462452
// by ignoring this data entry.
463453
log.Error("Failed to decode blob pool entry", "id", id, "err", err)
464454
return err
465455
}
466-
meta := newBlobTxMeta(id, size, item.Tx)
456+
if tx.BlobTxSidecar() == nil {
457+
log.Error("Missing sidecar in blob pool entry", "id", id, "hash", tx.Hash())
458+
return errors.New("missing blob sidecar")
459+
}
460+
461+
meta := newBlobTxMeta(id, size, tx)
467462

468-
sender, err := p.signer.Sender(item.Tx)
463+
sender, err := p.signer.Sender(tx)
469464
if err != nil {
470465
// This path is impossible unless the signature validity changes across
471466
// restarts. For that ever unprobable case, recover gracefully by ignoring
472467
// this data entry.
473-
log.Error("Failed to recover blob tx sender", "id", id, "hash", item.Tx.Hash(), "err", err)
468+
log.Error("Failed to recover blob tx sender", "id", id, "hash", tx.Hash(), "err", err)
474469
return err
475470
}
476471
if _, ok := p.index[sender]; !ok {
@@ -718,17 +713,17 @@ func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusi
718713
log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
719714
return
720715
}
721-
item := new(blobTx)
722-
if err = rlp.DecodeBytes(data, item); err != nil {
716+
var tx types.Transaction
717+
if err = rlp.DecodeBytes(data, tx); err != nil {
723718
log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
724719
return
725720
}
726-
block, ok := inclusions[item.Tx.Hash()]
721+
block, ok := inclusions[tx.Hash()]
727722
if !ok {
728723
log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id)
729724
return
730725
}
731-
if err := p.limbo.push(item.Tx.Hash(), block, item.Blobs, item.Commits, item.Proofs); err != nil {
726+
if err := p.limbo.push(&tx, block); err != nil {
732727
log.Warn("Failed to offload blob tx into limbo", "err", err)
733728
return
734729
}
@@ -760,7 +755,7 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
760755
for addr, txs := range reinject {
761756
// Blindly push all the lost transactions back into the pool
762757
for _, tx := range txs {
763-
p.reinject(addr, tx)
758+
p.reinject(addr, tx.Hash())
764759
}
765760
// Recheck the account's pooled transactions to drop included and
766761
// invalidated one
@@ -920,16 +915,19 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
920915
// Note, the method will not initialize the eviction cache values as those will
921916
// be done once for all transactions belonging to an account after all individual
922917
// transactions are injected back into the pool.
923-
func (p *BlobPool) reinject(addr common.Address, tx *types.Transaction) {
918+
func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) {
924919
// Retrieve the associated blob from the limbo. Without the blobs, we cannot
925920
// add the transaction back into the pool as it is not mineable.
926-
blobs, commits, proofs, err := p.limbo.pull(tx.Hash())
921+
tx, err := p.limbo.pull(txhash)
927922
if err != nil {
928923
log.Error("Blobs unavailable, dropping reorged tx", "err", err)
929924
return
930925
}
931-
// Serialize the transaction back into the primary datastore
932-
blob, err := rlp.EncodeToBytes(&blobTx{Tx: tx, Blobs: blobs, Commits: commits, Proofs: proofs})
926+
// TODO: seems like an easy optimization here would be getting the serialized tx
927+
// from limbo instead of re-serializing it here.
928+
929+
// Serialize the transaction back into the primary datastore.
930+
blob, err := rlp.EncodeToBytes(tx)
933931
if err != nil {
934932
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
935933
return
@@ -939,9 +937,9 @@ func (p *BlobPool) reinject(addr common.Address, tx *types.Transaction) {
939937
log.Error("Failed to write transaction into storage", "hash", tx.Hash(), "err", err)
940938
return
941939
}
940+
942941
// Update the indixes and metrics
943942
meta := newBlobTxMeta(id, p.store.Size(id), tx)
944-
945943
if _, ok := p.index[addr]; !ok {
946944
if err := p.reserve(addr, true); err != nil {
947945
log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err)
@@ -1023,7 +1021,7 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
10231021

10241022
// validateTx checks whether a transaction is valid according to the consensus
10251023
// rules and adheres to some heuristic limits of the local node (price and size).
1026-
func (p *BlobPool) validateTx(tx *types.Transaction, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error {
1024+
func (p *BlobPool) validateTx(tx *types.Transaction) error {
10271025
// Ensure the transaction adheres to basic pool filters (type, size, tip) and
10281026
// consensus rules
10291027
baseOpts := &txpool.ValidationOptions{
@@ -1032,7 +1030,7 @@ func (p *BlobPool) validateTx(tx *types.Transaction, blobs []kzg4844.Blob, commi
10321030
MaxSize: txMaxSize,
10331031
MinTip: p.gasTip.ToBig(),
10341032
}
1035-
if err := txpool.ValidateTransaction(tx, blobs, commits, proofs, p.head, p.signer, baseOpts); err != nil {
1033+
if err := txpool.ValidateTransaction(tx, p.head, p.signer, baseOpts); err != nil {
10361034
return err
10371035
}
10381036
// Ensure the transaction adheres to the stateful pool filters (nonce, balance)
@@ -1117,7 +1115,7 @@ func (p *BlobPool) Has(hash common.Hash) bool {
11171115
}
11181116

11191117
// Get returns a transaction if it is contained in the pool, or nil otherwise.
1120-
func (p *BlobPool) Get(hash common.Hash) *txpool.Transaction {
1118+
func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
11211119
// Track the amount of time waiting to retrieve a fully resolved blob tx from
11221120
// the pool and the amount of time actually spent on pulling the data from disk.
11231121
getStart := time.Now()
@@ -1139,32 +1137,27 @@ func (p *BlobPool) Get(hash common.Hash) *txpool.Transaction {
11391137
log.Error("Tracked blob transaction missing from store", "hash", hash, "id", id, "err", err)
11401138
return nil
11411139
}
1142-
item := new(blobTx)
1140+
item := new(types.Transaction)
11431141
if err = rlp.DecodeBytes(data, item); err != nil {
11441142
log.Error("Blobs corrupted for traced transaction", "hash", hash, "id", id, "err", err)
11451143
return nil
11461144
}
1147-
return &txpool.Transaction{
1148-
Tx: item.Tx,
1149-
BlobTxBlobs: item.Blobs,
1150-
BlobTxCommits: item.Commits,
1151-
BlobTxProofs: item.Proofs,
1152-
}
1145+
return item
11531146
}
11541147

11551148
// Add inserts a set of blob transactions into the pool if they pass validation (both
11561149
// consensus validity and pool restictions).
1157-
func (p *BlobPool) Add(txs []*txpool.Transaction, local bool, sync bool) []error {
1150+
func (p *BlobPool) Add(txs []*types.Transaction, local bool, sync bool) []error {
11581151
errs := make([]error, len(txs))
11591152
for i, tx := range txs {
1160-
errs[i] = p.add(tx.Tx, tx.BlobTxBlobs, tx.BlobTxCommits, tx.BlobTxProofs)
1153+
errs[i] = p.add(tx)
11611154
}
11621155
return errs
11631156
}
11641157

11651158
// Add inserts a new blob transaction into the pool if it passes validation (both
11661159
// consensus validity and pool restictions).
1167-
func (p *BlobPool) add(tx *types.Transaction, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) (err error) {
1160+
func (p *BlobPool) add(tx *types.Transaction) (err error) {
11681161
// The blob pool blocks on adding a transaction. This is because blob txs are
11691162
// only even pulled form the network, so this method will act as the overload
11701163
// protection for fetches.
@@ -1178,7 +1171,7 @@ func (p *BlobPool) add(tx *types.Transaction, blobs []kzg4844.Blob, commits []kz
11781171
}(time.Now())
11791172

11801173
// Ensure the transaction is valid from all perspectives
1181-
if err := p.validateTx(tx, blobs, commits, proofs); err != nil {
1174+
if err := p.validateTx(tx); err != nil {
11821175
log.Trace("Transaction validation failed", "hash", tx.Hash(), "err", err)
11831176
return err
11841177
}
@@ -1203,7 +1196,7 @@ func (p *BlobPool) add(tx *types.Transaction, blobs []kzg4844.Blob, commits []kz
12031196
}
12041197
// Transaction permitted into the pool from a nonce and cost perspective,
12051198
// insert it into the database and update the indices
1206-
blob, err := rlp.EncodeToBytes(&blobTx{Tx: tx, Blobs: blobs, Commits: commits, Proofs: proofs})
1199+
blob, err := rlp.EncodeToBytes(tx)
12071200
if err != nil {
12081201
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
12091202
return err

0 commit comments

Comments
 (0)