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