Merge pull request #8239 from ellemouton/loadSessionByActiveTower

wtclient: add DeactivateTower and TerminateSession commands
This commit is contained in:
Elle authored and GitHub committed 2024-02-21 11:28:25 +02:00
commit c398b0cc69
24 files changed
+2731 -762

No files matched your search

+86 -1
View File
@@ -10,7 +10,8 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
// wtclientCommands will return nil for non-wtclientrpc builds. // wtclientCommands is a list of commands that can be used to interact with the
// watchtower client.
func wtclientCommands() []cli.Command { func wtclientCommands() []cli.Command {
return []cli.Command{ return []cli.Command{
{ {
@@ -20,10 +21,12 @@ func wtclientCommands() []cli.Command {
Subcommands: []cli.Command{ Subcommands: []cli.Command{
addTowerCommand, addTowerCommand,
removeTowerCommand, removeTowerCommand,
deactivateTowerCommand,
listTowersCommand, listTowersCommand,
getTowerCommand, getTowerCommand,
statsCommand, statsCommand,
policyCommand, policyCommand,
sessionCommands,
}, },
}, },
} }
@@ -84,6 +87,44 @@ func addTower(ctx *cli.Context) error {
return nil return nil
} }
var deactivateTowerCommand = cli.Command{
Name: "deactivate",
Usage: "Deactivate a watchtower to temporarily prevent its use for " +
"sessions/backups.",
ArgsUsage: "pubkey",
Action: actionDecorator(deactivateTower),
}
func deactivateTower(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if the number of arguments/flags
// is not what we expect.
if ctx.NArg() != 1 || ctx.NumFlags() > 0 {
return cli.ShowCommandHelp(ctx, "deactivate")
}
pubKey, err := hex.DecodeString(ctx.Args().First())
if err != nil {
return fmt.Errorf("invalid public key: %w", err)
}
client, cleanUp := getWtclient(ctx)
defer cleanUp()
req := &wtclientrpc.DeactivateTowerRequest{
Pubkey: pubKey,
}
resp, err := client.DeactivateTower(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var removeTowerCommand = cli.Command{ var removeTowerCommand = cli.Command{
Name: "remove", Name: "remove",
Usage: "Remove a watchtower to prevent its use for future " + Usage: "Remove a watchtower to prevent its use for future " +
@@ -333,3 +374,47 @@ func policy(ctx *cli.Context) error {
printRespJSON(resp) printRespJSON(resp)
return nil return nil
} }
var sessionCommands = cli.Command{
Name: "session",
Subcommands: []cli.Command{
terminateSessionCommand,
},
}
var terminateSessionCommand = cli.Command{
Name: "terminate",
ArgsUsage: "id",
Action: actionDecorator(terminateSession),
}
func terminateSession(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if the number of arguments/flags
// is not what we expect.
if ctx.NArg() > 1 || ctx.NumFlags() != 0 {
return cli.ShowCommandHelp(ctx, "terminate")
}
client, cleanUp := getWtclient(ctx)
defer cleanUp()
sessionID, err := hex.DecodeString(ctx.Args().First())
if err != nil {
return fmt.Errorf("invalid session ID: %w", err)
}
resp, err := client.TerminateSession(
ctxc, &wtclientrpc.TerminateSessionRequest{
SessionId: sessionID,
},
)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
@@ -155,6 +155,13 @@
about the txid and don't want the calling code to block while the channel about the txid and don't want the calling code to block while the channel
drains the active HTLCs. drains the active HTLCs.
* [New watchtower client DeactivateTower and
TerminateSession](https://github.com/lightningnetwork/lnd/pull/8239) commands
have been added. The DeactivateTower command can be used to mark a tower as
inactive so that its sessions are not loaded on startup and so that the tower
is not considered for session negotiation. TerminateSession can be used to
mark a specific session as terminal so that that specific is never used again.
## lncli Additions ## lncli Additions
* Deprecate `bumpclosefee` for `bumpforceclosefee` to accommodate for the fact * Deprecate `bumpclosefee` for `bumpforceclosefee` to accommodate for the fact
+2 -10
View File
@@ -519,16 +519,8 @@ var allTestCases = []*lntest.TestCase{
TestFunc: testLookupHtlcResolution, TestFunc: testLookupHtlcResolution,
}, },
{ {
Name: "watchtower session management", Name: "watchtower",
TestFunc: testWatchtowerSessionManagement, TestFunc: testWatchtower,
},
{
// NOTE: this test must be put in the same tranche as
// `testWatchtowerSessionManagement` to avoid parallel use of
// the default watchtower port.
Name: "revoked uncooperative close retribution altruist " +
"watchtower",
TestFunc: testRevokedCloseRetributionAltruistWatchtower,
}, },
{ {
Name: "channel fundmax", Name: "channel fundmax",
-303
View File
@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
@@ -12,7 +11,6 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
"github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -707,304 +705,3 @@ func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) {
}) })
} }
} }
// testRevokedCloseRetributionAltruistWatchtower establishes a channel between
// Carol and Dave, where Carol is using a third node Willy as her watchtower.
// After sending some payments, Dave reverts his state and force closes to
// trigger a breach. Carol is kept offline throughout the process and the test
// asserts that Willy responds by broadcasting the justice transaction on
// Carol's behalf sweeping her funds without a reward.
func testRevokedCloseRetributionAltruistWatchtower(ht *lntest.HarnessTest) {
for _, commitType := range []lnrpc.CommitmentType{
lnrpc.CommitmentType_LEGACY,
lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SIMPLE_TAPROOT,
} {
testName := fmt.Sprintf("%v", commitType.String())
ct := commitType
testFunc := func(ht *lntest.HarnessTest) {
testRevokedCloseRetributionAltruistWatchtowerCase(
ht, ct,
)
}
success := ht.Run(testName, func(tt *testing.T) {
st := ht.Subtest(tt)
st.RunTestCase(&lntest.TestCase{
Name: testName,
TestFunc: testFunc,
})
})
if !success {
// Log failure time to help relate the lnd logs to the
// failure.
ht.Logf("Failure time: %v", time.Now().Format(
"2006-01-02 15:04:05.000",
))
break
}
}
}
func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
commitType lnrpc.CommitmentType) {
const (
chanAmt = funding.MaxBtcFundingAmount
paymentAmt = 10000
numInvoices = 6
externalIP = "1.2.3.4"
)
// Since we'd like to test some multi-hop failure scenarios, we'll
// introduce another node into our test network: Carol.
carolArgs := lntest.NodeArgsForCommitType(commitType)
carolArgs = append(carolArgs, "--hodl.exit-settle")
carol := ht.NewNode("Carol", carolArgs)
// Willy the watchtower will protect Dave from Carol's breach. He will
// remain online in order to punish Carol on Dave's behalf, since the
// breach will happen while Dave is offline.
willy := ht.NewNode(
"Willy", []string{
"--watchtower.active",
"--watchtower.externalip=" + externalIP,
},
)
willyInfo := willy.RPC.GetInfoWatchtower()
// Assert that Willy has one listener and it is 0.0.0.0:9911 or
// [::]:9911. Since no listener is explicitly specified, one of these
// should be the default depending on whether the host supports IPv6 or
// not.
require.Len(ht, willyInfo.Listeners, 1, "Willy should have 1 listener")
listener := willyInfo.Listeners[0]
if listener != "0.0.0.0:9911" && listener != "[::]:9911" {
ht.Fatalf("expected listener on 0.0.0.0:9911 or [::]:9911, "+
"got %v", listener)
}
// Assert the Willy's URIs properly display the chosen external IP.
require.Len(ht, willyInfo.Uris, 1, "Willy should have 1 uri")
require.Contains(ht, willyInfo.Uris[0], externalIP)
// Dave will be the breached party. We set --nolisten to ensure Carol
// won't be able to connect to him and trigger the channel data
// protection logic automatically.
daveArgs := lntest.NodeArgsForCommitType(commitType)
daveArgs = append(daveArgs, "--nolisten", "--wtclient.active")
dave := ht.NewNode("Dave", daveArgs)
addTowerReq := &wtclientrpc.AddTowerRequest{
Pubkey: willyInfo.Pubkey,
Address: listener,
}
dave.RPC.AddTower(addTowerReq)
// We must let Dave have an open channel before she can send a node
// announcement, so we open a channel with Carol,
ht.ConnectNodes(dave, carol)
// Before we make a channel, we'll load up Dave with some coins sent
// directly from the miner.
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
// Send one more UTXOs if this is a neutrino backend.
if ht.IsNeutrinoBackend() {
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
}
// In order to test Dave's response to an uncooperative channel
// closure by Carol, we'll first open up a channel between them with a
// 0.5 BTC value.
params := lntest.OpenChannelParams{
Amt: 3 * (chanAmt / 4),
PushAmt: chanAmt / 4,
CommitmentType: commitType,
Private: true,
}
chanPoint := ht.OpenChannel(dave, carol, params)
// With the channel open, we'll create a few invoices for Carol that
// Dave will pay to in order to advance the state of the channel.
carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices)
// Next query for Carol's channel state, as we sent 0 payments, Carol
// should still see her balance as the push amount, which is 1/4 of the
// capacity.
carolChan := ht.AssertChannelLocalBalance(
carol, chanPoint, int64(chanAmt/4),
)
// Grab Carol's current commitment height (update number), we'll later
// revert her to this state after additional updates to force him to
// broadcast this soon to be revoked state.
carolStateNumPreCopy := int(carolChan.NumUpdates)
// With the temporary file created, copy Carol's current state into the
// temporary file we created above. Later after more updates, we'll
// restore this state.
ht.BackupDB(carol)
// Reconnect the peers after the restart that was needed for the db
// backup.
ht.EnsureConnected(dave, carol)
// Once connected, give Dave some time to enable the channel again.
ht.AssertTopologyChannelOpen(dave, chanPoint)
// Finally, send payments from Dave to Carol, consuming Carol's
// remaining payment hashes.
ht.CompletePaymentRequestsNoWait(dave, carolPayReqs, chanPoint)
daveBalResp := dave.RPC.WalletBalance()
davePreSweepBalance := daveBalResp.ConfirmedBalance
// Wait until the backup has been accepted by the watchtower before
// shutting down Dave.
err := wait.NoError(func() error {
bkpStats := dave.RPC.WatchtowerStats()
if bkpStats == nil {
return errors.New("no active backup sessions")
}
if bkpStats.NumBackups == 0 {
return errors.New("no backups accepted")
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "unable to verify backup task completed")
// Shutdown Dave to simulate going offline for an extended period of
// time. Once he's not watching, Carol will try to breach the channel.
restart := ht.SuspendNode(dave)
// Now we shutdown Carol, copying over the his temporary database state
// which has the *prior* channel state over his current most up to date
// state. With this, we essentially force Carol to travel back in time
// within the channel's history.
ht.RestartNodeAndRestoreDB(carol)
// Now query for Carol's channel state, it should show that he's at a
// state number in the past, not the *latest* state.
ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy)
// Now force Carol to execute a *force* channel closure by unilaterally
// broadcasting his current channel state. This is actually the
// commitment transaction of a prior *revoked* state, so he'll soon
// feel the wrath of Dave's retribution.
closeUpdates, closeTxID := ht.CloseChannelAssertPending(
carol, chanPoint, true,
)
// Finally, generate a single block, wait for the final close status
// update, then ensure that the closing transaction was included in the
// block.
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
breachTXID := ht.WaitForChannelCloseEvent(closeUpdates)
ht.Miner.AssertTxInBlock(block, breachTXID)
// The breachTXID should match the above closeTxID.
require.EqualValues(ht, breachTXID, closeTxID)
// Query the mempool for Dave's justice transaction, this should be
// broadcast as Carol's contract breaching transaction gets confirmed
// above.
justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0]
// Query for the mempool transaction found above. Then assert that all
// the inputs of this transaction are spending outputs generated by
// Carol's breach transaction above.
justiceTx := ht.Miner.GetRawTransaction(justiceTXID)
for _, txIn := range justiceTx.MsgTx().TxIn {
require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:],
"justice tx not spending commitment utxo")
}
willyBalResp := willy.RPC.WalletBalance()
require.Zero(ht, willyBalResp.ConfirmedBalance,
"willy should have 0 balance before mining justice transaction")
// Now mine a block, this transaction should include Dave's justice
// transaction which was just accepted into the mempool.
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// The block should have exactly *two* transactions, one of which is
// the justice transaction.
require.Len(ht, block.Transactions, 2, "transaction wasn't mined")
justiceSha := block.Transactions[1].TxHash()
require.Equal(ht, justiceTx.Hash()[:], justiceSha[:],
"justice tx wasn't mined")
// Ensure that Willy doesn't get any funds, as he is acting as an
// altruist watchtower.
err = wait.NoError(func() error {
willyBalResp := willy.RPC.WalletBalance()
if willyBalResp.ConfirmedBalance != 0 {
return fmt.Errorf("expected Willy to have no funds "+
"after justice transaction was mined, found %v",
willyBalResp)
}
return nil
}, time.Second*5)
require.NoError(ht, err, "timeout checking willy's balance")
// Before restarting Dave, shutdown Carol so Dave won't sync with her.
// Otherwise, during the restart, Dave will realize Carol is falling
// behind and return `ErrCommitSyncRemoteDataLoss`, thus force closing
// the channel. Although this force close tx will be later replaced by
// the breach tx, it will create two anchor sweeping txes for neutrino
// backend, causing the confirmed wallet balance to be zero later on
// because the utxos are used in sweeping.
ht.Shutdown(carol)
// Restart Dave, who will still think his channel with Carol is open.
// We should him to detect the breach, but realize that the funds have
// then been swept to his wallet by Willy.
require.NoError(ht, restart(), "unable to restart dave")
err = wait.NoError(func() error {
daveBalResp := dave.RPC.ChannelBalance()
if daveBalResp.LocalBalance.Sat != 0 {
return fmt.Errorf("Dave should end up with zero "+
"channel balance, instead has %d",
daveBalResp.LocalBalance.Sat)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking dave's channel balance")
ht.AssertNumPendingForceClose(dave, 0)
// If this is an anchor channel, Dave would sweep the anchor.
if lntest.CommitTypeHasAnchors(commitType) {
ht.MineBlocksAndAssertNumTxes(1, 1)
}
// Check that Dave's wallet balance is increased.
err = wait.NoError(func() error {
daveBalResp := dave.RPC.WalletBalance()
if daveBalResp.ConfirmedBalance <= davePreSweepBalance {
return fmt.Errorf("Dave should have more than %d "+
"after sweep, instead has %d",
davePreSweepBalance,
daveBalResp.ConfirmedBalance)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking dave's wallet balance")
// Dave should have no open channels.
ht.AssertNodeNumChannels(dave, 0)
}
+602 -78
View File
@@ -2,24 +2,234 @@ package itest
import ( import (
"fmt" "fmt"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc" "github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
"github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/rpc"
"github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// testWatchtowerSessionManagement tests that session deletion is done // testWatchtower tests the behaviour of the watchtower client and server.
// correctly. func testWatchtower(ht *lntest.HarnessTest) {
func testWatchtowerSessionManagement(ht *lntest.HarnessTest) { ht.Run("revocation", func(t *testing.T) {
tt := ht.Subtest(t)
testRevokedCloseRetributionAltruistWatchtower(tt)
})
ht.Run("session deletion", func(t *testing.T) {
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
// when they are deemed closable.
func testTowerClientSessionDeletion(ht *lntest.HarnessTest) {
const ( const (
chanAmt = funding.MaxBtcFundingAmount chanAmt = funding.MaxBtcFundingAmount
paymentAmt = 10_000
numInvoices = 5 numInvoices = 5
maxUpdates = numInvoices * 2 maxUpdates = numInvoices * 2
externalIP = "1.2.3.4" externalIP = "1.2.3.4"
@@ -28,24 +238,7 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
// Set up Wallis the watchtower who will be used by Dave to watch over // Set up Wallis the watchtower who will be used by Dave to watch over
// his channel commitment transactions. // his channel commitment transactions.
wallis := ht.NewNode("Wallis", []string{ wallisPk, listener, _ := setUpNewTower(ht, "Wallis", externalIP)
"--watchtower.active",
"--watchtower.externalip=" + externalIP,
})
wallisInfo := wallis.RPC.GetInfoWatchtower()
// Assert that Wallis has one listener and it is 0.0.0.0:9911 or
// [::]:9911. Since no listener is explicitly specified, one of these
// should be the default depending on whether the host supports IPv6 or
// not.
require.Len(ht, wallisInfo.Listeners, 1)
listener := wallisInfo.Listeners[0]
require.True(ht, listener == "0.0.0.0:9911" || listener == "[::]:9911")
// Assert the Wallis's URIs properly display the chosen external IP.
require.Len(ht, wallisInfo.Uris, 1)
require.Contains(ht, wallisInfo.Uris[0], externalIP)
// Dave will be the tower client. // Dave will be the tower client.
daveArgs := []string{ daveArgs := []string{
@@ -58,7 +251,7 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
dave := ht.NewNode("Dave", daveArgs) dave := ht.NewNode("Dave", daveArgs)
addTowerReq := &wtclientrpc.AddTowerRequest{ addTowerReq := &wtclientrpc.AddTowerRequest{
Pubkey: wallisInfo.Pubkey, Pubkey: wallisPk,
Address: listener, Address: listener,
} }
dave.RPC.AddTower(addTowerReq) dave.RPC.AddTower(addTowerReq)
@@ -66,7 +259,7 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
// Assert that there exists a session between Dave and Wallis. // Assert that there exists a session between Dave and Wallis.
err := wait.NoError(func() error { err := wait.NoError(func() error {
info := dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{ info := dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{
Pubkey: wallisInfo.Pubkey, Pubkey: wallisPk,
IncludeSessions: true, IncludeSessions: true,
}) })
@@ -98,61 +291,10 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
// Since there are 2 updates made for every payment and the maximum // Since there are 2 updates made for every payment and the maximum
// number of updates per session has been set to 10, make 5 payments // number of updates per session has been set to 10, make 5 payments
// between the pair so that the session is exhausted. // between the pair so that the session is exhausted.
alicePayReqs, _, _ := ht.CreatePayReqs( generateBackups(ht, dave, ht.Alice, maxUpdates)
ht.Alice, paymentAmt, numInvoices,
)
send := func(node *node.HarnessNode, payReq string) {
stream := node.RPC.SendPayment(&routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
})
ht.AssertPaymentStatusFromStream(
stream, lnrpc.Payment_SUCCEEDED,
)
}
for i := 0; i < numInvoices; i++ {
send(dave, alicePayReqs[i])
}
// assertNumBackups is a closure that asserts that Dave has a certain
// number of backups backed up to the tower. If mineOnFail is true,
// then a block will be mined each time the assertion fails.
assertNumBackups := func(expected int, mineOnFail bool) {
err = wait.NoError(func() error {
info := dave.RPC.GetTowerInfo(
&wtclientrpc.GetTowerInfoRequest{
Pubkey: wallisInfo.Pubkey,
IncludeSessions: true,
},
)
var numBackups uint32
for _, sessionType := range info.SessionInfo {
for _, session := range sessionType.Sessions {
numBackups += session.NumBackups
}
}
if numBackups == uint32(expected) {
return nil
}
if mineOnFail {
ht.Miner.MineBlocksSlow(1)
}
return fmt.Errorf("expected %d backups, got %d",
expected, numBackups)
}, defaultTimeout)
require.NoError(ht, err)
}
// Assert that one of the sessions now has 10 backups. // Assert that one of the sessions now has 10 backups.
assertNumBackups(10, false) assertNumBackups(ht, dave.RPC, wallisPk, 10, false)
// Now close the channel and wait for the close transaction to appear // Now close the channel and wait for the close transaction to appear
// in the mempool so that it is included in a block when we mine. // in the mempool so that it is included in a block when we mine.
@@ -168,5 +310,387 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
// would immediately negotiate another session after deleting the // would immediately negotiate another session after deleting the
// exhausted one. This time we set the "mineOnFail" parameter to true to // exhausted one. This time we set the "mineOnFail" parameter to true to
// ensure that the session deleting logic is run. // ensure that the session deleting logic is run.
assertNumBackups(0, true) assertNumBackups(ht, dave.RPC, wallisPk, 0, true)
}
// testRevokedCloseRetributionAltruistWatchtower establishes a channel between
// Carol and Dave, where Carol is using a third node Willy as her watchtower.
// After sending some payments, Dave reverts his state and force closes to
// trigger a breach. Carol is kept offline throughout the process and the test
// asserts that Willy responds by broadcasting the justice transaction on
// Carol's behalf sweeping her funds without a reward.
func testRevokedCloseRetributionAltruistWatchtower(ht *lntest.HarnessTest) {
for _, commitType := range []lnrpc.CommitmentType{
lnrpc.CommitmentType_LEGACY,
lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SIMPLE_TAPROOT,
} {
testName := fmt.Sprintf("%v", commitType.String())
ct := commitType
testFunc := func(ht *lntest.HarnessTest) {
testRevokedCloseRetributionAltruistWatchtowerCase(
ht, ct,
)
}
success := ht.Run(testName, func(tt *testing.T) {
st := ht.Subtest(tt)
st.RunTestCase(&lntest.TestCase{
Name: testName,
TestFunc: testFunc,
})
})
if !success {
// Log failure time to help relate the lnd logs to the
// failure.
ht.Logf("Failure time: %v", time.Now().Format(
"2006-01-02 15:04:05.000",
))
break
}
}
}
func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
commitType lnrpc.CommitmentType) {
const (
chanAmt = funding.MaxBtcFundingAmount
paymentAmt = 10000
numInvoices = 6
externalIP = "1.2.3.4"
)
// Since we'd like to test some multi-hop failure scenarios, we'll
// introduce another node into our test network: Carol.
carolArgs := lntest.NodeArgsForCommitType(commitType)
carolArgs = append(carolArgs, "--hodl.exit-settle")
carol := ht.NewNode("Carol", carolArgs)
// Set up Willy the watchtower who will protect Dave from Carol's
// breach. He will remain online in order to punish Carol on Dave's
// behalf, since the breach will happen while Dave is offline.
willyInfoPk, listener, willy := setUpNewTower(ht, "Willy", externalIP)
// Dave will be the breached party. We set --nolisten to ensure Carol
// won't be able to connect to him and trigger the channel data
// protection logic automatically.
daveArgs := lntest.NodeArgsForCommitType(commitType)
daveArgs = append(daveArgs, "--nolisten", "--wtclient.active")
dave := ht.NewNode("Dave", daveArgs)
addTowerReq := &wtclientrpc.AddTowerRequest{
Pubkey: willyInfoPk,
Address: listener,
}
dave.RPC.AddTower(addTowerReq)
// We must let Dave have an open channel before she can send a node
// announcement, so we open a channel with Carol,
ht.ConnectNodes(dave, carol)
// Before we make a channel, we'll load up Dave with some coins sent
// directly from the miner.
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
// Send one more UTXOs if this is a neutrino backend.
if ht.IsNeutrinoBackend() {
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
}
// In order to test Dave's response to an uncooperative channel
// closure by Carol, we'll first open up a channel between them with a
// 0.5 BTC value.
params := lntest.OpenChannelParams{
Amt: 3 * (chanAmt / 4),
PushAmt: chanAmt / 4,
CommitmentType: commitType,
Private: true,
}
chanPoint := ht.OpenChannel(dave, carol, params)
// With the channel open, we'll create a few invoices for Carol that
// Dave will pay to in order to advance the state of the channel.
carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices)
// Next query for Carol's channel state, as we sent 0 payments, Carol
// should still see her balance as the push amount, which is 1/4 of the
// capacity.
carolChan := ht.AssertChannelLocalBalance(
carol, chanPoint, int64(chanAmt/4),
)
// Grab Carol's current commitment height (update number), we'll later
// revert her to this state after additional updates to force him to
// broadcast this soon to be revoked state.
carolStateNumPreCopy := int(carolChan.NumUpdates)
// With the temporary file created, copy Carol's current state into the
// temporary file we created above. Later after more updates, we'll
// restore this state.
ht.BackupDB(carol)
// Reconnect the peers after the restart that was needed for the db
// backup.
ht.EnsureConnected(dave, carol)
// Once connected, give Dave some time to enable the channel again.
ht.AssertTopologyChannelOpen(dave, chanPoint)
// Finally, send payments from Dave to Carol, consuming Carol's
// remaining payment hashes.
ht.CompletePaymentRequestsNoWait(dave, carolPayReqs, chanPoint)
daveBalResp := dave.RPC.WalletBalance()
davePreSweepBalance := daveBalResp.ConfirmedBalance
// Wait until the backup has been accepted by the watchtower before
// shutting down Dave.
err := wait.NoError(func() error {
bkpStats := dave.RPC.WatchtowerStats()
if bkpStats == nil {
return errors.New("no active backup sessions")
}
if bkpStats.NumBackups == 0 {
return errors.New("no backups accepted")
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "unable to verify backup task completed")
// Shutdown Dave to simulate going offline for an extended period of
// time. Once he's not watching, Carol will try to breach the channel.
restart := ht.SuspendNode(dave)
// Now we shutdown Carol, copying over the his temporary database state
// which has the *prior* channel state over his current most up to date
// state. With this, we essentially force Carol to travel back in time
// within the channel's history.
ht.RestartNodeAndRestoreDB(carol)
// Now query for Carol's channel state, it should show that he's at a
// state number in the past, not the *latest* state.
ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy)
// Now force Carol to execute a *force* channel closure by unilaterally
// broadcasting his current channel state. This is actually the
// commitment transaction of a prior *revoked* state, so he'll soon
// feel the wrath of Dave's retribution.
closeUpdates, closeTxID := ht.CloseChannelAssertPending(
carol, chanPoint, true,
)
// Finally, generate a single block, wait for the final close status
// update, then ensure that the closing transaction was included in the
// block.
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
breachTXID := ht.WaitForChannelCloseEvent(closeUpdates)
ht.Miner.AssertTxInBlock(block, breachTXID)
// The breachTXID should match the above closeTxID.
require.EqualValues(ht, breachTXID, closeTxID)
// Query the mempool for Dave's justice transaction, this should be
// broadcast as Carol's contract breaching transaction gets confirmed
// above.
justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0]
// Query for the mempool transaction found above. Then assert that all
// the inputs of this transaction are spending outputs generated by
// Carol's breach transaction above.
justiceTx := ht.Miner.GetRawTransaction(justiceTXID)
for _, txIn := range justiceTx.MsgTx().TxIn {
require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:],
"justice tx not spending commitment utxo")
}
willyBalResp := willy.WalletBalance()
require.Zero(ht, willyBalResp.ConfirmedBalance,
"willy should have 0 balance before mining justice transaction")
// Now mine a block, this transaction should include Dave's justice
// transaction which was just accepted into the mempool.
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// The block should have exactly *two* transactions, one of which is
// the justice transaction.
require.Len(ht, block.Transactions, 2, "transaction wasn't mined")
justiceSha := block.Transactions[1].TxHash()
require.Equal(ht, justiceTx.Hash()[:], justiceSha[:],
"justice tx wasn't mined")
// Ensure that Willy doesn't get any funds, as he is acting as an
// altruist watchtower.
err = wait.NoError(func() error {
willyBalResp := willy.WalletBalance()
if willyBalResp.ConfirmedBalance != 0 {
return fmt.Errorf("expected Willy to have no funds "+
"after justice transaction was mined, found %v",
willyBalResp)
}
return nil
}, time.Second*5)
require.NoError(ht, err, "timeout checking willy's balance")
// Before restarting Dave, shutdown Carol so Dave won't sync with her.
// Otherwise, during the restart, Dave will realize Carol is falling
// behind and return `ErrCommitSyncRemoteDataLoss`, thus force closing
// the channel. Although this force close tx will be later replaced by
// the breach tx, it will create two anchor sweeping txes for neutrino
// backend, causing the confirmed wallet balance to be zero later on
// because the utxos are used in sweeping.
ht.Shutdown(carol)
// Restart Dave, who will still think his channel with Carol is open.
// We should him to detect the breach, but realize that the funds have
// then been swept to his wallet by Willy.
require.NoError(ht, restart(), "unable to restart dave")
err = wait.NoError(func() error {
daveBalResp := dave.RPC.ChannelBalance()
if daveBalResp.LocalBalance.Sat != 0 {
return fmt.Errorf("Dave should end up with zero "+
"channel balance, instead has %d",
daveBalResp.LocalBalance.Sat)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking dave's channel balance")
ht.AssertNumPendingForceClose(dave, 0)
// If this is an anchor channel, Dave would sweep the anchor.
if lntest.CommitTypeHasAnchors(commitType) {
ht.MineBlocksAndAssertNumTxes(1, 1)
}
// Check that Dave's wallet balance is increased.
err = wait.NoError(func() error {
daveBalResp := dave.RPC.WalletBalance()
if daveBalResp.ConfirmedBalance <= davePreSweepBalance {
return fmt.Errorf("Dave should have more than %d "+
"after sweep, instead has %d",
davePreSweepBalance,
daveBalResp.ConfirmedBalance)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking dave's wallet balance")
// Dave should have no open channels.
ht.AssertNodeNumChannels(dave, 0)
}
func setUpNewTower(ht *lntest.HarnessTest, name, externalIP string) ([]byte,
string, *rpc.HarnessRPC) {
port := node.NextAvailablePort()
listenAddr := fmt.Sprintf("0.0.0.0:%d", port)
// Set up the new watchtower.
tower := ht.NewNode(name, []string{
"--watchtower.active",
"--watchtower.externalip=" + externalIP,
"--watchtower.listen=" + listenAddr,
})
towerInfo := tower.RPC.GetInfoWatchtower()
require.Len(ht, towerInfo.Listeners, 1)
listener := towerInfo.Listeners[0]
require.True(
ht, listener == listenAddr ||
listener == fmt.Sprintf("[::]:%d", port),
)
// Assert the Tower's URIs properly display the chosen external IP.
require.Len(ht, towerInfo.Uris, 1)
require.Contains(ht, towerInfo.Uris[0], externalIP)
return towerInfo.Pubkey, towerInfo.Listeners[0], tower.RPC
}
// generateBackups is a helper function that can be used to create a number of
// watchtower back-ups.
func generateBackups(ht *lntest.HarnessTest, srcNode,
dstNode *node.HarnessNode, numBackups int64) {
const paymentAmt = 10_000
require.EqualValuesf(ht, numBackups%2, 0, "the number of desired "+
"back-ups must be even")
// Two updates are made for every payment.
numPayments := int(numBackups / 2)
// Create the required number of invoices.
alicePayReqs, _, _ := ht.CreatePayReqs(
dstNode, paymentAmt, numPayments,
)
send := func(node *node.HarnessNode, payReq string) {
stream := node.RPC.SendPayment(
&routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
ht.AssertPaymentStatusFromStream(
stream, lnrpc.Payment_SUCCEEDED,
)
}
// Pay each invoice.
for i := 0; i < numPayments; i++ {
send(srcNode, alicePayReqs[i])
}
}
// assertNumBackups is a helper that asserts that the given node has a certain
// number of backups backed up to the tower. If mineOnFail is true, then a block
// will be mined each time the assertion fails.
func assertNumBackups(ht *lntest.HarnessTest, node *rpc.HarnessRPC,
towerPk []byte, expectedNumBackups int, mineOnFail bool) {
err := wait.NoError(func() error {
info := node.GetTowerInfo(
&wtclientrpc.GetTowerInfoRequest{
Pubkey: towerPk,
IncludeSessions: true,
},
)
var numBackups uint32
for _, sessionType := range info.SessionInfo {
for _, session := range sessionType.Sessions {
numBackups += session.NumBackups
}
}
if numBackups == uint32(expectedNumBackups) {
return nil
}
if mineOnFail {
ht.Miner.MineBlocksSlow(1)
}
return fmt.Errorf("expected %d backups, got %d",
expectedNumBackups, numBackups)
}, defaultTimeout)
require.NoError(ht, err)
} }
@@ -71,6 +71,56 @@ func RegisterWatchtowerClientJSONCallbacks(registry map[string]func(ctx context.
callback(string(respBytes), nil) callback(string(respBytes), nil)
} }
registry["wtclientrpc.WatchtowerClient.DeactivateTower"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &DeactivateTowerRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClientClient(conn)
resp, err := client.DeactivateTower(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["wtclientrpc.WatchtowerClient.TerminateSession"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &TerminateSessionRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClientClient(conn)
resp, err := client.TerminateSession(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["wtclientrpc.WatchtowerClient.ListTowers"] = func(ctx context.Context, registry["wtclientrpc.WatchtowerClient.ListTowers"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
+62
View File
@@ -44,6 +44,14 @@ var (
Entity: "offchain", Entity: "offchain",
Action: "write", Action: "write",
}}, }},
"/wtclientrpc.WatchtowerClient/DeactivateTower": {{
Entity: "offchain",
Action: "write",
}},
"/wtclientrpc.WatchtowerClient/TerminateSession": {{
Entity: "offchain",
Action: "write",
}},
"/wtclientrpc.WatchtowerClient/ListTowers": {{ "/wtclientrpc.WatchtowerClient/ListTowers": {{
Entity: "offchain", Entity: "offchain",
Action: "read", Action: "read",
@@ -251,6 +259,59 @@ func (c *WatchtowerClient) RemoveTower(ctx context.Context,
return &RemoveTowerResponse{}, nil return &RemoveTowerResponse{}, nil
} }
// DeactivateTower sets the given tower's status to inactive so that it is not
// considered for session negotiation. Its sessions will also not be used while
// the tower is inactive.
func (c *WatchtowerClient) DeactivateTower(_ context.Context,
req *DeactivateTowerRequest) (*DeactivateTowerResponse, error) {
if err := c.isActive(); err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(req.Pubkey)
if err != nil {
return nil, err
}
err = c.cfg.ClientMgr.DeactivateTower(pubKey)
if err != nil {
return nil, err
}
return &DeactivateTowerResponse{
Status: fmt.Sprintf("Successful deactivation of tower: %x",
req.Pubkey),
}, nil
}
// TerminateSession terminates the given session and marks it as terminal so
// that it is never used again.
func (c *WatchtowerClient) TerminateSession(_ context.Context,
req *TerminateSessionRequest) (*TerminateSessionResponse, error) {
if err := c.isActive(); err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(req.SessionId)
if err != nil {
return nil, err
}
sessionID := wtdb.NewSessionIDFromPubKey(pubKey)
err = c.cfg.ClientMgr.TerminateSession(sessionID)
if err != nil {
return nil, err
}
return &TerminateSessionResponse{
Status: fmt.Sprintf("Successful termination of session: %s",
sessionID),
}, nil
}
// ListTowers returns the list of watchtowers registered with the client. // ListTowers returns the list of watchtowers registered with the client.
func (c *WatchtowerClient) ListTowers(ctx context.Context, func (c *WatchtowerClient) ListTowers(ctx context.Context,
req *ListTowersRequest) (*ListTowersResponse, error) { req *ListTowersRequest) (*ListTowersResponse, error) {
@@ -489,6 +550,7 @@ func marshallTower(tower *wtclient.RegisteredTower, policyType PolicyType,
for _, session := range sessions { for _, session := range sessions {
satPerVByte := session.Policy.SweepFeeRate.FeePerVByte() satPerVByte := session.Policy.SweepFeeRate.FeePerVByte()
rpcSessions = append(rpcSessions, &TowerSession{ rpcSessions = append(rpcSessions, &TowerSession{
Id: session.ID[:],
NumBackups: uint32(ackCounts[session.ID]), NumBackups: uint32(ackCounts[session.ID]),
NumPendingBackups: uint32(pendingCounts[session.ID]), NumPendingBackups: uint32(pendingCounts[session.ID]),
MaxBackups: uint32(session.Policy.MaxUpdates), MaxBackups: uint32(session.Policy.MaxUpdates),
+490 -207
View File
@@ -264,6 +264,198 @@ func (*RemoveTowerResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{3} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{3}
} }
type DeactivateTowerRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identifying public key of the watchtower to deactivate.
Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
}
func (x *DeactivateTowerRequest) Reset() {
*x = DeactivateTowerRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeactivateTowerRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeactivateTowerRequest) ProtoMessage() {}
func (x *DeactivateTowerRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeactivateTowerRequest.ProtoReflect.Descriptor instead.
func (*DeactivateTowerRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{4}
}
func (x *DeactivateTowerRequest) GetPubkey() []byte {
if x != nil {
return x.Pubkey
}
return nil
}
type DeactivateTowerResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A string describing the action that took place.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *DeactivateTowerResponse) Reset() {
*x = DeactivateTowerResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeactivateTowerResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeactivateTowerResponse) ProtoMessage() {}
func (x *DeactivateTowerResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeactivateTowerResponse.ProtoReflect.Descriptor instead.
func (*DeactivateTowerResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{5}
}
func (x *DeactivateTowerResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type TerminateSessionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The ID of the session that should be terminated.
SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
}
func (x *TerminateSessionRequest) Reset() {
*x = TerminateSessionRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TerminateSessionRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TerminateSessionRequest) ProtoMessage() {}
func (x *TerminateSessionRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TerminateSessionRequest.ProtoReflect.Descriptor instead.
func (*TerminateSessionRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{6}
}
func (x *TerminateSessionRequest) GetSessionId() []byte {
if x != nil {
return x.SessionId
}
return nil
}
type TerminateSessionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A string describing the action that took place.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *TerminateSessionResponse) Reset() {
*x = TerminateSessionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TerminateSessionResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TerminateSessionResponse) ProtoMessage() {}
func (x *TerminateSessionResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TerminateSessionResponse.ProtoReflect.Descriptor instead.
func (*TerminateSessionResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{7}
}
func (x *TerminateSessionResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type GetTowerInfoRequest struct { type GetTowerInfoRequest struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -281,7 +473,7 @@ type GetTowerInfoRequest struct {
func (x *GetTowerInfoRequest) Reset() { func (x *GetTowerInfoRequest) Reset() {
*x = GetTowerInfoRequest{} *x = GetTowerInfoRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[4] mi := &file_wtclientrpc_wtclient_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -294,7 +486,7 @@ func (x *GetTowerInfoRequest) String() string {
func (*GetTowerInfoRequest) ProtoMessage() {} func (*GetTowerInfoRequest) ProtoMessage() {}
func (x *GetTowerInfoRequest) ProtoReflect() protoreflect.Message { func (x *GetTowerInfoRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[4] mi := &file_wtclientrpc_wtclient_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -307,7 +499,7 @@ func (x *GetTowerInfoRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetTowerInfoRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetTowerInfoRequest.ProtoReflect.Descriptor instead.
func (*GetTowerInfoRequest) Descriptor() ([]byte, []int) { func (*GetTowerInfoRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{4} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{8}
} }
func (x *GetTowerInfoRequest) GetPubkey() []byte { func (x *GetTowerInfoRequest) GetPubkey() []byte {
@@ -353,12 +545,14 @@ type TowerSession struct {
// The fee rate, in satoshis per vbyte, that will be used by the watchtower for // The fee rate, in satoshis per vbyte, that will be used by the watchtower for
// the justice transaction in the event of a channel breach. // the justice transaction in the event of a channel breach.
SweepSatPerVbyte uint32 `protobuf:"varint,5,opt,name=sweep_sat_per_vbyte,json=sweepSatPerVbyte,proto3" json:"sweep_sat_per_vbyte,omitempty"` SweepSatPerVbyte uint32 `protobuf:"varint,5,opt,name=sweep_sat_per_vbyte,json=sweepSatPerVbyte,proto3" json:"sweep_sat_per_vbyte,omitempty"`
// The ID of the session.
Id []byte `protobuf:"bytes,6,opt,name=id,proto3" json:"id,omitempty"`
} }
func (x *TowerSession) Reset() { func (x *TowerSession) Reset() {
*x = TowerSession{} *x = TowerSession{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[5] mi := &file_wtclientrpc_wtclient_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -371,7 +565,7 @@ func (x *TowerSession) String() string {
func (*TowerSession) ProtoMessage() {} func (*TowerSession) ProtoMessage() {}
func (x *TowerSession) ProtoReflect() protoreflect.Message { func (x *TowerSession) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[5] mi := &file_wtclientrpc_wtclient_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -384,7 +578,7 @@ func (x *TowerSession) ProtoReflect() protoreflect.Message {
// Deprecated: Use TowerSession.ProtoReflect.Descriptor instead. // Deprecated: Use TowerSession.ProtoReflect.Descriptor instead.
func (*TowerSession) Descriptor() ([]byte, []int) { func (*TowerSession) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{5} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{9}
} }
func (x *TowerSession) GetNumBackups() uint32 { func (x *TowerSession) GetNumBackups() uint32 {
@@ -423,6 +617,13 @@ func (x *TowerSession) GetSweepSatPerVbyte() uint32 {
return 0 return 0
} }
func (x *TowerSession) GetId() []byte {
if x != nil {
return x.Id
}
return nil
}
type Tower struct { type Tower struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -457,7 +658,7 @@ type Tower struct {
func (x *Tower) Reset() { func (x *Tower) Reset() {
*x = Tower{} *x = Tower{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[6] mi := &file_wtclientrpc_wtclient_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -470,7 +671,7 @@ func (x *Tower) String() string {
func (*Tower) ProtoMessage() {} func (*Tower) ProtoMessage() {}
func (x *Tower) ProtoReflect() protoreflect.Message { func (x *Tower) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[6] mi := &file_wtclientrpc_wtclient_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -483,7 +684,7 @@ func (x *Tower) ProtoReflect() protoreflect.Message {
// Deprecated: Use Tower.ProtoReflect.Descriptor instead. // Deprecated: Use Tower.ProtoReflect.Descriptor instead.
func (*Tower) Descriptor() ([]byte, []int) { func (*Tower) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{6} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{10}
} }
func (x *Tower) GetPubkey() []byte { func (x *Tower) GetPubkey() []byte {
@@ -549,7 +750,7 @@ type TowerSessionInfo struct {
func (x *TowerSessionInfo) Reset() { func (x *TowerSessionInfo) Reset() {
*x = TowerSessionInfo{} *x = TowerSessionInfo{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[7] mi := &file_wtclientrpc_wtclient_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -562,7 +763,7 @@ func (x *TowerSessionInfo) String() string {
func (*TowerSessionInfo) ProtoMessage() {} func (*TowerSessionInfo) ProtoMessage() {}
func (x *TowerSessionInfo) ProtoReflect() protoreflect.Message { func (x *TowerSessionInfo) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[7] mi := &file_wtclientrpc_wtclient_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -575,7 +776,7 @@ func (x *TowerSessionInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use TowerSessionInfo.ProtoReflect.Descriptor instead. // Deprecated: Use TowerSessionInfo.ProtoReflect.Descriptor instead.
func (*TowerSessionInfo) Descriptor() ([]byte, []int) { func (*TowerSessionInfo) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{7} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{11}
} }
func (x *TowerSessionInfo) GetActiveSessionCandidate() bool { func (x *TowerSessionInfo) GetActiveSessionCandidate() bool {
@@ -621,7 +822,7 @@ type ListTowersRequest struct {
func (x *ListTowersRequest) Reset() { func (x *ListTowersRequest) Reset() {
*x = ListTowersRequest{} *x = ListTowersRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] mi := &file_wtclientrpc_wtclient_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -634,7 +835,7 @@ func (x *ListTowersRequest) String() string {
func (*ListTowersRequest) ProtoMessage() {} func (*ListTowersRequest) ProtoMessage() {}
func (x *ListTowersRequest) ProtoReflect() protoreflect.Message { func (x *ListTowersRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] mi := &file_wtclientrpc_wtclient_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -647,7 +848,7 @@ func (x *ListTowersRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListTowersRequest.ProtoReflect.Descriptor instead. // Deprecated: Use ListTowersRequest.ProtoReflect.Descriptor instead.
func (*ListTowersRequest) Descriptor() ([]byte, []int) { func (*ListTowersRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{8} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{12}
} }
func (x *ListTowersRequest) GetIncludeSessions() bool { func (x *ListTowersRequest) GetIncludeSessions() bool {
@@ -676,7 +877,7 @@ type ListTowersResponse struct {
func (x *ListTowersResponse) Reset() { func (x *ListTowersResponse) Reset() {
*x = ListTowersResponse{} *x = ListTowersResponse{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] mi := &file_wtclientrpc_wtclient_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -689,7 +890,7 @@ func (x *ListTowersResponse) String() string {
func (*ListTowersResponse) ProtoMessage() {} func (*ListTowersResponse) ProtoMessage() {}
func (x *ListTowersResponse) ProtoReflect() protoreflect.Message { func (x *ListTowersResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] mi := &file_wtclientrpc_wtclient_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -702,7 +903,7 @@ func (x *ListTowersResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListTowersResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListTowersResponse.ProtoReflect.Descriptor instead.
func (*ListTowersResponse) Descriptor() ([]byte, []int) { func (*ListTowersResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{9} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{13}
} }
func (x *ListTowersResponse) GetTowers() []*Tower { func (x *ListTowersResponse) GetTowers() []*Tower {
@@ -721,7 +922,7 @@ type StatsRequest struct {
func (x *StatsRequest) Reset() { func (x *StatsRequest) Reset() {
*x = StatsRequest{} *x = StatsRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] mi := &file_wtclientrpc_wtclient_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -734,7 +935,7 @@ func (x *StatsRequest) String() string {
func (*StatsRequest) ProtoMessage() {} func (*StatsRequest) ProtoMessage() {}
func (x *StatsRequest) ProtoReflect() protoreflect.Message { func (x *StatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] mi := &file_wtclientrpc_wtclient_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -747,7 +948,7 @@ func (x *StatsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use StatsRequest.ProtoReflect.Descriptor instead. // Deprecated: Use StatsRequest.ProtoReflect.Descriptor instead.
func (*StatsRequest) Descriptor() ([]byte, []int) { func (*StatsRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{10} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{14}
} }
type StatsResponse struct { type StatsResponse struct {
@@ -773,7 +974,7 @@ type StatsResponse struct {
func (x *StatsResponse) Reset() { func (x *StatsResponse) Reset() {
*x = StatsResponse{} *x = StatsResponse{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] mi := &file_wtclientrpc_wtclient_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -786,7 +987,7 @@ func (x *StatsResponse) String() string {
func (*StatsResponse) ProtoMessage() {} func (*StatsResponse) ProtoMessage() {}
func (x *StatsResponse) ProtoReflect() protoreflect.Message { func (x *StatsResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] mi := &file_wtclientrpc_wtclient_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -799,7 +1000,7 @@ func (x *StatsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use StatsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use StatsResponse.ProtoReflect.Descriptor instead.
func (*StatsResponse) Descriptor() ([]byte, []int) { func (*StatsResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{11} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{15}
} }
func (x *StatsResponse) GetNumBackups() uint32 { func (x *StatsResponse) GetNumBackups() uint32 {
@@ -849,7 +1050,7 @@ type PolicyRequest struct {
func (x *PolicyRequest) Reset() { func (x *PolicyRequest) Reset() {
*x = PolicyRequest{} *x = PolicyRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] mi := &file_wtclientrpc_wtclient_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -862,7 +1063,7 @@ func (x *PolicyRequest) String() string {
func (*PolicyRequest) ProtoMessage() {} func (*PolicyRequest) ProtoMessage() {}
func (x *PolicyRequest) ProtoReflect() protoreflect.Message { func (x *PolicyRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] mi := &file_wtclientrpc_wtclient_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -875,7 +1076,7 @@ func (x *PolicyRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use PolicyRequest.ProtoReflect.Descriptor instead. // Deprecated: Use PolicyRequest.ProtoReflect.Descriptor instead.
func (*PolicyRequest) Descriptor() ([]byte, []int) { func (*PolicyRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{12} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{16}
} }
func (x *PolicyRequest) GetPolicyType() PolicyType { func (x *PolicyRequest) GetPolicyType() PolicyType {
@@ -907,7 +1108,7 @@ type PolicyResponse struct {
func (x *PolicyResponse) Reset() { func (x *PolicyResponse) Reset() {
*x = PolicyResponse{} *x = PolicyResponse{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[13] mi := &file_wtclientrpc_wtclient_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -920,7 +1121,7 @@ func (x *PolicyResponse) String() string {
func (*PolicyResponse) ProtoMessage() {} func (*PolicyResponse) ProtoMessage() {}
func (x *PolicyResponse) ProtoReflect() protoreflect.Message { func (x *PolicyResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[13] mi := &file_wtclientrpc_wtclient_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -933,7 +1134,7 @@ func (x *PolicyResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use PolicyResponse.ProtoReflect.Descriptor instead. // Deprecated: Use PolicyResponse.ProtoReflect.Descriptor instead.
func (*PolicyResponse) Descriptor() ([]byte, []int) { func (*PolicyResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{13} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{17}
} }
func (x *PolicyResponse) GetMaxUpdates() uint32 { func (x *PolicyResponse) GetMaxUpdates() uint32 {
@@ -975,141 +1176,167 @@ var file_wtclientrpc_wtclient_proto_rawDesc = []byte{
0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x96, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x65, 0x22, 0x30, 0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x54,
0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62,
0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x6b, 0x65, 0x79, 0x22, 0x31, 0x0a, 0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74,
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16,
0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x38, 0x0a, 0x17, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e,
0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18,
0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0c, 0x54, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64,
0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x22, 0x32, 0x0a, 0x18, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73,
0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06,
0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74,
0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x61, 0x74, 0x75, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65,
0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75,
0x6d, 0x61, 0x78, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f,
0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2f, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f,
0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12,
0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73, 0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75,
0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20,
0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x45, 0x78, 0x68, 0x61,
0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, 0x77, 0x65, 0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf0, 0x01,
0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0x9f, 0x02, 0x0a, 0x0c, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f,
0x0a, 0x05, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12,
0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62,
0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75,
0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12,
0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x42, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03,
0x02, 0x18, 0x01, 0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73,
0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, 0x0c, 0x6e, 0x12, 0x2f, 0x0a, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65,
0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01,
0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x52, 0x0f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74,
0x6e, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x65, 0x12, 0x2d, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10,
0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65,
0x02, 0x18, 0x01, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x40, 0x0a, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64,
0x0c, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x22, 0x9f, 0x02, 0x0a, 0x05, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75,
0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b,
0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18,
0x66, 0x6f, 0x52, 0x0b, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73,
0xe0, 0x01, 0x0a, 0x10, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69,
0x49, 0x6e, 0x66, 0x6f, 0x12, 0x38, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01,
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x25,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04,
0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x73, 0x12, 0x35, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65,
0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69,
0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6f, 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x12, 0x40, 0x0a, 0x0c, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f,
0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e,
0x70, 0x65, 0x22, 0x7c, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x66, 0x6f, 0x22, 0xe0, 0x01, 0x0a, 0x10, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x38, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76,
0x64, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64,
0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76,
0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x78, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74,
0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x45, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73,
0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
0x22, 0x40, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x70,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x06, 0x74, 0x6f, 0x77, 0x65, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e,
0x72, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50,
0x73, 0x74, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x7c, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77,
0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e,
0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01,
0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73,
0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65,
0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x5f, 0x66, 0x61, 0x69, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69,
0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75,
0x0d, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42, 0x61, 0x63, 0x6b, 0x64, 0x65, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69,
0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x40, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72,
0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x6f, 0x77,
0x28, 0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x41, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c,
0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x06, 0x74,
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65,
0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52,
0x69, 0x6f, 0x6e, 0x73, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x22, 0x49, 0x0a, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62,
0x0d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75,
0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f,
0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18,
0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e,
0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x0e, 0x50, 0x6f, 0x6c, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x5f,
0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03,
0x61, 0x78, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42,
0x52, 0x0a, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65,
0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18,
0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73, 0x77, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x6e, 0x73, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x6e, 0x75,
0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75,
0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, 0x77, 0x65, 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6e, 0x75, 0x6d, 0x53,
0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x2a, 0x31, 0x0a, 0x0a, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64,
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x22, 0x49, 0x0a, 0x0d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x74, 0x12, 0x38, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65,
0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x02, 0x32, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0xc5, 0x03, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x43, 0x6c, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52,
0x69, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x0e,
0x12, 0x1c, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f,
0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20,
0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x2f, 0x0a, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72,
0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x77, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52,
0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x0f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65,
0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x12, 0x2d, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73,
0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x2a,
0x4d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x2e, 0x31, 0x0a, 0x0a, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x10, 0x02, 0x32, 0x84, 0x05, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x54, 0x6f,
0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x20, 0x77, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x1a, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x6f, 0x77, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x19, 0x2e, 0x12, 0x50, 0x0a, 0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x1a, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0f, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65,
0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x74, 0x63, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x23, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x54, 0x6f,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, 0x74, 0x63,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76,
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x2f, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x12, 0x5f, 0x0a, 0x10, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73,
0x6f, 0x74, 0x6f, 0x33, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72,
0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x77, 0x74, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61,
0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x12,
0x1e, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x44, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f,
0x12, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47,
0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63,
0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74,
0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x74, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
0x12, 0x1a, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50,
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77,
0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e,
0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2f, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@@ -1125,45 +1352,53 @@ func file_wtclientrpc_wtclient_proto_rawDescGZIP() []byte {
} }
var file_wtclientrpc_wtclient_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_wtclientrpc_wtclient_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_wtclientrpc_wtclient_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_wtclientrpc_wtclient_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
var file_wtclientrpc_wtclient_proto_goTypes = []interface{}{ var file_wtclientrpc_wtclient_proto_goTypes = []interface{}{
(PolicyType)(0), // 0: wtclientrpc.PolicyType (PolicyType)(0), // 0: wtclientrpc.PolicyType
(*AddTowerRequest)(nil), // 1: wtclientrpc.AddTowerRequest (*AddTowerRequest)(nil), // 1: wtclientrpc.AddTowerRequest
(*AddTowerResponse)(nil), // 2: wtclientrpc.AddTowerResponse (*AddTowerResponse)(nil), // 2: wtclientrpc.AddTowerResponse
(*RemoveTowerRequest)(nil), // 3: wtclientrpc.RemoveTowerRequest (*RemoveTowerRequest)(nil), // 3: wtclientrpc.RemoveTowerRequest
(*RemoveTowerResponse)(nil), // 4: wtclientrpc.RemoveTowerResponse (*RemoveTowerResponse)(nil), // 4: wtclientrpc.RemoveTowerResponse
(*GetTowerInfoRequest)(nil), // 5: wtclientrpc.GetTowerInfoRequest (*DeactivateTowerRequest)(nil), // 5: wtclientrpc.DeactivateTowerRequest
(*TowerSession)(nil), // 6: wtclientrpc.TowerSession (*DeactivateTowerResponse)(nil), // 6: wtclientrpc.DeactivateTowerResponse
(*Tower)(nil), // 7: wtclientrpc.Tower (*TerminateSessionRequest)(nil), // 7: wtclientrpc.TerminateSessionRequest
(*TowerSessionInfo)(nil), // 8: wtclientrpc.TowerSessionInfo (*TerminateSessionResponse)(nil), // 8: wtclientrpc.TerminateSessionResponse
(*ListTowersRequest)(nil), // 9: wtclientrpc.ListTowersRequest (*GetTowerInfoRequest)(nil), // 9: wtclientrpc.GetTowerInfoRequest
(*ListTowersResponse)(nil), // 10: wtclientrpc.ListTowersResponse (*TowerSession)(nil), // 10: wtclientrpc.TowerSession
(*StatsRequest)(nil), // 11: wtclientrpc.StatsRequest (*Tower)(nil), // 11: wtclientrpc.Tower
(*StatsResponse)(nil), // 12: wtclientrpc.StatsResponse (*TowerSessionInfo)(nil), // 12: wtclientrpc.TowerSessionInfo
(*PolicyRequest)(nil), // 13: wtclientrpc.PolicyRequest (*ListTowersRequest)(nil), // 13: wtclientrpc.ListTowersRequest
(*PolicyResponse)(nil), // 14: wtclientrpc.PolicyResponse (*ListTowersResponse)(nil), // 14: wtclientrpc.ListTowersResponse
(*StatsRequest)(nil), // 15: wtclientrpc.StatsRequest
(*StatsResponse)(nil), // 16: wtclientrpc.StatsResponse
(*PolicyRequest)(nil), // 17: wtclientrpc.PolicyRequest
(*PolicyResponse)(nil), // 18: wtclientrpc.PolicyResponse
} }
var file_wtclientrpc_wtclient_proto_depIdxs = []int32{ var file_wtclientrpc_wtclient_proto_depIdxs = []int32{
6, // 0: wtclientrpc.Tower.sessions:type_name -> wtclientrpc.TowerSession 10, // 0: wtclientrpc.Tower.sessions:type_name -> wtclientrpc.TowerSession
8, // 1: wtclientrpc.Tower.session_info:type_name -> wtclientrpc.TowerSessionInfo 12, // 1: wtclientrpc.Tower.session_info:type_name -> wtclientrpc.TowerSessionInfo
6, // 2: wtclientrpc.TowerSessionInfo.sessions:type_name -> wtclientrpc.TowerSession 10, // 2: wtclientrpc.TowerSessionInfo.sessions:type_name -> wtclientrpc.TowerSession
0, // 3: wtclientrpc.TowerSessionInfo.policy_type:type_name -> wtclientrpc.PolicyType 0, // 3: wtclientrpc.TowerSessionInfo.policy_type:type_name -> wtclientrpc.PolicyType
7, // 4: wtclientrpc.ListTowersResponse.towers:type_name -> wtclientrpc.Tower 11, // 4: wtclientrpc.ListTowersResponse.towers:type_name -> wtclientrpc.Tower
0, // 5: wtclientrpc.PolicyRequest.policy_type:type_name -> wtclientrpc.PolicyType 0, // 5: wtclientrpc.PolicyRequest.policy_type:type_name -> wtclientrpc.PolicyType
1, // 6: wtclientrpc.WatchtowerClient.AddTower:input_type -> wtclientrpc.AddTowerRequest 1, // 6: wtclientrpc.WatchtowerClient.AddTower:input_type -> wtclientrpc.AddTowerRequest
3, // 7: wtclientrpc.WatchtowerClient.RemoveTower:input_type -> wtclientrpc.RemoveTowerRequest 3, // 7: wtclientrpc.WatchtowerClient.RemoveTower:input_type -> wtclientrpc.RemoveTowerRequest
9, // 8: wtclientrpc.WatchtowerClient.ListTowers:input_type -> wtclientrpc.ListTowersRequest 5, // 8: wtclientrpc.WatchtowerClient.DeactivateTower:input_type -> wtclientrpc.DeactivateTowerRequest
5, // 9: wtclientrpc.WatchtowerClient.GetTowerInfo:input_type -> wtclientrpc.GetTowerInfoRequest 7, // 9: wtclientrpc.WatchtowerClient.TerminateSession:input_type -> wtclientrpc.TerminateSessionRequest
11, // 10: wtclientrpc.WatchtowerClient.Stats:input_type -> wtclientrpc.StatsRequest 13, // 10: wtclientrpc.WatchtowerClient.ListTowers:input_type -> wtclientrpc.ListTowersRequest
13, // 11: wtclientrpc.WatchtowerClient.Policy:input_type -> wtclientrpc.PolicyRequest 9, // 11: wtclientrpc.WatchtowerClient.GetTowerInfo:input_type -> wtclientrpc.GetTowerInfoRequest
2, // 12: wtclientrpc.WatchtowerClient.AddTower:output_type -> wtclientrpc.AddTowerResponse 15, // 12: wtclientrpc.WatchtowerClient.Stats:input_type -> wtclientrpc.StatsRequest
4, // 13: wtclientrpc.WatchtowerClient.RemoveTower:output_type -> wtclientrpc.RemoveTowerResponse 17, // 13: wtclientrpc.WatchtowerClient.Policy:input_type -> wtclientrpc.PolicyRequest
10, // 14: wtclientrpc.WatchtowerClient.ListTowers:output_type -> wtclientrpc.ListTowersResponse 2, // 14: wtclientrpc.WatchtowerClient.AddTower:output_type -> wtclientrpc.AddTowerResponse
7, // 15: wtclientrpc.WatchtowerClient.GetTowerInfo:output_type -> wtclientrpc.Tower 4, // 15: wtclientrpc.WatchtowerClient.RemoveTower:output_type -> wtclientrpc.RemoveTowerResponse
12, // 16: wtclientrpc.WatchtowerClient.Stats:output_type -> wtclientrpc.StatsResponse 6, // 16: wtclientrpc.WatchtowerClient.DeactivateTower:output_type -> wtclientrpc.DeactivateTowerResponse
14, // 17: wtclientrpc.WatchtowerClient.Policy:output_type -> wtclientrpc.PolicyResponse 8, // 17: wtclientrpc.WatchtowerClient.TerminateSession:output_type -> wtclientrpc.TerminateSessionResponse
12, // [12:18] is the sub-list for method output_type 14, // 18: wtclientrpc.WatchtowerClient.ListTowers:output_type -> wtclientrpc.ListTowersResponse
6, // [6:12] is the sub-list for method input_type 11, // 19: wtclientrpc.WatchtowerClient.GetTowerInfo:output_type -> wtclientrpc.Tower
16, // 20: wtclientrpc.WatchtowerClient.Stats:output_type -> wtclientrpc.StatsResponse
18, // 21: wtclientrpc.WatchtowerClient.Policy:output_type -> wtclientrpc.PolicyResponse
14, // [14:22] is the sub-list for method output_type
6, // [6:14] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name 6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee 6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name 0, // [0:6] is the sub-list for field type_name
@@ -1224,7 +1459,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetTowerInfoRequest); i { switch v := v.(*DeactivateTowerRequest); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1236,7 +1471,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TowerSession); i { switch v := v.(*DeactivateTowerResponse); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1248,7 +1483,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Tower); i { switch v := v.(*TerminateSessionRequest); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1260,7 +1495,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TowerSessionInfo); i { switch v := v.(*TerminateSessionResponse); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1272,7 +1507,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListTowersRequest); i { switch v := v.(*GetTowerInfoRequest); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1284,7 +1519,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListTowersResponse); i { switch v := v.(*TowerSession); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1296,7 +1531,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatsRequest); i { switch v := v.(*Tower); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1308,7 +1543,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatsResponse); i { switch v := v.(*TowerSessionInfo); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1320,7 +1555,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PolicyRequest); i { switch v := v.(*ListTowersRequest); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1332,6 +1567,54 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListTowersResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PolicyRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PolicyResponse); i { switch v := v.(*PolicyResponse); i {
case 0: case 0:
return &v.state return &v.state
@@ -1350,7 +1633,7 @@ func file_wtclientrpc_wtclient_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_wtclientrpc_wtclient_proto_rawDesc, RawDescriptor: file_wtclientrpc_wtclient_proto_rawDesc,
NumEnums: 1, NumEnums: 1,
NumMessages: 14, NumMessages: 18,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },
+198
View File
@@ -135,6 +135,110 @@ func local_request_WatchtowerClient_RemoveTower_0(ctx context.Context, marshaler
} }
func request_WatchtowerClient_DeactivateTower_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeactivateTowerRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pubkey"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
}
protoReq.Pubkey, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
}
msg, err := client.DeactivateTower(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WatchtowerClient_DeactivateTower_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeactivateTowerRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pubkey"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
}
protoReq.Pubkey, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
}
msg, err := server.DeactivateTower(ctx, &protoReq)
return msg, metadata, err
}
func request_WatchtowerClient_TerminateSession_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TerminateSessionRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["session_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "session_id")
}
protoReq.SessionId, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "session_id", err)
}
msg, err := client.TerminateSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WatchtowerClient_TerminateSession_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TerminateSessionRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["session_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "session_id")
}
protoReq.SessionId, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "session_id", err)
}
msg, err := server.TerminateSession(ctx, &protoReq)
return msg, metadata, err
}
var ( var (
filter_WatchtowerClient_ListTowers_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} filter_WatchtowerClient_ListTowers_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
) )
@@ -347,6 +451,52 @@ func RegisterWatchtowerClientHandlerServer(ctx context.Context, mux *runtime.Ser
}) })
mux.Handle("POST", pattern_WatchtowerClient_DeactivateTower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/DeactivateTower", runtime.WithHTTPPathPattern("/v2/watchtower/client/tower/deactivate/{pubkey}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WatchtowerClient_DeactivateTower_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_DeactivateTower_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WatchtowerClient_TerminateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/TerminateSession", runtime.WithHTTPPathPattern("/v2/watchtower/client/sessions/terminate/{session_id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WatchtowerClient_TerminateSession_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_TerminateSession_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WatchtowerClient_ListTowers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("GET", pattern_WatchtowerClient_ListTowers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
@@ -520,6 +670,46 @@ func RegisterWatchtowerClientHandlerClient(ctx context.Context, mux *runtime.Ser
}) })
mux.Handle("POST", pattern_WatchtowerClient_DeactivateTower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/DeactivateTower", runtime.WithHTTPPathPattern("/v2/watchtower/client/tower/deactivate/{pubkey}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WatchtowerClient_DeactivateTower_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_DeactivateTower_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WatchtowerClient_TerminateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/TerminateSession", runtime.WithHTTPPathPattern("/v2/watchtower/client/sessions/terminate/{session_id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WatchtowerClient_TerminateSession_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_TerminateSession_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WatchtowerClient_ListTowers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("GET", pattern_WatchtowerClient_ListTowers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
@@ -608,6 +798,10 @@ var (
pattern_WatchtowerClient_RemoveTower_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "watchtower", "client", "pubkey"}, "")) pattern_WatchtowerClient_RemoveTower_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "watchtower", "client", "pubkey"}, ""))
pattern_WatchtowerClient_DeactivateTower_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"v2", "watchtower", "client", "tower", "deactivate", "pubkey"}, ""))
pattern_WatchtowerClient_TerminateSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"v2", "watchtower", "client", "sessions", "terminate", "session_id"}, ""))
pattern_WatchtowerClient_ListTowers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "watchtower", "client"}, "")) pattern_WatchtowerClient_ListTowers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "watchtower", "client"}, ""))
pattern_WatchtowerClient_GetTowerInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v2", "watchtower", "client", "info", "pubkey"}, "")) pattern_WatchtowerClient_GetTowerInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v2", "watchtower", "client", "info", "pubkey"}, ""))
@@ -622,6 +816,10 @@ var (
forward_WatchtowerClient_RemoveTower_0 = runtime.ForwardResponseMessage forward_WatchtowerClient_RemoveTower_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_DeactivateTower_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_TerminateSession_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_ListTowers_0 = runtime.ForwardResponseMessage forward_WatchtowerClient_ListTowers_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_GetTowerInfo_0 = runtime.ForwardResponseMessage forward_WatchtowerClient_GetTowerInfo_0 = runtime.ForwardResponseMessage
+40
View File
@@ -41,6 +41,21 @@ service WatchtowerClient {
*/ */
rpc RemoveTower (RemoveTowerRequest) returns (RemoveTowerResponse); rpc RemoveTower (RemoveTowerRequest) returns (RemoveTowerResponse);
/* lncli: `wtclient deactivate`
DeactivateTower sets the given tower's status to inactive so that it
is not considered for session negotiation. Its sessions will also not
be used while the tower is inactive.
*/
rpc DeactivateTower (DeactivateTowerRequest)
returns (DeactivateTowerResponse);
/* lncli: `wtclient session terminate`
Terminate terminates the given session and marks it as terminal so that
it is not used for backups anymore.
*/
rpc TerminateSession (TerminateSessionRequest)
returns (TerminateSessionResponse);
/* lncli: `wtclient towers` /* lncli: `wtclient towers`
ListTowers returns the list of watchtowers registered with the client. ListTowers returns the list of watchtowers registered with the client.
*/ */
@@ -88,6 +103,26 @@ message RemoveTowerRequest {
message RemoveTowerResponse { message RemoveTowerResponse {
} }
message DeactivateTowerRequest {
// The identifying public key of the watchtower to deactivate.
bytes pubkey = 1;
}
message DeactivateTowerResponse {
// A string describing the action that took place.
string status = 1;
}
message TerminateSessionRequest {
// The ID of the session that should be terminated.
bytes session_id = 1;
}
message TerminateSessionResponse {
// A string describing the action that took place.
string status = 1;
}
message GetTowerInfoRequest { message GetTowerInfoRequest {
// The identifying public key of the watchtower to retrieve information for. // The identifying public key of the watchtower to retrieve information for.
bytes pubkey = 1; bytes pubkey = 1;
@@ -128,6 +163,11 @@ message TowerSession {
the justice transaction in the event of a channel breach. the justice transaction in the event of a channel breach.
*/ */
uint32 sweep_sat_per_vbyte = 5; uint32 sweep_sat_per_vbyte = 5;
/*
The ID of the session.
*/
bytes id = 6;
} }
message Tower { message Tower {
+89
View File
@@ -171,6 +171,39 @@
] ]
} }
}, },
"/v2/watchtower/client/sessions/terminate/{session_id}": {
"post": {
"summary": "lncli: `wtclient session terminate`\nTerminate terminates the given session and marks it as terminal so that\nit is not used for backups anymore.",
"operationId": "WatchtowerClient_TerminateSession",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/wtclientrpcTerminateSessionResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "session_id",
"description": "The ID of the session that should be terminated.",
"in": "path",
"required": true,
"type": "string",
"format": "byte"
}
],
"tags": [
"WatchtowerClient"
]
}
},
"/v2/watchtower/client/stats": { "/v2/watchtower/client/stats": {
"get": { "get": {
"summary": "lncli: `wtclient stats`\nStats returns the in-memory statistics of the client since startup.", "summary": "lncli: `wtclient stats`\nStats returns the in-memory statistics of the client since startup.",
@@ -194,6 +227,39 @@
] ]
} }
}, },
"/v2/watchtower/client/tower/deactivate/{pubkey}": {
"post": {
"summary": "lncli: `wtclient deactivate`\nDeactivateTower sets the given tower's status to inactive so that it\nis not considered for session negotiation. Its sessions will also not\nbe used while the tower is inactive.",
"operationId": "WatchtowerClient_DeactivateTower",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/wtclientrpcDeactivateTowerResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "pubkey",
"description": "The identifying public key of the watchtower to deactivate.",
"in": "path",
"required": true,
"type": "string",
"format": "byte"
}
],
"tags": [
"WatchtowerClient"
]
}
},
"/v2/watchtower/client/{pubkey}": { "/v2/watchtower/client/{pubkey}": {
"delete": { "delete": {
"summary": "lncli: `wtclient remove`\nRemoveTower removes a watchtower from being considered for future session\nnegotiations and from being used for any subsequent backups until it's added\nagain. If an address is provided, then this RPC only serves as a way of\nremoving the address from the watchtower instead.", "summary": "lncli: `wtclient remove`\nRemoveTower removes a watchtower from being considered for future session\nnegotiations and from being used for any subsequent backups until it's added\nagain. If an address is provided, then this RPC only serves as a way of\nremoving the address from the watchtower instead.",
@@ -283,6 +349,15 @@
"wtclientrpcAddTowerResponse": { "wtclientrpcAddTowerResponse": {
"type": "object" "type": "object"
}, },
"wtclientrpcDeactivateTowerResponse": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "A string describing the action that took place."
}
}
},
"wtclientrpcListTowersResponse": { "wtclientrpcListTowersResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -358,6 +433,15 @@
} }
} }
}, },
"wtclientrpcTerminateSessionResponse": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "A string describing the action that took place."
}
}
},
"wtclientrpcTower": { "wtclientrpcTower": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -425,6 +509,11 @@
"type": "integer", "type": "integer",
"format": "int64", "format": "int64",
"description": "The fee rate, in satoshis per vbyte, that will be used by the watchtower for\nthe justice transaction in the event of a channel breach." "description": "The fee rate, in satoshis per vbyte, that will be used by the watchtower for\nthe justice transaction in the event of a channel breach."
},
"id": {
"type": "string",
"format": "byte",
"description": "The ID of the session."
} }
} }
}, },
+4
View File
@@ -8,6 +8,10 @@ http:
body: "*" body: "*"
- selector: wtclientrpc.WatchtowerClient.RemoveTower - selector: wtclientrpc.WatchtowerClient.RemoveTower
delete: "/v2/watchtower/client/{pubkey}" delete: "/v2/watchtower/client/{pubkey}"
- selector: wtclientrpc.WatchtowerClient.DeactivateTower
post: "/v2/watchtower/client/tower/deactivate/{pubkey}"
- selector: wtclientrpc.WatchtowerClient.TerminateSession
post: "/v2/watchtower/client/sessions/terminate/{session_id}"
- selector: wtclientrpc.WatchtowerClient.ListTowers - selector: wtclientrpc.WatchtowerClient.ListTowers
get: "/v2/watchtower/client" get: "/v2/watchtower/client"
- selector: wtclientrpc.WatchtowerClient.GetTowerInfo - selector: wtclientrpc.WatchtowerClient.GetTowerInfo
+86
View File
@@ -30,6 +30,15 @@ type WatchtowerClientClient interface {
// again. If an address is provided, then this RPC only serves as a way of // again. If an address is provided, then this RPC only serves as a way of
// removing the address from the watchtower instead. // removing the address from the watchtower instead.
RemoveTower(ctx context.Context, in *RemoveTowerRequest, opts ...grpc.CallOption) (*RemoveTowerResponse, error) RemoveTower(ctx context.Context, in *RemoveTowerRequest, opts ...grpc.CallOption) (*RemoveTowerResponse, error)
// lncli: `wtclient deactivate`
// DeactivateTower sets the given tower's status to inactive so that it
// is not considered for session negotiation. Its sessions will also not
// be used while the tower is inactive.
DeactivateTower(ctx context.Context, in *DeactivateTowerRequest, opts ...grpc.CallOption) (*DeactivateTowerResponse, error)
// lncli: `wtclient session terminate`
// Terminate terminates the given session and marks it as terminal so that
// it is not used for backups anymore.
TerminateSession(ctx context.Context, in *TerminateSessionRequest, opts ...grpc.CallOption) (*TerminateSessionResponse, error)
// lncli: `wtclient towers` // lncli: `wtclient towers`
// ListTowers returns the list of watchtowers registered with the client. // ListTowers returns the list of watchtowers registered with the client.
ListTowers(ctx context.Context, in *ListTowersRequest, opts ...grpc.CallOption) (*ListTowersResponse, error) ListTowers(ctx context.Context, in *ListTowersRequest, opts ...grpc.CallOption) (*ListTowersResponse, error)
@@ -70,6 +79,24 @@ func (c *watchtowerClientClient) RemoveTower(ctx context.Context, in *RemoveTowe
return out, nil return out, nil
} }
func (c *watchtowerClientClient) DeactivateTower(ctx context.Context, in *DeactivateTowerRequest, opts ...grpc.CallOption) (*DeactivateTowerResponse, error) {
out := new(DeactivateTowerResponse)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/DeactivateTower", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *watchtowerClientClient) TerminateSession(ctx context.Context, in *TerminateSessionRequest, opts ...grpc.CallOption) (*TerminateSessionResponse, error) {
out := new(TerminateSessionResponse)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/TerminateSession", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *watchtowerClientClient) ListTowers(ctx context.Context, in *ListTowersRequest, opts ...grpc.CallOption) (*ListTowersResponse, error) { func (c *watchtowerClientClient) ListTowers(ctx context.Context, in *ListTowersRequest, opts ...grpc.CallOption) (*ListTowersResponse, error) {
out := new(ListTowersResponse) out := new(ListTowersResponse)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/ListTowers", in, out, opts...) err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/ListTowers", in, out, opts...)
@@ -122,6 +149,15 @@ type WatchtowerClientServer interface {
// again. If an address is provided, then this RPC only serves as a way of // again. If an address is provided, then this RPC only serves as a way of
// removing the address from the watchtower instead. // removing the address from the watchtower instead.
RemoveTower(context.Context, *RemoveTowerRequest) (*RemoveTowerResponse, error) RemoveTower(context.Context, *RemoveTowerRequest) (*RemoveTowerResponse, error)
// lncli: `wtclient deactivate`
// DeactivateTower sets the given tower's status to inactive so that it
// is not considered for session negotiation. Its sessions will also not
// be used while the tower is inactive.
DeactivateTower(context.Context, *DeactivateTowerRequest) (*DeactivateTowerResponse, error)
// lncli: `wtclient session terminate`
// Terminate terminates the given session and marks it as terminal so that
// it is not used for backups anymore.
TerminateSession(context.Context, *TerminateSessionRequest) (*TerminateSessionResponse, error)
// lncli: `wtclient towers` // lncli: `wtclient towers`
// ListTowers returns the list of watchtowers registered with the client. // ListTowers returns the list of watchtowers registered with the client.
ListTowers(context.Context, *ListTowersRequest) (*ListTowersResponse, error) ListTowers(context.Context, *ListTowersRequest) (*ListTowersResponse, error)
@@ -147,6 +183,12 @@ func (UnimplementedWatchtowerClientServer) AddTower(context.Context, *AddTowerRe
func (UnimplementedWatchtowerClientServer) RemoveTower(context.Context, *RemoveTowerRequest) (*RemoveTowerResponse, error) { func (UnimplementedWatchtowerClientServer) RemoveTower(context.Context, *RemoveTowerRequest) (*RemoveTowerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveTower not implemented") return nil, status.Errorf(codes.Unimplemented, "method RemoveTower not implemented")
} }
func (UnimplementedWatchtowerClientServer) DeactivateTower(context.Context, *DeactivateTowerRequest) (*DeactivateTowerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeactivateTower not implemented")
}
func (UnimplementedWatchtowerClientServer) TerminateSession(context.Context, *TerminateSessionRequest) (*TerminateSessionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TerminateSession not implemented")
}
func (UnimplementedWatchtowerClientServer) ListTowers(context.Context, *ListTowersRequest) (*ListTowersResponse, error) { func (UnimplementedWatchtowerClientServer) ListTowers(context.Context, *ListTowersRequest) (*ListTowersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListTowers not implemented") return nil, status.Errorf(codes.Unimplemented, "method ListTowers not implemented")
} }
@@ -208,6 +250,42 @@ func _WatchtowerClient_RemoveTower_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WatchtowerClient_DeactivateTower_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeactivateTowerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerClientServer).DeactivateTower(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wtclientrpc.WatchtowerClient/DeactivateTower",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerClientServer).DeactivateTower(ctx, req.(*DeactivateTowerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WatchtowerClient_TerminateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TerminateSessionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerClientServer).TerminateSession(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wtclientrpc.WatchtowerClient/TerminateSession",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerClientServer).TerminateSession(ctx, req.(*TerminateSessionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WatchtowerClient_ListTowers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _WatchtowerClient_ListTowers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListTowersRequest) in := new(ListTowersRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@@ -295,6 +373,14 @@ var WatchtowerClient_ServiceDesc = grpc.ServiceDesc{
MethodName: "RemoveTower", MethodName: "RemoveTower",
Handler: _WatchtowerClient_RemoveTower_Handler, Handler: _WatchtowerClient_RemoveTower_Handler,
}, },
{
MethodName: "DeactivateTower",
Handler: _WatchtowerClient_DeactivateTower_Handler,
},
{
MethodName: "TerminateSession",
Handler: _WatchtowerClient_TerminateSession_Handler,
},
{ {
MethodName: "ListTowers", MethodName: "ListTowers",
Handler: _WatchtowerClient_ListTowers_Handler, Handler: _WatchtowerClient_ListTowers_Handler,
+32
View File
@@ -52,6 +52,38 @@ func (h *HarnessRPC) AddTower(
return resp 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 // WatchtowerStats makes a RPC call to the WatchtowerClient of the given node
// and asserts. // and asserts.
func (h *HarnessRPC) WatchtowerStats() *wtclientrpc.StatsResponse { func (h *HarnessRPC) WatchtowerStats() *wtclientrpc.StatsResponse {
+189 -17
View File
@@ -132,6 +132,35 @@ type staleTowerMsg struct {
errChan chan error errChan chan error
} }
// deactivateTowerMsg is an internal message we'll use within the TowerClient
// to signal that a tower should be marked as inactive.
type deactivateTowerMsg struct {
// id is the unique database identifier for the tower.
id wtdb.TowerID
// pubKey is the identifying public key of the watchtower.
pubKey *btcec.PublicKey
// errChan is the channel through which we'll send a response back to
// the caller when handling their request.
//
// NOTE: This channel must be buffered.
errChan chan error
}
// terminateSessMsg is an internal message we'll use within the TowerClient to
// signal that a session should be terminated.
type terminateSessMsg struct {
// id is the session identifier.
id wtdb.SessionID
// errChan is the channel through which we'll send a response back to
// the caller when handling their request.
//
// NOTE: This channel must be buffered.
errChan chan error
}
// clientCfg holds the configuration values required by a client. // clientCfg holds the configuration values required by a client.
type clientCfg struct { type clientCfg struct {
*Config *Config
@@ -165,8 +194,10 @@ type client struct {
statTicker *time.Ticker statTicker *time.Ticker
stats *clientStats stats *clientStats
newTowers chan *newTowerMsg newTowers chan *newTowerMsg
staleTowers chan *staleTowerMsg staleTowers chan *staleTowerMsg
deactivateTowers chan *deactivateTowerMsg
terminateSessions chan *terminateSessMsg
wg sync.WaitGroup wg sync.WaitGroup
quit chan struct{} quit chan struct{}
@@ -192,15 +223,17 @@ func newClient(cfg *clientCfg) (*client, error) {
} }
c := &client{ c := &client{
cfg: cfg, cfg: cfg,
log: plog, log: plog,
pipeline: queue, pipeline: queue,
activeSessions: newSessionQueueSet(), activeSessions: newSessionQueueSet(),
statTicker: time.NewTicker(DefaultStatInterval), statTicker: time.NewTicker(DefaultStatInterval),
stats: new(clientStats), stats: new(clientStats),
newTowers: make(chan *newTowerMsg), newTowers: make(chan *newTowerMsg),
staleTowers: make(chan *staleTowerMsg), staleTowers: make(chan *staleTowerMsg),
quit: make(chan struct{}), deactivateTowers: make(chan *deactivateTowerMsg),
terminateSessions: make(chan *terminateSessMsg),
quit: make(chan struct{}),
} }
candidateTowers := newTowerListIterator() candidateTowers := newTowerListIterator()
@@ -261,7 +294,10 @@ func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing,
opts ...wtdb.ClientSessionListOption) ( opts ...wtdb.ClientSessionListOption) (
map[wtdb.SessionID]*ClientSession, error) { map[wtdb.SessionID]*ClientSession, error) {
towers, err := db.ListTowers() // Fetch all active towers from the DB.
towers, err := db.ListTowers(func(tower *wtdb.Tower) bool {
return tower.Status == wtdb.TowerStatusActive
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -288,9 +324,9 @@ func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing,
// Add the session to the set of candidate sessions. // Add the session to the set of candidate sessions.
candidateSessions[s.ID] = cs candidateSessions[s.ID] = cs
perActiveTower(tower)
} }
perActiveTower(tower)
} }
return candidateSessions, nil return candidateSessions, nil
@@ -511,8 +547,8 @@ func (c *client) nextSessionQueue() (*sessionQueue, error) {
// stopAndRemoveSession stops the session with the given ID and removes it from // stopAndRemoveSession stops the session with the given ID and removes it from
// the in-memory active sessions set. // the in-memory active sessions set.
func (c *client) stopAndRemoveSession(id wtdb.SessionID) error { func (c *client) stopAndRemoveSession(id wtdb.SessionID, final bool) error {
return c.activeSessions.StopAndRemove(id) return c.activeSessions.StopAndRemove(id, final)
} }
// deleteSessionFromTower dials the tower that we created the session with and // deleteSessionFromTower dials the tower that we created the session with and
@@ -691,6 +727,16 @@ func (c *client) backupDispatcher() {
case msg := <-c.staleTowers: case msg := <-c.staleTowers:
msg.errChan <- c.handleStaleTower(msg) msg.errChan <- c.handleStaleTower(msg)
// A tower has been requested to be de-activated. We'll
// only allow this if the tower is not currently being
// used for session negotiation.
case msg := <-c.deactivateTowers:
msg.errChan <- c.handleDeactivateTower(msg)
// A request has come through to terminate a session.
case msg := <-c.terminateSessions:
msg.errChan <- c.handleTerminateSession(msg)
case <-c.quit: case <-c.quit:
return return
} }
@@ -776,6 +822,14 @@ func (c *client) backupDispatcher() {
case msg := <-c.staleTowers: case msg := <-c.staleTowers:
msg.errChan <- c.handleStaleTower(msg) msg.errChan <- c.handleStaleTower(msg)
// A tower has been requested to be de-activated.
case msg := <-c.deactivateTowers:
msg.errChan <- c.handleDeactivateTower(msg)
// A request has come through to terminate a session.
case msg := <-c.terminateSessions:
msg.errChan <- c.handleTerminateSession(msg)
case <-c.quit: case <-c.quit:
return return
} }
@@ -1043,6 +1097,124 @@ func (c *client) initActiveQueue(s *ClientSession,
return sq return sq
} }
// terminateSession sets the given session's status to CSessionTerminal meaning
// that it will not be used again.
func (c *client) terminateSession(id wtdb.SessionID) error {
errChan := make(chan error, 1)
select {
case c.terminateSessions <- &terminateSessMsg{
id: id,
errChan: errChan,
}:
case <-c.pipeline.quit:
return ErrClientExiting
}
select {
case err := <-errChan:
return err
case <-c.pipeline.quit:
return ErrClientExiting
}
}
// handleTerminateSession handles a request to terminate a session. It will
// first shut down the session if it is part of the active session set, then
// it will ensure that the active session queue is set reset if it is using the
// session in question. Finally, the session's status in the DB will be updated.
func (c *client) handleTerminateSession(msg *terminateSessMsg) error {
id := msg.id
delete(c.candidateSessions, id)
err := c.activeSessions.StopAndRemove(id, true)
if err != nil {
return fmt.Errorf("could not stop session %s: %w", id, err)
}
// If our active session queue corresponds to the session being
// terminated, then we'll proceed to negotiate a new one.
if c.sessionQueue != nil {
if bytes.Equal(c.sessionQueue.ID()[:], id[:]) {
c.sessionQueue = nil
}
}
return nil
}
// deactivateTower sends a tower deactivation request to the backupDispatcher
// where it will be handled synchronously. The request should result in all the
// sessions that we have with the given tower being shutdown and removed from
// our in-memory set of active sessions.
func (c *client) deactivateTower(id wtdb.TowerID,
pubKey *btcec.PublicKey) error {
errChan := make(chan error, 1)
select {
case c.deactivateTowers <- &deactivateTowerMsg{
id: id,
pubKey: pubKey,
errChan: errChan,
}:
case <-c.pipeline.quit:
return ErrClientExiting
}
select {
case err := <-errChan:
return err
case <-c.pipeline.quit:
return ErrClientExiting
}
}
// handleDeactivateTower handles a request to deactivate a tower. We will remove
// it from the in-memory candidate set, and we will also stop any active
// sessions we have with this tower.
func (c *client) handleDeactivateTower(msg *deactivateTowerMsg) error {
// Remove the tower from our in-memory candidate set so that it is not
// used for any new session negotiations.
err := c.candidateTowers.RemoveCandidate(msg.id, nil)
if err != nil {
return err
}
pubKey := msg.pubKey.SerializeCompressed()
sessions, err := c.cfg.DB.ListClientSessions(&msg.id)
if err != nil {
return fmt.Errorf("unable to retrieve sessions for tower %x: "+
"%v", pubKey, err)
}
// Iterate over all the sessions we have for this tower and remove them
// from our candidate set and also from our set of started, active
// sessions.
for sessionID := range sessions {
delete(c.candidateSessions, sessionID)
err = c.activeSessions.StopAndRemove(sessionID, false)
if err != nil {
return fmt.Errorf("could not stop session %s: %w",
sessionID, err)
}
}
// If our active session queue corresponds to the stale tower, we'll
// proceed to negotiate a new one.
if c.sessionQueue != nil {
towerKey := c.sessionQueue.tower.IdentityKey
if bytes.Equal(pubKey, towerKey.SerializeCompressed()) {
c.sessionQueue = nil
}
}
return nil
}
// addTower adds a new watchtower reachable at the given address and considers // addTower adds a new watchtower reachable at the given address and considers
// it for new sessions. If the watchtower already exists, then any new addresses // it for new sessions. If the watchtower already exists, then any new addresses
// included will be considered when dialing it for session negotiations and // included will be considered when dialing it for session negotiations and
@@ -1149,7 +1321,7 @@ func (c *client) handleStaleTower(msg *staleTowerMsg) error {
// Shutdown the session so that any pending updates are // Shutdown the session so that any pending updates are
// replayed back onto the main task pipeline. // replayed back onto the main task pipeline.
err = c.activeSessions.StopAndRemove(sessionID) err = c.activeSessions.StopAndRemove(sessionID, true)
if err != nil { if err != nil {
c.log.Errorf("could not stop session %s: %w", sessionID, c.log.Errorf("could not stop session %s: %w", sessionID,
err) err)
+209 -6
View File
@@ -1034,15 +1034,19 @@ func (s *serverHarness) waitForUpdates(hints []blob.BreachHint,
// Closure to assert the server's matches are consistent with the hint // Closure to assert the server's matches are consistent with the hint
// set. // set.
serverHasHints := func(matches []wtdb.Match) bool { serverHasHints := func(matches []wtdb.Match) bool {
if len(hintSet) != len(matches) { // De-dup the server matches since it might very well have
// multiple matches for a hint if that update was backed up on
// more than one session.
matchHints := make(map[blob.BreachHint]struct{})
for _, match := range matches {
matchHints[match.Hint] = struct{}{}
}
if len(hintSet) != len(matchHints) {
return false return false
} }
for _, match := range matches { require.EqualValues(s.t, hintSet, matchHints)
_, ok := hintSet[match.Hint]
require.Truef(s.t, ok, "match %v in db is not in "+
"hint set", match.Hint)
}
return true return true
} }
@@ -2688,6 +2692,205 @@ var clientTests = []clientTest{
require.NoError(h.t, err) require.NoError(h.t, err)
}, },
}, },
{
name: "de-activate a tower",
cfg: harnessCfg{
localBalance: localBalance,
remoteBalance: remoteBalance,
policy: wtpolicy.Policy{
TxPolicy: defaultTxPolicy,
MaxUpdates: 5,
},
},
fn: func(h *testHarness) {
const (
numUpdates = 10
chanIDInt = 0
)
// Advance the channel with a few updates.
hints := h.advanceChannelN(chanIDInt, numUpdates)
// Backup a few these updates and wait for them to
// arrive at the server.
h.backupStates(chanIDInt, 0, numUpdates/2, nil)
h.server.waitForUpdates(hints[:numUpdates/2], waitTime)
// Lookup the tower and assert that it currently is
// seen as an active session candidate.
resp, err := h.clientMgr.LookupTower(
h.server.addr.IdentityKey,
)
require.NoError(h.t, err)
tower, ok := resp[blob.TypeAltruistTaprootCommit]
require.True(h.t, ok)
require.True(h.t, tower.ActiveSessionCandidate)
// Deactivate the tower.
err = h.clientMgr.DeactivateTower(
h.server.addr.IdentityKey,
)
require.NoError(h.t, err)
// Assert that it is no longer seen as an active
// session candidate.
resp, err = h.clientMgr.LookupTower(
h.server.addr.IdentityKey,
)
require.NoError(h.t, err)
tower, ok = resp[blob.TypeAltruistTaprootCommit]
require.True(h.t, ok)
require.False(h.t, tower.ActiveSessionCandidate)
// Add a new tower.
server2 := newServerHarness(
h.t, h.net, towerAddr2Str, nil,
)
server2.start()
h.addTower(server2.addr)
// Backup a few more states and assert that they appear
// on the second tower server.
h.backupStates(
chanIDInt, numUpdates/2, numUpdates-1, nil,
)
server2.waitForUpdates(
hints[numUpdates/2:numUpdates-1], waitTime,
)
// Reactivate the first tower.
err = h.clientMgr.AddTower(h.server.addr)
require.NoError(h.t, err)
// Deactivate the second tower.
err = h.clientMgr.DeactivateTower(
server2.addr.IdentityKey,
)
require.NoError(h.t, err)
// Backup the last backup and assert that it appears
// on the first tower.
h.backupStates(chanIDInt, numUpdates-1, numUpdates, nil)
h.server.waitForUpdates(hints[numUpdates-1:], waitTime)
},
},
{
name: "terminate session",
cfg: harnessCfg{
localBalance: localBalance,
remoteBalance: remoteBalance,
policy: wtpolicy.Policy{
TxPolicy: defaultTxPolicy,
MaxUpdates: 5,
},
},
fn: func(h *testHarness) {
const (
numUpdates = 10
chanIDInt = 0
)
// Advance the channel with a few updates.
hints := h.advanceChannelN(chanIDInt, numUpdates)
// Backup one of these updates and wait for it to
// arrive at the server.
h.backupStates(chanIDInt, 0, 1, nil)
h.server.waitForUpdates(hints[:1], waitTime)
// Now, restart the server in a state where it will not
// ack updates. This will allow us to wait for an update
// to be un-acked and persisted.
h.server.restart(func(cfg *wtserver.Config) {
cfg.NoAckUpdates = true
})
// Backup another update. These should remain in the
// client as un-acked.
h.backupStates(chanIDInt, 1, 2, nil)
// Wait for the update to be persisted.
fetchUnacked := h.clientDB.FetchSessionCommittedUpdates
var sessID wtdb.SessionID
err := wait.Predicate(func() bool {
sessions, err := h.clientDB.ListClientSessions(
nil,
)
require.NoError(h.t, err)
var updates []wtdb.CommittedUpdate
for id := range sessions {
sessID = id
updates, err = fetchUnacked(&id)
require.NoError(h.t, err)
return len(updates) == 1
}
return false
}, waitTime)
require.NoError(h.t, err)
// Now try to terminate the session by directly calling
// the DB terminate method. This is expected to fail
// since the session still has un-acked updates.
err = h.clientDB.TerminateSession(sessID)
require.ErrorIs(
h.t, err, wtdb.ErrSessionHasUnackedUpdates,
)
// If we try to terminate the session through the client
// interface though, it should succeed since the client
// will handle the un-acked updates of the session.
err = h.clientMgr.TerminateSession(sessID)
require.NoError(h.t, err)
// Fetch the session from the DB and assert that it is
// in the terminal state and that it is not exhausted.
sess, err := h.clientDB.GetClientSession(sessID)
require.NoError(h.t, err)
require.Equal(h.t, wtdb.CSessionTerminal, sess.Status)
require.NotEqual(
h.t, sess.Policy.MaxUpdates, sess.SeqNum,
)
// Restart the server and allow it to ack updates again.
h.server.restart(func(cfg *wtserver.Config) {
cfg.NoAckUpdates = false
})
// Wait for the update from before to appear on the
// server. The server will actually have this back-up
// stored twice now since it would have stored it for
// the first session even though it did not send an ACK
// for it.
h.server.waitForUpdates(hints[1:2], waitTime)
// Now we want to assert that this update was definitely
// not sent on the terminated session but was instead
// sent in a new session.
var (
updateCounts = make(map[wtdb.SessionID]uint16)
totalUpdates uint16
)
sessions, err := h.clientDB.ListClientSessions(nil,
wtdb.WithPerNumAckedUpdates(
func(s *wtdb.ClientSession,
_ lnwire.ChannelID,
num uint16) {
updateCounts[s.ID] += num
totalUpdates += num
},
),
)
require.NoError(h.t, err)
require.Len(h.t, sessions, 2)
require.EqualValues(h.t, 1, updateCounts[sessID])
require.EqualValues(h.t, 2, totalUpdates)
},
},
} }
// TestClient executes the client test suite, asserting the ability to backup // TestClient executes the client test suite, asserting the ability to backup
+18 -9
View File
@@ -26,14 +26,17 @@ type DB interface {
// RemoveTower modifies a tower's record within the database. If an // RemoveTower modifies a tower's record within the database. If an
// address is provided, then _only_ the address record should be removed // address is provided, then _only_ the address record should be removed
// from the tower's persisted state. Otherwise, we'll attempt to mark // from the tower's persisted state. Otherwise, we'll attempt to mark
// the tower as inactive by marking all of its sessions inactive. If any // the tower as inactive. If any of its sessions have unacked updates,
// of its sessions has unacked updates, then ErrTowerUnackedUpdates is // then ErrTowerUnackedUpdates is returned. If the tower doesn't have
// returned. If the tower doesn't have any sessions at all, it'll be // any sessions at all, it'll be completely removed from the database.
// completely removed from the database.
// //
// NOTE: An error is not returned if the tower doesn't exist. // NOTE: An error is not returned if the tower doesn't exist.
RemoveTower(*btcec.PublicKey, net.Addr) error RemoveTower(*btcec.PublicKey, net.Addr) error
// TerminateSession sets the given session's status to CSessionTerminal
// meaning that it will not be usable again.
TerminateSession(id wtdb.SessionID) error
// LoadTower retrieves a tower by its public key. // LoadTower retrieves a tower by its public key.
LoadTower(*btcec.PublicKey) (*wtdb.Tower, error) LoadTower(*btcec.PublicKey) (*wtdb.Tower, error)
@@ -41,8 +44,9 @@ type DB interface {
LoadTowerByID(wtdb.TowerID) (*wtdb.Tower, error) LoadTowerByID(wtdb.TowerID) (*wtdb.Tower, error)
// ListTowers retrieves the list of towers available within the // ListTowers retrieves the list of towers available within the
// database. // database. The filter function may be set in order to filter out the
ListTowers() ([]*wtdb.Tower, error) // towers to be returned.
ListTowers(filter wtdb.TowerFilterFn) ([]*wtdb.Tower, error)
// NextSessionKeyIndex reserves a new session key derivation index for a // NextSessionKeyIndex reserves a new session key derivation index for a
// particular tower id and blob type. The index is reserved for that // particular tower id and blob type. The index is reserved for that
@@ -136,9 +140,14 @@ type DB interface {
// space. // space.
GetDBQueue(namespace []byte) wtdb.Queue[*wtdb.BackupID] GetDBQueue(namespace []byte) wtdb.Queue[*wtdb.BackupID]
// DeleteCommittedUpdate deletes the committed update belonging to the // DeleteCommittedUpdates deletes all the committed updates belonging to
// given session and with the given sequence number from the db. // the given session from the db.
DeleteCommittedUpdate(id *wtdb.SessionID, seqNum uint16) error DeleteCommittedUpdates(id *wtdb.SessionID) error
// DeactivateTower sets the given tower's status to inactive. This means
// that this tower's sessions won't be loaded and used for backups.
// CreateTower can be used to reactivate the tower again.
DeactivateTower(pubKey *btcec.PublicKey) error
} }
// AuthDialer connects to a remote node using an authenticated transport, such // AuthDialer connects to a remote node using an authenticated transport, such
+78 -2
View File
@@ -38,6 +38,15 @@ type ClientManager interface {
// instead. // instead.
RemoveTower(*btcec.PublicKey, net.Addr) error RemoveTower(*btcec.PublicKey, net.Addr) error
// DeactivateTower sets the given tower's status to inactive so that it
// is not considered for session negotiation. Its sessions will also not
// be used while the tower is inactive.
DeactivateTower(pubKey *btcec.PublicKey) error
// TerminateSession sets the given session's status to CSessionTerminal
// meaning that it will not be used again.
TerminateSession(id wtdb.SessionID) error
// Stats returns the in-memory statistics of the client since startup. // Stats returns the in-memory statistics of the client since startup.
Stats() ClientStats Stats() ClientStats
@@ -431,6 +440,73 @@ func (m *Manager) RemoveTower(key *btcec.PublicKey, addr net.Addr) error {
return nil return nil
} }
// TerminateSession sets the given session's status to CSessionTerminal meaning
// that it will not be used again.
func (m *Manager) TerminateSession(id wtdb.SessionID) error {
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
for _, client := range m.clients {
err := client.terminateSession(id)
if err != nil {
return err
}
}
// Finally, mark the session as terminated in the DB.
return m.cfg.DB.TerminateSession(id)
}
// DeactivateTower sets the given tower's status to inactive so that it is not
// considered for session negotiation. Its sessions will also not be used while
// the tower is inactive.
func (m *Manager) DeactivateTower(key *btcec.PublicKey) error {
// We'll load the tower in order to retrieve its ID within the database.
tower, err := m.cfg.DB.LoadTower(key)
if err != nil {
return err
}
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
for _, client := range m.clients {
err := client.deactivateTower(tower.ID, tower.IdentityKey)
if err != nil {
return err
}
}
// Finally, mark the tower as inactive in the DB.
err = m.cfg.DB.DeactivateTower(key)
if err != nil {
log.Errorf("Could not deactivate the tower. Re-activating. %v",
err)
// If the persisted state update fails, re-add the address to
// our client's in-memory state.
tower, newTowerErr := NewTowerFromDBTower(tower)
if newTowerErr != nil {
log.Errorf("Could not create new in-memory tower: %v",
newTowerErr)
return err
}
for _, client := range m.clients {
addTowerErr := client.addTower(tower)
if addTowerErr != nil {
log.Errorf("Could not re-add tower: %v",
addTowerErr)
}
}
return err
}
return nil
}
// Stats returns the in-memory statistics of the clients managed by the Manager // Stats returns the in-memory statistics of the clients managed by the Manager
// since startup. // since startup.
func (m *Manager) Stats() ClientStats { func (m *Manager) Stats() ClientStats {
@@ -455,7 +531,7 @@ func (m *Manager) Stats() ClientStats {
func (m *Manager) RegisteredTowers(opts ...wtdb.ClientSessionListOption) ( func (m *Manager) RegisteredTowers(opts ...wtdb.ClientSessionListOption) (
map[blob.Type][]*RegisteredTower, error) { map[blob.Type][]*RegisteredTower, error) {
towers, err := m.cfg.DB.ListTowers() towers, err := m.cfg.DB.ListTowers(nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -850,7 +926,7 @@ func (m *Manager) handleClosableSessions(
// Stop the session and remove it from the // Stop the session and remove it from the
// in-memory set. // in-memory set.
err = client.stopAndRemoveSession( err = client.stopAndRemoveSession(
item.sessionID, item.sessionID, true,
) )
if err != nil { if err != nil {
log.Errorf("could not remove "+ log.Errorf("could not remove "+
+6 -7
View File
@@ -211,14 +211,13 @@ func (q *sessionQueue) Stop(final bool) error {
update.BackupID, err) update.BackupID, err)
continue continue
} }
}
err = q.cfg.DB.DeleteCommittedUpdate( if final {
q.ID(), update.SeqNum, err = q.cfg.DB.DeleteCommittedUpdates(q.ID())
)
if err != nil { if err != nil {
log.Errorf("could not delete committed "+ log.Errorf("could not delete committed "+
"update %d for session %s", "updates for session %s", q.ID())
update.SeqNum, q.ID())
} }
} }
@@ -766,7 +765,7 @@ func (s *sessionQueueSet) AddAndStart(sessionQueue *sessionQueue) {
// StopAndRemove stops the given session queue and removes it from the // StopAndRemove stops the given session queue and removes it from the
// sessionQueueSet. // sessionQueueSet.
func (s *sessionQueueSet) StopAndRemove(id wtdb.SessionID) error { func (s *sessionQueueSet) StopAndRemove(id wtdb.SessionID, final bool) error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@@ -777,7 +776,7 @@ func (s *sessionQueueSet) StopAndRemove(id wtdb.SessionID) error {
delete(s.queues, id) delete(s.queues, id)
return queue.Stop(true) return queue.Stop(final)
} }
// Get fetches and returns the sessionQueue with the given ID. // Get fetches and returns the sessionQueue with the given ID.
+211 -89
View File
@@ -196,9 +196,9 @@ var (
// session has updates for channels that are still open. // session has updates for channels that are still open.
errSessionHasOpenChannels = errors.New("session has open channels") errSessionHasOpenChannels = errors.New("session has open channels")
// errSessionHasUnackedUpdates is an error used to indicate that a // ErrSessionHasUnackedUpdates is an error used to indicate that a
// session has un-acked updates. // session has un-acked updates.
errSessionHasUnackedUpdates = errors.New("session has un-acked updates") ErrSessionHasUnackedUpdates = errors.New("session has un-acked updates")
// errChannelHasMoreSessions is an error used to indicate that a channel // errChannelHasMoreSessions is an error used to indicate that a channel
// has updates in other non-closed sessions. // has updates in other non-closed sessions.
@@ -387,41 +387,13 @@ func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error) {
return err return err
} }
// Set its status to active.
tower.Status = TowerStatusActive
// Add the new address to the existing tower. If the // Add the new address to the existing tower. If the
// address is a duplicate, this will result in no // address is a duplicate, this will result in no
// change. // change.
tower.AddAddress(lnAddr.Address) tower.AddAddress(lnAddr.Address)
// If there are any client sessions that correspond to
// this tower, we'll mark them as active to ensure we
// load them upon restarts.
towerSessIndex := towerToSessionIndex.NestedReadBucket(
tower.ID.Bytes(),
)
if towerSessIndex == nil {
return ErrTowerNotFound
}
sessions := tx.ReadWriteBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
err = towerSessIndex.ForEach(func(k, _ []byte) error {
session, err := getClientSessionBody(
sessions, k,
)
if err != nil {
return err
}
return markSessionStatus(
sessions, session, CSessionActive,
)
})
if err != nil {
return err
}
} else { } else {
// No such tower exists, create a new tower id for our // No such tower exists, create a new tower id for our
// new tower. The error is unhandled since NextSequence // new tower. The error is unhandled since NextSequence
@@ -432,6 +404,7 @@ func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error) {
ID: TowerID(towerID), ID: TowerID(towerID),
IdentityKey: lnAddr.IdentityKey, IdentityKey: lnAddr.IdentityKey,
Addresses: []net.Addr{lnAddr.Address}, Addresses: []net.Addr{lnAddr.Address},
Status: TowerStatusActive,
} }
towerIDBytes = tower.ID.Bytes() towerIDBytes = tower.ID.Bytes()
@@ -465,10 +438,10 @@ func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error) {
// RemoveTower modifies a tower's record within the database. If an address is // RemoveTower modifies a tower's record within the database. If an address is
// provided, then _only_ the address record should be removed from the tower's // provided, then _only_ the address record should be removed from the tower's
// persisted state. Otherwise, we'll attempt to mark the tower as inactive by // persisted state. Otherwise, we'll attempt to mark the tower as inactive. If
// marking all of its sessions inactive. If any of its sessions has unacked // any of its sessions has unacked updates, then ErrTowerUnackedUpdates is
// updates, then ErrTowerUnackedUpdates is returned. If the tower doesn't have // returned. If the tower doesn't have any sessions at all, it'll be completely
// any sessions at all, it'll be completely removed from the database. // removed from the database.
// //
// NOTE: An error is not returned if the tower doesn't exist. // NOTE: An error is not returned if the tower doesn't exist.
func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error { func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error {
@@ -503,14 +476,14 @@ func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error {
return nil return nil
} }
tower, err := getTower(towers, towerIDBytes)
if err != nil {
return err
}
// If an address is provided, then we should _only_ remove the // If an address is provided, then we should _only_ remove the
// address record from the database. // address record from the database.
if addr != nil { if addr != nil {
tower, err := getTower(towers, towerIDBytes)
if err != nil {
return err
}
// Towers should always have at least one address saved. // Towers should always have at least one address saved.
tower.RemoveAddress(addr) tower.RemoveAddress(addr)
if len(tower.Addresses) == 0 { if len(tower.Addresses) == 0 {
@@ -560,25 +533,76 @@ func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error {
) )
} }
// We'll mark its sessions as inactive as long as they don't // Otherwise, we mark the tower as inactive.
// have any pending updates to ensure we don't load them upon tower.Status = TowerStatusInactive
// restarts. err = putTower(towers, tower)
if err != nil {
return err
}
// We'll do a check to ensure that the tower's sessions don't
// have any pending back-ups.
for _, session := range towerSessions { for _, session := range towerSessions {
if committedUpdateCount[session.ID] > 0 { if committedUpdateCount[session.ID] > 0 {
return ErrTowerUnackedUpdates return ErrTowerUnackedUpdates
} }
err := markSessionStatus(
sessions, session, CSessionInactive,
)
if err != nil {
return err
}
} }
return nil return nil
}, func() {}) }, func() {})
} }
// DeactivateTower sets the given tower's status to inactive. This means that
// this tower's sessions won't be loaded and used for backups. CreateTower can
// be used to reactivate the tower again.
func (c *ClientDB) DeactivateTower(pubKey *btcec.PublicKey) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
towers := tx.ReadWriteBucket(cTowerBkt)
if towers == nil {
return ErrUninitializedDB
}
towerIndex := tx.ReadWriteBucket(cTowerIndexBkt)
if towerIndex == nil {
return ErrUninitializedDB
}
towersToSessionsIndex := tx.ReadWriteBucket(
cTowerToSessionIndexBkt,
)
if towersToSessionsIndex == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
pubKeyBytes := pubKey.SerializeCompressed()
towerIDBytes := towerIndex.Get(pubKeyBytes)
if towerIDBytes == nil {
return ErrTowerNotFound
}
tower, err := getTower(towers, towerIDBytes)
if err != nil {
return err
}
// If the tower already has the desired status, then we can exit
// here.
if tower.Status == TowerStatusInactive {
return nil
}
// Otherwise, we update the status and re-store the tower.
tower.Status = TowerStatusInactive
return putTower(towers, tower)
}, func() {})
}
// LoadTowerByID retrieves a tower by its tower ID. // LoadTowerByID retrieves a tower by its tower ID.
func (c *ClientDB) LoadTowerByID(towerID TowerID) (*Tower, error) { func (c *ClientDB) LoadTowerByID(towerID TowerID) (*Tower, error) {
var tower *Tower var tower *Tower
@@ -632,8 +656,14 @@ func (c *ClientDB) LoadTower(pubKey *btcec.PublicKey) (*Tower, error) {
return tower, nil return tower, nil
} }
// ListTowers retrieves the list of towers available within the database. // TowerFilterFn is the signature of a call-back function that can be used to
func (c *ClientDB) ListTowers() ([]*Tower, error) { // skip certain towers in the ListTowers method.
type TowerFilterFn func(*Tower) bool
// ListTowers retrieves the list of towers available within the database that
// have a status matching the given status. The filter function may be set in
// order to filter out the towers to be returned.
func (c *ClientDB) ListTowers(filter TowerFilterFn) ([]*Tower, error) {
var towers []*Tower var towers []*Tower
err := kvdb.View(c.db, func(tx kvdb.RTx) error { err := kvdb.View(c.db, func(tx kvdb.RTx) error {
towerBucket := tx.ReadBucket(cTowerBkt) towerBucket := tx.ReadBucket(cTowerBkt)
@@ -646,7 +676,13 @@ func (c *ClientDB) ListTowers() ([]*Tower, error) {
if err != nil { if err != nil {
return err return err
} }
if filter != nil && !filter(tower) {
return nil
}
towers = append(towers, tower) towers = append(towers, tower)
return nil return nil
}) })
}, func() { }, func() {
@@ -1787,9 +1823,10 @@ func (c *ClientDB) MarkChannelClosed(chanID lnwire.ChannelID,
// isSessionClosable returns true if a session is considered closable. A session // isSessionClosable returns true if a session is considered closable. A session
// is considered closable only if all the following points are true: // is considered closable only if all the following points are true:
// 1) It has no un-acked updates. // 1. It has no un-acked updates.
// 2) It is exhausted (ie it can't accept any more updates) // 2. It is exhausted (ie it can't accept any more updates) OR it has been
// 3) All the channels that it has acked updates for are closed. // marked as terminal.
// 3. All the channels that it has acked updates for are closed.
func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket, func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket,
id *SessionID) (bool, error) { id *SessionID) (bool, error) {
@@ -1798,22 +1835,21 @@ func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket,
return false, ErrSessionNotFound return false, ErrSessionNotFound
} }
// Since the DeleteCommittedUpdates method deletes the cSessionCommits
// bucket in one go, it is possible for the session to be closable even
// if this bucket no longer exists.
commitsBkt := sessBkt.NestedReadBucket(cSessionCommits) commitsBkt := sessBkt.NestedReadBucket(cSessionCommits)
if commitsBkt == nil { if commitsBkt != nil {
// If the session has no cSessionCommits bucket then we can be // If the session has any un-acked updates, then it is not yet
// sure that no updates have ever been committed to the session // closable.
// and so it is not yet exhausted. err := commitsBkt.ForEach(func(_, _ []byte) error {
return false, nil return ErrSessionHasUnackedUpdates
} })
if errors.Is(err, ErrSessionHasUnackedUpdates) {
// If the session has any un-acked updates, then it is not yet closable. return false, nil
err := commitsBkt.ForEach(func(_, _ []byte) error { } else if err != nil {
return errSessionHasUnackedUpdates return false, err
}) }
if errors.Is(err, errSessionHasUnackedUpdates) {
return false, nil
} else if err != nil {
return false, err
} }
session, err := getClientSessionBody(sessionsBkt, id[:]) session, err := getClientSessionBody(sessionsBkt, id[:])
@@ -1821,10 +1857,14 @@ func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket,
return false, err return false, err
} }
isTerminal := session.Status == CSessionTerminal
// We have already checked that the session has no more committed // We have already checked that the session has no more committed
// updates. So now we can check if the session is exhausted. // updates. So now we can check if the session is exhausted or has a
if session.SeqNum < session.Policy.MaxUpdates { // terminal state.
// If the session is not yet exhausted, it is not yet closable. if !isTerminal && session.SeqNum < session.Policy.MaxUpdates {
// If the session is not yet exhausted, and it is not yet in a
// terminal state then it is not yet closable.
return false, nil return false, nil
} }
@@ -1844,11 +1884,16 @@ func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket,
} }
} }
// If the session has no acked-updates, then something is wrong since
// the above check ensures that this session has been exhausted meaning
// that it should have MaxUpdates acked updates.
ackedRangeBkt := sessBkt.NestedReadBucket(cSessionAckRangeIndex) ackedRangeBkt := sessBkt.NestedReadBucket(cSessionAckRangeIndex)
if ackedRangeBkt == nil { if ackedRangeBkt == nil {
if isTerminal {
return true, nil
}
// If the session has no acked-updates, and it is not in a
// terminal state then something is wrong since the above check
// ensures that this session has been exhausted meaning that it
// should have MaxUpdates acked updates.
return false, fmt.Errorf("no acked-updates found for "+ return false, fmt.Errorf("no acked-updates found for "+
"exhausted session %s", id) "exhausted session %s", id)
} }
@@ -1996,7 +2041,6 @@ func (c *ClientDB) CommitUpdate(id *SessionID,
lastApplied = session.TowerLastApplied lastApplied = session.TowerLastApplied
return nil return nil
}, func() { }, func() {
lastApplied = 0 lastApplied = 0
}) })
@@ -2215,9 +2259,56 @@ func (c *ClientDB) GetDBQueue(namespace []byte) Queue[*BackupID] {
) )
} }
// DeleteCommittedUpdate deletes the committed update with the given sequence // TerminateSession sets the given session's status to CSessionTerminal meaning
// number from the given session. // that it will not be usable again. An error will be returned if the given
func (c *ClientDB) DeleteCommittedUpdate(id *SessionID, seqNum uint16) error { // session still has un-acked updates that should be attended to.
func (c *ClientDB) TerminateSession(id SessionID) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
sessions := tx.ReadWriteBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
sessionsBkt := tx.ReadBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
// Collect any un-acked updates for this session.
committedUpdateCount := make(map[SessionID]uint16)
perCommittedUpdate := func(s *ClientSession,
_ *CommittedUpdate) {
committedUpdateCount[s.ID]++
}
session, err := c.getClientSession(
sessionsBkt, chanIDIndexBkt, id[:],
WithPerCommittedUpdate(perCommittedUpdate),
)
if err != nil {
return err
}
// If there are any un-acked updates for this session then
// we don't allow the change of status as these updates must
// first be dealt with somehow.
if committedUpdateCount[id] > 0 {
return ErrSessionHasUnackedUpdates
}
return markSessionStatus(sessions, session, CSessionTerminal)
}, func() {})
}
// DeleteCommittedUpdates deletes all the committed updates for the given
// session.
func (c *ClientDB) DeleteCommittedUpdates(id *SessionID) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error { return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
sessions := tx.ReadWriteBucket(cSessionBkt) sessions := tx.ReadWriteBucket(cSessionBkt)
if sessions == nil { if sessions == nil {
@@ -2231,23 +2322,54 @@ func (c *ClientDB) DeleteCommittedUpdate(id *SessionID, seqNum uint16) error {
} }
// If the commits sub-bucket doesn't exist, there can't possibly // If the commits sub-bucket doesn't exist, there can't possibly
// be a corresponding update to remove. // be corresponding updates to remove.
sessionCommits := sessionBkt.NestedReadWriteBucket( sessionCommits := sessionBkt.NestedReadWriteBucket(
cSessionCommits, cSessionCommits,
) )
if sessionCommits == nil { if sessionCommits == nil {
return ErrCommittedUpdateNotFound return nil
} }
var seqNumBuf [2]byte // errFoundUpdates is an error we will use to exit early from
byteOrder.PutUint16(seqNumBuf[:], seqNum) // the ForEach loop. The return of this error means that at
// least one committed update exists.
var errFoundUpdates = fmt.Errorf("found committed updates")
err := sessionCommits.ForEach(func(k, v []byte) error {
return errFoundUpdates
})
switch {
// If the errFoundUpdates signal error was returned then there
// are some updates that need to be deleted.
case errors.Is(err, errFoundUpdates):
if sessionCommits.Get(seqNumBuf[:]) == nil { // If no error is returned then the ForEach call back was never
return ErrCommittedUpdateNotFound // entered meaning that there are no un-acked committed updates.
// So we can exit now as there is nothing left to do.
case err == nil:
return nil
// If an expected error is returned, return that error.
default:
return err
} }
// Remove the corresponding committed update. session, err := getClientSessionBody(sessions, id[:])
return sessionCommits.Delete(seqNumBuf[:]) if err != nil {
return err
}
// Once we delete a committed update from the session, the
// SeqNum of the session will be incorrect and so the session
// should be marked as terminal.
session.Status = CSessionTerminal
err = putClientSessionBody(sessionBkt, session)
if err != nil {
return err
}
// Delete all the committed updates in one go by deleting the
// session commits bucket.
return sessionBkt.DeleteNestedBucket(cSessionCommits)
}, func() {}) }, func() {})
} }
+204 -28
View File
@@ -89,6 +89,26 @@ func (h *clientDBHarness) createTower(lnAddr *lnwire.NetAddress,
return tower return tower
} }
func (h *clientDBHarness) deactivateTower(pubKey *btcec.PublicKey,
expErr error) {
h.t.Helper()
err := h.db.DeactivateTower(pubKey)
require.ErrorIs(h.t, err, expErr)
}
func (h *clientDBHarness) listTowers(filterFn wtdb.TowerFilterFn,
expErr error) []*wtdb.Tower {
h.t.Helper()
towers, err := h.db.ListTowers(filterFn)
require.ErrorIs(h.t, err, expErr)
return towers
}
func (h *clientDBHarness) removeTower(pubKey *btcec.PublicKey, addr net.Addr, func (h *clientDBHarness) removeTower(pubKey *btcec.PublicKey, addr net.Addr,
hasSessions bool, expErr error) { hasSessions bool, expErr error) {
@@ -125,12 +145,9 @@ func (h *clientDBHarness) removeTower(pubKey *btcec.PublicKey, addr net.Addr,
return return
} }
for _, session := range h.listSessions(&tower.ID) { require.EqualValues(
require.Equal(h.t, wtdb.CSessionInactive, h.t, wtdb.TowerStatusInactive, tower.Status,
session.Status, "expected status for session "+ )
"%v to be %v, got %v", session.ID,
wtdb.CSessionInactive, session.Status)
}
} }
} }
@@ -156,6 +173,24 @@ func (h *clientDBHarness) loadTowerByID(id wtdb.TowerID,
return tower return tower
} }
func (h *clientDBHarness) terminateSession(id wtdb.SessionID, expErr error) {
h.t.Helper()
err := h.db.TerminateSession(id)
require.ErrorIs(h.t, err, expErr)
}
func (h *clientDBHarness) getClientSession(id wtdb.SessionID,
expErr error) *wtdb.ClientSession {
h.t.Helper()
session, err := h.db.GetClientSession(id)
require.ErrorIs(h.t, err, expErr)
return session
}
func (h *clientDBHarness) fetchChanInfos() wtdb.ChannelInfos { func (h *clientDBHarness) fetchChanInfos() wtdb.ChannelInfos {
h.t.Helper() h.t.Helper()
@@ -194,12 +229,12 @@ func (h *clientDBHarness) ackUpdate(id *wtdb.SessionID, seqNum uint16,
require.ErrorIs(h.t, err, expErr) require.ErrorIs(h.t, err, expErr)
} }
func (h *clientDBHarness) deleteCommittedUpdate(id *wtdb.SessionID, func (h *clientDBHarness) deleteCommittedUpdates(id *wtdb.SessionID,
seqNum uint16, expErr error) { expErr error) {
h.t.Helper() h.t.Helper()
err := h.db.DeleteCommittedUpdate(id, seqNum) err := h.db.DeleteCommittedUpdates(id)
require.ErrorIs(h.t, err, expErr) require.ErrorIs(h.t, err, expErr)
} }
@@ -531,20 +566,126 @@ func testRemoveTower(h *clientDBHarness) {
h.commitUpdate(&session.ID, update, nil) h.commitUpdate(&session.ID, update, nil)
// We should not be able to fully remove it from the database since // We should not be able to fully remove it from the database since
// there's a session and it has unacked updates. // there's a session, and it has unacked updates.
h.removeTower(pk, nil, true, wtdb.ErrTowerUnackedUpdates) h.removeTower(pk, nil, true, wtdb.ErrTowerUnackedUpdates)
// Removing the tower after all sessions no longer have unacked updates // Removing the tower after all sessions no longer have unacked updates
// should result in the sessions becoming inactive. // should succeed.
h.ackUpdate(&session.ID, 1, 1, nil) h.ackUpdate(&session.ID, 1, 1, nil)
h.removeTower(pk, nil, true, nil) h.removeTower(pk, nil, true, nil)
}
// Creating the tower again should mark all of the sessions active once func testTerminateSession(h *clientDBHarness) {
// again. const blobType = blob.TypeAltruistCommit
h.createTower(&lnwire.NetAddress{
tower := h.newTower()
// Create a new session that the updates in this will be tied to.
session := &wtdb.ClientSession{
ClientSessionBody: wtdb.ClientSessionBody{
TowerID: tower.ID,
Policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{
BlobType: blobType,
},
MaxUpdates: 100,
},
RewardPkScript: []byte{0x01, 0x02, 0x03},
},
ID: wtdb.SessionID([33]byte{0x03}),
}
// Reserve a session key and insert the client session.
session.KeyIndex = h.nextKeyIndex(session.TowerID, blobType, false)
h.insertSession(session, nil)
// Commit to a random update at seqnum 1.
update1 := randCommittedUpdate(h.t, 1)
h.registerChan(update1.BackupID.ChanID, nil, nil)
lastApplied := h.commitUpdate(&session.ID, update1, nil)
require.Zero(h.t, lastApplied)
// Terminating the session now should fail since the session has an
// un-acked update.
h.terminateSession(session.ID, wtdb.ErrSessionHasUnackedUpdates)
// Fetch the session and assert that the status is still active.
sess := h.getClientSession(session.ID, nil)
require.Equal(h.t, wtdb.CSessionActive, sess.Status)
// Delete the update.
h.deleteCommittedUpdates(&session.ID, nil)
// Terminating the session now should succeed.
h.terminateSession(session.ID, nil)
// Fetch the session again and assert that its status is now Terminal.
sess = h.getClientSession(session.ID, nil)
require.Equal(h.t, wtdb.CSessionTerminal, sess.Status)
}
// testTowerStatusChange tests that the Tower status is updated accordingly
// given a variety of commands.
func testTowerStatusChange(h *clientDBHarness) {
// Create a new tower.
pk, err := randPubKey()
require.NoError(h.t, err)
towerAddr := &lnwire.NetAddress{
IdentityKey: pk, IdentityKey: pk,
Address: addr1, Address: &net.TCPAddr{
}, nil) IP: []byte{0x01, 0x00, 0x00, 0x00}, Port: 9911,
},
}
tower := h.createTower(towerAddr, nil)
// Add a new session.
session := h.randSession(h.t, tower.ID, 100)
h.insertSession(session, nil)
// assertTowerStatus is a helper function that will assert that the
// tower's status is as expected.
assertTowerStatus := func(status wtdb.TowerStatus) {
activeFilter := func(tower *wtdb.Tower) bool {
return tower.Status == status
}
towers := h.listTowers(activeFilter, nil)
require.Len(h.t, towers, 1)
require.EqualValues(h.t, towers[0].Status, status)
}
// assertSessionStatus is a helper that will assert that the session's
// status is as expected
assertSessionStatus := func(status wtdb.CSessionStatus) {
sessions := h.listSessions(&tower.ID)
require.Len(h.t, sessions, 1)
for _, sess := range sessions {
require.EqualValues(h.t, sess.Status, status)
}
}
// Initially, the tower and session should be active.
assertTowerStatus(wtdb.TowerStatusActive)
assertSessionStatus(wtdb.CSessionActive)
// Removing the tower should change its status but its session
// status should remain active.
h.removeTower(tower.IdentityKey, nil, true, nil)
assertTowerStatus(wtdb.TowerStatusInactive)
assertSessionStatus(wtdb.CSessionActive)
// Re-adding the tower in some way should re-active it and its session.
h.createTower(towerAddr, nil)
assertTowerStatus(wtdb.TowerStatusActive)
assertSessionStatus(wtdb.CSessionActive)
// Deactivating the tower should change its status but its session
// status should remain active.
h.deactivateTower(tower.IdentityKey, nil)
assertTowerStatus(wtdb.TowerStatusInactive)
assertSessionStatus(wtdb.CSessionActive)
} }
// testChanSummaries tests the process of a registering a channel and its // testChanSummaries tests the process of a registering a channel and its
@@ -660,18 +801,7 @@ func testCommitUpdate(h *clientDBHarness) {
// We will now also test that the DeleteCommittedUpdates method also // We will now also test that the DeleteCommittedUpdates method also
// works. // works.
// First, try to delete a committed update that does not exist. h.deleteCommittedUpdates(&session.ID, nil)
h.deleteCommittedUpdate(
&session.ID, update4.SeqNum, wtdb.ErrCommittedUpdateNotFound,
)
// Now delete an existing committed update and ensure that it succeeds.
h.deleteCommittedUpdate(&session.ID, update1.SeqNum, nil)
h.assertUpdates(session.ID, []wtdb.CommittedUpdate{
*update2,
}, nil)
h.deleteCommittedUpdate(&session.ID, update2.SeqNum, nil)
h.assertUpdates(session.ID, []wtdb.CommittedUpdate{}, nil) h.assertUpdates(session.ID, []wtdb.CommittedUpdate{}, nil)
} }
@@ -929,6 +1059,44 @@ func testMarkChannelClosed(h *clientDBHarness) {
// Assert that we now can delete the session. // Assert that we now can delete the session.
h.deleteSession(session1.ID, nil) h.deleteSession(session1.ID, nil)
require.Empty(h.t, h.listClosableSessions(nil)) require.Empty(h.t, h.listClosableSessions(nil))
// We also want to test that a session can be deleted if it has been
// marked as terminal.
// Create session2 with MaxUpdates set to 5.
session2 := h.randSession(h.t, tower.ID, 5)
h.insertSession(session2, nil)
// Create and register channel 7.
chanID7 := randChannelID(h.t)
h.registerChan(chanID7, nil, nil)
// Add two updates for channel 7 in session 2. Ack one of them so that
// the mapping from this channel to this session is created but don't
// ack the other since we want to delete the committed update later.
update = randCommittedUpdateForChannel(h.t, chanID7, 1)
h.commitUpdate(&session2.ID, update, nil)
h.ackUpdate(&session2.ID, 1, 1, nil)
update = randCommittedUpdateForChannel(h.t, chanID7, 2)
h.commitUpdate(&session2.ID, update, nil)
// Check that attempting to delete the session will fail since it is not
// yet considered closable.
h.deleteSession(session2.ID, wtdb.ErrSessionNotClosable)
// Now delete the added committed updates. This should put the
// session in the terminal state after which we should be able to
// delete the session.
h.deleteCommittedUpdates(&session2.ID, nil)
// Marking channel 7 as closed will now return session 2 since it has
// been marked as terminal.
sl = h.markChannelClosed(chanID7, 1, nil)
require.ElementsMatch(h.t, sl, []wtdb.SessionID{session2.ID})
// We should now be able to delete the session.
h.deleteSession(session2.ID, nil)
} }
// testAckUpdate asserts the behavior of AckUpdate. // testAckUpdate asserts the behavior of AckUpdate.
@@ -1153,6 +1321,14 @@ func TestClientDB(t *testing.T) {
name: "max commitment heights", name: "max commitment heights",
run: testMaxCommitmentHeights, run: testMaxCommitmentHeights,
}, },
{
name: "test tower status change",
run: testTowerStatusChange,
},
{
name: "terminate session",
run: testTerminateSession,
},
} }
for _, database := range dbs { for _, database := range dbs {
+3 -3
View File
@@ -18,9 +18,9 @@ const (
// used for backups. // used for backups.
CSessionActive CSessionStatus = 0 CSessionActive CSessionStatus = 0
// CSessionInactive indicates that the ClientSession is inactive and // CSessionTerminal indicates that the ClientSession is in a terminal
// cannot be used for backups. // state and cannot be used for backups.
CSessionInactive CSessionStatus = 1 CSessionTerminal CSessionStatus = 1
) )
// ClientSession encapsulates a SessionInfo returned from a successful // ClientSession encapsulates a SessionInfo returned from a successful
+1
View File
@@ -179,6 +179,7 @@ func TestCodec(tt *testing.T) {
obj := wtdb.Tower{ obj := wtdb.Tower{
IdentityKey: pk, IdentityKey: pk,
Addresses: addrs, Addresses: addrs,
Status: wtdb.TowerStatus(r.Uint32()),
} }
v[0] = reflect.ValueOf(obj) v[0] = reflect.ValueOf(obj)
+64 -2
View File
@@ -7,6 +7,26 @@ import (
"net" "net"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/tlv"
)
// TowerStatus represents the state of the tower as set by the tower client.
type TowerStatus uint8
const (
// TowerStatusActive is the default state of the tower, and it indicates
// that this tower should be used to attempt session creation.
TowerStatusActive TowerStatus = 0
// TowerStatusInactive indicates that the tower should not be used to
// attempt session creation.
TowerStatusInactive TowerStatus = 1
)
const (
// TowerStatusTLVType is the TLV type number that will be used to store
// the tower's status.
TowerStatusTLVType = tlv.Type(0)
) )
// TowerID is a unique 64-bit identifier allocated to each unique watchtower. // TowerID is a unique 64-bit identifier allocated to each unique watchtower.
@@ -41,6 +61,9 @@ type Tower struct {
// Addresses is a list of possible addresses to reach the tower. // Addresses is a list of possible addresses to reach the tower.
Addresses []net.Addr Addresses []net.Addr
// Status is the status of this tower as set by the client.
Status TowerStatus
} }
// AddAddress adds the given address to the tower's in-memory list of addresses. // AddAddress adds the given address to the tower's in-memory list of addresses.
@@ -88,17 +111,56 @@ func (t *Tower) String() string {
// Encode writes the Tower to the passed io.Writer. The TowerID is not // Encode writes the Tower to the passed io.Writer. The TowerID is not
// serialized, since it acts as the key. // serialized, since it acts as the key.
func (t *Tower) Encode(w io.Writer) error { func (t *Tower) Encode(w io.Writer) error {
return WriteElements(w, err := WriteElements(w,
t.IdentityKey, t.IdentityKey,
t.Addresses, t.Addresses,
) )
if err != nil {
return err
}
status := uint8(t.Status)
tlvRecords := []tlv.Record{
tlv.MakePrimitiveRecord(TowerStatusTLVType, &status),
}
tlvStream, err := tlv.NewStream(tlvRecords...)
if err != nil {
return err
}
return tlvStream.Encode(w)
} }
// Decode reads a Tower from the passed io.Reader. The TowerID is meant to be // Decode reads a Tower from the passed io.Reader. The TowerID is meant to be
// decoded from the key. // decoded from the key.
func (t *Tower) Decode(r io.Reader) error { func (t *Tower) Decode(r io.Reader) error {
return ReadElements(r, err := ReadElements(r,
&t.IdentityKey, &t.IdentityKey,
&t.Addresses, &t.Addresses,
) )
if err != nil {
return err
}
var status uint8
tlvRecords := []tlv.Record{
tlv.MakePrimitiveRecord(TowerStatusTLVType, &status),
}
tlvStream, err := tlv.NewStream(tlvRecords...)
if err != nil {
return err
}
typeMap, err := tlvStream.DecodeWithParsedTypes(r)
if err != nil {
return err
}
if _, ok := typeMap[TowerStatusTLVType]; ok {
t.Status = TowerStatus(status)
}
return nil
} }