diff --git a/discovery/syncer.go b/discovery/syncer.go index a154f990..10b6d420 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -918,7 +918,7 @@ func (g *GossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro // Next, we'll consult the time series to obtain the set of known // channel ID's that match their query. startBlock := query.FirstBlockHeight - endBlock := startBlock + query.NumBlocks - 1 + endBlock := query.LastBlockHeight() channelRange, err := g.cfg.channelSeries.FilterChannelRange( query.ChainHash, startBlock, endBlock, ) diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go index 606fc062..8e99fa49 100644 --- a/discovery/syncer_test.go +++ b/discovery/syncer_test.go @@ -915,6 +915,118 @@ func TestGossipSyncerReplyChanRangeQuery(t *testing.T) { } } +// TestGossipSyncerReplyChanRangeQuery tests a variety of +// QueryChannelRange messages to ensure the underlying queries are +// executed with the correct block range +func TestGossipSyncerReplyChanRangeQueryBlockRange(t *testing.T) { + t.Parallel() + + // First create our test gossip syncer that will handle and + // respond to the test queries + _, syncer, chanSeries := newTestSyncer( + lnwire.NewShortChanIDFromInt(10), defaultEncoding, math.MaxInt32, + ) + + // Next construct test queries with various startBlock and endBlock + // ranges + queryReqs := []*lnwire.QueryChannelRange{ + // full range example + { + FirstBlockHeight: uint32(0), + NumBlocks: uint32(math.MaxUint32), + }, + + // small query example that does not overflow + { + FirstBlockHeight: uint32(1000), + NumBlocks: uint32(100), + }, + + // overflow example + { + FirstBlockHeight: uint32(1000), + NumBlocks: uint32(math.MaxUint32), + }, + } + + // Next construct the expected filterRangeReq startHeight and endHeight + // values that we will compare to the captured values + expFilterReqs := []filterRangeReq{ + { + startHeight: uint32(0), + endHeight: uint32(math.MaxUint32 - 1), + }, + { + startHeight: uint32(1000), + endHeight: uint32(1099), + }, + { + startHeight: uint32(1000), + endHeight: uint32(math.MaxUint32), + }, + } + + // We'll then launch a goroutine to capture the filterRangeReqs for + // each request and return those results once all queries have been + // received + resultsCh := make(chan []filterRangeReq, 1) + errCh := make(chan error, 1) + go func() { + // We will capture the values supplied to the chanSeries here + // and return the results once all the requests have been + // collected + capFilterReqs := []filterRangeReq{} + + for filterReq := range chanSeries.filterRangeReqs { + // capture the filter request so we can compare to the + // expected values later + capFilterReqs = append(capFilterReqs, filterReq) + + // Reply with an empty result for each query to allow + // unblock the caller + queryResp := []lnwire.ShortChannelID{} + chanSeries.filterRangeResp <- queryResp + + // Once we have collected all results send the results + // back to the main thread and terminate the goroutine + if len(capFilterReqs) == len(expFilterReqs) { + resultsCh <- capFilterReqs + return + } + + } + }() + + // We'll launch a goroutine to send the query sequentially. This + // goroutine ensures that the timeout logic below on the mainthread + // will be reached + go func() { + for _, query := range queryReqs { + if err := syncer.replyChanRangeQuery(query); err != nil { + errCh <- fmt.Errorf("unable to issue query: %v", err) + return + } + } + }() + + // Wait for the results to be collected and validate that the + // collected results match the expected results, the timeout to + // expire, or an error to occur + select { + case capFilterReq := <-resultsCh: + if !reflect.DeepEqual(expFilterReqs, capFilterReq) { + t.Fatalf("mismatched filter reqs: expected %v, got %v", + spew.Sdump(expFilterReqs), spew.Sdump(capFilterReq)) + } + case <-time.After(time.Second * 10): + t.Fatalf("goroutine did not return within 10 seconds") + case err := <-errCh: + if err != nil { + t.Fatal(err) + } + } +} + // TestGossipSyncerReplyChanRangeQueryNoNewChans tests that if we issue a reply // for a channel range query, and we don't have any new channels, then we send // back a single response that signals completion.