github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/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 "fmt" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "github.com/ethereum/go-ethereum" 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/common/lru" 31 "github.com/ethereum/go-ethereum/core" 32 "github.com/ethereum/go-ethereum/core/bloombits" 33 "github.com/ethereum/go-ethereum/core/types" 34 "github.com/ethereum/go-ethereum/ethdb" 35 "github.com/ethereum/go-ethereum/event" 36 "github.com/ethereum/go-ethereum/log" 37 "github.com/ethereum/go-ethereum/params" 38 "github.com/ethereum/go-ethereum/rpc" 39 ) 40 41 // Config represents the configuration of the filter system. 42 type Config struct { 43 LogCacheSize int // maximum number of cached blocks (default: 32) 44 Timeout time.Duration // how long filters stay active (default: 5min) 45 } 46 47 func (cfg Config) withDefaults() Config { 48 if cfg.Timeout == 0 { 49 cfg.Timeout = 5 * time.Minute 50 } 51 if cfg.LogCacheSize == 0 { 52 cfg.LogCacheSize = 32 53 } 54 return cfg 55 } 56 57 type Backend interface { 58 ChainDb() ethdb.Database 59 HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) 60 HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) 61 GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) 62 GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) 63 GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) 64 65 CurrentHeader() *types.Header 66 ChainConfig() *params.ChainConfig 67 SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription 68 SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription 69 SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription 70 SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription 71 72 BloomStatus() (uint64, uint64) 73 ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) 74 } 75 76 // FilterSystem holds resources shared by all filters. 77 type FilterSystem struct { 78 backend Backend 79 logsCache *lru.Cache[common.Hash, *logCacheElem] 80 cfg *Config 81 } 82 83 // NewFilterSystem creates a filter system. 84 func NewFilterSystem(backend Backend, config Config) *FilterSystem { 85 config = config.withDefaults() 86 return &FilterSystem{ 87 backend: backend, 88 logsCache: lru.NewCache[common.Hash, *logCacheElem](config.LogCacheSize), 89 cfg: &config, 90 } 91 } 92 93 type logCacheElem struct { 94 logs []*types.Log 95 body atomic.Pointer[types.Body] 96 } 97 98 // cachedLogElem loads block logs from the backend and caches the result. 99 func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Hash, number uint64) (*logCacheElem, error) { 100 cached, ok := sys.logsCache.Get(blockHash) 101 if ok { 102 return cached, nil 103 } 104 105 logs, err := sys.backend.GetLogs(ctx, blockHash, number) 106 if err != nil { 107 return nil, err 108 } 109 if logs == nil { 110 return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString()) 111 } 112 // Database logs are un-derived. 113 // Fill in whatever we can (txHash is inaccessible at this point). 114 flattened := make([]*types.Log, 0) 115 var logIdx uint 116 for i, txLogs := range logs { 117 for _, log := range txLogs { 118 log.BlockHash = blockHash 119 log.BlockNumber = number 120 log.TxIndex = uint(i) 121 log.Index = logIdx 122 logIdx++ 123 flattened = append(flattened, log) 124 } 125 } 126 elem := &logCacheElem{logs: flattened} 127 sys.logsCache.Add(blockHash, elem) 128 return elem, nil 129 } 130 131 func (sys *FilterSystem) cachedGetBody(ctx context.Context, elem *logCacheElem, hash common.Hash, number uint64) (*types.Body, error) { 132 if body := elem.body.Load(); body != nil { 133 return body, nil 134 } 135 body, err := sys.backend.GetBody(ctx, hash, rpc.BlockNumber(number)) 136 if err != nil { 137 return nil, err 138 } 139 elem.body.Store(body) 140 return body, nil 141 } 142 143 // Type determines the kind of filter and is used to put the filter in to 144 // the correct bucket when added. 145 type Type byte 146 147 const ( 148 // UnknownSubscription indicates an unknown subscription type 149 UnknownSubscription Type = iota 150 // LogsSubscription queries for new or removed (chain reorg) logs 151 LogsSubscription 152 // PendingTransactionsSubscription queries for pending transactions entering 153 // the pending state 154 PendingTransactionsSubscription 155 // BlocksSubscription queries hashes for blocks that are imported 156 BlocksSubscription 157 // LastIndexSubscription keeps track of the last index 158 LastIndexSubscription 159 ) 160 161 const ( 162 // txChanSize is the size of channel listening to NewTxsEvent. 163 // The number is referenced from the size of tx pool. 164 txChanSize = 4096 165 // rmLogsChanSize is the size of channel listening to RemovedLogsEvent. 166 rmLogsChanSize = 10 167 // logsChanSize is the size of channel listening to LogsEvent. 168 logsChanSize = 10 169 // chainEvChanSize is the size of channel listening to ChainEvent. 170 chainEvChanSize = 10 171 ) 172 173 type subscription struct { 174 id rpc.ID 175 typ Type 176 created time.Time 177 logsCrit ethereum.FilterQuery 178 logs chan []*types.Log 179 txs chan []*types.Transaction 180 headers chan *types.Header 181 installed chan struct{} // closed when the filter is installed 182 err chan error // closed when the filter is uninstalled 183 } 184 185 // EventSystem creates subscriptions, processes events and broadcasts them to the 186 // subscription which match the subscription criteria. 187 type EventSystem struct { 188 backend Backend 189 sys *FilterSystem 190 191 // Subscriptions 192 txsSub event.Subscription // Subscription for new transaction event 193 logsSub event.Subscription // Subscription for new log event 194 rmLogsSub event.Subscription // Subscription for removed log event 195 chainSub event.Subscription // Subscription for new chain event 196 197 // Channels 198 install chan *subscription // install filter for event notification 199 uninstall chan *subscription // remove filter for event notification 200 txsCh chan core.NewTxsEvent // Channel to receive new transactions event 201 logsCh chan []*types.Log // Channel to receive new log event 202 rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event 203 chainCh chan core.ChainEvent // Channel to receive new chain event 204 } 205 206 // NewEventSystem creates a new manager that listens for event on the given mux, 207 // parses and filters them. It uses the all map to retrieve filter changes. The 208 // work loop holds its own index that is used to forward events to filters. 209 // 210 // The returned manager has a loop that needs to be stopped with the Stop function 211 // or by stopping the given mux. 212 func NewEventSystem(sys *FilterSystem) *EventSystem { 213 m := &EventSystem{ 214 sys: sys, 215 backend: sys.backend, 216 install: make(chan *subscription), 217 uninstall: make(chan *subscription), 218 txsCh: make(chan core.NewTxsEvent, txChanSize), 219 logsCh: make(chan []*types.Log, logsChanSize), 220 rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize), 221 chainCh: make(chan core.ChainEvent, chainEvChanSize), 222 } 223 224 // Subscribe events 225 m.txsSub = m.backend.SubscribeNewTxsEvent(m.txsCh) 226 m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh) 227 m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh) 228 m.chainSub = m.backend.SubscribeChainEvent(m.chainCh) 229 230 // Make sure none of the subscriptions are empty 231 if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil { 232 log.Crit("Subscribe for event system failed") 233 } 234 235 go m.eventLoop() 236 return m 237 } 238 239 // Subscription is created when the client registers itself for a particular event. 240 type Subscription struct { 241 ID rpc.ID 242 f *subscription 243 es *EventSystem 244 unsubOnce sync.Once 245 } 246 247 // Err returns a channel that is closed when unsubscribed. 248 func (sub *Subscription) Err() <-chan error { 249 return sub.f.err 250 } 251 252 // Unsubscribe uninstalls the subscription from the event broadcast loop. 253 func (sub *Subscription) Unsubscribe() { 254 sub.unsubOnce.Do(func() { 255 uninstallLoop: 256 for { 257 // write uninstall request and consume logs/hashes. This prevents 258 // the eventLoop broadcast method to deadlock when writing to the 259 // filter event channel while the subscription loop is waiting for 260 // this method to return (and thus not reading these events). 261 select { 262 case sub.es.uninstall <- sub.f: 263 break uninstallLoop 264 case <-sub.f.logs: 265 case <-sub.f.txs: 266 case <-sub.f.headers: 267 } 268 } 269 270 // wait for filter to be uninstalled in work loop before returning 271 // this ensures that the manager won't use the event channel which 272 // will probably be closed by the client asap after this method returns. 273 <-sub.Err() 274 }) 275 } 276 277 // subscribe installs the subscription in the event broadcast loop. 278 func (es *EventSystem) subscribe(sub *subscription) *Subscription { 279 es.install <- sub 280 <-sub.installed 281 return &Subscription{ID: sub.id, f: sub, es: es} 282 } 283 284 // SubscribeLogs creates a subscription that will write all logs matching the 285 // given criteria to the given logs channel. Default value for the from and to 286 // block is "latest". If the fromBlock > toBlock an error is returned. 287 func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) (*Subscription, error) { 288 if len(crit.Topics) > maxTopics { 289 return nil, errExceedMaxTopics 290 } 291 var from, to rpc.BlockNumber 292 if crit.FromBlock == nil { 293 from = rpc.LatestBlockNumber 294 } else { 295 from = rpc.BlockNumber(crit.FromBlock.Int64()) 296 } 297 if crit.ToBlock == nil { 298 to = rpc.LatestBlockNumber 299 } else { 300 to = rpc.BlockNumber(crit.ToBlock.Int64()) 301 } 302 303 // Pending logs are not supported anymore. 304 if from == rpc.PendingBlockNumber || to == rpc.PendingBlockNumber { 305 return nil, errPendingLogsUnsupported 306 } 307 308 // only interested in new mined logs 309 if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber { 310 return es.subscribeLogs(crit, logs), nil 311 } 312 // only interested in mined logs within a specific block range 313 if from >= 0 && to >= 0 && to >= from { 314 return es.subscribeLogs(crit, logs), nil 315 } 316 // interested in logs from a specific block number to new mined blocks 317 if from >= 0 && to == rpc.LatestBlockNumber { 318 return es.subscribeLogs(crit, logs), nil 319 } 320 return nil, errInvalidBlockRange 321 } 322 323 // subscribeLogs creates a subscription that will write all logs matching the 324 // given criteria to the given logs channel. 325 func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription { 326 sub := &subscription{ 327 id: rpc.NewID(), 328 typ: LogsSubscription, 329 logsCrit: crit, 330 created: time.Now(), 331 logs: logs, 332 txs: make(chan []*types.Transaction), 333 headers: make(chan *types.Header), 334 installed: make(chan struct{}), 335 err: make(chan error), 336 } 337 return es.subscribe(sub) 338 } 339 340 // SubscribeNewHeads creates a subscription that writes the header of a block that is 341 // imported in the chain. 342 func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscription { 343 sub := &subscription{ 344 id: rpc.NewID(), 345 typ: BlocksSubscription, 346 created: time.Now(), 347 logs: make(chan []*types.Log), 348 txs: make(chan []*types.Transaction), 349 headers: headers, 350 installed: make(chan struct{}), 351 err: make(chan error), 352 } 353 return es.subscribe(sub) 354 } 355 356 // SubscribePendingTxs creates a subscription that writes transactions for 357 // transactions that enter the transaction pool. 358 func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subscription { 359 sub := &subscription{ 360 id: rpc.NewID(), 361 typ: PendingTransactionsSubscription, 362 created: time.Now(), 363 logs: make(chan []*types.Log), 364 txs: txs, 365 headers: make(chan *types.Header), 366 installed: make(chan struct{}), 367 err: make(chan error), 368 } 369 return es.subscribe(sub) 370 } 371 372 type filterIndex map[Type]map[rpc.ID]*subscription 373 374 func (es *EventSystem) handleLogs(filters filterIndex, ev []*types.Log) { 375 if len(ev) == 0 { 376 return 377 } 378 for _, f := range filters[LogsSubscription] { 379 matchedLogs := filterLogs(ev, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) 380 if len(matchedLogs) > 0 { 381 f.logs <- matchedLogs 382 } 383 } 384 } 385 386 func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) { 387 for _, f := range filters[PendingTransactionsSubscription] { 388 f.txs <- ev.Txs 389 } 390 } 391 392 func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) { 393 for _, f := range filters[BlocksSubscription] { 394 f.headers <- ev.Block.Header() 395 } 396 } 397 398 // eventLoop (un)installs filters and processes mux events. 399 func (es *EventSystem) eventLoop() { 400 // Ensure all subscriptions get cleaned up 401 defer func() { 402 es.txsSub.Unsubscribe() 403 es.logsSub.Unsubscribe() 404 es.rmLogsSub.Unsubscribe() 405 es.chainSub.Unsubscribe() 406 }() 407 408 index := make(filterIndex) 409 for i := UnknownSubscription; i < LastIndexSubscription; i++ { 410 index[i] = make(map[rpc.ID]*subscription) 411 } 412 413 for { 414 select { 415 case ev := <-es.txsCh: 416 es.handleTxsEvent(index, ev) 417 case ev := <-es.logsCh: 418 es.handleLogs(index, ev) 419 case ev := <-es.rmLogsCh: 420 es.handleLogs(index, ev.Logs) 421 case ev := <-es.chainCh: 422 es.handleChainEvent(index, ev) 423 424 case f := <-es.install: 425 index[f.typ][f.id] = f 426 close(f.installed) 427 428 case f := <-es.uninstall: 429 delete(index[f.typ], f.id) 430 close(f.err) 431 432 // System stopped 433 case <-es.txsSub.Err(): 434 return 435 case <-es.logsSub.Err(): 436 return 437 case <-es.rmLogsSub.Err(): 438 return 439 case <-es.chainSub.Err(): 440 return 441 } 442 } 443 }