github.com/ethereum/go-ethereum@v1.16.1/eth/filters/filter_system.go (about) 1 // Copyright 2015 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 filters implements an ethereum filtering system for block, 18 // transactions and log events. 19 package filters 20 21 import ( 22 "context" 23 "fmt" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "github.com/ethereum/go-ethereum" 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/common/lru" 31 "github.com/ethereum/go-ethereum/core" 32 "github.com/ethereum/go-ethereum/core/filtermaps" 33 "github.com/ethereum/go-ethereum/core/history" 34 "github.com/ethereum/go-ethereum/core/types" 35 "github.com/ethereum/go-ethereum/ethdb" 36 "github.com/ethereum/go-ethereum/event" 37 "github.com/ethereum/go-ethereum/log" 38 "github.com/ethereum/go-ethereum/params" 39 "github.com/ethereum/go-ethereum/rpc" 40 ) 41 42 // Config represents the configuration of the filter system. 43 type Config struct { 44 LogCacheSize int // maximum number of cached blocks (default: 32) 45 Timeout time.Duration // how long filters stay active (default: 5min) 46 } 47 48 func (cfg Config) withDefaults() Config { 49 if cfg.Timeout == 0 { 50 cfg.Timeout = 5 * time.Minute 51 } 52 if cfg.LogCacheSize == 0 { 53 cfg.LogCacheSize = 32 54 } 55 return cfg 56 } 57 58 type Backend interface { 59 ChainDb() ethdb.Database 60 HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) 61 HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) 62 GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) 63 GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) 64 GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) 65 66 CurrentHeader() *types.Header 67 ChainConfig() *params.ChainConfig 68 HistoryPruningCutoff() uint64 69 SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription 70 SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription 71 SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription 72 SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription 73 74 CurrentView() *filtermaps.ChainView 75 NewMatcherBackend() filtermaps.MatcherBackend 76 } 77 78 // FilterSystem holds resources shared by all filters. 79 type FilterSystem struct { 80 backend Backend 81 logsCache *lru.Cache[common.Hash, *logCacheElem] 82 cfg *Config 83 } 84 85 // NewFilterSystem creates a filter system. 86 func NewFilterSystem(backend Backend, config Config) *FilterSystem { 87 config = config.withDefaults() 88 return &FilterSystem{ 89 backend: backend, 90 logsCache: lru.NewCache[common.Hash, *logCacheElem](config.LogCacheSize), 91 cfg: &config, 92 } 93 } 94 95 type logCacheElem struct { 96 logs []*types.Log 97 body atomic.Pointer[types.Body] 98 } 99 100 // cachedLogElem loads block logs from the backend and caches the result. 101 func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Hash, number, time uint64) (*logCacheElem, error) { 102 cached, ok := sys.logsCache.Get(blockHash) 103 if ok { 104 return cached, nil 105 } 106 107 logs, err := sys.backend.GetLogs(ctx, blockHash, number) 108 if err != nil { 109 return nil, err 110 } 111 if logs == nil { 112 return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString()) 113 } 114 // Database logs are un-derived. 115 // Fill in whatever we can (txHash is inaccessible at this point). 116 flattened := make([]*types.Log, 0) 117 var logIdx uint 118 for i, txLogs := range logs { 119 for _, log := range txLogs { 120 log.BlockHash = blockHash 121 log.BlockNumber = number 122 log.BlockTimestamp = time 123 log.TxIndex = uint(i) 124 log.Index = logIdx 125 logIdx++ 126 flattened = append(flattened, log) 127 } 128 } 129 elem := &logCacheElem{logs: flattened} 130 sys.logsCache.Add(blockHash, elem) 131 return elem, nil 132 } 133 134 func (sys *FilterSystem) cachedGetBody(ctx context.Context, elem *logCacheElem, hash common.Hash, number uint64) (*types.Body, error) { 135 if body := elem.body.Load(); body != nil { 136 return body, nil 137 } 138 body, err := sys.backend.GetBody(ctx, hash, rpc.BlockNumber(number)) 139 if err != nil { 140 return nil, err 141 } 142 elem.body.Store(body) 143 return body, nil 144 } 145 146 // Type determines the kind of filter and is used to put the filter in to 147 // the correct bucket when added. 148 type Type byte 149 150 const ( 151 // UnknownSubscription indicates an unknown subscription type 152 UnknownSubscription Type = iota 153 // LogsSubscription queries for new or removed (chain reorg) logs 154 LogsSubscription 155 // PendingTransactionsSubscription queries for pending transactions entering 156 // the pending state 157 PendingTransactionsSubscription 158 // BlocksSubscription queries hashes for blocks that are imported 159 BlocksSubscription 160 // LastIndexSubscription keeps track of the last index 161 LastIndexSubscription 162 ) 163 164 const ( 165 // txChanSize is the size of channel listening to NewTxsEvent. 166 // The number is referenced from the size of tx pool. 167 txChanSize = 4096 168 // rmLogsChanSize is the size of channel listening to RemovedLogsEvent. 169 rmLogsChanSize = 10 170 // logsChanSize is the size of channel listening to LogsEvent. 171 logsChanSize = 10 172 // chainEvChanSize is the size of channel listening to ChainEvent. 173 chainEvChanSize = 10 174 ) 175 176 type subscription struct { 177 id rpc.ID 178 typ Type 179 created time.Time 180 logsCrit ethereum.FilterQuery 181 logs chan []*types.Log 182 txs chan []*types.Transaction 183 headers chan *types.Header 184 installed chan struct{} // closed when the filter is installed 185 err chan error // closed when the filter is uninstalled 186 } 187 188 // EventSystem creates subscriptions, processes events and broadcasts them to the 189 // subscription which match the subscription criteria. 190 type EventSystem struct { 191 backend Backend 192 sys *FilterSystem 193 194 // Subscriptions 195 txsSub event.Subscription // Subscription for new transaction event 196 logsSub event.Subscription // Subscription for new log event 197 rmLogsSub event.Subscription // Subscription for removed log event 198 chainSub event.Subscription // Subscription for new chain event 199 200 // Channels 201 install chan *subscription // install filter for event notification 202 uninstall chan *subscription // remove filter for event notification 203 txsCh chan core.NewTxsEvent // Channel to receive new transactions event 204 logsCh chan []*types.Log // Channel to receive new log event 205 rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event 206 chainCh chan core.ChainEvent // Channel to receive new chain event 207 } 208 209 // NewEventSystem creates a new manager that listens for event on the given mux, 210 // parses and filters them. It uses the all map to retrieve filter changes. The 211 // work loop holds its own index that is used to forward events to filters. 212 // 213 // The returned manager has a loop that needs to be stopped with the Stop function 214 // or by stopping the given mux. 215 func NewEventSystem(sys *FilterSystem) *EventSystem { 216 m := &EventSystem{ 217 sys: sys, 218 backend: sys.backend, 219 install: make(chan *subscription), 220 uninstall: make(chan *subscription), 221 txsCh: make(chan core.NewTxsEvent, txChanSize), 222 logsCh: make(chan []*types.Log, logsChanSize), 223 rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize), 224 chainCh: make(chan core.ChainEvent, chainEvChanSize), 225 } 226 227 // Subscribe events 228 m.txsSub = m.backend.SubscribeNewTxsEvent(m.txsCh) 229 m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh) 230 m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh) 231 m.chainSub = m.backend.SubscribeChainEvent(m.chainCh) 232 233 // Make sure none of the subscriptions are empty 234 if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil { 235 log.Crit("Subscribe for event system failed") 236 } 237 238 go m.eventLoop() 239 return m 240 } 241 242 // Subscription is created when the client registers itself for a particular event. 243 type Subscription struct { 244 ID rpc.ID 245 f *subscription 246 es *EventSystem 247 unsubOnce sync.Once 248 } 249 250 // Err returns a channel that is closed when unsubscribed. 251 func (sub *Subscription) Err() <-chan error { 252 return sub.f.err 253 } 254 255 // Unsubscribe uninstalls the subscription from the event broadcast loop. 256 func (sub *Subscription) Unsubscribe() { 257 sub.unsubOnce.Do(func() { 258 uninstallLoop: 259 for { 260 // write uninstall request and consume logs/hashes. This prevents 261 // the eventLoop broadcast method to deadlock when writing to the 262 // filter event channel while the subscription loop is waiting for 263 // this method to return (and thus not reading these events). 264 select { 265 case sub.es.uninstall <- sub.f: 266 break uninstallLoop 267 case <-sub.f.logs: 268 case <-sub.f.txs: 269 case <-sub.f.headers: 270 } 271 } 272 273 // wait for filter to be uninstalled in work loop before returning 274 // this ensures that the manager won't use the event channel which 275 // will probably be closed by the client asap after this method returns. 276 <-sub.Err() 277 }) 278 } 279 280 // subscribe installs the subscription in the event broadcast loop. 281 func (es *EventSystem) subscribe(sub *subscription) *Subscription { 282 es.install <- sub 283 <-sub.installed 284 return &Subscription{ID: sub.id, f: sub, es: es} 285 } 286 287 // SubscribeLogs creates a subscription that will write all logs matching the 288 // given criteria to the given logs channel. Default value for the from and to 289 // block is "latest". If the fromBlock > toBlock an error is returned. 290 func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) (*Subscription, error) { 291 if len(crit.Topics) > maxTopics { 292 return nil, errExceedMaxTopics 293 } 294 if len(crit.Addresses) > maxAddresses { 295 return nil, errExceedMaxAddresses 296 } 297 var from, to rpc.BlockNumber 298 if crit.FromBlock == nil { 299 from = rpc.LatestBlockNumber 300 } else { 301 from = rpc.BlockNumber(crit.FromBlock.Int64()) 302 } 303 if crit.ToBlock == nil { 304 to = rpc.LatestBlockNumber 305 } else { 306 to = rpc.BlockNumber(crit.ToBlock.Int64()) 307 } 308 309 // Pending logs are not supported anymore. 310 if from == rpc.PendingBlockNumber || to == rpc.PendingBlockNumber { 311 return nil, errPendingLogsUnsupported 312 } 313 314 if from == rpc.EarliestBlockNumber { 315 from = rpc.BlockNumber(es.backend.HistoryPruningCutoff()) 316 } 317 // Queries beyond the pruning cutoff are not supported. 318 if uint64(from) < es.backend.HistoryPruningCutoff() { 319 return nil, &history.PrunedHistoryError{} 320 } 321 322 // only interested in new mined logs 323 if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber { 324 return es.subscribeLogs(crit, logs), nil 325 } 326 // only interested in mined logs within a specific block range 327 if from >= 0 && to >= 0 && to >= from { 328 return es.subscribeLogs(crit, logs), nil 329 } 330 // interested in logs from a specific block number to new mined blocks 331 if from >= 0 && to == rpc.LatestBlockNumber { 332 return es.subscribeLogs(crit, logs), nil 333 } 334 return nil, errInvalidBlockRange 335 } 336 337 // subscribeLogs creates a subscription that will write all logs matching the 338 // given criteria to the given logs channel. 339 func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription { 340 sub := &subscription{ 341 id: rpc.NewID(), 342 typ: LogsSubscription, 343 logsCrit: crit, 344 created: time.Now(), 345 logs: logs, 346 txs: make(chan []*types.Transaction), 347 headers: make(chan *types.Header), 348 installed: make(chan struct{}), 349 err: make(chan error), 350 } 351 return es.subscribe(sub) 352 } 353 354 // SubscribeNewHeads creates a subscription that writes the header of a block that is 355 // imported in the chain. 356 func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscription { 357 sub := &subscription{ 358 id: rpc.NewID(), 359 typ: BlocksSubscription, 360 created: time.Now(), 361 logs: make(chan []*types.Log), 362 txs: make(chan []*types.Transaction), 363 headers: headers, 364 installed: make(chan struct{}), 365 err: make(chan error), 366 } 367 return es.subscribe(sub) 368 } 369 370 // SubscribePendingTxs creates a subscription that writes transactions for 371 // transactions that enter the transaction pool. 372 func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subscription { 373 sub := &subscription{ 374 id: rpc.NewID(), 375 typ: PendingTransactionsSubscription, 376 created: time.Now(), 377 logs: make(chan []*types.Log), 378 txs: txs, 379 headers: make(chan *types.Header), 380 installed: make(chan struct{}), 381 err: make(chan error), 382 } 383 return es.subscribe(sub) 384 } 385 386 type filterIndex map[Type]map[rpc.ID]*subscription 387 388 func (es *EventSystem) handleLogs(filters filterIndex, ev []*types.Log) { 389 if len(ev) == 0 { 390 return 391 } 392 for _, f := range filters[LogsSubscription] { 393 matchedLogs := filterLogs(ev, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) 394 if len(matchedLogs) > 0 { 395 f.logs <- matchedLogs 396 } 397 } 398 } 399 400 func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) { 401 for _, f := range filters[PendingTransactionsSubscription] { 402 f.txs <- ev.Txs 403 } 404 } 405 406 func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) { 407 for _, f := range filters[BlocksSubscription] { 408 f.headers <- ev.Header 409 } 410 } 411 412 // eventLoop (un)installs filters and processes mux events. 413 func (es *EventSystem) eventLoop() { 414 // Ensure all subscriptions get cleaned up 415 defer func() { 416 es.txsSub.Unsubscribe() 417 es.logsSub.Unsubscribe() 418 es.rmLogsSub.Unsubscribe() 419 es.chainSub.Unsubscribe() 420 }() 421 422 index := make(filterIndex) 423 for i := UnknownSubscription; i < LastIndexSubscription; i++ { 424 index[i] = make(map[rpc.ID]*subscription) 425 } 426 427 for { 428 select { 429 case ev := <-es.txsCh: 430 es.handleTxsEvent(index, ev) 431 case ev := <-es.logsCh: 432 es.handleLogs(index, ev) 433 case ev := <-es.rmLogsCh: 434 es.handleLogs(index, ev.Logs) 435 case ev := <-es.chainCh: 436 es.handleChainEvent(index, ev) 437 438 case f := <-es.install: 439 index[f.typ][f.id] = f 440 close(f.installed) 441 442 case f := <-es.uninstall: 443 delete(index[f.typ], f.id) 444 close(f.err) 445 446 // System stopped 447 case <-es.txsSub.Err(): 448 return 449 case <-es.logsSub.Err(): 450 return 451 case <-es.rmLogsSub.Err(): 452 return 453 case <-es.chainSub.Err(): 454 return 455 } 456 } 457 }