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