Merge pull request #4395 from bmancini55/reply-channel-range-query

discovery: prevent endBlock overflow in replyChanRangeQuery
This commit is contained in:
Wilmer Paulino 2020-06-18 15:19:52 -07:00 committed by GitHub
commit 8c79359247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 113 additions and 1 deletions

@ -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,
)

@ -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.