github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/core/rawdb/accessors_skipped_txs.go (about)

     1  package rawdb
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"encoding/json"
     7  	"math/big"
     8  	"sync"
     9  
    10  	"github.com/scroll-tech/go-ethereum/common"
    11  	"github.com/scroll-tech/go-ethereum/core/types"
    12  	"github.com/scroll-tech/go-ethereum/ethdb"
    13  	"github.com/scroll-tech/go-ethereum/log"
    14  	"github.com/scroll-tech/go-ethereum/rlp"
    15  )
    16  
    17  // mutex used to avoid concurrent updates of NumSkippedTransactions
    18  var mu sync.Mutex
    19  
    20  // writeNumSkippedTransactions writes the number of skipped transactions to the database.
    21  func writeNumSkippedTransactions(db ethdb.KeyValueWriter, numSkipped uint64) {
    22  	value := big.NewInt(0).SetUint64(numSkipped).Bytes()
    23  
    24  	if err := db.Put(numSkippedTransactionsKey, value); err != nil {
    25  		log.Crit("Failed to update the number of skipped transactions", "err", err)
    26  	}
    27  }
    28  
    29  // ReadNumSkippedTransactions retrieves the number of skipped transactions.
    30  func ReadNumSkippedTransactions(db ethdb.Reader) uint64 {
    31  	data, err := db.Get(numSkippedTransactionsKey)
    32  	if err != nil && isNotFoundErr(err) {
    33  		return 0
    34  	}
    35  	if err != nil {
    36  		log.Crit("Failed to read number of skipped transactions from database", "err", err)
    37  	}
    38  	if len(data) == 0 {
    39  		return 0
    40  	}
    41  
    42  	number := new(big.Int).SetBytes(data)
    43  	if !number.IsUint64() {
    44  		log.Crit("Unexpected number of skipped transactions in database", "number", number)
    45  	}
    46  	return number.Uint64()
    47  }
    48  
    49  // SkippedTransaction stores the transaction object, along with the skip reason and block context.
    50  type SkippedTransaction struct {
    51  	// Tx is the skipped transaction.
    52  	// We store the tx itself because otherwise geth will discard it after skipping.
    53  	Tx *types.Transaction
    54  
    55  	// Reason is the skip reason.
    56  	Reason string
    57  
    58  	// BlockNumber is the number of the block in which this transaction was skipped.
    59  	BlockNumber uint64
    60  
    61  	// BlockHash is the hash of the block in which this transaction was skipped or nil.
    62  	BlockHash *common.Hash
    63  }
    64  
    65  // SkippedTransactionV2 stores the SkippedTransaction object along with serialized traces.
    66  type SkippedTransactionV2 struct {
    67  	// Tx is the skipped transaction.
    68  	// We store the tx itself otherwise geth will discard it after skipping.
    69  	Tx *types.Transaction
    70  
    71  	// Traces is the serialized wrapped traces of the skipped transaction.
    72  	// We only store it when `MinerStoreSkippedTxTracesFlag` is enabled, so it might be empty.
    73  	// Note that we do not directly utilize `*types.BlockTrace` due to the fact that
    74  	// types.BlockTrace.StorageTrace.Proofs is of type `map[string][]hexutil.Bytes`, which is not RLP-serializable.
    75  	TracesBytes []byte
    76  
    77  	// Reason is the skip reason.
    78  	Reason string
    79  
    80  	// BlockNumber is the number of the block in which this transaction was skipped.
    81  	BlockNumber uint64
    82  
    83  	// BlockHash is the hash of the block in which this transaction was skipped or nil.
    84  	BlockHash *common.Hash
    85  }
    86  
    87  // writeSkippedTransaction writes a skipped transaction to the database.
    88  func writeSkippedTransaction(db ethdb.KeyValueWriter, tx *types.Transaction, traces *types.BlockTrace, reason string, blockNumber uint64, blockHash *common.Hash) {
    89  	var err error
    90  	// workaround: RLP decoding fails if this is nil
    91  	if blockHash == nil {
    92  		blockHash = &common.Hash{}
    93  	}
    94  	stx := SkippedTransactionV2{Tx: tx, Reason: reason, BlockNumber: blockNumber, BlockHash: blockHash}
    95  	if traces != nil {
    96  		if stx.TracesBytes, err = json.Marshal(traces); err != nil {
    97  			log.Crit("Failed to json marshal skipped transaction", "hash", tx.Hash().String(), "err", err)
    98  		}
    99  	}
   100  	bytes, err := rlp.EncodeToBytes(stx)
   101  	if err != nil {
   102  		log.Crit("Failed to RLP encode skipped transaction", "hash", tx.Hash().String(), "err", err)
   103  	}
   104  	if err := db.Put(SkippedTransactionKey(tx.Hash()), bytes); err != nil {
   105  		log.Crit("Failed to store skipped transaction", "hash", tx.Hash().String(), "err", err)
   106  	}
   107  }
   108  
   109  // writeSkippedTransactionV1 is the old version of writeSkippedTransaction, we keep it for testing compatibility purpose.
   110  func writeSkippedTransactionV1(db ethdb.KeyValueWriter, tx *types.Transaction, reason string, blockNumber uint64, blockHash *common.Hash) {
   111  	// workaround: RLP decoding fails if this is nil
   112  	if blockHash == nil {
   113  		blockHash = &common.Hash{}
   114  	}
   115  	stx := SkippedTransaction{Tx: tx, Reason: reason, BlockNumber: blockNumber, BlockHash: blockHash}
   116  	bytes, err := rlp.EncodeToBytes(stx)
   117  	if err != nil {
   118  		log.Crit("Failed to RLP encode skipped transaction", "hash", tx.Hash().String(), "err", err)
   119  	}
   120  	if err := db.Put(SkippedTransactionKey(tx.Hash()), bytes); err != nil {
   121  		log.Crit("Failed to store skipped transaction", "hash", tx.Hash().String(), "err", err)
   122  	}
   123  }
   124  
   125  // readSkippedTransactionRLP retrieves a skipped transaction in its raw RLP database encoding.
   126  func readSkippedTransactionRLP(db ethdb.Reader, txHash common.Hash) rlp.RawValue {
   127  	data, err := db.Get(SkippedTransactionKey(txHash))
   128  	if err != nil && isNotFoundErr(err) {
   129  		return nil
   130  	}
   131  	if err != nil {
   132  		log.Crit("Failed to load skipped transaction", "hash", txHash.String(), "err", err)
   133  	}
   134  	return data
   135  }
   136  
   137  // ReadSkippedTransaction retrieves a skipped transaction by its hash, along with its skipped reason.
   138  func ReadSkippedTransaction(db ethdb.Reader, txHash common.Hash) *SkippedTransactionV2 {
   139  	data := readSkippedTransactionRLP(db, txHash)
   140  	if len(data) == 0 {
   141  		return nil
   142  	}
   143  	var stxV2 SkippedTransactionV2
   144  	var stx SkippedTransaction
   145  	if err := rlp.Decode(bytes.NewReader(data), &stxV2); err != nil {
   146  		if err := rlp.Decode(bytes.NewReader(data), &stx); err != nil {
   147  			log.Crit("Invalid skipped transaction RLP", "hash", txHash.String(), "data", data, "err", err)
   148  		}
   149  		stxV2.Tx = stx.Tx
   150  		stxV2.Reason = stx.Reason
   151  		stxV2.BlockNumber = stx.BlockNumber
   152  		stxV2.BlockHash = stx.BlockHash
   153  	}
   154  
   155  	if stxV2.BlockHash != nil && *stxV2.BlockHash == (common.Hash{}) {
   156  		stxV2.BlockHash = nil
   157  	}
   158  	return &stxV2
   159  }
   160  
   161  // writeSkippedTransactionHash writes the hash of a skipped transaction to the database.
   162  func writeSkippedTransactionHash(db ethdb.KeyValueWriter, index uint64, txHash common.Hash) {
   163  	if err := db.Put(SkippedTransactionHashKey(index), txHash[:]); err != nil {
   164  		log.Crit("Failed to store skipped transaction hash", "index", index, "hash", txHash.String(), "err", err)
   165  	}
   166  }
   167  
   168  // ReadSkippedTransactionHash retrieves the hash of a skipped transaction by its index.
   169  func ReadSkippedTransactionHash(db ethdb.Reader, index uint64) *common.Hash {
   170  	data, err := db.Get(SkippedTransactionHashKey(index))
   171  	if err != nil && isNotFoundErr(err) {
   172  		return nil
   173  	}
   174  	if err != nil {
   175  		log.Crit("Failed to load skipped transaction hash", "index", index, "err", err)
   176  	}
   177  	hash := common.BytesToHash(data)
   178  	return &hash
   179  }
   180  
   181  // WriteSkippedTransaction writes a skipped transaction to the database and also updates the count and lookup index.
   182  // Note: The lookup index and count will include duplicates if there are chain reorgs.
   183  func WriteSkippedTransaction(db ethdb.Database, tx *types.Transaction, traces *types.BlockTrace, reason string, blockNumber uint64, blockHash *common.Hash) {
   184  	// this method is not accessed concurrently, but just to be sure...
   185  	mu.Lock()
   186  	defer mu.Unlock()
   187  
   188  	index := ReadNumSkippedTransactions(db)
   189  
   190  	// update in a batch
   191  	batch := db.NewBatch()
   192  	writeSkippedTransaction(batch, tx, traces, reason, blockNumber, blockHash)
   193  	writeSkippedTransactionHash(batch, index, tx.Hash())
   194  	writeNumSkippedTransactions(batch, index+1)
   195  
   196  	// write to DB
   197  	if err := batch.Write(); err != nil {
   198  		log.Crit("Failed to store skipped transaction", "hash", tx.Hash().String(), "err", err)
   199  	}
   200  }
   201  
   202  // SkippedTransactionIterator is a wrapper around ethdb.Iterator that
   203  // allows us to iterate over skipped transaction hashes in the database.
   204  // It implements an interface similar to ethdb.Iterator.
   205  type SkippedTransactionIterator struct {
   206  	inner     ethdb.Iterator
   207  	db        ethdb.Reader
   208  	keyLength int
   209  }
   210  
   211  // IterateSkippedTransactionsFrom creates a SkippedTransactionIterator that iterates
   212  // over all skipped transaction hashes in the database starting at the provided index.
   213  func IterateSkippedTransactionsFrom(db ethdb.Database, index uint64) SkippedTransactionIterator {
   214  	start := encodeBigEndian(index)
   215  	it := db.NewIterator(skippedTransactionHashPrefix, start)
   216  	keyLength := len(skippedTransactionHashPrefix) + 8
   217  
   218  	return SkippedTransactionIterator{
   219  		inner:     it,
   220  		db:        db,
   221  		keyLength: keyLength,
   222  	}
   223  }
   224  
   225  // Next moves the iterator to the next key/value pair.
   226  // It returns false when the iterator is exhausted.
   227  // TODO: Consider reading items in batches.
   228  func (it *SkippedTransactionIterator) Next() bool {
   229  	for it.inner.Next() {
   230  		key := it.inner.Key()
   231  		if len(key) == it.keyLength {
   232  			return true
   233  		}
   234  	}
   235  	return false
   236  }
   237  
   238  // Index returns the index of the current skipped transaction hash.
   239  func (it *SkippedTransactionIterator) Index() uint64 {
   240  	key := it.inner.Key()
   241  	raw := key[len(skippedTransactionHashPrefix) : len(skippedTransactionHashPrefix)+8]
   242  	index := binary.BigEndian.Uint64(raw)
   243  	return index
   244  }
   245  
   246  // TransactionHash returns the current skipped transaction hash.
   247  func (it *SkippedTransactionIterator) TransactionHash() common.Hash {
   248  	data := it.inner.Value()
   249  	return common.BytesToHash(data)
   250  }
   251  
   252  // Release releases the associated resources.
   253  func (it *SkippedTransactionIterator) Release() {
   254  	it.inner.Release()
   255  }