github.com/jimmyx0x/go-ethereum@v1.10.28/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 "time" 26 27 "github.com/ethereum/go-ethereum" 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/common/lru" 30 "github.com/ethereum/go-ethereum/core" 31 "github.com/ethereum/go-ethereum/core/bloombits" 32 "github.com/ethereum/go-ethereum/core/rawdb" 33 "github.com/ethereum/go-ethereum/core/types" 34 "github.com/ethereum/go-ethereum/ethdb" 35 "github.com/ethereum/go-ethereum/event" 36 "github.com/ethereum/go-ethereum/log" 37 "github.com/ethereum/go-ethereum/params" 38 "github.com/ethereum/go-ethereum/rpc" 39 ) 40 41 // Config represents the configuration of the filter system. 42 type Config struct { 43 LogCacheSize int // maximum number of cached blocks (default: 32) 44 Timeout time.Duration // how long filters stay active (default: 5min) 45 } 46 47 func (cfg Config) withDefaults() Config { 48 if cfg.Timeout == 0 { 49 cfg.Timeout = 5 * time.Minute 50 } 51 if cfg.LogCacheSize == 0 { 52 cfg.LogCacheSize = 32 53 } 54 return cfg 55 } 56 57 type Backend interface { 58 ChainDb() ethdb.Database 59 HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) 60 HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) 61 GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) 62 GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) 63 PendingBlockAndReceipts() (*types.Block, types.Receipts) 64 65 CurrentHeader() *types.Header 66 ChainConfig() *params.ChainConfig 67 SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription 68 SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription 69 SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription 70 SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription 71 SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription 72 73 BloomStatus() (uint64, uint64) 74 ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) 75 } 76 77 // FilterSystem holds resources shared by all filters. 78 type FilterSystem struct { 79 backend Backend 80 logsCache *lru.Cache[common.Hash, [][]*types.Log] 81 cfg *Config 82 } 83 84 // NewFilterSystem creates a filter system. 85 func NewFilterSystem(backend Backend, config Config) *FilterSystem { 86 config = config.withDefaults() 87 return &FilterSystem{ 88 backend: backend, 89 logsCache: lru.NewCache[common.Hash, [][]*types.Log](config.LogCacheSize), 90 cfg: &config, 91 } 92 } 93 94 // cachedGetLogs loads block logs from the backend and caches the result. 95 func (sys *FilterSystem) cachedGetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { 96 cached, ok := sys.logsCache.Get(blockHash) 97 if ok { 98 return cached, nil 99 } 100 101 logs, err := sys.backend.GetLogs(ctx, blockHash, number) 102 if err != nil { 103 return nil, err 104 } 105 if logs == nil { 106 return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString()) 107 } 108 sys.logsCache.Add(blockHash, logs) 109 return logs, nil 110 } 111 112 // Type determines the kind of filter and is used to put the filter in to 113 // the correct bucket when added. 114 type Type byte 115 116 const ( 117 // UnknownSubscription indicates an unknown subscription type 118 UnknownSubscription Type = iota 119 // LogsSubscription queries for new or removed (chain reorg) logs 120 LogsSubscription 121 // PendingLogsSubscription queries for logs in pending blocks 122 PendingLogsSubscription 123 // MinedAndPendingLogsSubscription queries for logs in mined and pending blocks. 124 MinedAndPendingLogsSubscription 125 // PendingTransactionsSubscription queries for pending transactions entering 126 // the pending state 127 PendingTransactionsSubscription 128 // BlocksSubscription queries hashes for blocks that are imported 129 BlocksSubscription 130 // LastIndexSubscription keeps track of the last index 131 LastIndexSubscription 132 ) 133 134 const ( 135 // txChanSize is the size of channel listening to NewTxsEvent. 136 // The number is referenced from the size of tx pool. 137 txChanSize = 4096 138 // rmLogsChanSize is the size of channel listening to RemovedLogsEvent. 139 rmLogsChanSize = 10 140 // logsChanSize is the size of channel listening to LogsEvent. 141 logsChanSize = 10 142 // chainEvChanSize is the size of channel listening to ChainEvent. 143 chainEvChanSize = 10 144 ) 145 146 type subscription struct { 147 id rpc.ID 148 typ Type 149 created time.Time 150 logsCrit ethereum.FilterQuery 151 logs chan []*types.Log 152 txs chan []*types.Transaction 153 headers chan *types.Header 154 installed chan struct{} // closed when the filter is installed 155 err chan error // closed when the filter is uninstalled 156 } 157 158 // EventSystem creates subscriptions, processes events and broadcasts them to the 159 // subscription which match the subscription criteria. 160 type EventSystem struct { 161 backend Backend 162 sys *FilterSystem 163 lightMode bool 164 lastHead *types.Header 165 166 // Subscriptions 167 txsSub event.Subscription // Subscription for new transaction event 168 logsSub event.Subscription // Subscription for new log event 169 rmLogsSub event.Subscription // Subscription for removed log event 170 pendingLogsSub event.Subscription // Subscription for pending log event 171 chainSub event.Subscription // Subscription for new chain event 172 173 // Channels 174 install chan *subscription // install filter for event notification 175 uninstall chan *subscription // remove filter for event notification 176 txsCh chan core.NewTxsEvent // Channel to receive new transactions event 177 logsCh chan []*types.Log // Channel to receive new log event 178 pendingLogsCh chan []*types.Log // Channel to receive new log event 179 rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event 180 chainCh chan core.ChainEvent // Channel to receive new chain event 181 } 182 183 // NewEventSystem creates a new manager that listens for event on the given mux, 184 // parses and filters them. It uses the all map to retrieve filter changes. The 185 // work loop holds its own index that is used to forward events to filters. 186 // 187 // The returned manager has a loop that needs to be stopped with the Stop function 188 // or by stopping the given mux. 189 func NewEventSystem(sys *FilterSystem, lightMode bool) *EventSystem { 190 m := &EventSystem{ 191 sys: sys, 192 backend: sys.backend, 193 lightMode: lightMode, 194 install: make(chan *subscription), 195 uninstall: make(chan *subscription), 196 txsCh: make(chan core.NewTxsEvent, txChanSize), 197 logsCh: make(chan []*types.Log, logsChanSize), 198 rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize), 199 pendingLogsCh: make(chan []*types.Log, logsChanSize), 200 chainCh: make(chan core.ChainEvent, chainEvChanSize), 201 } 202 203 // Subscribe events 204 m.txsSub = m.backend.SubscribeNewTxsEvent(m.txsCh) 205 m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh) 206 m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh) 207 m.chainSub = m.backend.SubscribeChainEvent(m.chainCh) 208 m.pendingLogsSub = m.backend.SubscribePendingLogsEvent(m.pendingLogsCh) 209 210 // Make sure none of the subscriptions are empty 211 if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil || m.pendingLogsSub == nil { 212 log.Crit("Subscribe for event system failed") 213 } 214 215 go m.eventLoop() 216 return m 217 } 218 219 // Subscription is created when the client registers itself for a particular event. 220 type Subscription struct { 221 ID rpc.ID 222 f *subscription 223 es *EventSystem 224 unsubOnce sync.Once 225 } 226 227 // Err returns a channel that is closed when unsubscribed. 228 func (sub *Subscription) Err() <-chan error { 229 return sub.f.err 230 } 231 232 // Unsubscribe uninstalls the subscription from the event broadcast loop. 233 func (sub *Subscription) Unsubscribe() { 234 sub.unsubOnce.Do(func() { 235 uninstallLoop: 236 for { 237 // write uninstall request and consume logs/hashes. This prevents 238 // the eventLoop broadcast method to deadlock when writing to the 239 // filter event channel while the subscription loop is waiting for 240 // this method to return (and thus not reading these events). 241 select { 242 case sub.es.uninstall <- sub.f: 243 break uninstallLoop 244 case <-sub.f.logs: 245 case <-sub.f.txs: 246 case <-sub.f.headers: 247 } 248 } 249 250 // wait for filter to be uninstalled in work loop before returning 251 // this ensures that the manager won't use the event channel which 252 // will probably be closed by the client asap after this method returns. 253 <-sub.Err() 254 }) 255 } 256 257 // subscribe installs the subscription in the event broadcast loop. 258 func (es *EventSystem) subscribe(sub *subscription) *Subscription { 259 es.install <- sub 260 <-sub.installed 261 return &Subscription{ID: sub.id, f: sub, es: es} 262 } 263 264 // SubscribeLogs creates a subscription that will write all logs matching the 265 // given criteria to the given logs channel. Default value for the from and to 266 // block is "latest". If the fromBlock > toBlock an error is returned. 267 func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) (*Subscription, error) { 268 var from, to rpc.BlockNumber 269 if crit.FromBlock == nil { 270 from = rpc.LatestBlockNumber 271 } else { 272 from = rpc.BlockNumber(crit.FromBlock.Int64()) 273 } 274 if crit.ToBlock == nil { 275 to = rpc.LatestBlockNumber 276 } else { 277 to = rpc.BlockNumber(crit.ToBlock.Int64()) 278 } 279 280 // only interested in pending logs 281 if from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber { 282 return es.subscribePendingLogs(crit, logs), nil 283 } 284 // only interested in new mined logs 285 if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber { 286 return es.subscribeLogs(crit, logs), nil 287 } 288 // only interested in mined logs within a specific block range 289 if from >= 0 && to >= 0 && to >= from { 290 return es.subscribeLogs(crit, logs), nil 291 } 292 // interested in mined logs from a specific block number, new logs and pending logs 293 if from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber { 294 return es.subscribeMinedPendingLogs(crit, logs), nil 295 } 296 // interested in logs from a specific block number to new mined blocks 297 if from >= 0 && to == rpc.LatestBlockNumber { 298 return es.subscribeLogs(crit, logs), nil 299 } 300 return nil, fmt.Errorf("invalid from and to block combination: from > to") 301 } 302 303 // subscribeMinedPendingLogs creates a subscription that returned mined and 304 // pending logs that match the given criteria. 305 func (es *EventSystem) subscribeMinedPendingLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription { 306 sub := &subscription{ 307 id: rpc.NewID(), 308 typ: MinedAndPendingLogsSubscription, 309 logsCrit: crit, 310 created: time.Now(), 311 logs: logs, 312 txs: make(chan []*types.Transaction), 313 headers: make(chan *types.Header), 314 installed: make(chan struct{}), 315 err: make(chan error), 316 } 317 return es.subscribe(sub) 318 } 319 320 // subscribeLogs creates a subscription that will write all logs matching the 321 // given criteria to the given logs channel. 322 func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription { 323 sub := &subscription{ 324 id: rpc.NewID(), 325 typ: LogsSubscription, 326 logsCrit: crit, 327 created: time.Now(), 328 logs: logs, 329 txs: make(chan []*types.Transaction), 330 headers: make(chan *types.Header), 331 installed: make(chan struct{}), 332 err: make(chan error), 333 } 334 return es.subscribe(sub) 335 } 336 337 // subscribePendingLogs creates a subscription that writes contract event logs for 338 // transactions that enter the transaction pool. 339 func (es *EventSystem) subscribePendingLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription { 340 sub := &subscription{ 341 id: rpc.NewID(), 342 typ: PendingLogsSubscription, 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) handlePendingLogs(filters filterIndex, ev []*types.Log) { 401 if len(ev) == 0 { 402 return 403 } 404 for _, f := range filters[PendingLogsSubscription] { 405 matchedLogs := filterLogs(ev, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) 406 if len(matchedLogs) > 0 { 407 f.logs <- matchedLogs 408 } 409 } 410 } 411 412 func (es *EventSystem) handleRemovedLogs(filters filterIndex, ev core.RemovedLogsEvent) { 413 for _, f := range filters[LogsSubscription] { 414 matchedLogs := filterLogs(ev.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) 415 if len(matchedLogs) > 0 { 416 f.logs <- matchedLogs 417 } 418 } 419 } 420 421 func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) { 422 for _, f := range filters[PendingTransactionsSubscription] { 423 f.txs <- ev.Txs 424 } 425 } 426 427 func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) { 428 for _, f := range filters[BlocksSubscription] { 429 f.headers <- ev.Block.Header() 430 } 431 if es.lightMode && len(filters[LogsSubscription]) > 0 { 432 es.lightFilterNewHead(ev.Block.Header(), func(header *types.Header, remove bool) { 433 for _, f := range filters[LogsSubscription] { 434 if matchedLogs := es.lightFilterLogs(header, f.logsCrit.Addresses, f.logsCrit.Topics, remove); len(matchedLogs) > 0 { 435 f.logs <- matchedLogs 436 } 437 } 438 }) 439 } 440 } 441 442 func (es *EventSystem) lightFilterNewHead(newHeader *types.Header, callBack func(*types.Header, bool)) { 443 oldh := es.lastHead 444 es.lastHead = newHeader 445 if oldh == nil { 446 return 447 } 448 newh := newHeader 449 // find common ancestor, create list of rolled back and new block hashes 450 var oldHeaders, newHeaders []*types.Header 451 for oldh.Hash() != newh.Hash() { 452 if oldh.Number.Uint64() >= newh.Number.Uint64() { 453 oldHeaders = append(oldHeaders, oldh) 454 oldh = rawdb.ReadHeader(es.backend.ChainDb(), oldh.ParentHash, oldh.Number.Uint64()-1) 455 } 456 if oldh.Number.Uint64() < newh.Number.Uint64() { 457 newHeaders = append(newHeaders, newh) 458 newh = rawdb.ReadHeader(es.backend.ChainDb(), newh.ParentHash, newh.Number.Uint64()-1) 459 if newh == nil { 460 // happens when CHT syncing, nothing to do 461 newh = oldh 462 } 463 } 464 } 465 // roll back old blocks 466 for _, h := range oldHeaders { 467 callBack(h, true) 468 } 469 // check new blocks (array is in reverse order) 470 for i := len(newHeaders) - 1; i >= 0; i-- { 471 callBack(newHeaders[i], false) 472 } 473 } 474 475 // filter logs of a single header in light client mode 476 func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.Address, topics [][]common.Hash, remove bool) []*types.Log { 477 if bloomFilter(header.Bloom, addresses, topics) { 478 // Get the logs of the block 479 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 480 defer cancel() 481 logsList, err := es.sys.cachedGetLogs(ctx, header.Hash(), header.Number.Uint64()) 482 if err != nil { 483 return nil 484 } 485 var unfiltered []*types.Log 486 for _, logs := range logsList { 487 for _, log := range logs { 488 logcopy := *log 489 logcopy.Removed = remove 490 unfiltered = append(unfiltered, &logcopy) 491 } 492 } 493 logs := filterLogs(unfiltered, nil, nil, addresses, topics) 494 if len(logs) > 0 && logs[0].TxHash == (common.Hash{}) { 495 // We have matching but non-derived logs 496 receipts, err := es.backend.GetReceipts(ctx, header.Hash()) 497 if err != nil { 498 return nil 499 } 500 unfiltered = unfiltered[:0] 501 for _, receipt := range receipts { 502 for _, log := range receipt.Logs { 503 logcopy := *log 504 logcopy.Removed = remove 505 unfiltered = append(unfiltered, &logcopy) 506 } 507 } 508 logs = filterLogs(unfiltered, nil, nil, addresses, topics) 509 } 510 return logs 511 } 512 return nil 513 } 514 515 // eventLoop (un)installs filters and processes mux events. 516 func (es *EventSystem) eventLoop() { 517 // Ensure all subscriptions get cleaned up 518 defer func() { 519 es.txsSub.Unsubscribe() 520 es.logsSub.Unsubscribe() 521 es.rmLogsSub.Unsubscribe() 522 es.pendingLogsSub.Unsubscribe() 523 es.chainSub.Unsubscribe() 524 }() 525 526 index := make(filterIndex) 527 for i := UnknownSubscription; i < LastIndexSubscription; i++ { 528 index[i] = make(map[rpc.ID]*subscription) 529 } 530 531 for { 532 select { 533 case ev := <-es.txsCh: 534 es.handleTxsEvent(index, ev) 535 case ev := <-es.logsCh: 536 es.handleLogs(index, ev) 537 case ev := <-es.rmLogsCh: 538 es.handleRemovedLogs(index, ev) 539 case ev := <-es.pendingLogsCh: 540 es.handlePendingLogs(index, ev) 541 case ev := <-es.chainCh: 542 es.handleChainEvent(index, ev) 543 544 case f := <-es.install: 545 if f.typ == MinedAndPendingLogsSubscription { 546 // the type are logs and pending logs subscriptions 547 index[LogsSubscription][f.id] = f 548 index[PendingLogsSubscription][f.id] = f 549 } else { 550 index[f.typ][f.id] = f 551 } 552 close(f.installed) 553 554 case f := <-es.uninstall: 555 if f.typ == MinedAndPendingLogsSubscription { 556 // the type are logs and pending logs subscriptions 557 delete(index[LogsSubscription], f.id) 558 delete(index[PendingLogsSubscription], f.id) 559 } else { 560 delete(index[f.typ], f.id) 561 } 562 close(f.err) 563 564 // System stopped 565 case <-es.txsSub.Err(): 566 return 567 case <-es.logsSub.Err(): 568 return 569 case <-es.rmLogsSub.Err(): 570 return 571 case <-es.chainSub.Err(): 572 return 573 } 574 } 575 }