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