core: index more about txs

This commit is contained in:
Ömer Faruk IRMAK 2025-06-17 21:35:09 +03:00
parent 12d163c5bd
commit 019a6b4921
8 changed files with 260 additions and 87 deletions

View file

@ -146,7 +146,11 @@ const (
// The following incompatible database changes were added:
// * Total difficulty has been removed from both the key-value store and the ancient store.
// * The metadata structure of freezer is changed by adding 'flushOffset'
BlockChainVersion uint64 = 9
//
// - Version 10
// The following incompatible database changes were added:
// * todo(omer)
BlockChainVersion uint64 = 10
)
// BlockChainConfig contains the configuration of the BlockChain object.
@ -1127,7 +1131,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
if err := batch.Write(); err != nil {
log.Crit("Failed to write genesis block", "err", err)
}
bc.writeHeadBlock(genesis)
bc.writeHeadBlock(genesis, types.Receipts{})
// Last update all in-memory chain markers
bc.genesisBlock = genesis
@ -1185,13 +1189,15 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
// or if they are on a different side chain.
//
// Note, this function assumes that the `mu` mutex is held!
func (bc *BlockChain) writeHeadBlock(block *types.Block) {
func (bc *BlockChain) writeHeadBlock(block *types.Block, receipts types.Receipts) {
// Add the block to the canonical chain number scheme and mark as the head
batch := bc.db.NewBatch()
rawdb.WriteHeadHeaderHash(batch, block.Hash())
rawdb.WriteHeadFastBlockHash(batch, block.Hash())
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
rawdb.WriteTxLookupEntriesByBlock(batch, block)
if bc.txIndexer != nil {
bc.txIndexer.indexHead(batch, block, receipts)
}
rawdb.WriteHeadBlockHash(batch, block.Hash())
// Flush the whole batch into the disk, exit the node if failed
@ -1536,7 +1542,7 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
return err
}
}
bc.writeHeadBlock(block)
bc.writeHeadBlock(block, bc.GetReceiptsByHash(block.Hash()))
return nil
}
@ -1638,7 +1644,7 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types
}
// Set new head.
bc.writeHeadBlock(block)
bc.writeHeadBlock(block, receipts)
bc.chainFeed.Send(ChainEvent{Header: block.Header()})
if len(logs) > 0 {
@ -2430,7 +2436,7 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
rebirthLogs = nil
}
// Update the head block
bc.writeHeadBlock(block)
bc.writeHeadBlock(block, bc.GetReceiptsByHash(block.Hash()))
}
if len(rebirthLogs) > 0 {
bc.logsFeed.Send(rebirthLogs)
@ -2505,7 +2511,7 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) {
return common.Hash{}, err
}
}
bc.writeHeadBlock(head)
bc.writeHeadBlock(head, bc.GetReceiptsByHash(head.Hash()))
// Emit events
logs := bc.collectLogs(head, false)

View file

@ -34,6 +34,10 @@ import (
// DecodeTxLookupEntry decodes the supplied tx lookup data.
func DecodeTxLookupEntry(data []byte, db ethdb.Reader) *uint64 {
var index TxIndex
if err := rlp.DecodeBytes(data, &index); err == nil {
return &index.BlockNumber
}
// Database v6 tx lookup just stores the block number
if len(data) < common.HashLength {
number := new(big.Int).SetBytes(data).Uint64()
@ -62,29 +66,53 @@ func ReadTxLookupEntry(db ethdb.Reader, hash common.Hash) *uint64 {
return DecodeTxLookupEntry(data, db)
}
// ReadTxIndex retrieves the full index data if exists
func ReadTxIndex(db ethdb.Reader, hash common.Hash) *TxIndex {
data, _ := db.Get(txLookupKey(hash))
var index TxIndex
if err := rlp.DecodeBytes(data, &index); err != nil {
return nil
}
return &index
}
// writeTxLookupEntry stores a positional metadata for a transaction,
// enabling hash based transaction and receipt lookups.
func writeTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash, numberBytes []byte) {
if err := db.Put(txLookupKey(hash), numberBytes); err != nil {
func writeTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash, bytes []byte) {
if err := db.Put(txLookupKey(hash), bytes); err != nil {
log.Crit("Failed to store transaction lookup entry", "err", err)
}
}
// WriteTxLookupEntries is identical to WriteTxLookupEntry, but it works on
// a list of hashes
func WriteTxLookupEntries(db ethdb.KeyValueWriter, number uint64, hashes []common.Hash) {
numberBytes := new(big.Int).SetUint64(number).Bytes()
for _, hash := range hashes {
writeTxLookupEntry(db, hash, numberBytes)
}
// TxIndex is the collection of data that is stored per transaction for
// speeding up hashed based lookups
type TxIndex struct {
Type uint8 // Transaction Type
Nonce uint64 // Transaction Nonce
BlockNumber uint64
BlockHash common.Hash
BlockTime uint64
BaseFee *big.Int
TxIndex uint32
Sender common.Address
EffectiveGasPrice *big.Int
GasUsed uint64
LogIndex uint32
To *common.Address `rlp:"optional"`
BlobGas uint64 `rlp:"optional"`
BlobGasPrice *big.Int `rlp:"optional"`
}
// WriteTxLookupEntriesByBlock stores a positional metadata for every transaction from
// a block, enabling hash based transaction and receipt lookups.
func WriteTxLookupEntriesByBlock(db ethdb.KeyValueWriter, block *types.Block) {
numberBytes := block.Number().Bytes()
for _, tx := range block.Transactions() {
writeTxLookupEntry(db, tx.Hash(), numberBytes)
// WriteTxLookupEntries stores a positional metadata for a set of transactions
// enabling hash based transaction and receipt lookups.
func WriteTxLookupEntries(db ethdb.KeyValueWriter, hashes []common.Hash, indexes []TxIndex) {
for index, hash := range hashes {
indexBytes, err := rlp.EncodeToBytes(indexes[index])
if err != nil {
log.Error("Failed to encode tx index", "error", err)
return
}
writeTxLookupEntry(db, hash, indexBytes)
}
}

View file

@ -38,10 +38,26 @@ func TestLookupStorage(t *testing.T) {
name string
writeTxLookupEntriesByBlock func(ethdb.KeyValueWriter, *types.Block)
}{
{
"DatabaseV10",
func(db ethdb.KeyValueWriter, block *types.Block) {
var (
txHashes []common.Hash
indexes []TxIndex
)
for txIndex, tx := range block.Transactions() {
txHashes = append(txHashes, tx.Hash())
indexes = append(indexes, TxIndex{BlockNumber: block.NumberU64(), TxIndex: uint32(txIndex)})
}
WriteTxLookupEntries(db, txHashes, indexes)
},
},
{
"DatabaseV6",
func(db ethdb.KeyValueWriter, block *types.Block) {
WriteTxLookupEntriesByBlock(db, block)
for _, tx := range block.Transactions() {
db.Put(txLookupKey(tx.Hash()), block.Number().Bytes())
}
},
},
{

View file

@ -17,7 +17,6 @@
package rawdb
import (
"encoding/binary"
"time"
"github.com/ethereum/go-ethereum/common"
@ -94,12 +93,8 @@ func PruneTransactionIndex(db ethdb.Database, pruneBlock uint64) {
if count%10000000 == 0 {
log.Info("Pruning tx index", "count", count, "removed", removed)
}
if len(v) > 8 {
log.Error("Skipping legacy tx index entry", "hash", txhash)
return false
}
bn := decodeNumber(v)
if bn < pruneBlock {
bn := DecodeTxLookupEntry(v, db)
if *bn < pruneBlock {
removed++
return true
}
@ -107,9 +102,3 @@ func PruneTransactionIndex(db ethdb.Database, pruneBlock uint64) {
})
WriteTxIndexTail(db, pruneBlock)
}
func decodeNumber(b []byte) uint64 {
var numBuffer [8]byte
copy(numBuffer[8-len(b):], b)
return binary.BigEndian.Uint64(numBuffer[:])
}

View file

@ -76,7 +76,15 @@ func TestPruneTransactionIndex(t *testing.T) {
pruneBlock := lastBlock - 3
for i := uint64(0); i <= lastBlock; i++ {
WriteTxLookupEntriesByBlock(chainDB, blocks[i])
var (
txHashes []common.Hash
indexes []TxIndex
)
for txIndex, tx := range blocks[i].Transactions() {
txHashes = append(txHashes, tx.Hash())
indexes = append(indexes, TxIndex{BlockNumber: blocks[i].NumberU64(), TxIndex: uint32(txIndex)})
}
WriteTxLookupEntries(chainDB, txHashes, indexes)
}
WriteTxIndexTail(chainDB, 0)

View file

@ -18,16 +18,19 @@ package core
import (
"fmt"
"math/big"
"runtime"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
)
@ -67,6 +70,7 @@ type txIndexer struct {
// be pruned and not available locally.
cutoff uint64
db ethdb.Database
config *params.ChainConfig
term chan chan struct{}
closed chan struct{}
}
@ -78,6 +82,7 @@ func newTxIndexer(limit uint64, chain *BlockChain) *txIndexer {
limit: limit,
cutoff: cutoff,
db: chain.db,
config: chain.chainConfig,
term: make(chan chan struct{}),
closed: make(chan struct{}),
}
@ -337,20 +342,62 @@ func (indexer *txIndexer) close() {
}
}
type blockTxHashes struct {
// generateTxIndex generates the data that will be stored alongside the transaction index
func (indexer *txIndexer) generateTxIndex(txs types.Transactions, header *types.Header, receipts types.Receipts) []rawdb.TxIndex {
var (
logIndex = 0
prevCumulativeGasUsed = uint64(0)
signer = types.MakeSigner(indexer.config, header.Number, header.Time)
indexes = make([]rawdb.TxIndex, len(txs))
)
var blobGasPrice *big.Int
if header.ExcessBlobGas != nil {
blobGasPrice = eip4844.CalcBlobFee(indexer.config, header)
}
for blockIndex, tx := range txs {
from, _ := signer.Sender(tx)
indexes[blockIndex] = rawdb.TxIndex{
Type: tx.Type(),
Nonce: tx.Nonce(),
To: tx.To(),
BlockNumber: header.Number.Uint64(),
BlockHash: header.Hash(),
BlockTime: header.Time,
BaseFee: header.BaseFee,
TxIndex: uint32(blockIndex),
Sender: from,
EffectiveGasPrice: tx.EffectiveGasPrice(header.BaseFee),
GasUsed: receipts[blockIndex].CumulativeGasUsed - prevCumulativeGasUsed,
LogIndex: uint32(logIndex),
BlobGas: tx.BlobGas(),
BlobGasPrice: blobGasPrice,
}
prevCumulativeGasUsed = receipts[blockIndex].CumulativeGasUsed
logIndex += len(receipts[blockIndex].Logs)
}
return indexes
}
type blockIndexingContext struct {
number uint64
hashes []common.Hash
txHashes []common.Hash
indexes []rawdb.TxIndex
}
// iterate iterates over all transactions in the (canon) block
// number(s) given, and yields the hashes on a channel. If there is a signal
// received from interrupt channel, the iteration will be aborted and result
// channel will be closed.
func (indexer *txIndexer) iterate(from uint64, to uint64, reverse bool, interrupt chan struct{}) chan *blockTxHashes {
func (indexer *txIndexer) iterate(from uint64, to uint64, reverse, indexing bool, interrupt chan struct{}) chan *blockIndexingContext {
// One thread sequentially reads data from db
type numberRlp struct {
number uint64
rlp rlp.RawValue
headerRLP rlp.RawValue
bodyRLP rlp.RawValue
receiptsRLP rlp.RawValue
}
if to == from {
return nil
@ -361,7 +408,7 @@ func (indexer *txIndexer) iterate(from uint64, to uint64, reverse bool, interrup
}
var (
rlpCh = make(chan *numberRlp, threads*2) // we send raw rlp over this channel
hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh
contextsCh = make(chan *blockIndexingContext, threads*2) // send indexing context over contextsCh
)
// lookup runs in one instance
lookup := func() {
@ -371,10 +418,20 @@ func (indexer *txIndexer) iterate(from uint64, to uint64, reverse bool, interrup
}
defer close(rlpCh)
for n != end {
data := rawdb.ReadCanonicalBodyRLP(indexer.db, n)
blockHash := rawdb.ReadCanonicalHash(indexer.db, n)
rlps := &numberRlp{
number: n,
bodyRLP: rawdb.ReadBodyRLP(indexer.db, blockHash, n),
}
if indexing {
// We are indexing, read more to build the index
rlps.headerRLP = rawdb.ReadHeaderRLP(indexer.db, blockHash, n)
rlps.receiptsRLP = rawdb.ReadReceiptsRLP(indexer.db, blockHash, n)
}
// Feed the block to the aggregator, or abort on interrupt
select {
case rlpCh <- &numberRlp{n, data}:
case rlpCh <- rlps:
case <-interrupt:
return
}
@ -392,26 +449,47 @@ func (indexer *txIndexer) iterate(from uint64, to uint64, reverse bool, interrup
defer func() {
// Last processor closes the result channel
if nThreadsAlive.Add(-1) == 0 {
close(hashesCh)
close(contextsCh)
}
}()
for data := range rlpCh {
var body types.Body
if err := rlp.DecodeBytes(data.rlp, &body); err != nil {
log.Warn("Failed to decode block body", "block", data.number, "error", err)
if err := rlp.DecodeBytes(data.bodyRLP, &body); err != nil {
log.Error("Failed to decode block body", "block", data.number, "error", err)
return
}
var hashes []common.Hash
txHashes := make([]common.Hash, 0, len(body.Transactions))
for _, tx := range body.Transactions {
hashes = append(hashes, tx.Hash())
txHashes = append(txHashes, tx.Hash())
}
result := &blockTxHashes{
hashes: hashes,
result := &blockIndexingContext{
number: data.number,
txHashes: txHashes,
}
if indexing {
var header types.Header
if err := rlp.DecodeBytes(data.headerRLP, &header); err != nil {
log.Error("failed to decode header", "block", data.number, "err", err)
return
}
var storageReceipts []*types.ReceiptForStorage
if err := rlp.DecodeBytes(data.receiptsRLP, &storageReceipts); err != nil {
log.Error("failed to decode receipts", "block", data.number, "err", err)
return
}
receipts := make([]*types.Receipt, len(storageReceipts))
for i, receipt := range storageReceipts {
receipts[i] = (*types.Receipt)(receipt)
}
result.indexes = indexer.generateTxIndex(body.Transactions, &header, receipts)
}
// Feed the block to the aggregator, or abort on interrupt
select {
case hashesCh <- result:
case contextsCh <- result:
case <-interrupt:
return
}
@ -421,7 +499,7 @@ func (indexer *txIndexer) iterate(from uint64, to uint64, reverse bool, interrup
for i := 0; i < int(threads); i++ {
go process()
}
return hashesCh
return contextsCh
}
// index creates txlookup indices of the specified block range.
@ -438,7 +516,7 @@ func (indexer *txIndexer) index(from uint64, to uint64, interrupt chan struct{},
return
}
var (
hashesCh = indexer.iterate(from, to, true, interrupt)
contextsCh = indexer.iterate(from, to, true, true, interrupt)
batch = indexer.db.NewBatch()
start = time.Now()
logged = start.Add(-7 * time.Second)
@ -447,10 +525,10 @@ func (indexer *txIndexer) index(from uint64, to uint64, interrupt chan struct{},
// in to be [to-1]. Therefore, setting lastNum to means that the
// queue gap-evaluation will work correctly
lastNum = to
queue = prque.New[int64, *blockTxHashes](nil)
queue = prque.New[int64, *blockIndexingContext](nil)
blocks, txs = 0, 0 // for stats reporting
)
for chanDelivery := range hashesCh {
for chanDelivery := range contextsCh {
// Push the delivery into the queue and process contiguous ranges.
// Since we iterate in reverse, so lower numbers have lower prio, and
// we can use the number directly as prio marker
@ -467,9 +545,9 @@ func (indexer *txIndexer) index(from uint64, to uint64, interrupt chan struct{},
// Next block available, pop it off and index it
delivery := queue.PopItem()
lastNum = delivery.number
rawdb.WriteTxLookupEntries(batch, delivery.number, delivery.hashes)
rawdb.WriteTxLookupEntries(batch, delivery.txHashes, delivery.indexes)
blocks++
txs += len(delivery.hashes)
txs += len(delivery.txHashes)
// If enough data was accumulated in memory or we're at the last block, dump to disk
if batch.ValueSize() > ethdb.IdealBatchSize {
rawdb.WriteTxIndexTail(batch, lastNum) // Also write the tail here
@ -516,7 +594,7 @@ func (indexer *txIndexer) unindex(from uint64, to uint64, interrupt chan struct{
return
}
var (
hashesCh = indexer.iterate(from, to, false, interrupt)
contextsCh = indexer.iterate(from, to, false, false, interrupt)
batch = indexer.db.NewBatch()
start = time.Now()
logged = start.Add(-7 * time.Second)
@ -524,11 +602,11 @@ func (indexer *txIndexer) unindex(from uint64, to uint64, interrupt chan struct{
// we expect the first number to come in to be [from]. Therefore, setting
// nextNum to from means that the queue gap-evaluation will work correctly
nextNum = from
queue = prque.New[int64, *blockTxHashes](nil)
queue = prque.New[int64, *blockIndexingContext](nil)
blocks, txs = 0, 0 // for stats reporting
)
// Otherwise spin up the concurrent iterator and unindexer
for delivery := range hashesCh {
for delivery := range contextsCh {
// Push the delivery into the queue and process contiguous ranges.
queue.Push(delivery, -int64(delivery.number))
for !queue.Empty() {
@ -542,8 +620,8 @@ func (indexer *txIndexer) unindex(from uint64, to uint64, interrupt chan struct{
}
delivery := queue.PopItem()
nextNum = delivery.number + 1
rawdb.DeleteTxLookupEntries(batch, delivery.hashes)
txs += len(delivery.hashes)
rawdb.DeleteTxLookupEntries(batch, delivery.txHashes)
txs += len(delivery.txHashes)
blocks++
// If enough data was accumulated in memory or we're at the last block, dump to disk
@ -583,3 +661,13 @@ func (indexer *txIndexer) unindex(from uint64, to uint64, interrupt chan struct{
logger("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
}
}
// indexHead indexes given head block synchronously
func (indexer *txIndexer) indexHead(db ethdb.KeyValueWriter, block *types.Block, receipts types.Receipts) {
indexes := indexer.generateTxIndex(block.Transactions(), block.Header(), receipts)
hashes := make([]common.Hash, len(block.Transactions()))
for i, tx := range block.Transactions() {
hashes[i] = tx.Hash()
}
rawdb.WriteTxLookupEntries(db, hashes, indexes)
}

View file

@ -27,10 +27,14 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func verifyIndexes(t *testing.T, db ethdb.Database, block *types.Block, exist bool) {
for _, tx := range block.Transactions() {
func verifyIndexes(t *testing.T, db ethdb.Database, block *types.Block, receipts types.Receipts, exist bool) {
signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Time())
for blockIndex, tx := range block.Transactions() {
lookup := rawdb.ReadTxLookupEntry(db, tx.Hash())
if exist && lookup == nil {
t.Fatalf("missing %d %x", block.NumberU64(), tx.Hash().Hex())
@ -38,10 +42,35 @@ func verifyIndexes(t *testing.T, db ethdb.Database, block *types.Block, exist bo
if !exist && lookup != nil {
t.Fatalf("unexpected %d %x", block.NumberU64(), tx.Hash().Hex())
}
if exist {
txIndex := rawdb.ReadTxIndex(db, tx.Hash())
require.NotNil(t, txIndex)
assert.Equal(t, tx.Type(), txIndex.Type)
assert.Equal(t, tx.Nonce(), txIndex.Nonce)
assert.Equal(t, block.NumberU64(), txIndex.BlockNumber)
assert.Equal(t, block.Hash(), txIndex.BlockHash)
assert.Equal(t, block.Time(), txIndex.BlockTime)
assert.Equal(t, block.BaseFee(), txIndex.BaseFee)
assert.Equal(t, uint32(blockIndex), txIndex.TxIndex)
sender, _ := signer.Sender(tx)
assert.Equal(t, txIndex.Sender, sender)
receipt := receipts[blockIndex]
assert.Equal(t, receipt.EffectiveGasPrice, txIndex.EffectiveGasPrice)
assert.Equal(t, receipt.GasUsed, txIndex.GasUsed)
if len(receipt.Logs) > 0 {
assert.Equal(t, receipt.Logs[0].Index, txIndex.LogIndex)
}
assert.Equal(t, tx.To(), txIndex.To)
assert.Equal(t, tx.BlobGas(), txIndex.BlobGas)
assert.Equal(t, receipt.BlobGasPrice, txIndex.BlobGasPrice)
}
}
}
func verify(t *testing.T, db ethdb.Database, blocks []*types.Block, expTail uint64) {
func verify(t *testing.T, db ethdb.Database, blocks []*types.Block, receipts []types.Receipts, expTail uint64) {
tail := rawdb.ReadTxIndexTail(db)
if tail == nil {
t.Fatal("Failed to write tx index tail")
@ -50,11 +79,11 @@ func verify(t *testing.T, db ethdb.Database, blocks []*types.Block, expTail uint
if *tail != expTail {
t.Fatalf("Unexpected tx index tail, want %v, got %d", expTail, *tail)
}
for _, b := range blocks {
for i, b := range blocks {
if b.Number().Uint64() < *tail {
verifyIndexes(t, db, b, false)
verifyIndexes(t, db, b, receipts[i], false)
} else {
verifyIndexes(t, db, b, true)
verifyIndexes(t, db, b, receipts[i], true)
}
}
}
@ -65,7 +94,7 @@ func verifyNoIndex(t *testing.T, db ethdb.Database, blocks []*types.Block) {
t.Fatalf("Unexpected tx index tail %d", *tail)
}
for _, b := range blocks {
verifyIndexes(t, db, b, false)
verifyIndexes(t, db, b, nil, false)
}
}
@ -123,11 +152,12 @@ func TestTxIndexer(t *testing.T) {
indexer := &txIndexer{
limit: 0,
db: db,
config: gspec.Config,
}
for i, limit := range c.limits {
indexer.limit = limit
indexer.run(chainHead, make(chan struct{}), make(chan struct{}))
verify(t, db, blocks, c.tails[i])
verify(t, db, blocks, receipts, c.tails[i])
}
db.Close()
}
@ -243,6 +273,7 @@ func TestTxIndexerRepair(t *testing.T) {
indexer := &txIndexer{
limit: c.limit,
db: db,
config: gspec.Config,
}
indexer.run(chainHead, make(chan struct{}), make(chan struct{}))
@ -252,7 +283,7 @@ func TestTxIndexerRepair(t *testing.T) {
if c.expTail == nil {
verifyNoIndex(t, db, blocks)
} else {
verify(t, db, blocks, *c.expTail)
verify(t, db, blocks, receipts, *c.expTail)
}
db.Close()
}

View file

@ -404,6 +404,13 @@ func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) i
return txTip.Cmp(other)
}
// EffectiveGasPrice computes the gas price paid by the transaction, given
// the inclusion block baseFee
func (tx *Transaction) EffectiveGasPrice(baseFee *big.Int) *big.Int {
var dst big.Int
return tx.inner.effectiveGasPrice(&dst, baseFee)
}
// BlobGas returns the blob gas limit of the transaction for blob transactions, 0 otherwise.
func (tx *Transaction) BlobGas() uint64 {
if blobtx, ok := tx.inner.(*BlobTx); ok {