itest: add test for new UpdateChanStatus RPC
This commit is contained in:
parent
5a54d787e1
commit
215bf637ea
@ -5171,6 +5171,308 @@ func testListChannels(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testUpdateChanStatus checks that calls to the UpdateChanStatus RPC update
|
||||||
|
// the channel graph as expected, and that channel state is properly updated
|
||||||
|
// in the presence of interleaved node disconnects / reconnects.
|
||||||
|
func testUpdateChanStatus(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
// Create two fresh nodes and open a channel between them.
|
||||||
|
alice, err := net.NewNode("Alice", []string{
|
||||||
|
"--minbackoff=10s",
|
||||||
|
"--chan-enable-timeout=1.5s",
|
||||||
|
"--chan-disable-timeout=3s",
|
||||||
|
"--chan-status-sample-interval=.5s",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create new node: %v", err)
|
||||||
|
}
|
||||||
|
defer shutdownAndAssert(net, t, alice)
|
||||||
|
|
||||||
|
bob, err := net.NewNode("Bob", []string{
|
||||||
|
"--minbackoff=10s",
|
||||||
|
"--chan-enable-timeout=1.5s",
|
||||||
|
"--chan-disable-timeout=3s",
|
||||||
|
"--chan-status-sample-interval=.5s",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create new node: %v", err)
|
||||||
|
}
|
||||||
|
defer shutdownAndAssert(net, t, bob)
|
||||||
|
|
||||||
|
// Connect Alice to Bob.
|
||||||
|
if err := net.ConnectNodes(ctxb, alice, bob); err != nil {
|
||||||
|
t.Fatalf("unable to connect alice to bob: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give Alice some coins so she can fund a channel.
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, alice)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to send coins to alice: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
||||||
|
// being the sole funder of the channel.
|
||||||
|
chanAmt := btcutil.Amount(100000)
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
|
||||||
|
chanPoint := openChannelAndAssert(
|
||||||
|
ctxt, t, net, alice, bob,
|
||||||
|
lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wait for Alice and Bob to receive the channel edge from the
|
||||||
|
// funding manager.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("alice didn't see the alice->bob channel before "+
|
||||||
|
"timeout: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bob didn't see the bob->alice channel before "+
|
||||||
|
"timeout: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch a node for Carol which will connect to Alice and Bob in
|
||||||
|
// order to receive graph updates. This will ensure that the
|
||||||
|
// channel updates are propagated throughout the network.
|
||||||
|
carol, err := net.NewNode("Carol", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create Carol's node: %v", err)
|
||||||
|
}
|
||||||
|
defer shutdownAndAssert(net, t, carol)
|
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
if err := net.ConnectNodes(ctxt, alice, carol); err != nil {
|
||||||
|
t.Fatalf("unable to connect alice to carol: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
if err := net.ConnectNodes(ctxt, bob, carol); err != nil {
|
||||||
|
t.Fatalf("unable to connect bob to carol: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
carolSub := subscribeGraphNotifications(t, ctxb, carol)
|
||||||
|
defer close(carolSub.quit)
|
||||||
|
|
||||||
|
// sendReq sends an UpdateChanStatus request to the given node.
|
||||||
|
sendReq := func(node *lntest.HarnessNode, chanPoint *lnrpc.ChannelPoint,
|
||||||
|
action routerrpc.ChanStatusAction) {
|
||||||
|
|
||||||
|
req := &routerrpc.UpdateChanStatusRequest{
|
||||||
|
ChanPoint: chanPoint,
|
||||||
|
Action: action,
|
||||||
|
}
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
_, err = node.RouterClient.UpdateChanStatus(ctxt, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to call UpdateChanStatus for %s's node: %v",
|
||||||
|
node.Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertEdgeDisabled ensures that a given node has the correct
|
||||||
|
// Disabled state for a channel.
|
||||||
|
assertEdgeDisabled := func(node *lntest.HarnessNode,
|
||||||
|
chanPoint *lnrpc.ChannelPoint, disabled bool) {
|
||||||
|
|
||||||
|
var predErr error
|
||||||
|
err = wait.Predicate(func() bool {
|
||||||
|
req := &lnrpc.ChannelGraphRequest{
|
||||||
|
IncludeUnannounced: true,
|
||||||
|
}
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
chanGraph, err := node.DescribeGraph(ctxt, req)
|
||||||
|
if err != nil {
|
||||||
|
predErr = fmt.Errorf("unable to query node %v's graph: %v", node, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
numEdges := len(chanGraph.Edges)
|
||||||
|
if numEdges != 1 {
|
||||||
|
predErr = fmt.Errorf("expected to find 1 edge in the graph, found %d", numEdges)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
edge := chanGraph.Edges[0]
|
||||||
|
if edge.ChanPoint != chanPoint.GetFundingTxidStr() {
|
||||||
|
predErr = fmt.Errorf("expected chan_point %v, got %v",
|
||||||
|
chanPoint.GetFundingTxidStr(), edge.ChanPoint)
|
||||||
|
}
|
||||||
|
var policy *lnrpc.RoutingPolicy
|
||||||
|
if node.PubKeyStr == edge.Node1Pub {
|
||||||
|
policy = edge.Node1Policy
|
||||||
|
} else {
|
||||||
|
policy = edge.Node2Policy
|
||||||
|
}
|
||||||
|
if disabled != policy.Disabled {
|
||||||
|
predErr = fmt.Errorf("expected policy.Disabled to be %v, "+
|
||||||
|
"but policy was %v", disabled, policy)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, defaultTimeout)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v", predErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When updating the state of the channel between Alice and Bob, we
|
||||||
|
// should expect to see channel updates with the default routing
|
||||||
|
// policy. The value of "Disabled" will depend on the specific
|
||||||
|
// scenario being tested.
|
||||||
|
expectedPolicy := &lnrpc.RoutingPolicy{
|
||||||
|
FeeBaseMsat: int64(chainreg.DefaultBitcoinBaseFeeMSat),
|
||||||
|
FeeRateMilliMsat: int64(chainreg.DefaultBitcoinFeeRate),
|
||||||
|
TimeLockDelta: chainreg.DefaultBitcoinTimeLockDelta,
|
||||||
|
MinHtlc: 1000, // default value
|
||||||
|
MaxHtlcMsat: calculateMaxHtlc(chanAmt),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initially, the channel between Alice and Bob should not be
|
||||||
|
// disabled.
|
||||||
|
assertEdgeDisabled(alice, chanPoint, false)
|
||||||
|
|
||||||
|
// Manually disable the channel and ensure that a "Disabled = true"
|
||||||
|
// update is propagated.
|
||||||
|
sendReq(alice, chanPoint, routerrpc.ChanStatusAction_DISABLE)
|
||||||
|
expectedPolicy.Disabled = true
|
||||||
|
waitForChannelUpdate(
|
||||||
|
t, carolSub,
|
||||||
|
[]expectedChanUpdate{
|
||||||
|
{alice.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Re-enable the channel and ensure that a "Disabled = false" update
|
||||||
|
// is propagated.
|
||||||
|
sendReq(alice, chanPoint, routerrpc.ChanStatusAction_ENABLE)
|
||||||
|
expectedPolicy.Disabled = false
|
||||||
|
waitForChannelUpdate(
|
||||||
|
t, carolSub,
|
||||||
|
[]expectedChanUpdate{
|
||||||
|
{alice.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manually enabling a channel should NOT prevent subsequent
|
||||||
|
// disconnections from automatically disabling the channel again
|
||||||
|
// (we don't want to clutter the network with channels that are
|
||||||
|
// falsely advertised as enabled when they don't work).
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
if err := net.DisconnectNodes(ctxt, alice, bob); err != nil {
|
||||||
|
t.Fatalf("unable to disconnect Alice from Bob: %v", err)
|
||||||
|
}
|
||||||
|
expectedPolicy.Disabled = true
|
||||||
|
waitForChannelUpdate(
|
||||||
|
t, carolSub,
|
||||||
|
[]expectedChanUpdate{
|
||||||
|
{alice.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
{bob.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reconnecting the nodes should propagate a "Disabled = false" update.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
if err := net.EnsureConnected(ctxt, alice, bob); err != nil {
|
||||||
|
t.Fatalf("unable to reconnect Alice to Bob: %v", err)
|
||||||
|
}
|
||||||
|
expectedPolicy.Disabled = false
|
||||||
|
waitForChannelUpdate(
|
||||||
|
t, carolSub,
|
||||||
|
[]expectedChanUpdate{
|
||||||
|
{alice.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
{bob.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manually disabling the channel should prevent a subsequent
|
||||||
|
// disconnect / reconnect from re-enabling the channel on
|
||||||
|
// Alice's end. Note the asymmetry between manual enable and
|
||||||
|
// manual disable!
|
||||||
|
sendReq(alice, chanPoint, routerrpc.ChanStatusAction_DISABLE)
|
||||||
|
|
||||||
|
// Alice sends out the "Disabled = true" update in response to
|
||||||
|
// the ChanStatusAction_DISABLE request.
|
||||||
|
expectedPolicy.Disabled = true
|
||||||
|
waitForChannelUpdate(
|
||||||
|
t, carolSub,
|
||||||
|
[]expectedChanUpdate{
|
||||||
|
{alice.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
if err := net.DisconnectNodes(ctxt, alice, bob); err != nil {
|
||||||
|
t.Fatalf("unable to disconnect Alice from Bob: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bob sends a "Disabled = true" update upon detecting the
|
||||||
|
// disconnect.
|
||||||
|
expectedPolicy.Disabled = true
|
||||||
|
waitForChannelUpdate(
|
||||||
|
t, carolSub,
|
||||||
|
[]expectedChanUpdate{
|
||||||
|
{bob.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bob sends a "Disabled = false" update upon detecting the
|
||||||
|
// reconnect.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
if err := net.EnsureConnected(ctxt, alice, bob); err != nil {
|
||||||
|
t.Fatalf("unable to reconnect Alice to Bob: %v", err)
|
||||||
|
}
|
||||||
|
expectedPolicy.Disabled = false
|
||||||
|
waitForChannelUpdate(
|
||||||
|
t, carolSub,
|
||||||
|
[]expectedChanUpdate{
|
||||||
|
{bob.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// However, since we manually disabled the channel on Alice's end,
|
||||||
|
// the policy on Alice's end should still be "Disabled = true". Again,
|
||||||
|
// note the asymmetry between manual enable and manual disable!
|
||||||
|
assertEdgeDisabled(alice, chanPoint, true)
|
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
if err := net.DisconnectNodes(ctxt, alice, bob); err != nil {
|
||||||
|
t.Fatalf("unable to disconnect Alice from Bob: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bob sends a "Disabled = true" update upon detecting the
|
||||||
|
// disconnect.
|
||||||
|
expectedPolicy.Disabled = true
|
||||||
|
waitForChannelUpdate(
|
||||||
|
t, carolSub,
|
||||||
|
[]expectedChanUpdate{
|
||||||
|
{bob.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// After restoring automatic channel state management on Alice's end,
|
||||||
|
// BOTH Alice and Bob should set the channel state back to "enabled"
|
||||||
|
// on reconnect.
|
||||||
|
sendReq(alice, chanPoint, routerrpc.ChanStatusAction_AUTO)
|
||||||
|
if err := net.EnsureConnected(ctxt, alice, bob); err != nil {
|
||||||
|
t.Fatalf("unable to reconnect Alice to Bob: %v", err)
|
||||||
|
}
|
||||||
|
expectedPolicy.Disabled = false
|
||||||
|
waitForChannelUpdate(
|
||||||
|
t, carolSub,
|
||||||
|
[]expectedChanUpdate{
|
||||||
|
{alice.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
{bob.PubKeyStr, expectedPolicy, chanPoint},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assertEdgeDisabled(alice, chanPoint, false)
|
||||||
|
}
|
||||||
|
|
||||||
func testListPayments(net *lntest.NetworkHarness, t *harnessTest) {
|
func testListPayments(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
@ -71,6 +71,10 @@ var allTestCases = []*testCase{
|
|||||||
name: "list channels",
|
name: "list channels",
|
||||||
test: testListChannels,
|
test: testListChannels,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "update channel status",
|
||||||
|
test: testUpdateChanStatus,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "list outgoing payments",
|
name: "list outgoing payments",
|
||||||
test: testListPayments,
|
test: testListPayments,
|
||||||
|
Loading…
Reference in New Issue
Block a user