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