forked from cerc-io/plugeth
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
This commit is contained in:
@@ -19,6 +19,7 @@ package blobpool
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
@@ -35,7 +36,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
@@ -83,16 +83,6 @@ const (
|
||||
limboedTransactionStore = "limbo"
|
||||
)
|
||||
|
||||
// blobTx is a wrapper around types.BlobTx which also contains the literal blob
|
||||
// data along with all the transaction metadata.
|
||||
type blobTx struct {
|
||||
Tx *types.Transaction
|
||||
|
||||
Blobs []kzg4844.Blob
|
||||
Commits []kzg4844.Commitment
|
||||
Proofs []kzg4844.Proof
|
||||
}
|
||||
|
||||
// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and
|
||||
// schedule the blob transactions into the following blocks. Only ever add the
|
||||
// bare minimum needed fields to keep the size down (and thus number of entries
|
||||
@@ -455,22 +445,27 @@ func (p *BlobPool) Close() error {
|
||||
// parseTransaction is a callback method on pool creation that gets called for
|
||||
// each transaction on disk to create the in-memory metadata index.
|
||||
func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
|
||||
item := new(blobTx)
|
||||
if err := rlp.DecodeBytes(blob, item); err != nil {
|
||||
tx := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(blob, tx); err != nil {
|
||||
// This path is impossible unless the disk data representation changes
|
||||
// across restarts. For that ever unprobable case, recover gracefully
|
||||
// by ignoring this data entry.
|
||||
log.Error("Failed to decode blob pool entry", "id", id, "err", err)
|
||||
return err
|
||||
}
|
||||
meta := newBlobTxMeta(id, size, item.Tx)
|
||||
if tx.BlobTxSidecar() == nil {
|
||||
log.Error("Missing sidecar in blob pool entry", "id", id, "hash", tx.Hash())
|
||||
return errors.New("missing blob sidecar")
|
||||
}
|
||||
|
||||
sender, err := p.signer.Sender(item.Tx)
|
||||
meta := newBlobTxMeta(id, size, tx)
|
||||
|
||||
sender, err := p.signer.Sender(tx)
|
||||
if err != nil {
|
||||
// This path is impossible unless the signature validity changes across
|
||||
// restarts. For that ever unprobable case, recover gracefully by ignoring
|
||||
// this data entry.
|
||||
log.Error("Failed to recover blob tx sender", "id", id, "hash", item.Tx.Hash(), "err", err)
|
||||
log.Error("Failed to recover blob tx sender", "id", id, "hash", tx.Hash(), "err", err)
|
||||
return err
|
||||
}
|
||||
if _, ok := p.index[sender]; !ok {
|
||||
@@ -718,17 +713,17 @@ 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)
|
||||
return
|
||||
}
|
||||
item := new(blobTx)
|
||||
if err = rlp.DecodeBytes(data, item); err != nil {
|
||||
var tx types.Transaction
|
||||
if err = rlp.DecodeBytes(data, tx); err != nil {
|
||||
log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
|
||||
return
|
||||
}
|
||||
block, ok := inclusions[item.Tx.Hash()]
|
||||
block, ok := inclusions[tx.Hash()]
|
||||
if !ok {
|
||||
log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id)
|
||||
return
|
||||
}
|
||||
if err := p.limbo.push(item.Tx.Hash(), block, item.Blobs, item.Commits, item.Proofs); err != nil {
|
||||
if err := p.limbo.push(&tx, block); err != nil {
|
||||
log.Warn("Failed to offload blob tx into limbo", "err", err)
|
||||
return
|
||||
}
|
||||
@@ -760,7 +755,7 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
|
||||
for addr, txs := range reinject {
|
||||
// Blindly push all the lost transactions back into the pool
|
||||
for _, tx := range txs {
|
||||
p.reinject(addr, tx)
|
||||
p.reinject(addr, tx.Hash())
|
||||
}
|
||||
// Recheck the account's pooled transactions to drop included and
|
||||
// invalidated one
|
||||
@@ -920,16 +915,19 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
|
||||
// Note, the method will not initialize the eviction cache values as those will
|
||||
// be done once for all transactions belonging to an account after all individual
|
||||
// transactions are injected back into the pool.
|
||||
func (p *BlobPool) reinject(addr common.Address, tx *types.Transaction) {
|
||||
func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) {
|
||||
// Retrieve the associated blob from the limbo. Without the blobs, we cannot
|
||||
// add the transaction back into the pool as it is not mineable.
|
||||
blobs, commits, proofs, err := p.limbo.pull(tx.Hash())
|
||||
tx, err := p.limbo.pull(txhash)
|
||||
if err != nil {
|
||||
log.Error("Blobs unavailable, dropping reorged tx", "err", err)
|
||||
return
|
||||
}
|
||||
// Serialize the transaction back into the primary datastore
|
||||
blob, err := rlp.EncodeToBytes(&blobTx{Tx: tx, Blobs: blobs, Commits: commits, Proofs: proofs})
|
||||
// TODO: seems like an easy optimization here would be getting the serialized tx
|
||||
// from limbo instead of re-serializing it here.
|
||||
|
||||
// Serialize the transaction back into the primary datastore.
|
||||
blob, err := rlp.EncodeToBytes(tx)
|
||||
if err != nil {
|
||||
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
|
||||
return
|
||||
@@ -939,9 +937,9 @@ func (p *BlobPool) reinject(addr common.Address, tx *types.Transaction) {
|
||||
log.Error("Failed to write transaction into storage", "hash", tx.Hash(), "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Update the indixes and metrics
|
||||
meta := newBlobTxMeta(id, p.store.Size(id), tx)
|
||||
|
||||
if _, ok := p.index[addr]; !ok {
|
||||
if err := p.reserve(addr, true); err != nil {
|
||||
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) {
|
||||
|
||||
// validateTx checks whether a transaction is valid according to the consensus
|
||||
// rules and adheres to some heuristic limits of the local node (price and size).
|
||||
func (p *BlobPool) validateTx(tx *types.Transaction, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error {
|
||||
func (p *BlobPool) validateTx(tx *types.Transaction) error {
|
||||
// Ensure the transaction adheres to basic pool filters (type, size, tip) and
|
||||
// consensus rules
|
||||
baseOpts := &txpool.ValidationOptions{
|
||||
@@ -1032,7 +1030,7 @@ func (p *BlobPool) validateTx(tx *types.Transaction, blobs []kzg4844.Blob, commi
|
||||
MaxSize: txMaxSize,
|
||||
MinTip: p.gasTip.ToBig(),
|
||||
}
|
||||
if err := txpool.ValidateTransaction(tx, blobs, commits, proofs, p.head, p.signer, baseOpts); err != nil {
|
||||
if err := txpool.ValidateTransaction(tx, p.head, p.signer, baseOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
// Ensure the transaction adheres to the stateful pool filters (nonce, balance)
|
||||
@@ -1117,7 +1115,7 @@ func (p *BlobPool) Has(hash common.Hash) bool {
|
||||
}
|
||||
|
||||
// Get returns a transaction if it is contained in the pool, or nil otherwise.
|
||||
func (p *BlobPool) Get(hash common.Hash) *txpool.Transaction {
|
||||
func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
|
||||
// Track the amount of time waiting to retrieve a fully resolved blob tx from
|
||||
// the pool and the amount of time actually spent on pulling the data from disk.
|
||||
getStart := time.Now()
|
||||
@@ -1139,32 +1137,27 @@ func (p *BlobPool) Get(hash common.Hash) *txpool.Transaction {
|
||||
log.Error("Tracked blob transaction missing from store", "hash", hash, "id", id, "err", err)
|
||||
return nil
|
||||
}
|
||||
item := new(blobTx)
|
||||
item := new(types.Transaction)
|
||||
if err = rlp.DecodeBytes(data, item); err != nil {
|
||||
log.Error("Blobs corrupted for traced transaction", "hash", hash, "id", id, "err", err)
|
||||
return nil
|
||||
}
|
||||
return &txpool.Transaction{
|
||||
Tx: item.Tx,
|
||||
BlobTxBlobs: item.Blobs,
|
||||
BlobTxCommits: item.Commits,
|
||||
BlobTxProofs: item.Proofs,
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
// Add inserts a set of blob transactions into the pool if they pass validation (both
|
||||
// consensus validity and pool restictions).
|
||||
func (p *BlobPool) Add(txs []*txpool.Transaction, local bool, sync bool) []error {
|
||||
func (p *BlobPool) Add(txs []*types.Transaction, local bool, sync bool) []error {
|
||||
errs := make([]error, len(txs))
|
||||
for i, tx := range txs {
|
||||
errs[i] = p.add(tx.Tx, tx.BlobTxBlobs, tx.BlobTxCommits, tx.BlobTxProofs)
|
||||
errs[i] = p.add(tx)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// Add inserts a new blob transaction into the pool if it passes validation (both
|
||||
// consensus validity and pool restictions).
|
||||
func (p *BlobPool) add(tx *types.Transaction, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) (err error) {
|
||||
func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
||||
// The blob pool blocks on adding a transaction. This is because blob txs are
|
||||
// only even pulled form the network, so this method will act as the overload
|
||||
// protection for fetches.
|
||||
@@ -1178,7 +1171,7 @@ func (p *BlobPool) add(tx *types.Transaction, blobs []kzg4844.Blob, commits []kz
|
||||
}(time.Now())
|
||||
|
||||
// Ensure the transaction is valid from all perspectives
|
||||
if err := p.validateTx(tx, blobs, commits, proofs); err != nil {
|
||||
if err := p.validateTx(tx); err != nil {
|
||||
log.Trace("Transaction validation failed", "hash", tx.Hash(), "err", err)
|
||||
return err
|
||||
}
|
||||
@@ -1203,7 +1196,7 @@ func (p *BlobPool) add(tx *types.Transaction, blobs []kzg4844.Blob, commits []kz
|
||||
}
|
||||
// Transaction permitted into the pool from a nonce and cost perspective,
|
||||
// insert it into the database and update the indices
|
||||
blob, err := rlp.EncodeToBytes(&blobTx{Tx: tx, Blobs: blobs, Commits: commits, Proofs: proofs})
|
||||
blob, err := rlp.EncodeToBytes(tx)
|
||||
if err != nil {
|
||||
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
|
||||
return err
|
||||
|
||||
@@ -193,8 +193,8 @@ func makeAddressReserver() txpool.AddressReserver {
|
||||
// with a valid key, only setting the interesting fields from the perspective of
|
||||
// the blob pool.
|
||||
func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction {
|
||||
tx, _ := types.SignNewTx(key, types.LatestSigner(testChainConfig), makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap))
|
||||
return tx
|
||||
blobtx := makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap)
|
||||
return types.MustSignNewTx(key, types.LatestSigner(testChainConfig), blobtx)
|
||||
}
|
||||
|
||||
// makeUnsignedTx is a utility method to construct a random blob tranasaction
|
||||
@@ -209,6 +209,11 @@ func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap
|
||||
BlobFeeCap: uint256.NewInt(blobFeeCap),
|
||||
BlobHashes: []common.Hash{emptyBlobVHash},
|
||||
Value: uint256.NewInt(100),
|
||||
Sidecar: &types.BlobTxSidecar{
|
||||
Blobs: []kzg4844.Blob{emptyBlob},
|
||||
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
||||
Proofs: []kzg4844.Proof{emptyBlobProof},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +346,7 @@ func TestOpenDrops(t *testing.T) {
|
||||
R: new(uint256.Int),
|
||||
S: new(uint256.Int),
|
||||
})
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
badsig, _ := store.Put(blob)
|
||||
|
||||
// Insert a sequence of transactions with a nonce gap in between to verify
|
||||
@@ -354,7 +359,7 @@ func TestOpenDrops(t *testing.T) {
|
||||
)
|
||||
for _, nonce := range []uint64{0, 1, 3, 4, 6, 7} { // first gap at #2, another at #5
|
||||
tx := makeTx(nonce, 1, 1, 1, gapper)
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce < 2 {
|
||||
@@ -371,7 +376,7 @@ func TestOpenDrops(t *testing.T) {
|
||||
)
|
||||
for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling
|
||||
tx := makeTx(nonce, 1, 1, 1, dangler)
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
dangling[id] = struct{}{}
|
||||
@@ -384,7 +389,7 @@ func TestOpenDrops(t *testing.T) {
|
||||
)
|
||||
for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled
|
||||
tx := makeTx(nonce, 1, 1, 1, filler)
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
filled[id] = struct{}{}
|
||||
@@ -397,7 +402,7 @@ func TestOpenDrops(t *testing.T) {
|
||||
)
|
||||
for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled
|
||||
tx := makeTx(nonce, 1, 1, 1, overlapper)
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce >= 2 {
|
||||
@@ -419,7 +424,7 @@ func TestOpenDrops(t *testing.T) {
|
||||
} else {
|
||||
tx = makeTx(uint64(i), 1, 1, 1, underpayer)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
underpaid[id] = struct{}{}
|
||||
@@ -438,7 +443,7 @@ func TestOpenDrops(t *testing.T) {
|
||||
} else {
|
||||
tx = makeTx(uint64(i), 1, 1, 1, outpricer)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if i < 2 {
|
||||
@@ -460,7 +465,7 @@ func TestOpenDrops(t *testing.T) {
|
||||
} else {
|
||||
tx = makeTx(nonce, 1, 1, 1, exceeder)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
exceeded[id] = struct{}{}
|
||||
@@ -478,7 +483,7 @@ func TestOpenDrops(t *testing.T) {
|
||||
} else {
|
||||
tx = makeTx(nonce, 1, 1, 1, overdrafter)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce < 1 {
|
||||
@@ -494,7 +499,7 @@ func TestOpenDrops(t *testing.T) {
|
||||
overcapped = make(map[uint64]struct{})
|
||||
)
|
||||
for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ {
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: makeTx(nonce, 1, 1, 1, overcapper)})
|
||||
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, overcapper))
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce < maxTxsPerAccount {
|
||||
@@ -625,7 +630,7 @@ func TestOpenIndex(t *testing.T) {
|
||||
)
|
||||
for _, i := range []int{5, 3, 4, 2, 0, 1} { // Randomize the tx insertion order to force sorting on load
|
||||
tx := makeTx(uint64(i), txExecTipCaps[i], txExecFeeCaps[i], txBlobFeeCaps[i], key)
|
||||
blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
store.Put(blob)
|
||||
}
|
||||
store.Close()
|
||||
@@ -718,9 +723,9 @@ func TestOpenHeap(t *testing.T) {
|
||||
tx2 = makeTx(0, 1, 800, 70, key2)
|
||||
tx3 = makeTx(0, 1, 1500, 110, key3)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(&blobTx{Tx: tx1})
|
||||
blob2, _ = rlp.EncodeToBytes(&blobTx{Tx: tx2})
|
||||
blob3, _ = rlp.EncodeToBytes(&blobTx{Tx: tx3})
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob3, _ = rlp.EncodeToBytes(tx3)
|
||||
|
||||
heapOrder = []common.Address{addr2, addr1, addr3}
|
||||
heapIndex = map[common.Address]int{addr2: 0, addr1: 1, addr3: 2}
|
||||
@@ -794,9 +799,9 @@ func TestOpenCap(t *testing.T) {
|
||||
tx2 = makeTx(0, 1, 800, 70, key2)
|
||||
tx3 = makeTx(0, 1, 1500, 110, key3)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(&blobTx{Tx: tx1, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}})
|
||||
blob2, _ = rlp.EncodeToBytes(&blobTx{Tx: tx2, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}})
|
||||
blob3, _ = rlp.EncodeToBytes(&blobTx{Tx: tx3, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}})
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob3, _ = rlp.EncodeToBytes(tx3)
|
||||
|
||||
keep = []common.Address{addr1, addr3}
|
||||
drop = []common.Address{addr2}
|
||||
@@ -1210,10 +1215,8 @@ func TestAdd(t *testing.T) {
|
||||
|
||||
// Sign the seed transactions and store them in the data store
|
||||
for _, tx := range seed.txs {
|
||||
var (
|
||||
signed, _ = types.SignNewTx(keys[acc], types.LatestSigner(testChainConfig), tx)
|
||||
blob, _ = rlp.EncodeToBytes(&blobTx{Tx: signed, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}})
|
||||
)
|
||||
signed := types.MustSignNewTx(keys[acc], types.LatestSigner(testChainConfig), tx)
|
||||
blob, _ := rlp.EncodeToBytes(signed)
|
||||
store.Put(blob)
|
||||
}
|
||||
}
|
||||
@@ -1236,7 +1239,7 @@ func TestAdd(t *testing.T) {
|
||||
// Add each transaction one by one, verifying the pool internals in between
|
||||
for j, add := range tt.adds {
|
||||
signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(testChainConfig), add.tx)
|
||||
if err := pool.add(signed, []kzg4844.Blob{emptyBlob}, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}); !errors.Is(err, add.err) {
|
||||
if err := pool.add(signed); !errors.Is(err, add.err) {
|
||||
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
|
||||
}
|
||||
verifyPoolInternals(t, pool)
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/holiman/billy"
|
||||
@@ -31,12 +30,9 @@ import (
|
||||
// to which it belongs as well as the block number in which it was included for
|
||||
// finality eviction.
|
||||
type limboBlob struct {
|
||||
Owner common.Hash // Owner transaction's hash to support resurrecting reorged txs
|
||||
Block uint64 // Block in which the blob transaction was included
|
||||
|
||||
Blobs []kzg4844.Blob // The opaque blobs originally part of the transaction
|
||||
Commits []kzg4844.Commitment // The commitments for the original blobs
|
||||
Proofs []kzg4844.Proof // The proofs verifying the commitments
|
||||
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
|
||||
}
|
||||
|
||||
// limbo is a light, indexed database to temporarily store recently included
|
||||
@@ -98,19 +94,19 @@ func (l *limbo) parseBlob(id uint64, data []byte) error {
|
||||
log.Error("Failed to decode blob limbo entry", "id", id, "err", err)
|
||||
return err
|
||||
}
|
||||
if _, ok := l.index[item.Owner]; ok {
|
||||
if _, ok := l.index[item.TxHash]; ok {
|
||||
// This path is impossible, unless due to a programming error a blob gets
|
||||
// inserted into the limbo which was already part of if. Recover gracefully
|
||||
// by ignoring this data entry.
|
||||
log.Error("Dropping duplicate blob limbo entry", "owner", item.Owner, "id", id)
|
||||
log.Error("Dropping duplicate blob limbo entry", "owner", item.TxHash, "id", id)
|
||||
return errors.New("duplicate blob")
|
||||
}
|
||||
l.index[item.Owner] = id
|
||||
l.index[item.TxHash] = id
|
||||
|
||||
if _, ok := l.groups[item.Block]; !ok {
|
||||
l.groups[item.Block] = make(map[uint64]common.Hash)
|
||||
}
|
||||
l.groups[item.Block][id] = item.Owner
|
||||
l.groups[item.Block][id] = item.TxHash
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -139,14 +135,14 @@ func (l *limbo) finalize(final *types.Header) {
|
||||
|
||||
// push stores a new blob transaction into the limbo, waiting until finality for
|
||||
// it to be automatically evicted.
|
||||
func (l *limbo) push(tx common.Hash, block uint64, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error {
|
||||
func (l *limbo) push(tx *types.Transaction, block uint64) error {
|
||||
// If the blobs are already tracked by the limbo, consider it a programming
|
||||
// error. There's not much to do against it, but be loud.
|
||||
if _, ok := l.index[tx]; ok {
|
||||
if _, ok := l.index[tx.Hash()]; ok {
|
||||
log.Error("Limbo cannot push already tracked blobs", "tx", tx)
|
||||
return errors.New("already tracked blob transaction")
|
||||
}
|
||||
if err := l.setAndIndex(tx, block, blobs, commits, proofs); err != nil {
|
||||
if err := l.setAndIndex(tx, block); err != nil {
|
||||
log.Error("Failed to set and index liboed blobs", "tx", tx, "err", err)
|
||||
return err
|
||||
}
|
||||
@@ -156,21 +152,21 @@ func (l *limbo) push(tx common.Hash, block uint64, blobs []kzg4844.Blob, commits
|
||||
// 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
|
||||
// transaction gets reorged out.
|
||||
func (l *limbo) pull(tx common.Hash) ([]kzg4844.Blob, []kzg4844.Commitment, []kzg4844.Proof, error) {
|
||||
func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) {
|
||||
// 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
|
||||
// into the network first.
|
||||
id, ok := l.index[tx]
|
||||
if !ok {
|
||||
log.Trace("Limbo cannot pull non-tracked blobs", "tx", tx)
|
||||
return nil, nil, nil, errors.New("unseen blob transaction")
|
||||
return nil, errors.New("unseen blob transaction")
|
||||
}
|
||||
item, err := l.getAndDrop(id)
|
||||
if err != nil {
|
||||
log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
|
||||
return nil, nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
return item.Blobs, item.Commits, item.Proofs, nil
|
||||
return item.Tx, nil
|
||||
}
|
||||
|
||||
// update changes the block number under which a blob transaction is tracked. This
|
||||
@@ -180,33 +176,33 @@ func (l *limbo) pull(tx common.Hash) ([]kzg4844.Blob, []kzg4844.Commitment, []kz
|
||||
// any of it since there's no clear error case. Some errors may be due to coding
|
||||
// issues, others caused by signers mining MEV stuff or swapping transactions. In
|
||||
// all cases, the pool needs to continue operating.
|
||||
func (l *limbo) update(tx common.Hash, block uint64) {
|
||||
func (l *limbo) update(txhash common.Hash, block uint64) {
|
||||
// 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
|
||||
// into the network first.
|
||||
id, ok := l.index[tx]
|
||||
id, ok := l.index[txhash]
|
||||
if !ok {
|
||||
log.Trace("Limbo cannot update non-tracked blobs", "tx", tx)
|
||||
log.Trace("Limbo cannot update non-tracked blobs", "tx", txhash)
|
||||
return
|
||||
}
|
||||
// If there was no change in the blob's inclusion block, don't mess around
|
||||
// with heavy database operations.
|
||||
if _, ok := l.groups[block][id]; ok {
|
||||
log.Trace("Blob transaction unchanged in limbo", "tx", tx, "block", block)
|
||||
log.Trace("Blob transaction unchanged in limbo", "tx", txhash, "block", block)
|
||||
return
|
||||
}
|
||||
// Retrieve the old blobs from the data store and write tehm back with a new
|
||||
// block number. IF anything fails, there's not much to do, go on.
|
||||
item, err := l.getAndDrop(id)
|
||||
if err != nil {
|
||||
log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
|
||||
log.Error("Failed to get and drop limboed blobs", "tx", txhash, "id", id, "err", err)
|
||||
return
|
||||
}
|
||||
if err := l.setAndIndex(tx, block, item.Blobs, item.Commits, item.Proofs); err != nil {
|
||||
log.Error("Failed to set and index limboed blobs", "tx", tx, "err", err)
|
||||
if err := l.setAndIndex(item.Tx, block); err != nil {
|
||||
log.Error("Failed to set and index limboed blobs", "tx", txhash, "err", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Blob transaction updated in limbo", "tx", tx, "old-block", item.Block, "new-block", block)
|
||||
log.Trace("Blob transaction updated in limbo", "tx", txhash, "old-block", item.Block, "new-block", block)
|
||||
}
|
||||
|
||||
// getAndDrop retrieves a blob item from the limbo store and deletes it both from
|
||||
@@ -220,7 +216,7 @@ func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) {
|
||||
if err = rlp.DecodeBytes(data, item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(l.index, item.Owner)
|
||||
delete(l.index, item.TxHash)
|
||||
delete(l.groups[item.Block], id)
|
||||
if len(l.groups[item.Block]) == 0 {
|
||||
delete(l.groups, item.Block)
|
||||
@@ -233,13 +229,12 @@ func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) {
|
||||
|
||||
// setAndIndex assembles a limbo blob database entry and stores it, also updating
|
||||
// the in-memory indices.
|
||||
func (l *limbo) setAndIndex(tx common.Hash, block uint64, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error {
|
||||
func (l *limbo) setAndIndex(tx *types.Transaction, block uint64) error {
|
||||
txhash := tx.Hash()
|
||||
item := &limboBlob{
|
||||
Owner: tx,
|
||||
Block: block,
|
||||
Blobs: blobs,
|
||||
Commits: commits,
|
||||
Proofs: proofs,
|
||||
TxHash: txhash,
|
||||
Block: block,
|
||||
Tx: tx,
|
||||
}
|
||||
data, err := rlp.EncodeToBytes(item)
|
||||
if err != nil {
|
||||
@@ -249,10 +244,10 @@ func (l *limbo) setAndIndex(tx common.Hash, block uint64, blobs []kzg4844.Blob,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.index[tx] = id
|
||||
l.index[txhash] = id
|
||||
if _, ok := l.groups[block]; !ok {
|
||||
l.groups[block] = make(map[uint64]common.Hash)
|
||||
}
|
||||
l.groups[block][id] = tx
|
||||
l.groups[block][id] = txhash
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user