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