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