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