Skip to content

Commit 9422e73

Browse files
committed
eth/filters,ethclient,node: install newSideHeads subscription (ethereum#293)
etclabscore/core-geth#293 commit 85e4d0b644e4ec692d199899dd6e4e34c41ff36c Author: meows <[email protected]> Date: Tue Jan 19 07:07:02 2021 -0600 ethclient: install missing eth_newSideBlockFilter method for rpc.discover test Date: 2021-01-19 07:07:02-06:00 Signed-off-by: meows <[email protected]> commit c8cf61df8b9014620508d71f745b316569fec5e1 Author: meows <[email protected]> Date: Tue Jan 12 09:54:37 2021 -0600 filters: (lint) fix comment Resolves https://github.com/etclabscore/core-geth/pull/293/files/bf5bd1290fdb0ad59e8d4255100934d053be6e5d#r555805816 Date: 2021-01-12 09:54:37-06:00 Signed-off-by: meows <[email protected]> commit bf5bd1290fdb0ad59e8d4255100934d053be6e5d Author: meows <[email protected]> Date: Tue Jan 12 08:05:16 2021 -0600 filters: (lint) goimports Date: 2021-01-12 08:05:16-06:00 Signed-off-by: meows <[email protected]> commit 6ae8387e1a45a407fdb12a2c30dc368fa001b20e Author: meows <[email protected]> Date: Mon Jan 11 18:13:48 2021 -0600 eth/filters,ethclient,node: install newSideHeads subscription The newSideHeads subscription work very similarly to the newHeads subscription; instead, non-canonical blocks are channeled.
1 parent c354dd4 commit 9422e73

File tree

9 files changed

+362
-4
lines changed

9 files changed

+362
-4
lines changed

accounts/abi/bind/backends/simulated.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,33 @@ func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *type
677677
}), nil
678678
}
679679

680+
// SubscribeNewHead returns an event subscription for a new header imported as non-canonical (side status).
681+
func (b *SimulatedBackend) SubscribeNewSideHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
682+
// subscribe to a new head
683+
sink := make(chan *types.Header)
684+
sub := b.events.SubscribeNewSideHeads(sink)
685+
686+
return event.NewSubscription(func(quit <-chan struct{}) error {
687+
defer sub.Unsubscribe()
688+
for {
689+
select {
690+
case head := <-sink:
691+
select {
692+
case ch <- head:
693+
case err := <-sub.Err():
694+
return err
695+
case <-quit:
696+
return nil
697+
}
698+
case err := <-sub.Err():
699+
return err
700+
case <-quit:
701+
return nil
702+
}
703+
}
704+
}), nil
705+
}
706+
680707
// AdjustTime adds a time shift to the simulated clock.
681708
// It can only be called on empty blocks.
682709
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
@@ -770,6 +797,10 @@ func (fb *filterBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Su
770797
return fb.bc.SubscribeChainEvent(ch)
771798
}
772799

800+
func (fb *filterBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
801+
return fb.bc.SubscribeChainSideEvent(ch)
802+
}
803+
773804
func (fb *filterBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
774805
return fb.bc.SubscribeRemovedLogsEvent(ch)
775806
}

core/blockchain.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1695,7 +1695,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
16951695
}
16961696
// In theory we should fire a ChainHeadEvent when we inject
16971697
// a canonical block, but sometimes we can insert a batch of
1698-
// canonicial blocks. Avoid firing too much ChainHeadEvents,
1698+
// canonical blocks. Avoid firing too much ChainHeadEvents,
16991699
// we will fire an accumulated ChainHeadEvent and disable fire
17001700
// event here.
17011701
if emitHeadEvent {

eth/filters/api.go

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,39 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
203203
return headerSub.ID
204204
}
205205

206+
// NewSideBlockFilter creates a filter that fetches blocks that are imported into the chain with a non-canonical status.
207+
// It is part of the filter package since polling goes with eth_getFilterChanges.
208+
func (api *PublicFilterAPI) NewSideBlockFilter() rpc.ID {
209+
var (
210+
headers = make(chan *types.Header)
211+
headerSub = api.events.SubscribeNewSideHeads(headers)
212+
)
213+
214+
api.filtersMu.Lock()
215+
api.filters[headerSub.ID] = &filter{typ: SideBlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub}
216+
api.filtersMu.Unlock()
217+
218+
go func() {
219+
for {
220+
select {
221+
case h := <-headers:
222+
api.filtersMu.Lock()
223+
if f, found := api.filters[headerSub.ID]; found {
224+
f.hashes = append(f.hashes, h.Hash())
225+
}
226+
api.filtersMu.Unlock()
227+
case <-headerSub.Err():
228+
api.filtersMu.Lock()
229+
delete(api.filters, headerSub.ID)
230+
api.filtersMu.Unlock()
231+
return
232+
}
233+
}
234+
}()
235+
236+
return headerSub.ID
237+
}
238+
206239
// NewHeads send a notification each time a new (header) block is appended to the chain.
207240
func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) {
208241
notifier, supported := rpc.NotifierFromContext(ctx)
@@ -233,7 +266,37 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er
233266
return rpcSub, nil
234267
}
235268

236-
// Logs creates a subscription that fires for all new log that match the given filter criteria.
269+
// NewSideHeads send a notification each time a new non-canonical (header) block is written to the database.
270+
func (api *PublicFilterAPI) NewSideHeads(ctx context.Context) (*rpc.Subscription, error) {
271+
notifier, supported := rpc.NotifierFromContext(ctx)
272+
if !supported {
273+
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
274+
}
275+
276+
rpcSub := notifier.CreateSubscription()
277+
278+
go func() {
279+
headers := make(chan *types.Header)
280+
headersSub := api.events.SubscribeNewSideHeads(headers)
281+
282+
for {
283+
select {
284+
case h := <-headers:
285+
notifier.Notify(rpcSub.ID, h)
286+
case <-rpcSub.Err():
287+
headersSub.Unsubscribe()
288+
return
289+
case <-notifier.Closed():
290+
headersSub.Unsubscribe()
291+
return
292+
}
293+
}
294+
}()
295+
296+
return rpcSub, nil
297+
}
298+
299+
// Logs creates a subscription that fires for all new logs that match the given filter criteria.
237300
func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) {
238301
notifier, supported := rpc.NotifierFromContext(ctx)
239302
if !supported {
@@ -424,7 +487,7 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
424487
f.deadline.Reset(deadline)
425488

426489
switch f.typ {
427-
case PendingTransactionsSubscription, BlocksSubscription:
490+
case PendingTransactionsSubscription, BlocksSubscription, SideBlocksSubscription:
428491
hashes := f.hashes
429492
f.hashes = nil
430493
return returnHashes(hashes), nil

eth/filters/filter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type Backend interface {
3939

4040
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
4141
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
42+
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
4243
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
4344
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
4445
SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription

eth/filters/filter_system.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ const (
5252
PendingTransactionsSubscription
5353
// BlocksSubscription queries hashes for blocks that are imported
5454
BlocksSubscription
55+
// SideBlocksSubscription queries blocks that are imported non-canonically
56+
SideBlocksSubscription
5557
// LastSubscription keeps track of the last index
5658
LastIndexSubscription
5759
)
@@ -93,6 +95,7 @@ type EventSystem struct {
9395
rmLogsSub event.Subscription // Subscription for removed log event
9496
pendingLogsSub event.Subscription // Subscription for pending log event
9597
chainSub event.Subscription // Subscription for new chain event
98+
chainSideSub event.Subscription // Subscription for new side chain event
9699

97100
// Channels
98101
install chan *subscription // install filter for event notification
@@ -102,6 +105,7 @@ type EventSystem struct {
102105
pendingLogsCh chan []*types.Log // Channel to receive new log event
103106
rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event
104107
chainCh chan core.ChainEvent // Channel to receive new chain event
108+
chainSideCh chan core.ChainSideEvent // Channel to receive new side chain event
105109
}
106110

107111
// NewEventSystem creates a new manager that listens for event on the given mux,
@@ -121,17 +125,19 @@ func NewEventSystem(backend Backend, lightMode bool) *EventSystem {
121125
rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize),
122126
pendingLogsCh: make(chan []*types.Log, logsChanSize),
123127
chainCh: make(chan core.ChainEvent, chainEvChanSize),
128+
chainSideCh: make(chan core.ChainSideEvent, chainEvChanSize),
124129
}
125130

126131
// Subscribe events
127132
m.txsSub = m.backend.SubscribeNewTxsEvent(m.txsCh)
128133
m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh)
129134
m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh)
130135
m.chainSub = m.backend.SubscribeChainEvent(m.chainCh)
136+
m.chainSideSub = m.backend.SubscribeChainSideEvent(m.chainSideCh)
131137
m.pendingLogsSub = m.backend.SubscribePendingLogsEvent(m.pendingLogsCh)
132138

133139
// Make sure none of the subscriptions are empty
134-
if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil || m.pendingLogsSub == nil {
140+
if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil || m.chainSideSub == nil || m.pendingLogsSub == nil {
135141
log.Crit("Subscribe for event system failed")
136142
}
137143

@@ -290,6 +296,22 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti
290296
return es.subscribe(sub)
291297
}
292298

299+
// SubscribeNewSideHeads creates a subscription that writes the header of a block that is
300+
// imported as a side chain.
301+
func (es *EventSystem) SubscribeNewSideHeads(headers chan *types.Header) *Subscription {
302+
sub := &subscription{
303+
id: rpc.NewID(),
304+
typ: SideBlocksSubscription,
305+
created: time.Now(),
306+
logs: make(chan []*types.Log),
307+
hashes: make(chan []common.Hash),
308+
headers: headers,
309+
installed: make(chan struct{}),
310+
err: make(chan error),
311+
}
312+
return es.subscribe(sub)
313+
}
314+
293315
// SubscribePendingTxs creates a subscription that writes transaction hashes for
294316
// transactions that enter the transaction pool.
295317
func (es *EventSystem) SubscribePendingTxs(hashes chan []common.Hash) *Subscription {
@@ -366,6 +388,25 @@ func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent)
366388
}
367389
}
368390

391+
func (es *EventSystem) handleChainSideEvent(filters filterIndex, ev core.ChainSideEvent) {
392+
for _, f := range filters[SideBlocksSubscription] {
393+
f.headers <- ev.Block.Header()
394+
}
395+
// Handle filtered log eventing similarly to the newHead event, except that 'remove' will always be set to true
396+
// (indicating the logs come from a non-canonical block).
397+
// When newHeads and newSideHeads are subscribed to at the same time, this can result in certain logs being broadcast
398+
// repetitiously.
399+
if es.lightMode && len(filters[LogsSubscription]) > 0 {
400+
es.lightFilterNewSideHead(ev.Block.Header(), func(header *types.Header, remove bool) {
401+
for _, f := range filters[LogsSubscription] {
402+
if matchedLogs := es.lightFilterLogs(header, f.logsCrit.Addresses, f.logsCrit.Topics, remove); len(matchedLogs) > 0 {
403+
f.logs <- matchedLogs
404+
}
405+
}
406+
})
407+
}
408+
}
409+
369410
func (es *EventSystem) lightFilterNewHead(newHeader *types.Header, callBack func(*types.Header, bool)) {
370411
oldh := es.lastHead
371412
es.lastHead = newHeader
@@ -399,6 +440,10 @@ func (es *EventSystem) lightFilterNewHead(newHeader *types.Header, callBack func
399440
}
400441
}
401442

443+
func (es *EventSystem) lightFilterNewSideHead(header *types.Header, callBack func(*types.Header, bool)) {
444+
callBack(header, true)
445+
}
446+
402447
// filter logs of a single header in light client mode
403448
func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.Address, topics [][]common.Hash, remove bool) []*types.Log {
404449
if bloomFilter(header.Bloom, addresses, topics) {
@@ -448,6 +493,7 @@ func (es *EventSystem) eventLoop() {
448493
es.rmLogsSub.Unsubscribe()
449494
es.pendingLogsSub.Unsubscribe()
450495
es.chainSub.Unsubscribe()
496+
es.chainSideSub.Unsubscribe()
451497
}()
452498

453499
index := make(filterIndex)
@@ -467,6 +513,8 @@ func (es *EventSystem) eventLoop() {
467513
es.handlePendingLogs(index, ev)
468514
case ev := <-es.chainCh:
469515
es.handleChainEvent(index, ev)
516+
case ev := <-es.chainSideCh:
517+
es.handleChainSideEvent(index, ev)
470518

471519
case f := <-es.install:
472520
if f.typ == MinedAndPendingLogsSubscription {
@@ -497,6 +545,8 @@ func (es *EventSystem) eventLoop() {
497545
return
498546
case <-es.chainSub.Err():
499547
return
548+
case <-es.chainSideSub.Err():
549+
return
500550
}
501551
}
502552
}

eth/filters/filter_system_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type testBackend struct {
4949
rmLogsFeed event.Feed
5050
pendingLogsFeed event.Feed
5151
chainFeed event.Feed
52+
chainSideFeed event.Feed
5253
}
5354

5455
func (b *testBackend) ChainDb() ethdb.Database {
@@ -123,6 +124,10 @@ func (b *testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subsc
123124
return b.chainFeed.Subscribe(ch)
124125
}
125126

127+
func (b *testBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
128+
return b.chainSideFeed.Subscribe(ch)
129+
}
130+
126131
func (b *testBackend) BloomStatus() (uint64, uint64) {
127132
return vars.BloomBitsBlocks, b.sections
128133
}
@@ -210,6 +215,62 @@ func TestBlockSubscription(t *testing.T) {
210215
<-sub1.Err()
211216
}
212217

218+
// TestSideBlockSubscription tests if a block subscription returns block hashes for posted chain events.
219+
// It creates multiple subscriptions:
220+
// - one at the start and should receive all posted chain events and a second (blockHashes)
221+
// - one that is created after a cutoff moment and uninstalled after a second cutoff moment (blockHashes[cutoff1:cutoff2])
222+
// - one that is created after the second cutoff moment (blockHashes[cutoff2:])
223+
func TestSideBlockSubscription(t *testing.T) {
224+
t.Parallel()
225+
226+
var (
227+
db = rawdb.NewMemoryDatabase()
228+
backend = &testBackend{db: db}
229+
api = NewPublicFilterAPI(backend, false)
230+
genesis = core.MustCommitGenesis(db, new(genesisT.Genesis))
231+
chain, _ = core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 10, func(i int, gen *core.BlockGen) {})
232+
chainSideEvents = []core.ChainSideEvent{}
233+
)
234+
235+
for _, blk := range chain {
236+
chainSideEvents = append(chainSideEvents, core.ChainSideEvent{Block: blk})
237+
}
238+
239+
chan0 := make(chan *types.Header)
240+
sub0 := api.events.SubscribeNewSideHeads(chan0)
241+
chan1 := make(chan *types.Header)
242+
sub1 := api.events.SubscribeNewSideHeads(chan1)
243+
244+
go func() { // simulate client
245+
i1, i2 := 0, 0
246+
for i1 != len(chainSideEvents) || i2 != len(chainSideEvents) {
247+
select {
248+
case header := <-chan0:
249+
if chainSideEvents[i1].Block.Hash() != header.Hash() {
250+
t.Errorf("sub0 received invalid hash on index %d, want %x, got %x", i1, chainSideEvents[i1].Block.Hash(), header.Hash())
251+
}
252+
i1++
253+
case header := <-chan1:
254+
if chainSideEvents[i2].Block.Hash() != header.Hash() {
255+
t.Errorf("sub1 received invalid hash on index %d, want %x, got %x", i2, chainSideEvents[i2].Block.Hash(), header.Hash())
256+
}
257+
i2++
258+
}
259+
}
260+
261+
sub0.Unsubscribe()
262+
sub1.Unsubscribe()
263+
}()
264+
265+
time.Sleep(1 * time.Second)
266+
for _, e := range chainSideEvents {
267+
backend.chainSideFeed.Send(e)
268+
}
269+
270+
<-sub0.Err()
271+
<-sub1.Err()
272+
}
273+
213274
// TestPendingTxFilter tests whether pending tx filters retrieve all pending transactions that are posted to the event mux.
214275
func TestPendingTxFilter(t *testing.T) {
215276
t.Parallel()

ethclient/ethclient.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,12 @@ func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header)
335335
return ec.c.EthSubscribe(ctx, ch, "newHeads")
336336
}
337337

338+
// SubscribeNewSideHead subscribes to notifications about the current blockchain head
339+
// on the given channel.
340+
func (ec *Client) SubscribeNewSideHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
341+
return ec.c.EthSubscribe(ctx, ch, "newSideHeads")
342+
}
343+
338344
// State Access
339345

340346
// NetworkID returns the network ID (also known as the chain ID) for this chain.

0 commit comments

Comments
 (0)