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