discovery: adhere to proper channel chunk splitting for ReplyChannelRange
This commit is contained in:
7 files changed
+340
-145
No files matched your search
+121
-11
@@ -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))
|
||||
}
|
||||
Reference in new issue
Block a user