discovery: adhere to proper channel chunk splitting for ReplyChannelRange

This commit is contained in:
Wilmer Paulino committed 2020-12-08 15:18:07 -08:00
1 parent c5fc7334a4
commit a4f33ae63c
7 files changed
+340 -145

No files matched your search

+121 -11
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"math"
"reflect"
"sort"
"sync"
"testing"
"time"
@@ -12,7 +13,9 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
const (
@@ -95,11 +98,36 @@ func (m *mockChannelGraphTimeSeries) FilterKnownChanIDs(chain chainhash.Hash,
return <-m.filterResp, nil
}
func (m *mockChannelGraphTimeSeries) FilterChannelRange(chain chainhash.Hash,
startHeight, endHeight uint32) ([]lnwire.ShortChannelID, error) {
startHeight, endHeight uint32) ([]channeldb.BlockChannelRange, error) {
m.filterRangeReqs <- filterRangeReq{startHeight, endHeight}
reply := <-m.filterRangeResp
return <-m.filterRangeResp, nil
channelsPerBlock := make(map[uint32][]lnwire.ShortChannelID)
for _, cid := range reply {
channelsPerBlock[cid.BlockHeight] = append(
channelsPerBlock[cid.BlockHeight], cid,
)
}
// Return the channel ranges in ascending block height order.
blocks := make([]uint32, 0, len(channelsPerBlock))
for block := range channelsPerBlock {
blocks = append(blocks, block)
}
sort.Slice(blocks, func(i, j int) bool {
return blocks[i] < blocks[j]
})
channelRanges := make([]channeldb.BlockChannelRange, 0, len(channelsPerBlock))
for _, block := range blocks {
channelRanges = append(channelRanges, channeldb.BlockChannelRange{
Height: block,
Channels: channelsPerBlock[block],
})
}
return channelRanges, nil
}
func (m *mockChannelGraphTimeSeries) FetchChanAnns(chain chainhash.Hash,
shortChanIDs []lnwire.ShortChannelID) ([]lnwire.Message, error) {
@@ -161,6 +189,7 @@ func newTestSyncer(hID lnwire.ShortChannelID,
bestHeight: func() uint32 {
return latestKnownHeight
},
maxQueryChanRangeReplies: maxQueryChanRangeReplies,
}
syncer := newGossipSyncer(cfg)
@@ -828,6 +857,7 @@ func TestGossipSyncerReplyChanRangeQuery(t *testing.T) {
// reply. We should get three sets of messages as two of them should be
// full, while the other is the final fragment.
const numExpectedChunks = 3
var prevResp *lnwire.ReplyChannelRange
respMsgs := make([]lnwire.ShortChannelID, 0, 5)
for i := 0; i < numExpectedChunks; i++ {
select {
@@ -855,14 +885,14 @@ func TestGossipSyncerReplyChanRangeQuery(t *testing.T) {
// channels.
case i == 0:
expectedFirstBlockHeight = startingBlockHeight
expectedNumBlocks = chunkSize + 1
expectedNumBlocks = 4
// The last reply should range starting from the next
// block of our previous reply up until the ending
// height of the query. It should also have the Complete
// bit set.
case i == numExpectedChunks-1:
expectedFirstBlockHeight = respMsgs[len(respMsgs)-1].BlockHeight
expectedFirstBlockHeight = prevResp.LastBlockHeight() + 1
expectedNumBlocks = endingBlockHeight - expectedFirstBlockHeight + 1
expectedComplete = 1
@@ -870,8 +900,8 @@ func TestGossipSyncerReplyChanRangeQuery(t *testing.T) {
// the next block of our previous reply up until it
// reaches its maximum capacity of channels.
default:
expectedFirstBlockHeight = respMsgs[len(respMsgs)-1].BlockHeight
expectedNumBlocks = 5
expectedFirstBlockHeight = prevResp.LastBlockHeight() + 1
expectedNumBlocks = 4
}
switch {
@@ -889,9 +919,10 @@ func TestGossipSyncerReplyChanRangeQuery(t *testing.T) {
case rangeResp.Complete != expectedComplete:
t.Fatalf("Complete in resp #%d incorrect: "+
"expected %v, got %v", i+1,
expectedNumBlocks, rangeResp.Complete)
expectedComplete, rangeResp.Complete)
}
prevResp = rangeResp
respMsgs = append(respMsgs, rangeResp.ShortChanIDs...)
}
}
@@ -1498,10 +1529,12 @@ func TestGossipSyncerDelayDOS(t *testing.T) {
// inherently disjoint.
var syncer2Chans []lnwire.ShortChannelID
for i := 0; i < numTotalChans; i++ {
syncer2Chans = append(syncer2Chans, lnwire.ShortChannelID{
BlockHeight: highestID.BlockHeight - 1,
TxIndex: uint32(i),
})
syncer2Chans = append([]lnwire.ShortChannelID{
{
BlockHeight: highestID.BlockHeight - uint32(i) - 1,
TxIndex: uint32(i),
},
}, syncer2Chans...)
}
// We'll kick off the test by asserting syncer1 sends over the
@@ -2305,3 +2338,80 @@ func TestGossipSyncerSyncedSignal(t *testing.T) {
t.Fatal("expected to receive chansSynced signal")
}
}
// TestGossipSyncerMaxChannelRangeReplies ensures that a gossip syncer
// transitions its state after receiving the maximum possible number of replies
// for a single QueryChannelRange message, and that any further replies after
// said limit are not processed.
func TestGossipSyncerMaxChannelRangeReplies(t *testing.T) {
t.Parallel()
msgChan, syncer, chanSeries := newTestSyncer(
lnwire.ShortChannelID{BlockHeight: latestKnownHeight},
defaultEncoding, defaultChunkSize,
)
// We'll tune the maxQueryChanRangeReplies to a more sensible value for
// the sake of testing.
syncer.cfg.maxQueryChanRangeReplies = 100
syncer.Start()
defer syncer.Stop()
// Upon initialization, the syncer should submit a QueryChannelRange
// request.
var query *lnwire.QueryChannelRange
select {
case msgs := <-msgChan:
require.Len(t, msgs, 1)
require.IsType(t, &lnwire.QueryChannelRange{}, msgs[0])
query = msgs[0].(*lnwire.QueryChannelRange)
case <-time.After(time.Second):
t.Fatal("expected query channel range request msg")
}
// We'll send the maximum number of replies allowed to a
// QueryChannelRange request with each reply consuming only one block in
// order to transition the syncer's state.
for i := uint32(0); i < syncer.cfg.maxQueryChanRangeReplies; i++ {
reply := &lnwire.ReplyChannelRange{
QueryChannelRange: *query,
ShortChanIDs: []lnwire.ShortChannelID{
{
BlockHeight: query.FirstBlockHeight + i,
},
},
}
reply.FirstBlockHeight = query.FirstBlockHeight + i
reply.NumBlocks = 1
require.NoError(t, syncer.ProcessQueryMsg(reply, nil))
}
// We should receive a filter request for the syncer's local channels
// after processing all of the replies. We'll send back a nil response
// indicating that no new channels need to be synced, so it should
// transition to its final chansSynced state.
select {
case <-chanSeries.filterReq:
case <-time.After(time.Second):
t.Fatal("expected local filter request of known channels")
}
select {
case chanSeries.filterResp <- nil:
case <-time.After(time.Second):
t.Fatal("timed out sending filter response")
}
assertSyncerStatus(t, syncer, chansSynced, ActiveSync)
// Finally, attempting to process another reply for the same query
// should result in an error.
require.Error(t, syncer.ProcessQueryMsg(&lnwire.ReplyChannelRange{
QueryChannelRange: *query,
ShortChanIDs: []lnwire.ShortChannelID{
{
BlockHeight: query.LastBlockHeight() + 1,
},
},
}, nil))
}