upgrade blobpool

This commit is contained in:
maskpp 2025-07-03 15:58:16 +08:00
parent 663fa7b496
commit 22db68b81b
5 changed files with 263 additions and 292 deletions

View file

@ -89,20 +89,20 @@ const (
// bare minimum needed fields to keep the size down (and thus number of entries
// larger with the same memory consumption).
type blobTxMeta struct {
hash common.Hash // Transaction hash to maintain the lookup table
vhashes []common.Hash // Blob versioned hashes to maintain the lookup table
TxHash common.Hash // Transaction TxHash to maintain the lookup table
VHashes []common.Hash // Blob versioned hashes to maintain the lookup table
id uint64 // Storage ID in the pool's persistent store
storageSize uint32 // Byte size in the pool's persistent store
size uint64 // RLP-encoded size of transaction including the attached blob
Id uint64 // Storage ID in the pool's persistent store
StorageSize uint32 // Byte size in the pool's persistent store
Size uint64 // RLP-encoded Size of transaction including the attached blob
nonce uint64 // Needed to prioritize inclusion order within an account
costCap *uint256.Int // Needed to validate cumulative balance sufficiency
execTipCap *uint256.Int // Needed to prioritize inclusion order across accounts and validate replacement price bump
execFeeCap *uint256.Int // Needed to validate replacement price bump
blobFeeCap *uint256.Int // Needed to validate replacement price bump
execGas uint64 // Needed to check inclusion validity before reading the blob
blobGas uint64 // Needed to check inclusion validity before reading the blob
Nonce uint64 // Needed to prioritize inclusion order within an account
CostCap *uint256.Int // Needed to validate cumulative balance sufficiency
ExecTipCap *uint256.Int // Needed to prioritize inclusion order across accounts and validate replacement price bump
ExecFeeCap *uint256.Int // Needed to validate replacement price bump
BlobFeeCap *uint256.Int // Needed to validate replacement price bump
ExecGas uint64 // Needed to check inclusion validity before reading the blob
BlobGas uint64 // Needed to check inclusion validity before reading the blob
basefeeJumps float64 // Absolute number of 1559 fee adjustments needed to reach the tx's fee cap
blobfeeJumps float64 // Absolute number of 4844 fee adjustments needed to reach the tx's blob fee cap
@ -116,21 +116,21 @@ type blobTxMeta struct {
// and assembles a helper struct to track in memory.
func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transaction) *blobTxMeta {
meta := &blobTxMeta{
hash: tx.Hash(),
vhashes: tx.BlobHashes(),
id: id,
storageSize: storageSize,
size: size,
nonce: tx.Nonce(),
costCap: uint256.MustFromBig(tx.Cost()),
execTipCap: uint256.MustFromBig(tx.GasTipCap()),
execFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()),
execGas: tx.Gas(),
blobGas: tx.BlobGas(),
TxHash: tx.Hash(),
VHashes: tx.BlobHashes(),
Id: id,
StorageSize: storageSize,
Size: size,
Nonce: tx.Nonce(),
CostCap: uint256.MustFromBig(tx.Cost()),
ExecTipCap: uint256.MustFromBig(tx.GasTipCap()),
ExecFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
BlobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()),
ExecGas: tx.Gas(),
BlobGas: tx.BlobGas(),
}
meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap)
meta.blobfeeJumps = dynamicFeeJumps(meta.blobFeeCap)
meta.basefeeJumps = dynamicFeeJumps(meta.ExecFeeCap)
meta.blobfeeJumps = dynamicFeeJumps(meta.BlobFeeCap)
return meta
}
@ -490,7 +490,7 @@ func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
}
meta := newBlobTxMeta(id, tx.Size(), size, tx)
if p.lookup.exists(meta.hash) {
if p.lookup.exists(meta.TxHash) {
// This path is only possible after a crash, where deleted items are not
// removed via the normal shutdown-startup procedure and thus may get
// partially resurrected.
@ -513,10 +513,10 @@ func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
p.spent[sender] = new(uint256.Int)
}
p.index[sender] = append(p.index[sender], meta)
p.spent[sender] = new(uint256.Int).Add(p.spent[sender], meta.costCap)
p.spent[sender] = new(uint256.Int).Add(p.spent[sender], meta.CostCap)
p.lookup.track(meta)
p.stored += uint64(meta.storageSize)
p.stored += uint64(meta.StorageSize)
return nil
}
@ -529,15 +529,15 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
return
}
sort.Slice(txs, func(i, j int) bool {
return txs[i].nonce < txs[j].nonce
return txs[i].Nonce < txs[j].Nonce
})
// If there is a gap between the chain state and the blob pool, drop
// all the transactions as they are non-executable. Similarly, if the
// entire tx range was included, drop all.
var (
next = p.state.GetNonce(addr)
gapped = txs[0].nonce > next
filled = txs[len(txs)-1].nonce < next
gapped = txs[0].Nonce > next
filled = txs[len(txs)-1].Nonce < next
)
if gapped || filled {
var (
@ -545,15 +545,15 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
nonces []uint64
)
for i := 0; i < len(txs); i++ {
ids = append(ids, txs[i].id)
nonces = append(nonces, txs[i].nonce)
ids = append(ids, txs[i].Id)
nonces = append(nonces, txs[i].Nonce)
p.stored -= uint64(txs[i].storageSize)
p.stored -= uint64(txs[i].StorageSize)
p.lookup.untrack(txs[i])
// Included transactions blobs need to be moved to the limbo
// Included transactions blobs need to be recorded in the limbo
if filled && inclusions != nil {
p.offload(addr, txs[i].nonce, txs[i].id, inclusions)
p.offload(addr, txs[i], inclusions)
}
}
delete(p.index, addr)
@ -570,57 +570,64 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
log.Trace("Dropping filled blob transactions", "from", addr, "filled", nonces, "ids", ids)
dropFilledMeter.Mark(int64(len(ids)))
}
for _, id := range ids {
if err := p.store.Delete(id); err != nil {
log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err)
// If the txs were recorded in the limbo, we don't delete them.
if !(filled && inclusions != nil) {
for _, id := range ids {
if err := p.store.Delete(id); err != nil {
log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err)
}
}
}
return
}
// If there is overlap between the chain state and the blob pool, drop
// anything below the current state
if txs[0].nonce < next {
if txs[0].Nonce < next {
var (
ids []uint64
nonces []uint64
)
for len(txs) > 0 && txs[0].nonce < next {
ids = append(ids, txs[0].id)
nonces = append(nonces, txs[0].nonce)
for len(txs) > 0 && txs[0].Nonce < next {
ids = append(ids, txs[0].Id)
nonces = append(nonces, txs[0].Nonce)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[0].costCap)
p.stored -= uint64(txs[0].storageSize)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[0].CostCap)
p.stored -= uint64(txs[0].StorageSize)
p.lookup.untrack(txs[0])
// Included transactions blobs need to be moved to the limbo
// Included transactions blobs need to be recorded in the limbo.
if inclusions != nil {
p.offload(addr, txs[0].nonce, txs[0].id, inclusions)
p.offload(addr, txs[0], inclusions)
}
txs = txs[1:]
}
log.Trace("Dropping overlapped blob transactions", "from", addr, "overlapped", nonces, "ids", ids, "left", len(txs))
dropOverlappedMeter.Mark(int64(len(ids)))
for _, id := range ids {
if err := p.store.Delete(id); err != nil {
log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err)
// If the txs were recorded in the limbo, we don't delete them.
if inclusions == nil {
for _, id := range ids {
if err := p.store.Delete(id); err != nil {
log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err)
}
}
}
p.index[addr] = txs
}
// Iterate over the transactions to initialize their eviction thresholds
// and to detect any nonce gaps
txs[0].evictionExecTip = txs[0].execTipCap
txs[0].evictionExecTip = txs[0].ExecTipCap
txs[0].evictionExecFeeJumps = txs[0].basefeeJumps
txs[0].evictionBlobFeeJumps = txs[0].blobfeeJumps
for i := 1; i < len(txs); i++ {
// If there's no nonce gap, initialize the eviction thresholds as the
// minimum between the cumulative thresholds and the current tx fees
if txs[i].nonce == txs[i-1].nonce+1 {
if txs[i].Nonce == txs[i-1].Nonce+1 {
txs[i].evictionExecTip = txs[i-1].evictionExecTip
if txs[i].evictionExecTip.Cmp(txs[i].execTipCap) > 0 {
txs[i].evictionExecTip = txs[i].execTipCap
if txs[i].evictionExecTip.Cmp(txs[i].ExecTipCap) > 0 {
txs[i].evictionExecTip = txs[i].ExecTipCap
}
txs[i].evictionExecFeeJumps = txs[i-1].evictionExecFeeJumps
if txs[i].evictionExecFeeJumps > txs[i].basefeeJumps {
@ -638,14 +645,14 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
// Also, Billy behind the blobpool does not journal deletes. A process
// crash would result in previously deleted entities being resurrected.
// That could potentially cause a duplicate nonce to appear.
if txs[i].nonce == txs[i-1].nonce {
id, _ := p.lookup.storeidOfTx(txs[i].hash)
if txs[i].Nonce == txs[i-1].Nonce {
id, _ := p.lookup.storeidOfTx(txs[i].TxHash)
log.Error("Dropping repeat nonce blob transaction", "from", addr, "nonce", txs[i].nonce, "id", id)
log.Error("Dropping repeat Nonce blob transaction", "from", addr, "nonce", txs[i].Nonce, "id", id)
dropRepeatedMeter.Mark(1)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap)
p.stored -= uint64(txs[i].storageSize)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].CostCap)
p.stored -= uint64(txs[i].StorageSize)
p.lookup.untrack(txs[i])
if err := p.store.Delete(id); err != nil {
@ -663,16 +670,16 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
nonces []uint64
)
for j := i; j < len(txs); j++ {
ids = append(ids, txs[j].id)
nonces = append(nonces, txs[j].nonce)
ids = append(ids, txs[j].Id)
nonces = append(nonces, txs[j].Nonce)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[j].costCap)
p.stored -= uint64(txs[j].storageSize)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[j].CostCap)
p.stored -= uint64(txs[j].StorageSize)
p.lookup.untrack(txs[j])
}
txs = txs[:i]
log.Error("Dropping gapped blob transactions", "from", addr, "missing", txs[i-1].nonce+1, "drop", nonces, "ids", ids)
log.Error("Dropping gapped blob transactions", "from", addr, "missing", txs[i-1].Nonce+1, "drop", nonces, "ids", ids)
dropGappedMeter.Mark(int64(len(ids)))
for _, id := range ids {
@ -701,11 +708,11 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
txs[len(txs)-1] = nil
txs = txs[:len(txs)-1]
ids = append(ids, last.id)
nonces = append(nonces, last.nonce)
ids = append(ids, last.Id)
nonces = append(nonces, last.Nonce)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
p.stored -= uint64(last.storageSize)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.CostCap)
p.stored -= uint64(last.StorageSize)
p.lookup.untrack(last)
}
if len(txs) == 0 {
@ -741,11 +748,11 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
txs[len(txs)-1] = nil
txs = txs[:len(txs)-1]
ids = append(ids, last.id)
nonces = append(nonces, last.nonce)
ids = append(ids, last.Id)
nonces = append(nonces, last.Nonce)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
p.stored -= uint64(last.storageSize)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.CostCap)
p.stored -= uint64(last.StorageSize)
p.lookup.untrack(last)
}
p.index[addr] = txs
@ -773,23 +780,13 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
// 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 (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusions map[common.Hash]uint64) {
data, err := p.store.Get(id)
if err != nil {
log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
return
}
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[tx.Hash()]
func (p *BlobPool) offload(addr common.Address, blobTxMeta *blobTxMeta, inclusions map[common.Hash]uint64) {
block, ok := inclusions[blobTxMeta.TxHash]
if !ok {
log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id)
log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", blobTxMeta.Nonce, "id", blobTxMeta.Id)
return
}
if err := p.limbo.push(&tx, block); err != nil {
if err := p.limbo.push(blobTxMeta, block); err != nil {
log.Warn("Failed to offload blob tx into limbo", "err", err)
return
}
@ -835,8 +832,13 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
}
}
// Flush out any blobs from limbo that are older than the latest finality
// and also delete the txs from the store.
if p.chain.Config().IsCancun(p.head.Number, p.head.Time) {
p.limbo.finalize(p.chain.CurrentFinalBlock())
p.limbo.finalize(p.chain.CurrentFinalBlock(), func(id uint64, txHash common.Hash) {
if err := p.store.Delete(id); err != nil {
log.Error("Failed to delete blob transaction", "hash", txHash, "id", id, "err", err)
}
})
}
// Reset the price heap for the new set of basefee/blobfee pairs
var (
@ -990,42 +992,25 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
// Retrieve the associated blob from the limbo. Without the blobs, we cannot
// add the transaction back into the pool as it is not mineable.
tx, err := p.limbo.pull(txhash)
meta, err := p.limbo.pull(txhash)
if err != nil {
log.Error("Blobs unavailable, dropping reorged tx", "err", err)
return err
}
// 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 err
}
id, err := p.store.Put(blob)
if err != nil {
log.Error("Failed to write transaction into storage", "hash", tx.Hash(), "err", err)
return err
}
// Update the indices and metrics
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
if _, ok := p.index[addr]; !ok {
if err := p.reserver.Hold(addr); err != nil {
log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err)
log.Warn("Failed to reserve account for blob pool", "tx", meta.TxHash, "from", addr, "err", err)
return err
}
p.index[addr] = []*blobTxMeta{meta}
p.spent[addr] = meta.costCap
p.spent[addr] = meta.CostCap
p.evict.Push(addr)
} else {
p.index[addr] = append(p.index[addr], meta)
p.spent[addr] = new(uint256.Int).Add(p.spent[addr], meta.costCap)
p.spent[addr] = new(uint256.Int).Add(p.spent[addr], meta.CostCap)
}
p.lookup.track(meta)
p.stored += uint64(meta.storageSize)
p.stored += uint64(meta.StorageSize)
return nil
}
@ -1043,24 +1028,24 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
if old == nil || p.gasTip.Cmp(old) > 0 {
for addr, txs := range p.index {
for i, tx := range txs {
if tx.execTipCap.Cmp(p.gasTip) < 0 {
if tx.ExecTipCap.Cmp(p.gasTip) < 0 {
// Drop the offending transaction
var (
ids = []uint64{tx.id}
nonces = []uint64{tx.nonce}
ids = []uint64{tx.Id}
nonces = []uint64{tx.Nonce}
)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap)
p.stored -= uint64(tx.storageSize)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].CostCap)
p.stored -= uint64(tx.StorageSize)
p.lookup.untrack(tx)
txs[i] = nil
// Drop everything afterwards, no gaps allowed
for j, tx := range txs[i+1:] {
ids = append(ids, tx.id)
nonces = append(nonces, tx.nonce)
ids = append(ids, tx.Id)
nonces = append(nonces, tx.Nonce)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], tx.costCap)
p.stored -= uint64(tx.storageSize)
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], tx.CostCap)
p.stored -= uint64(tx.StorageSize)
p.lookup.untrack(tx)
txs[i+1+j] = nil
}
@ -1076,7 +1061,7 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
p.reserver.Release(addr)
}
// Clear out the transactions from the data store
log.Warn("Dropping underpriced blob transaction", "from", addr, "rejected", tx.nonce, "tip", tx.execTipCap, "want", tip, "drop", nonces, "ids", ids)
log.Warn("Dropping underpriced blob transaction", "from", addr, "rejected", tx.Nonce, "tip", tx.ExecTipCap, "want", tip, "drop", nonces, "ids", ids)
dropUnderpricedMeter.Mark(int64(len(ids)))
for _, id := range ids {
@ -1136,7 +1121,7 @@ func (p *BlobPool) checkDelegationLimit(tx *types.Transaction) error {
return nil
}
// If account already has a pending transaction, allow replacement only.
if len(pending) == 1 && pending[0].nonce == tx.Nonce() {
if len(pending) == 1 && pending[0].Nonce == tx.Nonce() {
return nil
}
return txpool.ErrInflightTxLimitReached
@ -1174,7 +1159,7 @@ func (p *BlobPool) validateTx(tx *types.Transaction) error {
ExistingCost: func(addr common.Address, nonce uint64) *big.Int {
next := p.state.GetNonce(addr)
if uint64(len(p.index[addr])) > nonce-next {
return p.index[addr][int(nonce-next)].costCap.ToBig()
return p.index[addr][int(nonce-next)].CostCap.ToBig()
}
return nil
},
@ -1194,33 +1179,33 @@ func (p *BlobPool) validateTx(tx *types.Transaction) error {
if uint64(len(p.index[from])) > tx.Nonce()-next {
prev := p.index[from][int(tx.Nonce()-next)]
// Ensure the transaction is different than the one tracked locally
if prev.hash == tx.Hash() {
if prev.TxHash == tx.Hash() {
return txpool.ErrAlreadyKnown
}
// Account can support the replacement, but the price bump must also be met
switch {
case tx.GasFeeCapIntCmp(prev.execFeeCap.ToBig()) <= 0:
return fmt.Errorf("%w: new tx gas fee cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.GasFeeCap(), prev.execFeeCap)
case tx.GasTipCapIntCmp(prev.execTipCap.ToBig()) <= 0:
return fmt.Errorf("%w: new tx gas tip cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.GasTipCap(), prev.execTipCap)
case tx.BlobGasFeeCapIntCmp(prev.blobFeeCap.ToBig()) <= 0:
return fmt.Errorf("%w: new tx blob gas fee cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.BlobGasFeeCap(), prev.blobFeeCap)
case tx.GasFeeCapIntCmp(prev.ExecFeeCap.ToBig()) <= 0:
return fmt.Errorf("%w: new tx gas fee cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.GasFeeCap(), prev.ExecFeeCap)
case tx.GasTipCapIntCmp(prev.ExecTipCap.ToBig()) <= 0:
return fmt.Errorf("%w: new tx gas tip cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.GasTipCap(), prev.ExecTipCap)
case tx.BlobGasFeeCapIntCmp(prev.BlobFeeCap.ToBig()) <= 0:
return fmt.Errorf("%w: new tx blob gas fee cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.BlobGasFeeCap(), prev.BlobFeeCap)
}
var (
multiplier = uint256.NewInt(100 + p.config.PriceBump)
onehundred = uint256.NewInt(100)
minGasFeeCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.execFeeCap), onehundred)
minGasTipCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.execTipCap), onehundred)
minBlobGasFeeCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.blobFeeCap), onehundred)
minGasFeeCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.ExecFeeCap), onehundred)
minGasTipCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.ExecTipCap), onehundred)
minBlobGasFeeCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.BlobFeeCap), onehundred)
)
switch {
case tx.GasFeeCapIntCmp(minGasFeeCap.ToBig()) < 0:
return fmt.Errorf("%w: new tx gas fee cap %v < %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.GasFeeCap(), prev.execFeeCap, p.config.PriceBump)
return fmt.Errorf("%w: new tx gas fee cap %v < %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.GasFeeCap(), prev.ExecFeeCap, p.config.PriceBump)
case tx.GasTipCapIntCmp(minGasTipCap.ToBig()) < 0:
return fmt.Errorf("%w: new tx gas tip cap %v < %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.GasTipCap(), prev.execTipCap, p.config.PriceBump)
return fmt.Errorf("%w: new tx gas tip cap %v < %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.GasTipCap(), prev.ExecTipCap, p.config.PriceBump)
case tx.BlobGasFeeCapIntCmp(minBlobGasFeeCap.ToBig()) < 0:
return fmt.Errorf("%w: new tx blob gas fee cap %v < %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.BlobGasFeeCap(), prev.blobFeeCap, p.config.PriceBump)
return fmt.Errorf("%w: new tx blob gas fee cap %v < %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.BlobGasFeeCap(), prev.BlobFeeCap, p.config.PriceBump)
}
}
return nil
@ -1387,7 +1372,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
// Ensure the transaction is valid from all perspectives
if err := p.validateTx(tx); err != nil {
log.Trace("Transaction validation failed", "hash", tx.Hash(), "err", err)
log.Trace("Transaction validation failed", "TxHash", tx.Hash(), "err", err)
switch {
case errors.Is(err, txpool.ErrUnderpriced):
addUnderpricedMeter.Mark(1)
@ -1456,18 +1441,18 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
dropReplacedMeter.Mark(1)
prev := p.index[from][offset]
if err := p.store.Delete(prev.id); err != nil {
if err := p.store.Delete(prev.Id); err != nil {
// Shitty situation, but try to recover gracefully instead of going boom
log.Error("Failed to delete replaced transaction", "id", prev.id, "err", err)
log.Error("Failed to delete replaced transaction", "id", prev.Id, "err", err)
}
// Update the transaction index
p.index[from][offset] = meta
p.spent[from] = new(uint256.Int).Sub(p.spent[from], prev.costCap)
p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap)
p.spent[from] = new(uint256.Int).Sub(p.spent[from], prev.CostCap)
p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.CostCap)
p.lookup.untrack(prev)
p.lookup.track(meta)
p.stored += uint64(meta.storageSize) - uint64(prev.storageSize)
p.stored += uint64(meta.StorageSize) - uint64(prev.StorageSize)
} else {
// Transaction extends previously scheduled ones
p.index[from] = append(p.index[from], meta)
@ -1475,9 +1460,9 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
p.spent[from] = new(uint256.Int)
newacc = true
}
p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap)
p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.CostCap)
p.lookup.track(meta)
p.stored += uint64(meta.storageSize)
p.stored += uint64(meta.StorageSize)
}
// Recompute the rolling eviction fields. In case of a replacement, this will
// recompute all subsequent fields. In case of an append, this will only do
@ -1487,7 +1472,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
for i := offset; i < len(txs); i++ {
// The first transaction will always use itself
if i == 0 {
txs[0].evictionExecTip = txs[0].execTipCap
txs[0].evictionExecTip = txs[0].ExecTipCap
txs[0].evictionExecFeeJumps = txs[0].basefeeJumps
txs[0].evictionBlobFeeJumps = txs[0].blobfeeJumps
@ -1495,8 +1480,8 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
}
// Subsequent transactions will use a rolling calculation
txs[i].evictionExecTip = txs[i-1].evictionExecTip
if txs[i].evictionExecTip.Cmp(txs[i].execTipCap) > 0 {
txs[i].evictionExecTip = txs[i].execTipCap
if txs[i].evictionExecTip.Cmp(txs[i].ExecTipCap) > 0 {
txs[i].evictionExecTip = txs[i].ExecTipCap
}
txs[i].evictionExecFeeJumps = txs[i-1].evictionExecFeeJumps
if txs[i].evictionExecFeeJumps > txs[i].basefeeJumps {
@ -1562,9 +1547,9 @@ func (p *BlobPool) drop() {
txs = txs[:len(txs)-1]
p.index[from] = txs
p.spent[from] = new(uint256.Int).Sub(p.spent[from], drop.costCap)
p.spent[from] = new(uint256.Int).Sub(p.spent[from], drop.CostCap)
}
p.stored -= uint64(drop.storageSize)
p.stored -= uint64(drop.StorageSize)
p.lookup.untrack(drop)
// Remove the transaction from the pool's eviction heap:
@ -1583,11 +1568,11 @@ func (p *BlobPool) drop() {
}
}
// Remove the transaction from the data store
log.Debug("Evicting overflown blob transaction", "from", from, "evicted", drop.nonce, "id", drop.id)
log.Debug("Evicting overflown blob transaction", "from", from, "evicted", drop.Nonce, "id", drop.Id)
dropOverflownMeter.Mark(1)
if err := p.store.Delete(drop.id); err != nil {
log.Error("Failed to drop evicted transaction", "id", drop.id, "err", err)
if err := p.store.Delete(drop.Id); err != nil {
log.Error("Failed to drop evicted transaction", "id", drop.Id, "err", err)
}
}
@ -1622,31 +1607,31 @@ func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*tx
for _, tx := range txs {
// If transaction filtering was requested, discard badly priced ones
if filter.MinTip != nil && filter.BaseFee != nil {
if tx.execFeeCap.Lt(filter.BaseFee) {
if tx.ExecFeeCap.Lt(filter.BaseFee) {
break // basefee too low, cannot be included, discard rest of txs from the account
}
tip := new(uint256.Int).Sub(tx.execFeeCap, filter.BaseFee)
if tip.Gt(tx.execTipCap) {
tip = tx.execTipCap
tip := new(uint256.Int).Sub(tx.ExecFeeCap, filter.BaseFee)
if tip.Gt(tx.ExecTipCap) {
tip = tx.ExecTipCap
}
if tip.Lt(filter.MinTip) {
break // allowed or remaining tip too low, cannot be included, discard rest of txs from the account
}
}
if filter.BlobFee != nil {
if tx.blobFeeCap.Lt(filter.BlobFee) {
if tx.BlobFeeCap.Lt(filter.BlobFee) {
break // blobfee too low, cannot be included, discard rest of txs from the account
}
}
// Transaction was accepted according to the filter, append to the pending list
lazies = append(lazies, &txpool.LazyTransaction{
Pool: p,
Hash: tx.hash,
Hash: tx.TxHash,
Time: execStart, // TODO(karalabe): Maybe save these and use that?
GasFeeCap: tx.execFeeCap,
GasTipCap: tx.execTipCap,
Gas: tx.execGas,
BlobGas: tx.blobGas,
GasFeeCap: tx.ExecFeeCap,
GasTipCap: tx.ExecTipCap,
Gas: tx.ExecGas,
BlobGas: tx.BlobGas,
})
}
if len(lazies) > 0 {
@ -1750,7 +1735,7 @@ func (p *BlobPool) Nonce(addr common.Address) uint64 {
defer p.lock.Unlock()
if txs, ok := p.index[addr]; ok {
return txs[len(txs)-1].nonce + 1
return txs[len(txs)-1].Nonce + 1
}
return p.state.GetNonce(addr)
}
@ -1769,7 +1754,7 @@ func (p *BlobPool) Stats() (int, int) {
}
// Content retrieves the data content of the transaction pool, returning all the
// pending as well as queued transactions, grouped by account and sorted by nonce.
// pending as well as queued transactions, grouped by account and sorted by Nonce.
//
// For the blob pool, this method will return nothing for now.
// TODO(karalabe): Abstract out the returned metadata.

View file

@ -283,10 +283,10 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
seen := make(map[common.Hash]struct{})
for addr, txs := range pool.index {
for _, tx := range txs {
if _, ok := seen[tx.hash]; ok {
t.Errorf("duplicate hash #%x in transaction index: address %s, nonce %d", tx.hash, addr, tx.nonce)
if _, ok := seen[tx.TxHash]; ok {
t.Errorf("duplicate hash #%x in transaction index: address %s, nonce %d", tx.TxHash, addr, tx.Nonce)
}
seen[tx.hash] = struct{}{}
seen[tx.TxHash] = struct{}{}
}
}
for hash, id := range pool.lookup.txIndex {
@ -302,11 +302,11 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
blobs := make(map[common.Hash]map[common.Hash]struct{})
for _, txs := range pool.index {
for _, tx := range txs {
for _, vhash := range tx.vhashes {
for _, vhash := range tx.VHashes {
if blobs[vhash] == nil {
blobs[vhash] = make(map[common.Hash]struct{})
}
blobs[vhash][tx.hash] = struct{}{}
blobs[vhash][tx.TxHash] = struct{}{}
}
}
}
@ -328,18 +328,18 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
// and that the first nonce is the next expected one based on the state.
for addr, txs := range pool.index {
for i := 1; i < len(txs); i++ {
if txs[i].nonce != txs[i-1].nonce+1 {
t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].nonce, txs[i-1].nonce+1)
if txs[i].Nonce != txs[i-1].Nonce+1 {
t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].Nonce, txs[i-1].Nonce+1)
}
}
if txs[0].nonce != pool.state.GetNonce(addr) {
t.Errorf("addr %v, first tx nonce mismatch: have %d, want %d", addr, txs[0].nonce, pool.state.GetNonce(addr))
if txs[0].Nonce != pool.state.GetNonce(addr) {
t.Errorf("addr %v, first tx nonce mismatch: have %d, want %d", addr, txs[0].Nonce, pool.state.GetNonce(addr))
}
}
// Verify that calculated evacuation thresholds are correct
for addr, txs := range pool.index {
if !txs[0].evictionExecTip.Eq(txs[0].execTipCap) {
t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, 0, txs[0].evictionExecTip, txs[0].execTipCap)
if !txs[0].evictionExecTip.Eq(txs[0].ExecTipCap) {
t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, 0, txs[0].evictionExecTip, txs[0].ExecTipCap)
}
if math.Abs(txs[0].evictionExecFeeJumps-txs[0].basefeeJumps) > 0.001 {
t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionExecFeeJumps, txs[0].basefeeJumps)
@ -349,8 +349,8 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
}
for i := 1; i < len(txs); i++ {
wantExecTip := txs[i-1].evictionExecTip
if wantExecTip.Gt(txs[i].execTipCap) {
wantExecTip = txs[i].execTipCap
if wantExecTip.Gt(txs[i].ExecTipCap) {
wantExecTip = txs[i].ExecTipCap
}
if !txs[i].evictionExecTip.Eq(wantExecTip) {
t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, i, txs[i].evictionExecTip, wantExecTip)
@ -377,7 +377,7 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
for addr, txs := range pool.index {
spent := new(uint256.Int)
for _, tx := range txs {
spent.Add(spent, tx.costCap)
spent.Add(spent, tx.CostCap)
}
if !pool.spent[addr].Eq(spent) {
t.Errorf("addr %v expenditure mismatch: have %d, want %d", addr, pool.spent[addr], spent)
@ -387,7 +387,7 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
var stored uint64
for _, txs := range pool.index {
for _, tx := range txs {
stored += uint64(tx.storageSize)
stored += uint64(tx.StorageSize)
}
}
if pool.stored != stored {
@ -407,7 +407,7 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
known := make(map[common.Hash]struct{})
for _, txs := range pool.index {
for _, tx := range txs {
for _, vhash := range tx.vhashes {
for _, vhash := range tx.VHashes {
known[vhash] = struct{}{}
}
}
@ -736,36 +736,36 @@ func TestOpenDrops(t *testing.T) {
alive := make(map[uint64]struct{})
for _, txs := range pool.index {
for _, tx := range txs {
switch tx.id {
switch tx.Id {
case malformed:
t.Errorf("malformed RLP transaction remained in storage")
case badsig:
t.Errorf("invalidly signed transaction remained in storage")
default:
if _, ok := dangling[tx.id]; ok {
t.Errorf("dangling transaction remained in storage: %d", tx.id)
} else if _, ok := filled[tx.id]; ok {
t.Errorf("filled transaction remained in storage: %d", tx.id)
} else if _, ok := overlapped[tx.id]; ok {
t.Errorf("overlapped transaction remained in storage: %d", tx.id)
} else if _, ok := gapped[tx.id]; ok {
t.Errorf("gapped transaction remained in storage: %d", tx.id)
} else if _, ok := underpaid[tx.id]; ok {
t.Errorf("underpaid transaction remained in storage: %d", tx.id)
} else if _, ok := outpriced[tx.id]; ok {
t.Errorf("outpriced transaction remained in storage: %d", tx.id)
} else if _, ok := exceeded[tx.id]; ok {
t.Errorf("fully overdrafted transaction remained in storage: %d", tx.id)
} else if _, ok := overdrafted[tx.id]; ok {
t.Errorf("partially overdrafted transaction remained in storage: %d", tx.id)
} else if _, ok := overcapped[tx.id]; ok {
t.Errorf("overcapped transaction remained in storage: %d", tx.id)
} else if _, ok := duplicated[tx.id]; ok {
t.Errorf("duplicated transaction remained in storage: %d", tx.id)
} else if _, ok := repeated[tx.id]; ok {
t.Errorf("repeated nonce transaction remained in storage: %d", tx.id)
if _, ok := dangling[tx.Id]; ok {
t.Errorf("dangling transaction remained in storage: %d", tx.Id)
} else if _, ok := filled[tx.Id]; ok {
t.Errorf("filled transaction remained in storage: %d", tx.Id)
} else if _, ok := overlapped[tx.Id]; ok {
t.Errorf("overlapped transaction remained in storage: %d", tx.Id)
} else if _, ok := gapped[tx.Id]; ok {
t.Errorf("gapped transaction remained in storage: %d", tx.Id)
} else if _, ok := underpaid[tx.Id]; ok {
t.Errorf("underpaid transaction remained in storage: %d", tx.Id)
} else if _, ok := outpriced[tx.Id]; ok {
t.Errorf("outpriced transaction remained in storage: %d", tx.Id)
} else if _, ok := exceeded[tx.Id]; ok {
t.Errorf("fully overdrafted transaction remained in storage: %d", tx.Id)
} else if _, ok := overdrafted[tx.Id]; ok {
t.Errorf("partially overdrafted transaction remained in storage: %d", tx.Id)
} else if _, ok := overcapped[tx.Id]; ok {
t.Errorf("overcapped transaction remained in storage: %d", tx.Id)
} else if _, ok := duplicated[tx.Id]; ok {
t.Errorf("duplicated transaction remained in storage: %d", tx.Id)
} else if _, ok := repeated[tx.Id]; ok {
t.Errorf("repeated Nonce transaction remained in storage: %d", tx.Id)
} else {
alive[tx.id] = struct{}{}
alive[tx.Id] = struct{}{}
}
}
}
@ -851,8 +851,8 @@ func TestOpenIndex(t *testing.T) {
// Verify that the transactions have been sorted by nonce (case 1)
for i := 0; i < len(pool.index[addr]); i++ {
if pool.index[addr][i].nonce != uint64(i) {
t.Errorf("tx %d nonce mismatch: have %d, want %d", i, pool.index[addr][i].nonce, uint64(i))
if pool.index[addr][i].Nonce != uint64(i) {
t.Errorf("tx %d nonce mismatch: have %d, want %d", i, pool.index[addr][i].Nonce, uint64(i))
}
}
// Verify that the cumulative fee minimums have been correctly calculated (case 2)

View file

@ -145,12 +145,12 @@ func TestPriceHeapSorting(t *testing.T) {
blobfeeJumps = dynamicFeeJumps(blobFee)
)
index[addr] = []*blobTxMeta{{
id: uint64(j),
storageSize: 128 * 1024,
nonce: 0,
execTipCap: execTip,
execFeeCap: execFee,
blobFeeCap: blobFee,
Id: uint64(j),
StorageSize: 128 * 1024,
Nonce: 0,
ExecTipCap: execTip,
ExecFeeCap: execFee,
BlobFeeCap: blobFee,
basefeeJumps: basefeeJumps,
blobfeeJumps: blobfeeJumps,
evictionExecTip: execTip,
@ -204,12 +204,12 @@ func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) {
blobfeeJumps = dynamicFeeJumps(blobFee)
)
index[addr] = []*blobTxMeta{{
id: uint64(i),
storageSize: 128 * 1024,
nonce: 0,
execTipCap: execTip,
execFeeCap: execFee,
blobFeeCap: blobFee,
Id: uint64(i),
StorageSize: 128 * 1024,
Nonce: 0,
ExecTipCap: execTip,
ExecFeeCap: execFee,
BlobFeeCap: blobFee,
basefeeJumps: basefeeJumps,
blobfeeJumps: blobfeeJumps,
evictionExecTip: execTip,
@ -280,12 +280,12 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
blobfeeJumps = dynamicFeeJumps(blobFee)
)
index[addr] = []*blobTxMeta{{
id: uint64(i),
storageSize: 128 * 1024,
nonce: 0,
execTipCap: execTip,
execFeeCap: execFee,
blobFeeCap: blobFee,
Id: uint64(i),
StorageSize: 128 * 1024,
Nonce: 0,
ExecTipCap: execTip,
ExecFeeCap: execFee,
BlobFeeCap: blobFee,
basefeeJumps: basefeeJumps,
blobfeeJumps: blobfeeJumps,
evictionExecTip: execTip,
@ -311,12 +311,12 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
blobfeeJumps = dynamicFeeJumps(blobFee)
)
metas[i] = &blobTxMeta{
id: uint64(int(blobs) + i),
storageSize: 128 * 1024,
nonce: 0,
execTipCap: execTip,
execFeeCap: execFee,
blobFeeCap: blobFee,
Id: uint64(int(blobs) + i),
StorageSize: 128 * 1024,
Nonce: 0,
ExecTipCap: execTip,
ExecFeeCap: execFee,
BlobFeeCap: blobFee,
basefeeJumps: basefeeJumps,
blobfeeJumps: blobfeeJumps,
evictionExecTip: execTip,

View file

@ -30,9 +30,8 @@ import (
// to which it belongs as well as the block number in which it was included for
// finality eviction.
type limboBlob struct {
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
Block uint64 // Block in which the blob transaction was included
BlobTxMeta *blobTxMeta
}
// limbo is a light, indexed database to temporarily store recently included
@ -43,15 +42,15 @@ type limboBlob struct {
type limbo struct {
store billy.Database // Persistent data store for limboed blobs
index map[common.Hash]uint64 // Mappings from tx hashes to datastore ids
groups map[uint64]map[uint64]common.Hash // Set of txs included in past blocks
index map[common.Hash]uint64 // Mappings from tx hashes to datastore ids
limbos map[uint64]*limboBlob // Mappings from datastore ids to limboBlobs
}
// newLimbo opens and indexes a set of limboed blob transactions.
func newLimbo(datadir string, maxBlobsPerTransaction int) (*limbo, error) {
l := &limbo{
index: make(map[common.Hash]uint64),
groups: make(map[uint64]map[uint64]common.Hash),
limbos: make(map[uint64]*limboBlob),
}
// Index all limboed blobs on disk and delete anything unprocessable
var fails []uint64
@ -94,56 +93,55 @@ 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.TxHash]; ok {
txHash := item.BlobTxMeta.TxHash
if _, ok := l.index[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.TxHash, "id", id)
log.Error("Dropping duplicate blob limbo entry", "owner", txHash, "id", id)
return errors.New("duplicate blob")
}
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.TxHash
l.index[txHash] = id
l.limbos[id] = item
return nil
}
// finalize evicts all blobs belonging to a recently finalized block or older.
func (l *limbo) finalize(final *types.Header) {
func (l *limbo) finalize(final *types.Header, fn func(id uint64, txHash common.Hash)) {
// Just in case there's no final block yet (network not yet merged, weird
// restart, sethead, etc), fail gracefully.
if final == nil {
log.Error("Nil finalized block cannot evict old blobs")
return
}
for block, ids := range l.groups {
if block > final.Number.Uint64() {
for id, item := range l.limbos {
if item.Block > final.Number.Uint64() {
continue
}
for id, owner := range ids {
if err := l.store.Delete(id); err != nil {
log.Error("Failed to drop finalized blob", "block", block, "id", id, "err", err)
}
delete(l.index, owner)
// Delete limbo metadata.
if err := l.store.Delete(id); err != nil {
log.Error("Failed to drop finalized blob", "block", item.Block, "id", id, "err", err)
}
delete(l.index, item.BlobTxMeta.TxHash)
delete(l.limbos, id)
if fn != nil { // Delete blob tx if fn is not null.
fn(item.BlobTxMeta.Id, item.BlobTxMeta.TxHash)
}
delete(l.groups, block)
}
}
// push stores a new blob transaction into the limbo, waiting until finality for
// it to be automatically evicted.
func (l *limbo) push(tx *types.Transaction, block uint64) error {
func (l *limbo) push(metaData *blobTxMeta, 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.Hash()]; ok {
log.Error("Limbo cannot push already tracked blobs", "tx", tx)
if _, ok := l.index[metaData.TxHash]; ok {
log.Error("Limbo cannot push already tracked blobs", "tx", metaData.TxHash)
return errors.New("already tracked blob transaction")
}
if err := l.setAndIndex(tx, block); err != nil {
log.Error("Failed to set and index limboed blobs", "tx", tx, "err", err)
if err := l.setAndIndex(metaData, block); err != nil {
log.Error("Failed to set and index limboed blobs", "tx", metaData.TxHash, "err", err)
return err
}
return nil
@ -152,7 +150,7 @@ func (l *limbo) push(tx *types.Transaction, block uint64) error {
// 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) (*types.Transaction, error) {
func (l *limbo) pull(tx common.Hash) (*blobTxMeta, 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.
@ -166,7 +164,10 @@ func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) {
log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
return nil, err
}
return item.Tx, nil
meta := item.BlobTxMeta
meta.basefeeJumps = dynamicFeeJumps(meta.ExecFeeCap)
meta.blobfeeJumps = dynamicFeeJumps(meta.BlobFeeCap)
return meta, nil
}
// update changes the block number under which a blob transaction is tracked. This
@ -187,7 +188,7 @@ func (l *limbo) update(txhash common.Hash, block uint64) {
}
// 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 {
if item, ok := l.limbos[id]; ok && item.Block == block {
log.Trace("Blob transaction unchanged in limbo", "tx", txhash, "block", block)
return
}
@ -198,7 +199,7 @@ func (l *limbo) update(txhash common.Hash, block uint64) {
log.Error("Failed to get and drop limboed blobs", "tx", txhash, "id", id, "err", err)
return
}
if err := l.setAndIndex(item.Tx, block); err != nil {
if err := l.setAndIndex(item.BlobTxMeta, block); err != nil {
log.Error("Failed to set and index limboed blobs", "tx", txhash, "err", err)
return
}
@ -208,19 +209,9 @@ func (l *limbo) update(txhash common.Hash, block uint64) {
// getAndDrop retrieves a blob item from the limbo store and deletes it both from
// the store and indices.
func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) {
data, err := l.store.Get(id)
if err != nil {
return nil, err
}
item := new(limboBlob)
if err = rlp.DecodeBytes(data, item); err != nil {
return nil, err
}
delete(l.index, item.TxHash)
delete(l.groups[item.Block], id)
if len(l.groups[item.Block]) == 0 {
delete(l.groups, item.Block)
}
item := l.limbos[id]
delete(l.index, item.BlobTxMeta.TxHash)
delete(l.limbos, id)
if err := l.store.Delete(id); err != nil {
return nil, err
}
@ -229,12 +220,10 @@ 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 *types.Transaction, block uint64) error {
txhash := tx.Hash()
func (l *limbo) setAndIndex(metaData *blobTxMeta, block uint64) error {
item := &limboBlob{
TxHash: txhash,
Block: block,
Tx: tx,
Block: block,
BlobTxMeta: metaData,
}
data, err := rlp.EncodeToBytes(item)
if err != nil {
@ -244,10 +233,7 @@ func (l *limbo) setAndIndex(tx *types.Transaction, block uint64) error {
if err != nil {
return err
}
l.index[txhash] = id
if _, ok := l.groups[block]; !ok {
l.groups[block] = make(map[uint64]common.Hash)
}
l.groups[block][id] = txhash
l.index[item.BlobTxMeta.TxHash] = id
l.limbos[id] = item
return nil
}

View file

@ -83,16 +83,16 @@ func (l *lookup) sizeOfTx(txhash common.Hash) (uint64, bool) {
// 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 {
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
l.blobIndex[vhash][tx.TxHash] = 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,
l.txIndex[tx.TxHash] = &txMetadata{
id: tx.Id,
size: tx.Size,
}
}
@ -100,11 +100,11 @@ func (l *lookup) track(tx *blobTxMeta) {
// hashes from the blob index.
func (l *lookup) untrack(tx *blobTxMeta) {
// Unmap the transaction hash from the datastore id
delete(l.txIndex, tx.hash)
delete(l.txIndex, tx.TxHash)
// 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
for _, vhash := range tx.VHashes {
delete(l.blobIndex[vhash], tx.TxHash) // may be double deleted if a tx contains the same blob twice
if len(l.blobIndex[vhash]) == 0 {
delete(l.blobIndex, vhash)
}