fix future reorged log edge case

Signed-off-by: jsvisa <delweng@gmail.com>
This commit is contained in:
Sina Mahmoodi 2023-08-14 14:16:00 +03:00 committed by jsvisa
parent d1f2277b5d
commit 7f30c9e4d1

View file

@ -41,6 +41,7 @@ var (
errConnectDropped = errors.New("connection dropped") errConnectDropped = errors.New("connection dropped")
errInvalidToBlock = errors.New("log subscription does not support history block range") errInvalidToBlock = errors.New("log subscription does not support history block range")
errInvalidFromBlock = errors.New("from block can be only a number, or \"safe\", or \"finalized\"") errInvalidFromBlock = errors.New("from block can be only a number, or \"safe\", or \"finalized\"")
errClientUnsubscribed = errors.New("client unsubscribed")
) )
// filter is a helper struct that holds meta information over the filter type // filter is a helper struct that holds meta information over the filter type
@ -337,15 +338,17 @@ func (api *FilterAPI) histLogs(notifier notifier, rpcSub *rpc.Subscription, from
liveLogs = make(chan []*types.Log) liveLogs = make(chan []*types.Log)
histLogs = make(chan []*types.Log) histLogs = make(chan []*types.Log)
histDone = make(chan error) histDone = make(chan error)
closeC = make(chan struct{})
) )
liveLogsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), liveLogs) liveLogsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), liveLogs)
if err != nil { if err != nil {
return err return err
} }
// The original request ctx will be canceled as soon as the parent goroutine
// returns a subscription. Use a new context instead.
ctx, cancel := context.WithCancel(context.Background())
go func() { go func() {
histDone <- api.doHistLogs(from, crit.Addresses, crit.Topics, histLogs, closeC) histDone <- api.doHistLogs(ctx, from, crit.Addresses, crit.Topics, histLogs)
}() }()
// Compose and notify the logs from liveLogs and histLogs // Compose and notify the logs from liveLogs and histLogs
@ -354,6 +357,7 @@ func (api *FilterAPI) histLogs(notifier notifier, rpcSub *rpc.Subscription, from
delivered uint64 delivered uint64
liveOnly bool liveOnly bool
reorged bool reorged bool
staleHash common.Hash
) )
for { for {
select { select {
@ -381,6 +385,10 @@ func (api *FilterAPI) histLogs(notifier notifier, rpcSub *rpc.Subscription, from
reorged = true reorged = true
} }
if !reorged { if !reorged {
// Reorg in future. Remember fork point.
if logs[0].Removed && staleHash == (common.Hash{}) {
staleHash = logs[0].BlockHash
}
continue continue
} }
if logs[0].Removed { if logs[0].Removed {
@ -401,18 +409,29 @@ func (api *FilterAPI) histLogs(notifier notifier, rpcSub *rpc.Subscription, from
if reorged { if reorged {
continue continue
} }
for _, log := range logs { if logs[0].BlockHash == staleHash {
notifier.Notify(rpcSub.ID, &log) // We have reached the fork point and the historical producer
// is emitting old logs because of delay. Restart the process
// from last delivered block.
logger.Info("Restarting historical logs delivery", "from", logs[0].BlockNumber, "delivered", delivered)
liveLogsSub.Unsubscribe()
// Stop hist logs fetcher
cancel()
if err := api.histLogs(notifier, rpcSub, int64(logs[0].BlockNumber), crit); err != nil {
logger.Warn("failed to restart historical logs delivery", "err", err)
} }
return
}
notifyLogsIf(notifier, rpcSub.ID, logs, nil)
// Assuming batch = all logs of a single block // Assuming batch = all logs of a single block
delivered = logs[0].BlockNumber delivered = logs[0].BlockNumber
case <-rpcSub.Err(): // client send an unsubscribe request case <-rpcSub.Err(): // client send an unsubscribe request
liveLogsSub.Unsubscribe() liveLogsSub.Unsubscribe()
closeC <- struct{}{} cancel()
return return
case <-notifier.Closed(): // connection dropped case <-notifier.Closed(): // connection dropped
liveLogsSub.Unsubscribe() liveLogsSub.Unsubscribe()
closeC <- struct{}{} cancel()
return return
} }
} }
@ -422,29 +441,22 @@ func (api *FilterAPI) histLogs(notifier notifier, rpcSub *rpc.Subscription, from
} }
// doHistLogs retrieves the logs older than current header, and forward them to the histLogs channel. // doHistLogs retrieves the logs older than current header, and forward them to the histLogs channel.
func (api *FilterAPI) doHistLogs(from int64, addrs []common.Address, topics [][]common.Hash, histLogs chan<- []*types.Log, closeC chan struct{}) error { func (api *FilterAPI) doHistLogs(ctx context.Context, from int64, addrs []common.Address, topics [][]common.Hash, histLogs chan<- []*types.Log) error {
// The original request ctx will be canceled as soon as the parent goroutine
// returns a subscription. Use a new context instead.
ctx, cancel := context.WithCancel(context.Background())
// Fetch logs from a range of blocks. // Fetch logs from a range of blocks.
fetchRange := func(from, to int64) error { fetchRange := func(from, to int64) error {
f := api.sys.NewRangeFilter(from, to, addrs, topics) f := api.sys.NewRangeFilter(from, to, addrs, topics)
logsCh, errChan := f.rangeLogsAsync(ctx) logsCh, errChan := f.rangeLogsAsync(ctx)
for { for {
select { select {
case <-closeC:
cancel()
return nil
case logs := <-logsCh: case logs := <-logsCh:
histLogs <- logs select {
case err := <-errChan: case histLogs <- logs:
if err != nil { case <-ctx.Done():
logger.Error("Error while fetching historical logs", "err", err) // Flush out all logs until the range filter voluntarily exits.
return err continue
} }
// Range filter is done. case err := <-errChan:
return nil return err
} }
} }
} }
@ -461,6 +473,10 @@ func (api *FilterAPI) doHistLogs(from int64, addrs []common.Address, topics [][]
return nil return nil
} }
if err := fetchRange(from, head); err != nil { if err := fetchRange(from, head); err != nil {
if errors.Is(err, context.Canceled) {
logger.Info("Historical logs delivery canceled", "from", from, "to", head)
return nil
}
return err return err
} }
// Move forward to the next batch // Move forward to the next batch