github.com/ava-labs/subnet-evm@v0.6.4/eth/filters/filter_system.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2015 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 // Package filters implements an ethereum filtering system for block, 28 // transactions and log events. 29 package filters 30 31 import ( 32 "context" 33 "errors" 34 "fmt" 35 "sync" 36 "time" 37 38 "github.com/ava-labs/subnet-evm/core" 39 "github.com/ava-labs/subnet-evm/core/bloombits" 40 "github.com/ava-labs/subnet-evm/core/types" 41 "github.com/ava-labs/subnet-evm/interfaces" 42 "github.com/ava-labs/subnet-evm/params" 43 "github.com/ava-labs/subnet-evm/rpc" 44 "github.com/ethereum/go-ethereum/common" 45 "github.com/ethereum/go-ethereum/ethdb" 46 "github.com/ethereum/go-ethereum/event" 47 "github.com/ethereum/go-ethereum/log" 48 ) 49 50 // Config represents the configuration of the filter system. 51 type Config struct { 52 Timeout time.Duration // how long filters stay active (default: 5min) 53 } 54 55 func (cfg Config) withDefaults() Config { 56 if cfg.Timeout == 0 { 57 cfg.Timeout = 5 * time.Minute 58 } 59 return cfg 60 } 61 62 type Backend interface { 63 ChainDb() ethdb.Database 64 HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) 65 HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) 66 GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) 67 GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) 68 GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) 69 70 CurrentHeader() *types.Header 71 ChainConfig() *params.ChainConfig 72 SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription 73 SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription 74 SubscribeChainAcceptedEvent(ch chan<- core.ChainEvent) event.Subscription 75 SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription 76 SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription 77 SubscribeAcceptedLogsEvent(ch chan<- []*types.Log) event.Subscription 78 79 SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription 80 81 SubscribeAcceptedTransactionEvent(ch chan<- core.NewTxsEvent) event.Subscription 82 83 BloomStatus() (uint64, uint64) 84 ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) 85 86 // Added to the backend interface to support limiting of logs requests 87 IsAllowUnfinalizedQueries() bool 88 LastAcceptedBlock() *types.Block 89 GetMaxBlocksPerRequest() int64 90 } 91 92 // FilterSystem holds resources shared by all filters. 93 type FilterSystem struct { 94 // Note: go-ethereum uses an LRU cache for logs, 95 // instead we cache logs on the blockchain object itself. 96 backend Backend 97 cfg *Config 98 } 99 100 // NewFilterSystem creates a filter system. 101 func NewFilterSystem(backend Backend, config Config) *FilterSystem { 102 config = config.withDefaults() 103 return &FilterSystem{ 104 backend: backend, 105 cfg: &config, 106 } 107 } 108 109 // getLogs loads block logs from the backend. The backend is responsible for 110 // performing any log caching. 111 func (sys *FilterSystem) getLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { 112 logs, err := sys.backend.GetLogs(ctx, blockHash, number) 113 if err != nil { 114 return nil, err 115 } 116 if logs == nil { 117 return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString()) 118 } 119 return logs, nil 120 } 121 122 // Type determines the kind of filter and is used to put the filter in to 123 // the correct bucket when added. 124 type Type byte 125 126 const ( 127 // UnknownSubscription indicates an unknown subscription type 128 UnknownSubscription Type = iota 129 // LogsSubscription queries for new or removed (chain reorg) logs 130 LogsSubscription 131 // AcceptedLogsSubscription queries for new or removed (chain reorg) logs 132 AcceptedLogsSubscription 133 // PendingLogsSubscription queries for logs in pending blocks 134 PendingLogsSubscription 135 // MinedAndPendingLogsSubscription queries for logs in mined and pending blocks. 136 MinedAndPendingLogsSubscription 137 // PendingTransactionsSubscription queries for pending transactions entering 138 // the pending state 139 PendingTransactionsSubscription 140 // AcceptedTransactionsSubscription queries for accepted transactions 141 AcceptedTransactionsSubscription 142 // BlocksSubscription queries hashes for blocks that are imported 143 BlocksSubscription 144 // AcceptedBlocksSubscription queries hashes for blocks that are accepted 145 AcceptedBlocksSubscription 146 // LastIndexSubscription keeps track of the last index 147 LastIndexSubscription 148 ) 149 150 const ( 151 // txChanSize is the size of channel listening to NewTxsEvent. 152 // The number is referenced from the size of tx pool. 153 txChanSize = 4096 154 // rmLogsChanSize is the size of channel listening to RemovedLogsEvent. 155 rmLogsChanSize = 10 156 // logsChanSize is the size of channel listening to LogsEvent. 157 logsChanSize = 10 158 // chainEvChanSize is the size of channel listening to ChainEvent. 159 chainEvChanSize = 10 160 ) 161 162 type subscription struct { 163 id rpc.ID 164 typ Type 165 created time.Time 166 logsCrit interfaces.FilterQuery 167 logs chan []*types.Log 168 txs chan []*types.Transaction 169 headers chan *types.Header 170 installed chan struct{} // closed when the filter is installed 171 err chan error // closed when the filter is uninstalled 172 } 173 174 // EventSystem creates subscriptions, processes events and broadcasts them to the 175 // subscription which match the subscription criteria. 176 type EventSystem struct { 177 backend Backend 178 sys *FilterSystem 179 180 // Subscriptions 181 txsSub event.Subscription // Subscription for new transaction event 182 logsSub event.Subscription // Subscription for new log event 183 logsAcceptedSub event.Subscription // Subscription for new accepted log event 184 rmLogsSub event.Subscription // Subscription for removed log event 185 pendingLogsSub event.Subscription // Subscription for pending log event 186 chainSub event.Subscription // Subscription for new chain event 187 chainAcceptedSub event.Subscription // Subscription for new chain accepted event 188 txsAcceptedSub event.Subscription // Subscription for new accepted txs 189 190 // Channels 191 install chan *subscription // install filter for event notification 192 uninstall chan *subscription // remove filter for event notification 193 txsCh chan core.NewTxsEvent // Channel to receive new transactions event 194 logsCh chan []*types.Log // Channel to receive new log event 195 logsAcceptedCh chan []*types.Log // Channel to receive new accepted log event 196 pendingLogsCh chan []*types.Log // Channel to receive new log event 197 rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event 198 chainCh chan core.ChainEvent // Channel to receive new chain event 199 chainAcceptedCh chan core.ChainEvent // Channel to receive new chain accepted event 200 txsAcceptedCh chan core.NewTxsEvent // Channel to receive new accepted txs 201 } 202 203 // NewEventSystem creates a new manager that listens for event on the given mux, 204 // parses and filters them. It uses the all map to retrieve filter changes. The 205 // work loop holds its own index that is used to forward events to filters. 206 // 207 // The returned manager has a loop that needs to be stopped with the Stop function 208 // or by stopping the given mux. 209 func NewEventSystem(sys *FilterSystem) *EventSystem { 210 m := &EventSystem{ 211 sys: sys, 212 backend: sys.backend, 213 install: make(chan *subscription), 214 uninstall: make(chan *subscription), 215 txsCh: make(chan core.NewTxsEvent, txChanSize), 216 logsCh: make(chan []*types.Log, logsChanSize), 217 logsAcceptedCh: make(chan []*types.Log, logsChanSize), 218 rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize), 219 pendingLogsCh: make(chan []*types.Log, logsChanSize), 220 chainCh: make(chan core.ChainEvent, chainEvChanSize), 221 chainAcceptedCh: make(chan core.ChainEvent, chainEvChanSize), 222 txsAcceptedCh: make(chan core.NewTxsEvent, txChanSize), 223 } 224 225 // Subscribe events 226 m.txsSub = m.backend.SubscribeNewTxsEvent(m.txsCh) 227 m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh) 228 m.logsAcceptedSub = m.backend.SubscribeAcceptedLogsEvent(m.logsAcceptedCh) 229 m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh) 230 m.chainSub = m.backend.SubscribeChainEvent(m.chainCh) 231 m.chainAcceptedSub = m.backend.SubscribeChainAcceptedEvent(m.chainAcceptedCh) 232 m.pendingLogsSub = m.backend.SubscribePendingLogsEvent(m.pendingLogsCh) 233 m.txsAcceptedSub = m.backend.SubscribeAcceptedTransactionEvent(m.txsAcceptedCh) 234 235 // Make sure none of the subscriptions are empty 236 if m.txsSub == nil || m.logsSub == nil || m.logsAcceptedSub == nil || m.rmLogsSub == nil || m.chainSub == nil || m.chainAcceptedSub == nil || m.pendingLogsSub == nil || m.txsAcceptedSub == nil { 237 log.Crit("Subscribe for event system failed") 238 } 239 240 go m.eventLoop() 241 return m 242 } 243 244 // Subscription is created when the client registers itself for a particular event. 245 type Subscription struct { 246 ID rpc.ID 247 f *subscription 248 es *EventSystem 249 unsubOnce sync.Once 250 } 251 252 // Err returns a channel that is closed when unsubscribed. 253 func (sub *Subscription) Err() <-chan error { 254 return sub.f.err 255 } 256 257 // Unsubscribe uninstalls the subscription from the event broadcast loop. 258 func (sub *Subscription) Unsubscribe() { 259 sub.unsubOnce.Do(func() { 260 uninstallLoop: 261 for { 262 // write uninstall request and consume logs/hashes. This prevents 263 // the eventLoop broadcast method to deadlock when writing to the 264 // filter event channel while the subscription loop is waiting for 265 // this method to return (and thus not reading these events). 266 select { 267 case sub.es.uninstall <- sub.f: 268 break uninstallLoop 269 case <-sub.f.logs: 270 case <-sub.f.txs: 271 case <-sub.f.headers: 272 } 273 } 274 275 // wait for filter to be uninstalled in work loop before returning 276 // this ensures that the manager won't use the event channel which 277 // will probably be closed by the client asap after this method returns. 278 <-sub.Err() 279 }) 280 } 281 282 // subscribe installs the subscription in the event broadcast loop. 283 func (es *EventSystem) subscribe(sub *subscription) *Subscription { 284 es.install <- sub 285 <-sub.installed 286 return &Subscription{ID: sub.id, f: sub, es: es} 287 } 288 289 // SubscribeLogs creates a subscription that will write all logs matching the 290 // given criteria to the given logs channel. Default value for the from and to 291 // block is "latest". If the fromBlock > toBlock an error is returned. 292 func (es *EventSystem) SubscribeLogs(crit interfaces.FilterQuery, logs chan []*types.Log) (*Subscription, error) { 293 var from, to rpc.BlockNumber 294 if crit.FromBlock == nil { 295 from = rpc.LatestBlockNumber 296 } else { 297 from = rpc.BlockNumber(crit.FromBlock.Int64()) 298 } 299 if crit.ToBlock == nil { 300 to = rpc.LatestBlockNumber 301 } else { 302 to = rpc.BlockNumber(crit.ToBlock.Int64()) 303 } 304 305 // only interested in pending logs 306 if from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber { 307 return es.subscribePendingLogs(crit, logs), nil 308 } 309 // only interested in new mined logs 310 if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber { 311 return es.subscribeLogs(crit, logs), nil 312 } 313 // only interested in mined logs within a specific block range 314 if from >= 0 && to >= 0 && to >= from { 315 return es.subscribeLogs(crit, logs), nil 316 } 317 // interested in mined logs from a specific block number, new logs and pending logs 318 if from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber { 319 return es.subscribeMinedPendingLogs(crit, logs), nil 320 } 321 // interested in logs from a specific block number to new mined blocks 322 if from >= 0 && to == rpc.LatestBlockNumber { 323 return es.subscribeLogs(crit, logs), nil 324 } 325 return nil, errors.New("invalid from and to block combination: from > to") 326 } 327 328 func (es *EventSystem) SubscribeAcceptedLogs(crit interfaces.FilterQuery, logs chan []*types.Log) (*Subscription, error) { 329 var from, to rpc.BlockNumber 330 if crit.FromBlock == nil { 331 from = rpc.LatestBlockNumber 332 } else { 333 from = rpc.BlockNumber(crit.FromBlock.Int64()) 334 } 335 if crit.ToBlock == nil { 336 to = rpc.LatestBlockNumber 337 } else { 338 to = rpc.BlockNumber(crit.ToBlock.Int64()) 339 } 340 341 // subscribeAcceptedLogs if filter is valid (from SubscribeLogs) 342 if from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber || 343 from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber || 344 from >= 0 && to >= 0 && to >= from || 345 from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber || 346 from >= 0 && to == rpc.LatestBlockNumber { 347 return es.subscribeAcceptedLogs(crit, logs), nil 348 } 349 350 return nil, fmt.Errorf("invalid from and to block combination: from > to") 351 } 352 353 func (es *EventSystem) subscribeAcceptedLogs(crit interfaces.FilterQuery, logs chan []*types.Log) *Subscription { 354 sub := &subscription{ 355 id: rpc.NewID(), 356 typ: AcceptedLogsSubscription, 357 logsCrit: crit, 358 created: time.Now(), 359 logs: logs, 360 txs: make(chan []*types.Transaction), 361 headers: make(chan *types.Header), 362 installed: make(chan struct{}), 363 err: make(chan error), 364 } 365 return es.subscribe(sub) 366 } 367 368 // subscribeMinedPendingLogs creates a subscription that returned mined and 369 // pending logs that match the given criteria. 370 func (es *EventSystem) subscribeMinedPendingLogs(crit interfaces.FilterQuery, logs chan []*types.Log) *Subscription { 371 sub := &subscription{ 372 id: rpc.NewID(), 373 typ: MinedAndPendingLogsSubscription, 374 logsCrit: crit, 375 created: time.Now(), 376 logs: logs, 377 txs: make(chan []*types.Transaction), 378 headers: make(chan *types.Header), 379 installed: make(chan struct{}), 380 err: make(chan error), 381 } 382 return es.subscribe(sub) 383 } 384 385 // subscribeLogs creates a subscription that will write all logs matching the 386 // given criteria to the given logs channel. 387 func (es *EventSystem) subscribeLogs(crit interfaces.FilterQuery, logs chan []*types.Log) *Subscription { 388 sub := &subscription{ 389 id: rpc.NewID(), 390 typ: LogsSubscription, 391 logsCrit: crit, 392 created: time.Now(), 393 logs: logs, 394 txs: make(chan []*types.Transaction), 395 headers: make(chan *types.Header), 396 installed: make(chan struct{}), 397 err: make(chan error), 398 } 399 return es.subscribe(sub) 400 } 401 402 // subscribePendingLogs creates a subscription that writes contract event logs for 403 // transactions that enter the transaction pool. 404 func (es *EventSystem) subscribePendingLogs(crit interfaces.FilterQuery, logs chan []*types.Log) *Subscription { 405 sub := &subscription{ 406 id: rpc.NewID(), 407 typ: PendingLogsSubscription, 408 logsCrit: crit, 409 created: time.Now(), 410 logs: logs, 411 txs: make(chan []*types.Transaction), 412 headers: make(chan *types.Header), 413 installed: make(chan struct{}), 414 err: make(chan error), 415 } 416 return es.subscribe(sub) 417 } 418 419 // SubscribeNewHeads creates a subscription that writes the header of a block that is 420 // imported in the chain. 421 func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscription { 422 sub := &subscription{ 423 id: rpc.NewID(), 424 typ: BlocksSubscription, 425 created: time.Now(), 426 logs: make(chan []*types.Log), 427 txs: make(chan []*types.Transaction), 428 headers: headers, 429 installed: make(chan struct{}), 430 err: make(chan error), 431 } 432 return es.subscribe(sub) 433 } 434 435 // SubscribeAcceptedHeads creates a subscription that writes the header of an accepted block that is 436 // imported in the chain. 437 func (es *EventSystem) SubscribeAcceptedHeads(headers chan *types.Header) *Subscription { 438 sub := &subscription{ 439 id: rpc.NewID(), 440 typ: AcceptedBlocksSubscription, 441 created: time.Now(), 442 logs: make(chan []*types.Log), 443 txs: make(chan []*types.Transaction), 444 headers: headers, 445 installed: make(chan struct{}), 446 err: make(chan error), 447 } 448 return es.subscribe(sub) 449 } 450 451 // SubscribePendingTxs creates a subscription that writes transactions for 452 // transactions that enter the transaction pool. 453 func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subscription { 454 sub := &subscription{ 455 id: rpc.NewID(), 456 typ: PendingTransactionsSubscription, 457 created: time.Now(), 458 logs: make(chan []*types.Log), 459 txs: txs, 460 headers: make(chan *types.Header), 461 installed: make(chan struct{}), 462 err: make(chan error), 463 } 464 return es.subscribe(sub) 465 } 466 467 // SubscribeAcceptedTxs creates a subscription that writes transactions for 468 // transactions have been accepted. 469 func (es *EventSystem) SubscribeAcceptedTxs(txs chan []*types.Transaction) *Subscription { 470 sub := &subscription{ 471 id: rpc.NewID(), 472 typ: AcceptedTransactionsSubscription, 473 created: time.Now(), 474 logs: make(chan []*types.Log), 475 txs: txs, 476 headers: make(chan *types.Header), 477 installed: make(chan struct{}), 478 err: make(chan error), 479 } 480 return es.subscribe(sub) 481 } 482 483 type filterIndex map[Type]map[rpc.ID]*subscription 484 485 func (es *EventSystem) handleLogs(filters filterIndex, ev []*types.Log) { 486 if len(ev) == 0 { 487 return 488 } 489 for _, f := range filters[LogsSubscription] { 490 matchedLogs := filterLogs(ev, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) 491 if len(matchedLogs) > 0 { 492 f.logs <- matchedLogs 493 } 494 } 495 } 496 497 func (es *EventSystem) handleAcceptedLogs(filters filterIndex, ev []*types.Log) { 498 if len(ev) == 0 { 499 return 500 } 501 for _, f := range filters[AcceptedLogsSubscription] { 502 matchedLogs := filterLogs(ev, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) 503 if len(matchedLogs) > 0 { 504 f.logs <- matchedLogs 505 } 506 } 507 } 508 509 func (es *EventSystem) handlePendingLogs(filters filterIndex, ev []*types.Log) { 510 if len(ev) == 0 { 511 return 512 } 513 for _, f := range filters[PendingLogsSubscription] { 514 matchedLogs := filterLogs(ev, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) 515 if len(matchedLogs) > 0 { 516 f.logs <- matchedLogs 517 } 518 } 519 } 520 521 func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent, accepted bool) { 522 for _, f := range filters[PendingTransactionsSubscription] { 523 f.txs <- ev.Txs 524 } 525 if accepted { 526 for _, f := range filters[AcceptedTransactionsSubscription] { 527 f.txs <- ev.Txs 528 } 529 } 530 } 531 532 func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) { 533 for _, f := range filters[BlocksSubscription] { 534 f.headers <- ev.Block.Header() 535 } 536 } 537 538 func (es *EventSystem) handleChainAcceptedEvent(filters filterIndex, ev core.ChainEvent) { 539 for _, f := range filters[AcceptedBlocksSubscription] { 540 f.headers <- ev.Block.Header() 541 } 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.logsAcceptedSub.Unsubscribe() 551 es.rmLogsSub.Unsubscribe() 552 es.pendingLogsSub.Unsubscribe() 553 es.chainSub.Unsubscribe() 554 es.chainAcceptedSub.Unsubscribe() 555 es.txsAcceptedSub.Unsubscribe() 556 }() 557 558 index := make(filterIndex) 559 for i := UnknownSubscription; i < LastIndexSubscription; i++ { 560 index[i] = make(map[rpc.ID]*subscription) 561 } 562 563 for { 564 select { 565 case ev := <-es.txsCh: 566 es.handleTxsEvent(index, ev, false) 567 case ev := <-es.logsCh: 568 es.handleLogs(index, ev) 569 case ev := <-es.logsAcceptedCh: 570 es.handleAcceptedLogs(index, ev) 571 case ev := <-es.rmLogsCh: 572 es.handleLogs(index, ev.Logs) 573 case ev := <-es.pendingLogsCh: 574 es.handlePendingLogs(index, ev) 575 case ev := <-es.chainCh: 576 es.handleChainEvent(index, ev) 577 case ev := <-es.chainAcceptedCh: 578 es.handleChainAcceptedEvent(index, ev) 579 case ev := <-es.txsAcceptedCh: 580 es.handleTxsEvent(index, ev, true) 581 582 case f := <-es.install: 583 if f.typ == MinedAndPendingLogsSubscription { 584 // the type are logs and pending logs subscriptions 585 index[LogsSubscription][f.id] = f 586 index[PendingLogsSubscription][f.id] = f 587 } else { 588 index[f.typ][f.id] = f 589 } 590 close(f.installed) 591 592 case f := <-es.uninstall: 593 if f.typ == MinedAndPendingLogsSubscription { 594 // the type are logs and pending logs subscriptions 595 delete(index[LogsSubscription], f.id) 596 delete(index[PendingLogsSubscription], f.id) 597 } else { 598 delete(index[f.typ], f.id) 599 } 600 close(f.err) 601 602 // System stopped 603 case <-es.txsSub.Err(): 604 return 605 case <-es.logsSub.Err(): 606 return 607 case <-es.logsAcceptedSub.Err(): 608 return 609 case <-es.rmLogsSub.Err(): 610 return 611 case <-es.chainSub.Err(): 612 return 613 case <-es.chainAcceptedSub.Err(): 614 return 615 case <-es.txsAcceptedSub.Err(): 616 return 617 } 618 } 619 }