itest+lntest: add wtclient session & tower management test

This commit is contained in:
Elle Mouton 2024-02-09 12:47:35 +02:00
parent 59a4bfbc59
commit 4ab17525ce
No known key found for this signature in database
GPG Key ID: D7D916376026F177
2 changed files with 226 additions and 0 deletions

View File

@ -29,6 +29,200 @@ func testWatchtower(ht *lntest.HarnessTest) {
tt := ht.Subtest(t)
testTowerClientSessionDeletion(tt)
})
ht.Run("tower and session activation", func(t *testing.T) {
tt := ht.Subtest(t)
testTowerClientTowerAndSessionManagement(tt)
})
}
// testTowerClientTowerAndSessionManagement tests the various control commands
// that a user has over the client's set of active towers and sessions.
func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) {
const (
chanAmt = funding.MaxBtcFundingAmount
externalIP = "1.2.3.4"
externalIP2 = "1.2.3.5"
sessionCloseRange = 1
)
// Set up Wallis the watchtower who will be used by Dave to watch over
// his channel commitment transactions.
wallisPk, wallisListener, _ := setUpNewTower(ht, "Wallis", externalIP)
// Dave will be the tower client.
daveArgs := []string{
"--wtclient.active",
fmt.Sprintf(
"--wtclient.session-close-range=%d", sessionCloseRange,
),
}
dave := ht.NewNode("Dave", daveArgs)
addWallisReq := &wtclientrpc.AddTowerRequest{
Pubkey: wallisPk,
Address: wallisListener,
}
dave.RPC.AddTower(addWallisReq)
assertNumSessions := func(towerPk []byte, expectedNum int) {
err := wait.NoError(func() error {
info := dave.RPC.GetTowerInfo(
&wtclientrpc.GetTowerInfoRequest{
Pubkey: towerPk,
IncludeSessions: true,
},
)
var numSessions uint32
for _, sessionType := range info.SessionInfo {
numSessions += sessionType.NumSessions
}
if numSessions == uint32(expectedNum) {
return nil
}
return fmt.Errorf("expected %d sessions, got %d",
expectedNum, numSessions)
}, defaultTimeout)
require.NoError(ht, err)
}
// Assert that there are a few sessions between Dave and Wallis. There
// should be one per client. There are currently 3 types of clients, so
// we expect 3 sessions.
assertNumSessions(wallisPk, 3)
// Before we make a channel, we'll load up Dave with some coins sent
// directly from the miner.
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
// Connect Dave and Alice.
ht.ConnectNodes(dave, ht.Alice)
// Open a channel between Dave and Alice.
params := lntest.OpenChannelParams{
Amt: chanAmt,
}
chanPoint := ht.OpenChannel(dave, ht.Alice, params)
// Show that the Wallis tower is currently seen as an active session
// candidate.
info := dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{
Pubkey: wallisPk,
})
require.GreaterOrEqual(ht, len(info.SessionInfo), 1)
require.True(ht, info.SessionInfo[0].ActiveSessionCandidate)
// Make some back-ups and assert that they are added to a session with
// the tower.
generateBackups(ht, dave, ht.Alice, 4)
// Assert that one of the sessions now has 4 backups.
assertNumBackups(ht, dave.RPC, wallisPk, 4, false)
// Now, deactivate the tower and show that it is no longer considered
// an active session candidate.
dave.RPC.DeactivateTower(&wtclientrpc.DeactivateTowerRequest{
Pubkey: wallisPk,
})
info = dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{
Pubkey: wallisPk,
})
require.GreaterOrEqual(ht, len(info.SessionInfo), 1)
require.False(ht, info.SessionInfo[0].ActiveSessionCandidate)
// Back up a few more states.
generateBackups(ht, dave, ht.Alice, 4)
// These should _not_ be on the tower. Therefore, the number of
// back-ups on the tower should be the same as before.
assertNumBackups(ht, dave.RPC, wallisPk, 4, false)
// Add new tower and connect Dave to it.
wilmaPk, wilmaListener, _ := setUpNewTower(ht, "Wilma", externalIP2)
dave.RPC.AddTower(&wtclientrpc.AddTowerRequest{
Pubkey: wilmaPk,
Address: wilmaListener,
})
assertNumSessions(wilmaPk, 3)
// The updates from before should now appear on the new watchtower.
assertNumBackups(ht, dave.RPC, wilmaPk, 4, false)
// Reactivate the Wallis tower and then deactivate the Wilma one.
dave.RPC.AddTower(addWallisReq)
dave.RPC.DeactivateTower(&wtclientrpc.DeactivateTowerRequest{
Pubkey: wilmaPk,
})
// Generate some more back-ups.
generateBackups(ht, dave, ht.Alice, 4)
// Assert that they get added to the first tower (Wallis) and that the
// number of sessions with Wallis has not changed - in other words, the
// previously used session was re-used.
assertNumBackups(ht, dave.RPC, wallisPk, 8, false)
assertNumSessions(wallisPk, 3)
findSession := func(towerPk []byte, numBackups uint32) []byte {
info := dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{
Pubkey: towerPk,
IncludeSessions: true,
})
for _, sessionType := range info.SessionInfo {
for _, session := range sessionType.Sessions {
if session.NumBackups == numBackups {
return session.Id
}
}
}
ht.Fatalf("session with %d backups not found", numBackups)
return nil
}
// Now we will test the termination of a session.
// First, we need to figure out the ID of the session that has been used
// for back-ups.
sessionID := findSession(wallisPk, 8)
// Now, terminate the session.
dave.RPC.TerminateSession(&wtclientrpc.TerminateSessionRequest{
SessionId: sessionID,
})
// This should force the client to negotiate a new session. The old
// session still remains in our session list since the channel for which
// it has updates for is still open.
assertNumSessions(wallisPk, 4)
// Any new back-ups should now be backed up on a different session.
generateBackups(ht, dave, ht.Alice, 2)
assertNumBackups(ht, dave.RPC, wallisPk, 10, false)
findSession(wallisPk, 2)
// Close the channel.
ht.CloseChannelAssertPending(dave, chanPoint, false)
// Mine enough blocks to surpass the session close range buffer.
ht.MineBlocksAndAssertNumTxes(sessionCloseRange+6, 1)
// The session that was previously terminated now gets deleted since
// the channel for which it has updates has now been closed. All the
// remaining sessions are not yet closable since they are not yet
// exhausted and are all still active.
assertNumSessions(wallisPk, 3)
// For the sake of completion, we call RemoveTower here for both towers
// to show that this should never error.
dave.RPC.RemoveTower(&wtclientrpc.RemoveTowerRequest{
Pubkey: wallisPk,
})
dave.RPC.RemoveTower(&wtclientrpc.RemoveTowerRequest{
Pubkey: wilmaPk,
})
}
// testTowerClientSessionDeletion tests that sessions are correctly deleted

View File

@ -52,6 +52,38 @@ func (h *HarnessRPC) AddTower(
return resp
}
// DeactivateTower makes an RPC call to the WatchtowerClient of the given node
// and asserts.
func (h *HarnessRPC) DeactivateTower(req *wtclientrpc.DeactivateTowerRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WatchtowerClient.DeactivateTower(ctxt, req)
h.NoError(err, "DeactivateTower")
}
// TerminateSession makes an RPC call to the WatchtowerClient of the given node
// and asserts.
func (h *HarnessRPC) TerminateSession(
req *wtclientrpc.TerminateSessionRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WatchtowerClient.TerminateSession(ctxt, req)
h.NoError(err, "TerminateSession")
}
// RemoveTower makes an RPC call to the WatchtowerClient of the given node
// and asserts.
func (h *HarnessRPC) RemoveTower(req *wtclientrpc.RemoveTowerRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WatchtowerClient.RemoveTower(ctxt, req)
h.NoError(err, "RemoveTower")
}
// WatchtowerStats makes a RPC call to the WatchtowerClient of the given node
// and asserts.
func (h *HarnessRPC) WatchtowerStats() *wtclientrpc.StatsResponse {