github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/core/txindexer.go (about) 1 // Copyright 2024 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/> 16 17 package core 18 19 import ( 20 "errors" 21 "fmt" 22 23 "github.com/ethereum/go-ethereum/core/rawdb" 24 "github.com/ethereum/go-ethereum/ethdb" 25 "github.com/ethereum/go-ethereum/log" 26 ) 27 28 // TxIndexProgress is the struct describing the progress for transaction indexing. 29 type TxIndexProgress struct { 30 Indexed uint64 // number of blocks whose transactions are indexed 31 Remaining uint64 // number of blocks whose transactions are not indexed yet 32 } 33 34 // Done returns an indicator if the transaction indexing is finished. 35 func (progress TxIndexProgress) Done() bool { 36 return progress.Remaining == 0 37 } 38 39 // txIndexer is the module responsible for maintaining transaction indexes 40 // according to the configured indexing range by users. 41 type txIndexer struct { 42 // limit is the maximum number of blocks from head whose tx indexes 43 // are reserved: 44 // * 0: means the entire chain should be indexed 45 // * N: means the latest N blocks [HEAD-N+1, HEAD] should be indexed 46 // and all others shouldn't. 47 limit uint64 48 db ethdb.Database 49 progress chan chan TxIndexProgress 50 term chan chan struct{} 51 closed chan struct{} 52 } 53 54 // newTxIndexer initializes the transaction indexer. 55 func newTxIndexer(limit uint64, chain *BlockChain) *txIndexer { 56 indexer := &txIndexer{ 57 limit: limit, 58 db: chain.db, 59 progress: make(chan chan TxIndexProgress), 60 term: make(chan chan struct{}), 61 closed: make(chan struct{}), 62 } 63 go indexer.loop(chain) 64 65 var msg string 66 if limit == 0 { 67 msg = "entire chain" 68 } else { 69 msg = fmt.Sprintf("last %d blocks", limit) 70 } 71 log.Info("Initialized transaction indexer", "range", msg) 72 73 return indexer 74 } 75 76 // run executes the scheduled indexing/unindexing task in a separate thread. 77 // If the stop channel is closed, the task should be terminated as soon as 78 // possible, the done channel will be closed once the task is finished. 79 func (indexer *txIndexer) run(tail *uint64, head uint64, stop chan struct{}, done chan struct{}) { 80 defer func() { close(done) }() 81 82 // Short circuit if chain is empty and nothing to index. 83 if head == 0 { 84 return 85 } 86 // The tail flag is not existent, it means the node is just initialized 87 // and all blocks in the chain (part of them may from ancient store) are 88 // not indexed yet, index the chain according to the configured limit. 89 if tail == nil { 90 from := uint64(0) 91 if indexer.limit != 0 && head >= indexer.limit { 92 from = head - indexer.limit + 1 93 } 94 rawdb.IndexTransactions(indexer.db, from, head+1, stop, true) 95 return 96 } 97 // The tail flag is existent (which means indexes in [tail, head] should be 98 // present), while the whole chain are requested for indexing. 99 if indexer.limit == 0 || head < indexer.limit { 100 if *tail > 0 { 101 // It can happen when chain is rewound to a historical point which 102 // is even lower than the indexes tail, recap the indexing target 103 // to new head to avoid reading non-existent block bodies. 104 end := *tail 105 if end > head+1 { 106 end = head + 1 107 } 108 rawdb.IndexTransactions(indexer.db, 0, end, stop, true) 109 } 110 return 111 } 112 // The tail flag is existent, adjust the index range according to configured 113 // limit and the latest chain head. 114 if head-indexer.limit+1 < *tail { 115 // Reindex a part of missing indices and rewind index tail to HEAD-limit 116 rawdb.IndexTransactions(indexer.db, head-indexer.limit+1, *tail, stop, true) 117 } else { 118 // Unindex a part of stale indices and forward index tail to HEAD-limit 119 rawdb.UnindexTransactions(indexer.db, *tail, head-indexer.limit+1, stop, false) 120 } 121 } 122 123 // loop is the scheduler of the indexer, assigning indexing/unindexing tasks depending 124 // on the received chain event. 125 func (indexer *txIndexer) loop(chain *BlockChain) { 126 defer close(indexer.closed) 127 128 // Listening to chain events and manipulate the transaction indexes. 129 var ( 130 stop chan struct{} // Non-nil if background routine is active. 131 done chan struct{} // Non-nil if background routine is active. 132 lastHead uint64 // The latest announced chain head (whose tx indexes are assumed created) 133 lastTail = rawdb.ReadTxIndexTail(indexer.db) // The oldest indexed block, nil means nothing indexed 134 135 headCh = make(chan ChainHeadEvent) 136 sub = chain.SubscribeChainHeadEvent(headCh) 137 ) 138 defer sub.Unsubscribe() 139 140 // Launch the initial processing if chain is not empty (head != genesis). 141 // This step is useful in these scenarios that chain has no progress. 142 if head := rawdb.ReadHeadBlock(indexer.db); head != nil && head.Number().Uint64() != 0 { 143 stop = make(chan struct{}) 144 done = make(chan struct{}) 145 lastHead = head.Number().Uint64() 146 go indexer.run(rawdb.ReadTxIndexTail(indexer.db), head.NumberU64(), stop, done) 147 } 148 for { 149 select { 150 case head := <-headCh: 151 if done == nil { 152 stop = make(chan struct{}) 153 done = make(chan struct{}) 154 go indexer.run(rawdb.ReadTxIndexTail(indexer.db), head.Block.NumberU64(), stop, done) 155 } 156 lastHead = head.Block.NumberU64() 157 case <-done: 158 stop = nil 159 done = nil 160 lastTail = rawdb.ReadTxIndexTail(indexer.db) 161 case ch := <-indexer.progress: 162 ch <- indexer.report(lastHead, lastTail) 163 case ch := <-indexer.term: 164 if stop != nil { 165 close(stop) 166 } 167 if done != nil { 168 log.Info("Waiting background transaction indexer to exit") 169 <-done 170 } 171 close(ch) 172 return 173 } 174 } 175 } 176 177 // report returns the tx indexing progress. 178 func (indexer *txIndexer) report(head uint64, tail *uint64) TxIndexProgress { 179 total := indexer.limit 180 if indexer.limit == 0 || total > head { 181 total = head + 1 // genesis included 182 } 183 var indexed uint64 184 if tail != nil { 185 indexed = head - *tail + 1 186 } 187 // The value of indexed might be larger than total if some blocks need 188 // to be unindexed, avoiding a negative remaining. 189 var remaining uint64 190 if indexed < total { 191 remaining = total - indexed 192 } 193 return TxIndexProgress{ 194 Indexed: indexed, 195 Remaining: remaining, 196 } 197 } 198 199 // txIndexProgress retrieves the tx indexing progress, or an error if the 200 // background tx indexer is already stopped. 201 func (indexer *txIndexer) txIndexProgress() (TxIndexProgress, error) { 202 ch := make(chan TxIndexProgress, 1) 203 select { 204 case indexer.progress <- ch: 205 return <-ch, nil 206 case <-indexer.closed: 207 return TxIndexProgress{}, errors.New("indexer is closed") 208 } 209 } 210 211 // close shutdown the indexer. Safe to be called for multiple times. 212 func (indexer *txIndexer) close() { 213 ch := make(chan struct{}) 214 select { 215 case indexer.term <- ch: 216 <-ch 217 case <-indexer.closed: 218 } 219 }