mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
Merge pull request #8239 from ellemouton/loadSessionByActiveTower
wtclient: add DeactivateTower and TerminateSession commands
This commit is contained in:
commit
c398b0cc69
@ -10,7 +10,8 @@ import (
|
||||
"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 {
|
||||
return []cli.Command{
|
||||
{
|
||||
@ -20,10 +21,12 @@ func wtclientCommands() []cli.Command {
|
||||
Subcommands: []cli.Command{
|
||||
addTowerCommand,
|
||||
removeTowerCommand,
|
||||
deactivateTowerCommand,
|
||||
listTowersCommand,
|
||||
getTowerCommand,
|
||||
statsCommand,
|
||||
policyCommand,
|
||||
sessionCommands,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -84,6 +87,44 @@ func addTower(ctx *cli.Context) error {
|
||||
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{
|
||||
Name: "remove",
|
||||
Usage: "Remove a watchtower to prevent its use for future " +
|
||||
@ -333,3 +374,47 @@ func policy(ctx *cli.Context) error {
|
||||
printRespJSON(resp)
|
||||
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
|
||||
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
|
||||
|
||||
* Deprecate `bumpclosefee` for `bumpforceclosefee` to accommodate for the fact
|
||||
|
@ -519,16 +519,8 @@ var allTestCases = []*lntest.TestCase{
|
||||
TestFunc: testLookupHtlcResolution,
|
||||
},
|
||||
{
|
||||
Name: "watchtower session management",
|
||||
TestFunc: testWatchtowerSessionManagement,
|
||||
},
|
||||
{
|
||||
// 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: "watchtower",
|
||||
TestFunc: testWatchtower,
|
||||
},
|
||||
{
|
||||
Name: "channel fundmax",
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
@ -12,7 +11,6 @@ import (
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/funding"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"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)
|
||||
}
|
||||
|
@ -2,24 +2,234 @@ package itest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/funding"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/node"
|
||||
"github.com/lightningnetwork/lnd/lntest/rpc"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testWatchtowerSessionManagement tests that session deletion is done
|
||||
// correctly.
|
||||
func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
|
||||
// testWatchtower tests the behaviour of the watchtower client and server.
|
||||
func testWatchtower(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 (
|
||||
chanAmt = funding.MaxBtcFundingAmount
|
||||
paymentAmt = 10_000
|
||||
numInvoices = 5
|
||||
maxUpdates = numInvoices * 2
|
||||
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
|
||||
// his channel commitment transactions.
|
||||
wallis := ht.NewNode("Wallis", []string{
|
||||
"--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)
|
||||
wallisPk, listener, _ := setUpNewTower(ht, "Wallis", externalIP)
|
||||
|
||||
// Dave will be the tower client.
|
||||
daveArgs := []string{
|
||||
@ -58,7 +251,7 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
|
||||
dave := ht.NewNode("Dave", daveArgs)
|
||||
|
||||
addTowerReq := &wtclientrpc.AddTowerRequest{
|
||||
Pubkey: wallisInfo.Pubkey,
|
||||
Pubkey: wallisPk,
|
||||
Address: listener,
|
||||
}
|
||||
dave.RPC.AddTower(addTowerReq)
|
||||
@ -66,7 +259,7 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
|
||||
// Assert that there exists a session between Dave and Wallis.
|
||||
err := wait.NoError(func() error {
|
||||
info := dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{
|
||||
Pubkey: wallisInfo.Pubkey,
|
||||
Pubkey: wallisPk,
|
||||
IncludeSessions: true,
|
||||
})
|
||||
|
||||
@ -98,61 +291,10 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
|
||||
// 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
|
||||
// between the pair so that the session is exhausted.
|
||||
alicePayReqs, _, _ := ht.CreatePayReqs(
|
||||
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)
|
||||
}
|
||||
generateBackups(ht, dave, ht.Alice, maxUpdates)
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// exhausted one. This time we set the "mineOnFail" parameter to true to
|
||||
// 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)
|
||||
}
|
||||
|
||||
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,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
|
@ -44,6 +44,14 @@ var (
|
||||
Entity: "offchain",
|
||||
Action: "write",
|
||||
}},
|
||||
"/wtclientrpc.WatchtowerClient/DeactivateTower": {{
|
||||
Entity: "offchain",
|
||||
Action: "write",
|
||||
}},
|
||||
"/wtclientrpc.WatchtowerClient/TerminateSession": {{
|
||||
Entity: "offchain",
|
||||
Action: "write",
|
||||
}},
|
||||
"/wtclientrpc.WatchtowerClient/ListTowers": {{
|
||||
Entity: "offchain",
|
||||
Action: "read",
|
||||
@ -251,6 +259,59 @@ func (c *WatchtowerClient) RemoveTower(ctx context.Context,
|
||||
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.
|
||||
func (c *WatchtowerClient) ListTowers(ctx context.Context,
|
||||
req *ListTowersRequest) (*ListTowersResponse, error) {
|
||||
@ -489,6 +550,7 @@ func marshallTower(tower *wtclient.RegisteredTower, policyType PolicyType,
|
||||
for _, session := range sessions {
|
||||
satPerVByte := session.Policy.SweepFeeRate.FeePerVByte()
|
||||
rpcSessions = append(rpcSessions, &TowerSession{
|
||||
Id: session.ID[:],
|
||||
NumBackups: uint32(ackCounts[session.ID]),
|
||||
NumPendingBackups: uint32(pendingCounts[session.ID]),
|
||||
MaxBackups: uint32(session.Policy.MaxUpdates),
|
||||
|
@ -264,6 +264,198 @@ func (*RemoveTowerResponse) Descriptor() ([]byte, []int) {
|
||||
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 {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@ -281,7 +473,7 @@ type GetTowerInfoRequest struct {
|
||||
func (x *GetTowerInfoRequest) Reset() {
|
||||
*x = GetTowerInfoRequest{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -294,7 +486,7 @@ func (x *GetTowerInfoRequest) String() string {
|
||||
func (*GetTowerInfoRequest) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -307,7 +499,7 @@ func (x *GetTowerInfoRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GetTowerInfoRequest.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
@ -353,12 +545,14 @@ type TowerSession struct {
|
||||
// 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.
|
||||
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() {
|
||||
*x = TowerSession{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -371,7 +565,7 @@ func (x *TowerSession) String() string {
|
||||
func (*TowerSession) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -384,7 +578,7 @@ func (x *TowerSession) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use TowerSession.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
@ -423,6 +617,13 @@ func (x *TowerSession) GetSweepSatPerVbyte() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *TowerSession) GetId() []byte {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Tower struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@ -457,7 +658,7 @@ type Tower struct {
|
||||
func (x *Tower) Reset() {
|
||||
*x = Tower{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -470,7 +671,7 @@ func (x *Tower) String() string {
|
||||
func (*Tower) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -483,7 +684,7 @@ func (x *Tower) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Tower.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
@ -549,7 +750,7 @@ type TowerSessionInfo struct {
|
||||
func (x *TowerSessionInfo) Reset() {
|
||||
*x = TowerSessionInfo{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -562,7 +763,7 @@ func (x *TowerSessionInfo) String() string {
|
||||
func (*TowerSessionInfo) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -575,7 +776,7 @@ func (x *TowerSessionInfo) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use TowerSessionInfo.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
@ -621,7 +822,7 @@ type ListTowersRequest struct {
|
||||
func (x *ListTowersRequest) Reset() {
|
||||
*x = ListTowersRequest{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -634,7 +835,7 @@ func (x *ListTowersRequest) String() string {
|
||||
func (*ListTowersRequest) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -647,7 +848,7 @@ func (x *ListTowersRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ListTowersRequest.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
@ -676,7 +877,7 @@ type ListTowersResponse struct {
|
||||
func (x *ListTowersResponse) Reset() {
|
||||
*x = ListTowersResponse{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -689,7 +890,7 @@ func (x *ListTowersResponse) String() string {
|
||||
func (*ListTowersResponse) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -702,7 +903,7 @@ func (x *ListTowersResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ListTowersResponse.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
@ -721,7 +922,7 @@ type StatsRequest struct {
|
||||
func (x *StatsRequest) Reset() {
|
||||
*x = StatsRequest{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -734,7 +935,7 @@ func (x *StatsRequest) String() string {
|
||||
func (*StatsRequest) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -747,7 +948,7 @@ func (x *StatsRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StatsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*StatsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{10}
|
||||
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{14}
|
||||
}
|
||||
|
||||
type StatsResponse struct {
|
||||
@ -773,7 +974,7 @@ type StatsResponse struct {
|
||||
func (x *StatsResponse) Reset() {
|
||||
*x = StatsResponse{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -786,7 +987,7 @@ func (x *StatsResponse) String() string {
|
||||
func (*StatsResponse) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -799,7 +1000,7 @@ func (x *StatsResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StatsResponse.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
@ -849,7 +1050,7 @@ type PolicyRequest struct {
|
||||
func (x *PolicyRequest) Reset() {
|
||||
*x = PolicyRequest{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -862,7 +1063,7 @@ func (x *PolicyRequest) String() string {
|
||||
func (*PolicyRequest) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -875,7 +1076,7 @@ func (x *PolicyRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use PolicyRequest.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
@ -907,7 +1108,7 @@ type PolicyResponse struct {
|
||||
func (x *PolicyResponse) Reset() {
|
||||
*x = PolicyResponse{}
|
||||
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.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -920,7 +1121,7 @@ func (x *PolicyResponse) String() string {
|
||||
func (*PolicyResponse) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -933,7 +1134,7 @@ func (x *PolicyResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use PolicyResponse.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
@ -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,
|
||||
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,
|
||||
0x65, 0x22, 0x96, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e,
|
||||
0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62,
|
||||
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65,
|
||||
0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x73, 0x65, 0x73,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63,
|
||||
0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a,
|
||||
0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65,
|
||||
0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74,
|
||||
0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0c, 0x54,
|
||||
0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6e,
|
||||
0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13,
|
||||
0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b,
|
||||
0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65,
|
||||
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x0a, 0x0b,
|
||||
0x6d, 0x61, 0x78, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2f, 0x0a,
|
||||
0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62,
|
||||
0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73,
|
||||
0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d,
|
||||
0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f,
|
||||
0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, 0x77, 0x65,
|
||||
0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0x9f, 0x02,
|
||||
0x0a, 0x05, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65,
|
||||
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12,
|
||||
0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03,
|
||||
0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x3c, 0x0a,
|
||||
0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
|
||||
0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x42,
|
||||
0x02, 0x18, 0x01, 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, 0x0a, 0x0c, 0x6e,
|
||||
0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42,
|
||||
0x02, 0x18, 0x01, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x40, 0x0a,
|
||||
0x0c, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e,
|
||||
0x66, 0x6f, 0x52, 0x0b, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x22,
|
||||
0xe0, 0x01, 0x0a, 0x10, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x49, 0x6e, 0x66, 0x6f, 0x12, 0x38, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73,
|
||||
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 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, 0x21,
|
||||
0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x12, 0x35, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08,
|
||||
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69,
|
||||
0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e,
|
||||
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69,
|
||||
0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79,
|
||||
0x70, 0x65, 0x22, 0x7c, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75,
|
||||
0x64, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x78,
|
||||
0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x45,
|
||||
0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x22, 0x40, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x73,
|
||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x06, 0x74, 0x6f, 0x77, 0x65,
|
||||
0x72, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b,
|
||||
0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61,
|
||||
0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e,
|
||||
0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61,
|
||||
0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x5f, 0x66, 0x61, 0x69,
|
||||
0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x0d, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42, 0x61, 0x63, 0x6b,
|
||||
0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x41,
|
||||
0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x6e, 0x75, 0x6d, 0x5f, 0x73,
|
||||
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65,
|
||||
0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x22, 0x49, 0x0a,
|
||||
0x0d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38,
|
||||
0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x70, 0x6f,
|
||||
0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x0e, 0x50, 0x6f, 0x6c,
|
||||
0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d,
|
||||
0x61, 0x78, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x0a, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12,
|
||||
0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79,
|
||||
0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73, 0x77,
|
||||
0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a,
|
||||
0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76,
|
||||
0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, 0x77, 0x65, 0x65,
|
||||
0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x2a, 0x31, 0x0a, 0x0a,
|
||||
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45,
|
||||
0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52,
|
||||
0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x02, 0x32,
|
||||
0xc5, 0x03, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x43, 0x6c,
|
||||
0x69, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72,
|
||||
0x12, 0x1c, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41,
|
||||
0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d,
|
||||
0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64,
|
||||
0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a,
|
||||
0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x77,
|
||||
0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76,
|
||||
0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e,
|
||||
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f,
|
||||
0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 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,
|
||||
0x65, 0x22, 0x30, 0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x54,
|
||||
0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70,
|
||||
0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62,
|
||||
0x6b, 0x65, 0x79, 0x22, 0x31, 0x0a, 0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74,
|
||||
0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16,
|
||||
0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x38, 0x0a, 0x17, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e,
|
||||
0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64,
|
||||
0x22, 0x32, 0x0a, 0x18, 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, 0x16, 0x0a, 0x06,
|
||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65,
|
||||
0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06,
|
||||
0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75,
|
||||
0x62, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f,
|
||||
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f,
|
||||
0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12,
|
||||
0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75,
|
||||
0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x45, 0x78, 0x68, 0x61,
|
||||
0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf0, 0x01,
|
||||
0x0a, 0x0c, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f,
|
||||
0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12,
|
||||
0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62,
|
||||
0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75,
|
||||
0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12,
|
||||
0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73,
|
||||
0x12, 0x2f, 0x0a, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65,
|
||||
0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01,
|
||||
0x52, 0x0f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74,
|
||||
0x65, 0x12, 0x2d, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70,
|
||||
0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10,
|
||||
0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65,
|
||||
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64,
|
||||
0x22, 0x9f, 0x02, 0x0a, 0x05, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75,
|
||||
0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b,
|
||||
0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18,
|
||||
0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73,
|
||||
0x12, 0x3c, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 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,
|
||||
0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65,
|
||||
0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x12, 0x40, 0x0a, 0x0c, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f,
|
||||
0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e,
|
||||
0x66, 0x6f, 0x22, 0xe0, 0x01, 0x0a, 0x10, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x38, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76,
|
||||
0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64,
|
||||
0x61, 0x74, 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, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x70,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e,
|
||||
0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50,
|
||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63,
|
||||
0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x7c, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77,
|
||||
0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e,
|
||||
0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65,
|
||||
0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75,
|
||||
0x64, 0x65, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x22, 0x40, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72,
|
||||
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x6f, 0x77,
|
||||
0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c,
|
||||
0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x06, 0x74,
|
||||
0x6f, 0x77, 0x65, 0x72, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62,
|
||||
0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75,
|
||||
0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f,
|
||||
0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e,
|
||||
0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x5f,
|
||||
0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42,
|
||||
0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65,
|
||||
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x6e, 0x75,
|
||||
0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75,
|
||||
0x73, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6e, 0x75, 0x6d, 0x53,
|
||||
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64,
|
||||
0x22, 0x49, 0x0a, 0x0d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x38, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52,
|
||||
0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x0e,
|
||||
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f,
|
||||
0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12,
|
||||
0x2f, 0x0a, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72,
|
||||
0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52,
|
||||
0x0f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65,
|
||||
0x12, 0x2d, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65,
|
||||
0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73,
|
||||
0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x2a,
|
||||
0x31, 0x0a, 0x0a, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a,
|
||||
0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43,
|
||||
0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54,
|
||||
0x10, 0x02, 0x32, 0x84, 0x05, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65,
|
||||
0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x54, 0x6f,
|
||||
0x77, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x50, 0x0a, 0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12,
|
||||
0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65,
|
||||
0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52,
|
||||
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0f, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65,
|
||||
0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x23, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x54, 0x6f,
|
||||
0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, 0x74, 0x63,
|
||||
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76,
|
||||
0x61, 0x74, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x5f, 0x0a, 0x10, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73,
|
||||
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 (
|
||||
@ -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_msgTypes = make([]protoimpl.MessageInfo, 14)
|
||||
var file_wtclientrpc_wtclient_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
|
||||
var file_wtclientrpc_wtclient_proto_goTypes = []interface{}{
|
||||
(PolicyType)(0), // 0: wtclientrpc.PolicyType
|
||||
(*AddTowerRequest)(nil), // 1: wtclientrpc.AddTowerRequest
|
||||
(*AddTowerResponse)(nil), // 2: wtclientrpc.AddTowerResponse
|
||||
(*RemoveTowerRequest)(nil), // 3: wtclientrpc.RemoveTowerRequest
|
||||
(*RemoveTowerResponse)(nil), // 4: wtclientrpc.RemoveTowerResponse
|
||||
(*GetTowerInfoRequest)(nil), // 5: wtclientrpc.GetTowerInfoRequest
|
||||
(*TowerSession)(nil), // 6: wtclientrpc.TowerSession
|
||||
(*Tower)(nil), // 7: wtclientrpc.Tower
|
||||
(*TowerSessionInfo)(nil), // 8: wtclientrpc.TowerSessionInfo
|
||||
(*ListTowersRequest)(nil), // 9: wtclientrpc.ListTowersRequest
|
||||
(*ListTowersResponse)(nil), // 10: wtclientrpc.ListTowersResponse
|
||||
(*StatsRequest)(nil), // 11: wtclientrpc.StatsRequest
|
||||
(*StatsResponse)(nil), // 12: wtclientrpc.StatsResponse
|
||||
(*PolicyRequest)(nil), // 13: wtclientrpc.PolicyRequest
|
||||
(*PolicyResponse)(nil), // 14: wtclientrpc.PolicyResponse
|
||||
(PolicyType)(0), // 0: wtclientrpc.PolicyType
|
||||
(*AddTowerRequest)(nil), // 1: wtclientrpc.AddTowerRequest
|
||||
(*AddTowerResponse)(nil), // 2: wtclientrpc.AddTowerResponse
|
||||
(*RemoveTowerRequest)(nil), // 3: wtclientrpc.RemoveTowerRequest
|
||||
(*RemoveTowerResponse)(nil), // 4: wtclientrpc.RemoveTowerResponse
|
||||
(*DeactivateTowerRequest)(nil), // 5: wtclientrpc.DeactivateTowerRequest
|
||||
(*DeactivateTowerResponse)(nil), // 6: wtclientrpc.DeactivateTowerResponse
|
||||
(*TerminateSessionRequest)(nil), // 7: wtclientrpc.TerminateSessionRequest
|
||||
(*TerminateSessionResponse)(nil), // 8: wtclientrpc.TerminateSessionResponse
|
||||
(*GetTowerInfoRequest)(nil), // 9: wtclientrpc.GetTowerInfoRequest
|
||||
(*TowerSession)(nil), // 10: wtclientrpc.TowerSession
|
||||
(*Tower)(nil), // 11: wtclientrpc.Tower
|
||||
(*TowerSessionInfo)(nil), // 12: wtclientrpc.TowerSessionInfo
|
||||
(*ListTowersRequest)(nil), // 13: wtclientrpc.ListTowersRequest
|
||||
(*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{
|
||||
6, // 0: wtclientrpc.Tower.sessions:type_name -> wtclientrpc.TowerSession
|
||||
8, // 1: wtclientrpc.Tower.session_info:type_name -> wtclientrpc.TowerSessionInfo
|
||||
6, // 2: wtclientrpc.TowerSessionInfo.sessions:type_name -> wtclientrpc.TowerSession
|
||||
10, // 0: wtclientrpc.Tower.sessions:type_name -> wtclientrpc.TowerSession
|
||||
12, // 1: wtclientrpc.Tower.session_info:type_name -> wtclientrpc.TowerSessionInfo
|
||||
10, // 2: wtclientrpc.TowerSessionInfo.sessions:type_name -> wtclientrpc.TowerSession
|
||||
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
|
||||
1, // 6: wtclientrpc.WatchtowerClient.AddTower:input_type -> wtclientrpc.AddTowerRequest
|
||||
3, // 7: wtclientrpc.WatchtowerClient.RemoveTower:input_type -> wtclientrpc.RemoveTowerRequest
|
||||
9, // 8: wtclientrpc.WatchtowerClient.ListTowers:input_type -> wtclientrpc.ListTowersRequest
|
||||
5, // 9: wtclientrpc.WatchtowerClient.GetTowerInfo:input_type -> wtclientrpc.GetTowerInfoRequest
|
||||
11, // 10: wtclientrpc.WatchtowerClient.Stats:input_type -> wtclientrpc.StatsRequest
|
||||
13, // 11: wtclientrpc.WatchtowerClient.Policy:input_type -> wtclientrpc.PolicyRequest
|
||||
2, // 12: wtclientrpc.WatchtowerClient.AddTower:output_type -> wtclientrpc.AddTowerResponse
|
||||
4, // 13: wtclientrpc.WatchtowerClient.RemoveTower:output_type -> wtclientrpc.RemoveTowerResponse
|
||||
10, // 14: wtclientrpc.WatchtowerClient.ListTowers:output_type -> wtclientrpc.ListTowersResponse
|
||||
7, // 15: wtclientrpc.WatchtowerClient.GetTowerInfo:output_type -> wtclientrpc.Tower
|
||||
12, // 16: wtclientrpc.WatchtowerClient.Stats:output_type -> wtclientrpc.StatsResponse
|
||||
14, // 17: wtclientrpc.WatchtowerClient.Policy:output_type -> wtclientrpc.PolicyResponse
|
||||
12, // [12:18] is the sub-list for method output_type
|
||||
6, // [6:12] is the sub-list for method input_type
|
||||
5, // 8: wtclientrpc.WatchtowerClient.DeactivateTower:input_type -> wtclientrpc.DeactivateTowerRequest
|
||||
7, // 9: wtclientrpc.WatchtowerClient.TerminateSession:input_type -> wtclientrpc.TerminateSessionRequest
|
||||
13, // 10: wtclientrpc.WatchtowerClient.ListTowers:input_type -> wtclientrpc.ListTowersRequest
|
||||
9, // 11: wtclientrpc.WatchtowerClient.GetTowerInfo:input_type -> wtclientrpc.GetTowerInfoRequest
|
||||
15, // 12: wtclientrpc.WatchtowerClient.Stats:input_type -> wtclientrpc.StatsRequest
|
||||
17, // 13: wtclientrpc.WatchtowerClient.Policy:input_type -> wtclientrpc.PolicyRequest
|
||||
2, // 14: wtclientrpc.WatchtowerClient.AddTower:output_type -> wtclientrpc.AddTowerResponse
|
||||
4, // 15: wtclientrpc.WatchtowerClient.RemoveTower:output_type -> wtclientrpc.RemoveTowerResponse
|
||||
6, // 16: wtclientrpc.WatchtowerClient.DeactivateTower:output_type -> wtclientrpc.DeactivateTowerResponse
|
||||
8, // 17: wtclientrpc.WatchtowerClient.TerminateSession:output_type -> wtclientrpc.TerminateSessionResponse
|
||||
14, // 18: wtclientrpc.WatchtowerClient.ListTowers:output_type -> wtclientrpc.ListTowersResponse
|
||||
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 extendee
|
||||
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{} {
|
||||
switch v := v.(*GetTowerInfoRequest); i {
|
||||
switch v := v.(*DeactivateTowerRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
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{} {
|
||||
switch v := v.(*TowerSession); i {
|
||||
switch v := v.(*DeactivateTowerResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
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{} {
|
||||
switch v := v.(*Tower); i {
|
||||
switch v := v.(*TerminateSessionRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
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{} {
|
||||
switch v := v.(*TowerSessionInfo); i {
|
||||
switch v := v.(*TerminateSessionResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
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{} {
|
||||
switch v := v.(*ListTowersRequest); i {
|
||||
switch v := v.(*GetTowerInfoRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
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{} {
|
||||
switch v := v.(*ListTowersResponse); i {
|
||||
switch v := v.(*TowerSession); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
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{} {
|
||||
switch v := v.(*StatsRequest); i {
|
||||
switch v := v.(*Tower); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
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{} {
|
||||
switch v := v.(*StatsResponse); i {
|
||||
switch v := v.(*TowerSessionInfo); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
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{} {
|
||||
switch v := v.(*PolicyRequest); i {
|
||||
switch v := v.(*ListTowersRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
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{} {
|
||||
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 {
|
||||
case 0:
|
||||
return &v.state
|
||||
@ -1350,7 +1633,7 @@ func file_wtclientrpc_wtclient_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_wtclientrpc_wtclient_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 14,
|
||||
NumMessages: 18,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
@ -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 (
|
||||
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) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
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) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
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_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_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_DeactivateTower_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_WatchtowerClient_TerminateSession_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_WatchtowerClient_ListTowers_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_WatchtowerClient_GetTowerInfo_0 = runtime.ForwardResponseMessage
|
||||
|
@ -41,6 +41,21 @@ service WatchtowerClient {
|
||||
*/
|
||||
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`
|
||||
ListTowers returns the list of watchtowers registered with the client.
|
||||
*/
|
||||
@ -88,6 +103,26 @@ message RemoveTowerRequest {
|
||||
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 {
|
||||
// The identifying public key of the watchtower to retrieve information for.
|
||||
bytes pubkey = 1;
|
||||
@ -128,6 +163,11 @@ message TowerSession {
|
||||
the justice transaction in the event of a channel breach.
|
||||
*/
|
||||
uint32 sweep_sat_per_vbyte = 5;
|
||||
|
||||
/*
|
||||
The ID of the session.
|
||||
*/
|
||||
bytes id = 6;
|
||||
}
|
||||
|
||||
message Tower {
|
||||
|
@ -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": {
|
||||
"get": {
|
||||
"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}": {
|
||||
"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.",
|
||||
@ -283,6 +349,15 @@
|
||||
"wtclientrpcAddTowerResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"wtclientrpcDeactivateTowerResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "A string describing the action that took place."
|
||||
}
|
||||
}
|
||||
},
|
||||
"wtclientrpcListTowersResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -358,6 +433,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"wtclientrpcTerminateSessionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "A string describing the action that took place."
|
||||
}
|
||||
}
|
||||
},
|
||||
"wtclientrpcTower": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -425,6 +509,11 @@
|
||||
"type": "integer",
|
||||
"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."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The ID of the session."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -8,6 +8,10 @@ http:
|
||||
body: "*"
|
||||
- selector: wtclientrpc.WatchtowerClient.RemoveTower
|
||||
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
|
||||
get: "/v2/watchtower/client"
|
||||
- selector: wtclientrpc.WatchtowerClient.GetTowerInfo
|
||||
|
@ -30,6 +30,15 @@ type WatchtowerClientClient interface {
|
||||
// again. If an address is provided, then this RPC only serves as a way of
|
||||
// removing the address from the watchtower instead.
|
||||
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`
|
||||
// ListTowers returns the list of watchtowers registered with the client.
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
out := new(ListTowersResponse)
|
||||
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
|
||||
// removing the address from the watchtower instead.
|
||||
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`
|
||||
// ListTowers returns the list of watchtowers registered with the client.
|
||||
ListTowers(context.Context, *ListTowersRequest) (*ListTowersResponse, error)
|
||||
@ -147,6 +183,12 @@ func (UnimplementedWatchtowerClientServer) AddTower(context.Context, *AddTowerRe
|
||||
func (UnimplementedWatchtowerClientServer) RemoveTower(context.Context, *RemoveTowerRequest) (*RemoveTowerResponse, error) {
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
in := new(ListTowersRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@ -295,6 +373,14 @@ var WatchtowerClient_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "RemoveTower",
|
||||
Handler: _WatchtowerClient_RemoveTower_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeactivateTower",
|
||||
Handler: _WatchtowerClient_DeactivateTower_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "TerminateSession",
|
||||
Handler: _WatchtowerClient_TerminateSession_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListTowers",
|
||||
Handler: _WatchtowerClient_ListTowers_Handler,
|
||||
|
@ -52,6 +52,38 @@ func (h *HarnessRPC) AddTower(
|
||||
return resp
|
||||
}
|
||||
|
||||
// DeactivateTower makes an RPC call to the WatchtowerClient of the given node
|
||||
// and asserts.
|
||||
func (h *HarnessRPC) DeactivateTower(req *wtclientrpc.DeactivateTowerRequest) {
|
||||
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err := h.WatchtowerClient.DeactivateTower(ctxt, req)
|
||||
h.NoError(err, "DeactivateTower")
|
||||
}
|
||||
|
||||
// TerminateSession makes an RPC call to the WatchtowerClient of the given node
|
||||
// and asserts.
|
||||
func (h *HarnessRPC) TerminateSession(
|
||||
req *wtclientrpc.TerminateSessionRequest) {
|
||||
|
||||
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err := h.WatchtowerClient.TerminateSession(ctxt, req)
|
||||
h.NoError(err, "TerminateSession")
|
||||
}
|
||||
|
||||
// RemoveTower makes an RPC call to the WatchtowerClient of the given node
|
||||
// and asserts.
|
||||
func (h *HarnessRPC) RemoveTower(req *wtclientrpc.RemoveTowerRequest) {
|
||||
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err := h.WatchtowerClient.RemoveTower(ctxt, req)
|
||||
h.NoError(err, "RemoveTower")
|
||||
}
|
||||
|
||||
// WatchtowerStats makes a RPC call to the WatchtowerClient of the given node
|
||||
// and asserts.
|
||||
func (h *HarnessRPC) WatchtowerStats() *wtclientrpc.StatsResponse {
|
||||
|
@ -132,6 +132,35 @@ type staleTowerMsg struct {
|
||||
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.
|
||||
type clientCfg struct {
|
||||
*Config
|
||||
@ -165,8 +194,10 @@ type client struct {
|
||||
statTicker *time.Ticker
|
||||
stats *clientStats
|
||||
|
||||
newTowers chan *newTowerMsg
|
||||
staleTowers chan *staleTowerMsg
|
||||
newTowers chan *newTowerMsg
|
||||
staleTowers chan *staleTowerMsg
|
||||
deactivateTowers chan *deactivateTowerMsg
|
||||
terminateSessions chan *terminateSessMsg
|
||||
|
||||
wg sync.WaitGroup
|
||||
quit chan struct{}
|
||||
@ -192,15 +223,17 @@ func newClient(cfg *clientCfg) (*client, error) {
|
||||
}
|
||||
|
||||
c := &client{
|
||||
cfg: cfg,
|
||||
log: plog,
|
||||
pipeline: queue,
|
||||
activeSessions: newSessionQueueSet(),
|
||||
statTicker: time.NewTicker(DefaultStatInterval),
|
||||
stats: new(clientStats),
|
||||
newTowers: make(chan *newTowerMsg),
|
||||
staleTowers: make(chan *staleTowerMsg),
|
||||
quit: make(chan struct{}),
|
||||
cfg: cfg,
|
||||
log: plog,
|
||||
pipeline: queue,
|
||||
activeSessions: newSessionQueueSet(),
|
||||
statTicker: time.NewTicker(DefaultStatInterval),
|
||||
stats: new(clientStats),
|
||||
newTowers: make(chan *newTowerMsg),
|
||||
staleTowers: make(chan *staleTowerMsg),
|
||||
deactivateTowers: make(chan *deactivateTowerMsg),
|
||||
terminateSessions: make(chan *terminateSessMsg),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
candidateTowers := newTowerListIterator()
|
||||
@ -261,7 +294,10 @@ func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing,
|
||||
opts ...wtdb.ClientSessionListOption) (
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -288,9 +324,9 @@ func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing,
|
||||
|
||||
// Add the session to the set of candidate sessions.
|
||||
candidateSessions[s.ID] = cs
|
||||
|
||||
perActiveTower(tower)
|
||||
}
|
||||
|
||||
perActiveTower(tower)
|
||||
}
|
||||
|
||||
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
|
||||
// the in-memory active sessions set.
|
||||
func (c *client) stopAndRemoveSession(id wtdb.SessionID) error {
|
||||
return c.activeSessions.StopAndRemove(id)
|
||||
func (c *client) stopAndRemoveSession(id wtdb.SessionID, final bool) error {
|
||||
return c.activeSessions.StopAndRemove(id, final)
|
||||
}
|
||||
|
||||
// deleteSessionFromTower dials the tower that we created the session with and
|
||||
@ -691,6 +727,16 @@ func (c *client) backupDispatcher() {
|
||||
case msg := <-c.staleTowers:
|
||||
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:
|
||||
return
|
||||
}
|
||||
@ -776,6 +822,14 @@ func (c *client) backupDispatcher() {
|
||||
case msg := <-c.staleTowers:
|
||||
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:
|
||||
return
|
||||
}
|
||||
@ -1043,6 +1097,124 @@ func (c *client) initActiveQueue(s *ClientSession,
|
||||
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
|
||||
// it for new sessions. If the watchtower already exists, then any new addresses
|
||||
// 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
|
||||
// replayed back onto the main task pipeline.
|
||||
err = c.activeSessions.StopAndRemove(sessionID)
|
||||
err = c.activeSessions.StopAndRemove(sessionID, true)
|
||||
if err != nil {
|
||||
c.log.Errorf("could not stop session %s: %w", sessionID,
|
||||
err)
|
||||
|
@ -1034,15 +1034,19 @@ func (s *serverHarness) waitForUpdates(hints []blob.BreachHint,
|
||||
// Closure to assert the server's matches are consistent with the hint
|
||||
// set.
|
||||
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
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
_, ok := hintSet[match.Hint]
|
||||
require.Truef(s.t, ok, "match %v in db is not in "+
|
||||
"hint set", match.Hint)
|
||||
}
|
||||
require.EqualValues(s.t, hintSet, matchHints)
|
||||
|
||||
return true
|
||||
}
|
||||
@ -2688,6 +2692,205 @@ var clientTests = []clientTest{
|
||||
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
|
||||
|
@ -26,14 +26,17 @@ type DB interface {
|
||||
// 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 persisted state. Otherwise, we'll attempt to mark
|
||||
// the tower as inactive by marking all of its sessions inactive. If any
|
||||
// of its sessions has unacked updates, then ErrTowerUnackedUpdates is
|
||||
// returned. If the tower doesn't have any sessions at all, it'll be
|
||||
// completely removed from the database.
|
||||
// the tower as inactive. If any of its sessions have unacked updates,
|
||||
// then ErrTowerUnackedUpdates is returned. If the tower doesn't have
|
||||
// any sessions at all, it'll be completely removed from the database.
|
||||
//
|
||||
// NOTE: An error is not returned if the tower doesn't exist.
|
||||
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(*btcec.PublicKey) (*wtdb.Tower, error)
|
||||
|
||||
@ -41,8 +44,9 @@ type DB interface {
|
||||
LoadTowerByID(wtdb.TowerID) (*wtdb.Tower, error)
|
||||
|
||||
// ListTowers retrieves the list of towers available within the
|
||||
// database.
|
||||
ListTowers() ([]*wtdb.Tower, error)
|
||||
// database. The filter function may be set in order to filter out the
|
||||
// towers to be returned.
|
||||
ListTowers(filter wtdb.TowerFilterFn) ([]*wtdb.Tower, error)
|
||||
|
||||
// NextSessionKeyIndex reserves a new session key derivation index for a
|
||||
// particular tower id and blob type. The index is reserved for that
|
||||
@ -136,9 +140,14 @@ type DB interface {
|
||||
// space.
|
||||
GetDBQueue(namespace []byte) wtdb.Queue[*wtdb.BackupID]
|
||||
|
||||
// DeleteCommittedUpdate deletes the committed update belonging to the
|
||||
// given session and with the given sequence number from the db.
|
||||
DeleteCommittedUpdate(id *wtdb.SessionID, seqNum uint16) error
|
||||
// DeleteCommittedUpdates deletes all the committed updates belonging to
|
||||
// the given session from the db.
|
||||
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
|
||||
|
@ -38,6 +38,15 @@ type ClientManager interface {
|
||||
// instead.
|
||||
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() ClientStats
|
||||
|
||||
@ -431,6 +440,73 @@ func (m *Manager) RemoveTower(key *btcec.PublicKey, addr net.Addr) error {
|
||||
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
|
||||
// since startup.
|
||||
func (m *Manager) Stats() ClientStats {
|
||||
@ -455,7 +531,7 @@ func (m *Manager) Stats() ClientStats {
|
||||
func (m *Manager) RegisteredTowers(opts ...wtdb.ClientSessionListOption) (
|
||||
map[blob.Type][]*RegisteredTower, error) {
|
||||
|
||||
towers, err := m.cfg.DB.ListTowers()
|
||||
towers, err := m.cfg.DB.ListTowers(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -850,7 +926,7 @@ func (m *Manager) handleClosableSessions(
|
||||
// Stop the session and remove it from the
|
||||
// in-memory set.
|
||||
err = client.stopAndRemoveSession(
|
||||
item.sessionID,
|
||||
item.sessionID, true,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("could not remove "+
|
||||
|
@ -211,14 +211,13 @@ func (q *sessionQueue) Stop(final bool) error {
|
||||
update.BackupID, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
err = q.cfg.DB.DeleteCommittedUpdate(
|
||||
q.ID(), update.SeqNum,
|
||||
)
|
||||
if final {
|
||||
err = q.cfg.DB.DeleteCommittedUpdates(q.ID())
|
||||
if err != nil {
|
||||
log.Errorf("could not delete committed "+
|
||||
"update %d for session %s",
|
||||
update.SeqNum, q.ID())
|
||||
"updates for session %s", q.ID())
|
||||
}
|
||||
}
|
||||
|
||||
@ -766,7 +765,7 @@ func (s *sessionQueueSet) AddAndStart(sessionQueue *sessionQueue) {
|
||||
|
||||
// StopAndRemove stops the given session queue and removes it from the
|
||||
// sessionQueueSet.
|
||||
func (s *sessionQueueSet) StopAndRemove(id wtdb.SessionID) error {
|
||||
func (s *sessionQueueSet) StopAndRemove(id wtdb.SessionID, final bool) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
@ -777,7 +776,7 @@ func (s *sessionQueueSet) StopAndRemove(id wtdb.SessionID) error {
|
||||
|
||||
delete(s.queues, id)
|
||||
|
||||
return queue.Stop(true)
|
||||
return queue.Stop(final)
|
||||
}
|
||||
|
||||
// Get fetches and returns the sessionQueue with the given ID.
|
||||
|
@ -196,9 +196,9 @@ var (
|
||||
// session has updates for channels that are still open.
|
||||
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.
|
||||
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
|
||||
// has updates in other non-closed sessions.
|
||||
@ -387,41 +387,13 @@ func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set its status to active.
|
||||
tower.Status = TowerStatusActive
|
||||
|
||||
// Add the new address to the existing tower. If the
|
||||
// address is a duplicate, this will result in no
|
||||
// change.
|
||||
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 {
|
||||
// No such tower exists, create a new tower id for our
|
||||
// new tower. The error is unhandled since NextSequence
|
||||
@ -432,6 +404,7 @@ func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error) {
|
||||
ID: TowerID(towerID),
|
||||
IdentityKey: lnAddr.IdentityKey,
|
||||
Addresses: []net.Addr{lnAddr.Address},
|
||||
Status: TowerStatusActive,
|
||||
}
|
||||
|
||||
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
|
||||
// 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
|
||||
// marking all of its sessions inactive. If any of its sessions has unacked
|
||||
// updates, then ErrTowerUnackedUpdates is returned. If the tower doesn't have
|
||||
// any sessions at all, it'll be completely removed from the database.
|
||||
// persisted state. Otherwise, we'll attempt to mark the tower as inactive. If
|
||||
// any of its sessions has unacked updates, then ErrTowerUnackedUpdates is
|
||||
// returned. If the tower doesn't have any sessions at all, it'll be completely
|
||||
// removed from the database.
|
||||
//
|
||||
// NOTE: An error is not returned if the tower doesn't exist.
|
||||
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
|
||||
}
|
||||
|
||||
tower, err := getTower(towers, towerIDBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If an address is provided, then we should _only_ remove the
|
||||
// address record from the database.
|
||||
if addr != nil {
|
||||
tower, err := getTower(towers, towerIDBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Towers should always have at least one address saved.
|
||||
tower.RemoveAddress(addr)
|
||||
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
|
||||
// have any pending updates to ensure we don't load them upon
|
||||
// restarts.
|
||||
// Otherwise, we mark the tower as inactive.
|
||||
tower.Status = TowerStatusInactive
|
||||
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 {
|
||||
if committedUpdateCount[session.ID] > 0 {
|
||||
return ErrTowerUnackedUpdates
|
||||
}
|
||||
err := markSessionStatus(
|
||||
sessions, session, CSessionInactive,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}, 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.
|
||||
func (c *ClientDB) LoadTowerByID(towerID TowerID) (*Tower, error) {
|
||||
var tower *Tower
|
||||
@ -632,8 +656,14 @@ func (c *ClientDB) LoadTower(pubKey *btcec.PublicKey) (*Tower, error) {
|
||||
return tower, nil
|
||||
}
|
||||
|
||||
// ListTowers retrieves the list of towers available within the database.
|
||||
func (c *ClientDB) ListTowers() ([]*Tower, error) {
|
||||
// TowerFilterFn is the signature of a call-back function that can be used to
|
||||
// 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
|
||||
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
|
||||
towerBucket := tx.ReadBucket(cTowerBkt)
|
||||
@ -646,7 +676,13 @@ func (c *ClientDB) ListTowers() ([]*Tower, error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if filter != nil && !filter(tower) {
|
||||
return nil
|
||||
}
|
||||
|
||||
towers = append(towers, tower)
|
||||
|
||||
return nil
|
||||
})
|
||||
}, func() {
|
||||
@ -1787,9 +1823,10 @@ func (c *ClientDB) MarkChannelClosed(chanID lnwire.ChannelID,
|
||||
|
||||
// isSessionClosable returns true if a session is considered closable. A session
|
||||
// is considered closable only if all the following points are true:
|
||||
// 1) It has no un-acked updates.
|
||||
// 2) It is exhausted (ie it can't accept any more updates)
|
||||
// 3) All the channels that it has acked updates for are closed.
|
||||
// 1. It has no un-acked updates.
|
||||
// 2. It is exhausted (ie it can't accept any more updates) OR it has been
|
||||
// marked as terminal.
|
||||
// 3. All the channels that it has acked updates for are closed.
|
||||
func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket,
|
||||
id *SessionID) (bool, error) {
|
||||
|
||||
@ -1798,22 +1835,21 @@ func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket,
|
||||
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)
|
||||
if commitsBkt == nil {
|
||||
// If the session has no cSessionCommits bucket then we can be
|
||||
// sure that no updates have ever been committed to the session
|
||||
// and so it is not yet exhausted.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// If the session has any un-acked updates, then it is not yet closable.
|
||||
err := commitsBkt.ForEach(func(_, _ []byte) error {
|
||||
return errSessionHasUnackedUpdates
|
||||
})
|
||||
if errors.Is(err, errSessionHasUnackedUpdates) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
if commitsBkt != nil {
|
||||
// If the session has any un-acked updates, then it is not yet
|
||||
// closable.
|
||||
err := commitsBkt.ForEach(func(_, _ []byte) error {
|
||||
return ErrSessionHasUnackedUpdates
|
||||
})
|
||||
if errors.Is(err, ErrSessionHasUnackedUpdates) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
session, err := getClientSessionBody(sessionsBkt, id[:])
|
||||
@ -1821,10 +1857,14 @@ func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket,
|
||||
return false, err
|
||||
}
|
||||
|
||||
isTerminal := session.Status == CSessionTerminal
|
||||
|
||||
// We have already checked that the session has no more committed
|
||||
// updates. So now we can check if the session is exhausted.
|
||||
if session.SeqNum < session.Policy.MaxUpdates {
|
||||
// If the session is not yet exhausted, it is not yet closable.
|
||||
// updates. So now we can check if the session is exhausted or has a
|
||||
// terminal state.
|
||||
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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
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 "+
|
||||
"exhausted session %s", id)
|
||||
}
|
||||
@ -1996,7 +2041,6 @@ func (c *ClientDB) CommitUpdate(id *SessionID,
|
||||
lastApplied = session.TowerLastApplied
|
||||
|
||||
return nil
|
||||
|
||||
}, func() {
|
||||
lastApplied = 0
|
||||
})
|
||||
@ -2215,9 +2259,56 @@ func (c *ClientDB) GetDBQueue(namespace []byte) Queue[*BackupID] {
|
||||
)
|
||||
}
|
||||
|
||||
// DeleteCommittedUpdate deletes the committed update with the given sequence
|
||||
// number from the given session.
|
||||
func (c *ClientDB) DeleteCommittedUpdate(id *SessionID, seqNum uint16) error {
|
||||
// TerminateSession sets the given session's status to CSessionTerminal meaning
|
||||
// that it will not be usable again. An error will be returned if the given
|
||||
// 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 {
|
||||
sessions := tx.ReadWriteBucket(cSessionBkt)
|
||||
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
|
||||
// be a corresponding update to remove.
|
||||
// be corresponding updates to remove.
|
||||
sessionCommits := sessionBkt.NestedReadWriteBucket(
|
||||
cSessionCommits,
|
||||
)
|
||||
if sessionCommits == nil {
|
||||
return ErrCommittedUpdateNotFound
|
||||
return nil
|
||||
}
|
||||
|
||||
var seqNumBuf [2]byte
|
||||
byteOrder.PutUint16(seqNumBuf[:], seqNum)
|
||||
// errFoundUpdates is an error we will use to exit early from
|
||||
// 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 {
|
||||
return ErrCommittedUpdateNotFound
|
||||
// If no error is returned then the ForEach call back was never
|
||||
// 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.
|
||||
return sessionCommits.Delete(seqNumBuf[:])
|
||||
session, err := getClientSessionBody(sessions, id[:])
|
||||
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() {})
|
||||
}
|
||||
|
||||
|
@ -89,6 +89,26 @@ func (h *clientDBHarness) createTower(lnAddr *lnwire.NetAddress,
|
||||
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,
|
||||
hasSessions bool, expErr error) {
|
||||
|
||||
@ -125,12 +145,9 @@ func (h *clientDBHarness) removeTower(pubKey *btcec.PublicKey, addr net.Addr,
|
||||
return
|
||||
}
|
||||
|
||||
for _, session := range h.listSessions(&tower.ID) {
|
||||
require.Equal(h.t, wtdb.CSessionInactive,
|
||||
session.Status, "expected status for session "+
|
||||
"%v to be %v, got %v", session.ID,
|
||||
wtdb.CSessionInactive, session.Status)
|
||||
}
|
||||
require.EqualValues(
|
||||
h.t, wtdb.TowerStatusInactive, tower.Status,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,6 +173,24 @@ func (h *clientDBHarness) loadTowerByID(id wtdb.TowerID,
|
||||
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 {
|
||||
h.t.Helper()
|
||||
|
||||
@ -194,12 +229,12 @@ func (h *clientDBHarness) ackUpdate(id *wtdb.SessionID, seqNum uint16,
|
||||
require.ErrorIs(h.t, err, expErr)
|
||||
}
|
||||
|
||||
func (h *clientDBHarness) deleteCommittedUpdate(id *wtdb.SessionID,
|
||||
seqNum uint16, expErr error) {
|
||||
func (h *clientDBHarness) deleteCommittedUpdates(id *wtdb.SessionID,
|
||||
expErr error) {
|
||||
|
||||
h.t.Helper()
|
||||
|
||||
err := h.db.DeleteCommittedUpdate(id, seqNum)
|
||||
err := h.db.DeleteCommittedUpdates(id)
|
||||
require.ErrorIs(h.t, err, expErr)
|
||||
}
|
||||
|
||||
@ -531,20 +566,126 @@ func testRemoveTower(h *clientDBHarness) {
|
||||
h.commitUpdate(&session.ID, update, nil)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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.removeTower(pk, nil, true, nil)
|
||||
}
|
||||
|
||||
// Creating the tower again should mark all of the sessions active once
|
||||
// again.
|
||||
h.createTower(&lnwire.NetAddress{
|
||||
func testTerminateSession(h *clientDBHarness) {
|
||||
const blobType = blob.TypeAltruistCommit
|
||||
|
||||
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,
|
||||
Address: addr1,
|
||||
}, nil)
|
||||
Address: &net.TCPAddr{
|
||||
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
|
||||
@ -660,18 +801,7 @@ func testCommitUpdate(h *clientDBHarness) {
|
||||
|
||||
// We will now also test that the DeleteCommittedUpdates method also
|
||||
// works.
|
||||
// First, try to delete a committed update that does not exist.
|
||||
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.deleteCommittedUpdates(&session.ID, nil)
|
||||
h.assertUpdates(session.ID, []wtdb.CommittedUpdate{}, nil)
|
||||
}
|
||||
|
||||
@ -929,6 +1059,44 @@ func testMarkChannelClosed(h *clientDBHarness) {
|
||||
// Assert that we now can delete the session.
|
||||
h.deleteSession(session1.ID, 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.
|
||||
@ -1153,6 +1321,14 @@ func TestClientDB(t *testing.T) {
|
||||
name: "max commitment heights",
|
||||
run: testMaxCommitmentHeights,
|
||||
},
|
||||
{
|
||||
name: "test tower status change",
|
||||
run: testTowerStatusChange,
|
||||
},
|
||||
{
|
||||
name: "terminate session",
|
||||
run: testTerminateSession,
|
||||
},
|
||||
}
|
||||
|
||||
for _, database := range dbs {
|
||||
|
@ -18,9 +18,9 @@ const (
|
||||
// used for backups.
|
||||
CSessionActive CSessionStatus = 0
|
||||
|
||||
// CSessionInactive indicates that the ClientSession is inactive and
|
||||
// cannot be used for backups.
|
||||
CSessionInactive CSessionStatus = 1
|
||||
// CSessionTerminal indicates that the ClientSession is in a terminal
|
||||
// state and cannot be used for backups.
|
||||
CSessionTerminal CSessionStatus = 1
|
||||
)
|
||||
|
||||
// ClientSession encapsulates a SessionInfo returned from a successful
|
||||
|
@ -179,6 +179,7 @@ func TestCodec(tt *testing.T) {
|
||||
obj := wtdb.Tower{
|
||||
IdentityKey: pk,
|
||||
Addresses: addrs,
|
||||
Status: wtdb.TowerStatus(r.Uint32()),
|
||||
}
|
||||
|
||||
v[0] = reflect.ValueOf(obj)
|
||||
|
@ -7,6 +7,26 @@ import (
|
||||
"net"
|
||||
|
||||
"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.
|
||||
@ -41,6 +61,9 @@ type Tower struct {
|
||||
|
||||
// Addresses is a list of possible addresses to reach the tower.
|
||||
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.
|
||||
@ -88,17 +111,56 @@ func (t *Tower) String() string {
|
||||
// Encode writes the Tower to the passed io.Writer. The TowerID is not
|
||||
// serialized, since it acts as the key.
|
||||
func (t *Tower) Encode(w io.Writer) error {
|
||||
return WriteElements(w,
|
||||
err := WriteElements(w,
|
||||
t.IdentityKey,
|
||||
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
|
||||
// decoded from the key.
|
||||
func (t *Tower) Decode(r io.Reader) error {
|
||||
return ReadElements(r,
|
||||
err := ReadElements(r,
|
||||
&t.IdentityKey,
|
||||
&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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user