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 }