From 1ae802812cd7a4968eacc9d52e3a4fedf31d1350 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 30 Nov 2023 11:31:04 +0200 Subject: [PATCH 01/18] watchtower: update DeleteCommittedUpdate to delete all This commit updates the DeleteCommittedUpdate DB method to delete all of a given session's committed updates instead of just one at a time. The reason for this is that in an upcoming commit, we will introduce a "Terminal" session state - once we have deleted a committed update for a session it should be considered "Terminal" and there is never a case where we would only want to delete one committed update and not the rest. So we want these two actions (deleting committed updates of a session and setting it's status to terminal) to be atomic. --- watchtower/wtclient/interface.go | 6 +-- watchtower/wtclient/session_queue.go | 9 ++-- watchtower/wtdb/client_db.go | 72 +++++++++++++++++----------- watchtower/wtdb/client_db_test.go | 19 ++------ 4 files changed, 55 insertions(+), 51 deletions(-) diff --git a/watchtower/wtclient/interface.go b/watchtower/wtclient/interface.go index e69155288..c1d71c0e7 100644 --- a/watchtower/wtclient/interface.go +++ b/watchtower/wtclient/interface.go @@ -136,9 +136,9 @@ 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 } // AuthDialer connects to a remote node using an authenticated transport, such diff --git a/watchtower/wtclient/session_queue.go b/watchtower/wtclient/session_queue.go index 1bd7a4ddb..3c1126a89 100644 --- a/watchtower/wtclient/session_queue.go +++ b/watchtower/wtclient/session_queue.go @@ -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()) } } diff --git a/watchtower/wtdb/client_db.go b/watchtower/wtdb/client_db.go index 3a8eb6725..46b3e73f5 100644 --- a/watchtower/wtdb/client_db.go +++ b/watchtower/wtdb/client_db.go @@ -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. @@ -1798,22 +1798,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[:]) @@ -2215,9 +2214,9 @@ 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 { +// 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 +2230,40 @@ 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[:]) + // Delete all the committed updates in one go by deleting the + // session commits bucket. + return sessionBkt.DeleteNestedBucket(cSessionCommits) }, func() {}) } diff --git a/watchtower/wtdb/client_db_test.go b/watchtower/wtdb/client_db_test.go index 9348b845f..475b72837 100644 --- a/watchtower/wtdb/client_db_test.go +++ b/watchtower/wtdb/client_db_test.go @@ -194,12 +194,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) } @@ -660,18 +660,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) } From ffd355c6c4e1b81a4e98016d190ef292d635a717 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 24 Nov 2023 12:03:23 +0200 Subject: [PATCH 02/18] wtdb: add TowerStatus to Tower This is added as a TLV record meaning that all the towers currently on disk that don't have this new field will be seen as Active. --- watchtower/wtdb/codec_test.go | 1 + watchtower/wtdb/tower.go | 66 +++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/watchtower/wtdb/codec_test.go b/watchtower/wtdb/codec_test.go index 228d2a0ec..3f2e59864 100644 --- a/watchtower/wtdb/codec_test.go +++ b/watchtower/wtdb/codec_test.go @@ -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) diff --git a/watchtower/wtdb/tower.go b/watchtower/wtdb/tower.go index ca9dbeb28..9a0e052b8 100644 --- a/watchtower/wtdb/tower.go +++ b/watchtower/wtdb/tower.go @@ -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 } From 0bb1816fff19ecc505f7177e8e9b10cba6d70df2 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 24 Nov 2023 12:14:04 +0200 Subject: [PATCH 03/18] watchtower: add filter function to ListTowers And then only load active towers on client start up. --- watchtower/wtclient/client.go | 5 ++++- watchtower/wtclient/interface.go | 5 +++-- watchtower/wtclient/manager.go | 2 +- watchtower/wtdb/client_db.go | 16 ++++++++++++++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/watchtower/wtclient/client.go b/watchtower/wtclient/client.go index fa76ebd58..a5afb2def 100644 --- a/watchtower/wtclient/client.go +++ b/watchtower/wtclient/client.go @@ -261,7 +261,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 } diff --git a/watchtower/wtclient/interface.go b/watchtower/wtclient/interface.go index c1d71c0e7..2ae0c108a 100644 --- a/watchtower/wtclient/interface.go +++ b/watchtower/wtclient/interface.go @@ -41,8 +41,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 diff --git a/watchtower/wtclient/manager.go b/watchtower/wtclient/manager.go index 70f2a0542..7ffc60c48 100644 --- a/watchtower/wtclient/manager.go +++ b/watchtower/wtclient/manager.go @@ -455,7 +455,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 } diff --git a/watchtower/wtdb/client_db.go b/watchtower/wtdb/client_db.go index 46b3e73f5..e681e6c75 100644 --- a/watchtower/wtdb/client_db.go +++ b/watchtower/wtdb/client_db.go @@ -632,8 +632,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 +652,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() { From 4548e72f79e372d8db12631599b2aae3cd137bf8 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 27 Nov 2023 13:23:18 +0200 Subject: [PATCH 04/18] wtdb: add a DeactivateTower method This new method sets the tower's status to inactive so that it is not loaded at startup as a candidate tower. We also ensure that a tower's status is set to active if the CreateTower is called when the tower already exists. --- watchtower/wtclient/interface.go | 5 ++ watchtower/wtdb/client_db.go | 71 +++++++++++++++++++++++-- watchtower/wtdb/client_db_test.go | 88 +++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 5 deletions(-) diff --git a/watchtower/wtclient/interface.go b/watchtower/wtclient/interface.go index 2ae0c108a..df5ee8257 100644 --- a/watchtower/wtclient/interface.go +++ b/watchtower/wtclient/interface.go @@ -140,6 +140,11 @@ type DB interface { // 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 diff --git a/watchtower/wtdb/client_db.go b/watchtower/wtdb/client_db.go index e681e6c75..5ed986ff6 100644 --- a/watchtower/wtdb/client_db.go +++ b/watchtower/wtdb/client_db.go @@ -387,6 +387,9 @@ 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. @@ -503,14 +506,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,6 +563,13 @@ func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error { ) } + // Otherwise, we mark the tower as inactive. + tower.Status = TowerStatusInactive + err = putTower(towers, tower) + if err != nil { + return err + } + // 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. @@ -579,6 +589,57 @@ func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error { }, 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 diff --git a/watchtower/wtdb/client_db_test.go b/watchtower/wtdb/client_db_test.go index 475b72837..464a6a7d9 100644 --- a/watchtower/wtdb/client_db_test.go +++ b/watchtower/wtdb/client_db_test.go @@ -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) { @@ -547,6 +567,70 @@ func testRemoveTower(h *clientDBHarness) { }, nil) } +// 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: &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 and its session status + // to inactive. + h.removeTower(tower.IdentityKey, nil, true, nil) + assertTowerStatus(wtdb.TowerStatusInactive) + assertSessionStatus(wtdb.CSessionInactive) + + // 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 // associated sweep pkscript. func testChanSummaries(h *clientDBHarness) { @@ -1142,6 +1226,10 @@ func TestClientDB(t *testing.T) { name: "max commitment heights", run: testMaxCommitmentHeights, }, + { + name: "test tower status change", + run: testTowerStatusChange, + }, } for _, database := range dbs { From beb9b2eeb872363574a0277a374ca4ec6529a951 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 5 Dec 2023 13:01:18 +0200 Subject: [PATCH 05/18] wtclient: add DeactivateTower method This commit adds the DeactiateTower method to the wtclient.ClientManager interface along with its implementation. A test is also added for the new method. --- watchtower/wtclient/client.go | 127 ++++++++++++++++++++++++--- watchtower/wtclient/client_test.go | 82 +++++++++++++++++ watchtower/wtclient/manager.go | 57 +++++++++++- watchtower/wtclient/session_queue.go | 4 +- 4 files changed, 253 insertions(+), 17 deletions(-) diff --git a/watchtower/wtclient/client.go b/watchtower/wtclient/client.go index a5afb2def..04faa1231 100644 --- a/watchtower/wtclient/client.go +++ b/watchtower/wtclient/client.go @@ -132,6 +132,22 @@ 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 +} + // clientCfg holds the configuration values required by a client. type clientCfg struct { *Config @@ -165,8 +181,9 @@ type client struct { statTicker *time.Ticker stats *clientStats - newTowers chan *newTowerMsg - staleTowers chan *staleTowerMsg + newTowers chan *newTowerMsg + staleTowers chan *staleTowerMsg + deactivateTowers chan *deactivateTowerMsg wg sync.WaitGroup quit chan struct{} @@ -192,15 +209,16 @@ 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), + quit: make(chan struct{}), } candidateTowers := newTowerListIterator() @@ -514,8 +532,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 @@ -694,6 +712,12 @@ 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) + case <-c.quit: return } @@ -779,6 +803,10 @@ 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) + case <-c.quit: return } @@ -1046,6 +1074,77 @@ func (c *client) initActiveQueue(s *ClientSession, return sq } +// 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 @@ -1152,7 +1251,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) diff --git a/watchtower/wtclient/client_test.go b/watchtower/wtclient/client_test.go index 81674f0c0..0e5352f88 100644 --- a/watchtower/wtclient/client_test.go +++ b/watchtower/wtclient/client_test.go @@ -2688,6 +2688,88 @@ 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) + }, + }, } // TestClient executes the client test suite, asserting the ability to backup diff --git a/watchtower/wtclient/manager.go b/watchtower/wtclient/manager.go index 7ffc60c48..17a351c15 100644 --- a/watchtower/wtclient/manager.go +++ b/watchtower/wtclient/manager.go @@ -38,6 +38,11 @@ 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 + // Stats returns the in-memory statistics of the client since startup. Stats() ClientStats @@ -431,6 +436,56 @@ func (m *Manager) RemoveTower(key *btcec.PublicKey, addr net.Addr) error { return 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 (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 { @@ -850,7 +905,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 "+ diff --git a/watchtower/wtclient/session_queue.go b/watchtower/wtclient/session_queue.go index 3c1126a89..786410515 100644 --- a/watchtower/wtclient/session_queue.go +++ b/watchtower/wtclient/session_queue.go @@ -765,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() @@ -776,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. From 26432359ad8b2e108fc8d9aa6aa292353c4236bd Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 28 Nov 2023 18:21:44 +0200 Subject: [PATCH 06/18] lnrpc+lncli: add DeactivateTower to rpc --- cmd/lncli/wtclient.go | 39 ++ lnrpc/wtclientrpc/watchtowerclient.pb.json.go | 25 + lnrpc/wtclientrpc/wtclient.go | 30 + lnrpc/wtclientrpc/wtclient.pb.go | 548 +++++++++++------- lnrpc/wtclientrpc/wtclient.pb.gw.go | 99 ++++ lnrpc/wtclientrpc/wtclient.proto | 18 + lnrpc/wtclientrpc/wtclient.swagger.json | 42 ++ lnrpc/wtclientrpc/wtclient.yaml | 2 + lnrpc/wtclientrpc/wtclient_grpc.pb.go | 44 ++ 9 files changed, 641 insertions(+), 206 deletions(-) diff --git a/cmd/lncli/wtclient.go b/cmd/lncli/wtclient.go index 068fc3005..488e828b9 100644 --- a/cmd/lncli/wtclient.go +++ b/cmd/lncli/wtclient.go @@ -20,6 +20,7 @@ func wtclientCommands() []cli.Command { Subcommands: []cli.Command{ addTowerCommand, removeTowerCommand, + deactivateTowerCommand, listTowersCommand, getTowerCommand, statsCommand, @@ -84,6 +85,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 " + diff --git a/lnrpc/wtclientrpc/watchtowerclient.pb.json.go b/lnrpc/wtclientrpc/watchtowerclient.pb.json.go index aed077de5..338c65331 100644 --- a/lnrpc/wtclientrpc/watchtowerclient.pb.json.go +++ b/lnrpc/wtclientrpc/watchtowerclient.pb.json.go @@ -71,6 +71,31 @@ 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.ListTowers"] = func(ctx context.Context, conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { diff --git a/lnrpc/wtclientrpc/wtclient.go b/lnrpc/wtclientrpc/wtclient.go index 4a68e22a0..045a01b57 100644 --- a/lnrpc/wtclientrpc/wtclient.go +++ b/lnrpc/wtclientrpc/wtclient.go @@ -44,6 +44,10 @@ var ( Entity: "offchain", Action: "write", }}, + "/wtclientrpc.WatchtowerClient/DeactivateTower": {{ + Entity: "offchain", + Action: "write", + }}, "/wtclientrpc.WatchtowerClient/ListTowers": {{ Entity: "offchain", Action: "read", @@ -251,6 +255,32 @@ 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 +} + // ListTowers returns the list of watchtowers registered with the client. func (c *WatchtowerClient) ListTowers(ctx context.Context, req *ListTowersRequest) (*ListTowersResponse, error) { diff --git a/lnrpc/wtclientrpc/wtclient.pb.go b/lnrpc/wtclientrpc/wtclient.pb.go index 063b5f534..32f124fee 100644 --- a/lnrpc/wtclientrpc/wtclient.pb.go +++ b/lnrpc/wtclientrpc/wtclient.pb.go @@ -264,6 +264,102 @@ 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 GetTowerInfoRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -281,7 +377,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[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -294,7 +390,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[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -307,7 +403,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{6} } func (x *GetTowerInfoRequest) GetPubkey() []byte { @@ -358,7 +454,7 @@ type TowerSession struct { func (x *TowerSession) Reset() { *x = TowerSession{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[5] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -371,7 +467,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[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -384,7 +480,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{7} } func (x *TowerSession) GetNumBackups() uint32 { @@ -457,7 +553,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[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -470,7 +566,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[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -483,7 +579,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{8} } func (x *Tower) GetPubkey() []byte { @@ -549,7 +645,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[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -562,7 +658,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[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -575,7 +671,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{9} } func (x *TowerSessionInfo) GetActiveSessionCandidate() bool { @@ -621,7 +717,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[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -634,7 +730,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[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -647,7 +743,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{10} } func (x *ListTowersRequest) GetIncludeSessions() bool { @@ -676,7 +772,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[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -689,7 +785,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[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -702,7 +798,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{11} } func (x *ListTowersResponse) GetTowers() []*Tower { @@ -721,7 +817,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[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -734,7 +830,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[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -747,7 +843,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{12} } type StatsResponse struct { @@ -773,7 +869,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[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -786,7 +882,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[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -799,7 +895,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{13} } func (x *StatsResponse) GetNumBackups() uint32 { @@ -849,7 +945,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[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -862,7 +958,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[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -875,7 +971,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{14} } func (x *PolicyRequest) GetPolicyType() PolicyType { @@ -907,7 +1003,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[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -920,7 +1016,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[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -933,7 +1029,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{15} } func (x *PolicyResponse) GetMaxUpdates() uint32 { @@ -975,141 +1071,153 @@ 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, + 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, 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, 0xa3, 0x04, 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, 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, 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, + 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 +1233,49 @@ 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, 16) 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 + (*GetTowerInfoRequest)(nil), // 7: wtclientrpc.GetTowerInfoRequest + (*TowerSession)(nil), // 8: wtclientrpc.TowerSession + (*Tower)(nil), // 9: wtclientrpc.Tower + (*TowerSessionInfo)(nil), // 10: wtclientrpc.TowerSessionInfo + (*ListTowersRequest)(nil), // 11: wtclientrpc.ListTowersRequest + (*ListTowersResponse)(nil), // 12: wtclientrpc.ListTowersResponse + (*StatsRequest)(nil), // 13: wtclientrpc.StatsRequest + (*StatsResponse)(nil), // 14: wtclientrpc.StatsResponse + (*PolicyRequest)(nil), // 15: wtclientrpc.PolicyRequest + (*PolicyResponse)(nil), // 16: 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 + 8, // 0: wtclientrpc.Tower.sessions:type_name -> wtclientrpc.TowerSession + 10, // 1: wtclientrpc.Tower.session_info:type_name -> wtclientrpc.TowerSessionInfo + 8, // 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 + 9, // 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 + 11, // 9: wtclientrpc.WatchtowerClient.ListTowers:input_type -> wtclientrpc.ListTowersRequest + 7, // 10: wtclientrpc.WatchtowerClient.GetTowerInfo:input_type -> wtclientrpc.GetTowerInfoRequest + 13, // 11: wtclientrpc.WatchtowerClient.Stats:input_type -> wtclientrpc.StatsRequest + 15, // 12: wtclientrpc.WatchtowerClient.Policy:input_type -> wtclientrpc.PolicyRequest + 2, // 13: wtclientrpc.WatchtowerClient.AddTower:output_type -> wtclientrpc.AddTowerResponse + 4, // 14: wtclientrpc.WatchtowerClient.RemoveTower:output_type -> wtclientrpc.RemoveTowerResponse + 6, // 15: wtclientrpc.WatchtowerClient.DeactivateTower:output_type -> wtclientrpc.DeactivateTowerResponse + 12, // 16: wtclientrpc.WatchtowerClient.ListTowers:output_type -> wtclientrpc.ListTowersResponse + 9, // 17: wtclientrpc.WatchtowerClient.GetTowerInfo:output_type -> wtclientrpc.Tower + 14, // 18: wtclientrpc.WatchtowerClient.Stats:output_type -> wtclientrpc.StatsResponse + 16, // 19: wtclientrpc.WatchtowerClient.Policy:output_type -> wtclientrpc.PolicyResponse + 13, // [13:20] is the sub-list for method output_type + 6, // [6:13] 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 +1336,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 +1348,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 +1360,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.(*GetTowerInfoRequest); i { case 0: return &v.state case 1: @@ -1260,7 +1372,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.(*TowerSession); i { case 0: return &v.state case 1: @@ -1272,7 +1384,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.(*Tower); i { case 0: return &v.state case 1: @@ -1284,7 +1396,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.(*TowerSessionInfo); i { case 0: return &v.state case 1: @@ -1296,7 +1408,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.(*ListTowersRequest); i { case 0: return &v.state case 1: @@ -1308,7 +1420,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.(*ListTowersResponse); i { case 0: return &v.state case 1: @@ -1320,7 +1432,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.(*StatsRequest); i { case 0: return &v.state case 1: @@ -1332,6 +1444,30 @@ func file_wtclientrpc_wtclient_proto_init() { } } file_wtclientrpc_wtclient_proto_msgTypes[13].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[14].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[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PolicyResponse); i { case 0: return &v.state @@ -1350,7 +1486,7 @@ func file_wtclientrpc_wtclient_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_wtclientrpc_wtclient_proto_rawDesc, NumEnums: 1, - NumMessages: 14, + NumMessages: 16, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/wtclientrpc/wtclient.pb.gw.go b/lnrpc/wtclientrpc/wtclient.pb.gw.go index 331976431..f3d3e70de 100644 --- a/lnrpc/wtclientrpc/wtclient.pb.gw.go +++ b/lnrpc/wtclientrpc/wtclient.pb.gw.go @@ -135,6 +135,58 @@ 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 + +} + var ( filter_WatchtowerClient_ListTowers_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -347,6 +399,29 @@ 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("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 +595,26 @@ 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("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 +703,8 @@ 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_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 +719,8 @@ var ( forward_WatchtowerClient_RemoveTower_0 = runtime.ForwardResponseMessage + forward_WatchtowerClient_DeactivateTower_0 = runtime.ForwardResponseMessage + forward_WatchtowerClient_ListTowers_0 = runtime.ForwardResponseMessage forward_WatchtowerClient_GetTowerInfo_0 = runtime.ForwardResponseMessage diff --git a/lnrpc/wtclientrpc/wtclient.proto b/lnrpc/wtclientrpc/wtclient.proto index b236e0158..b0bf53cf0 100644 --- a/lnrpc/wtclientrpc/wtclient.proto +++ b/lnrpc/wtclientrpc/wtclient.proto @@ -41,6 +41,14 @@ 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 towers` ListTowers returns the list of watchtowers registered with the client. */ @@ -88,6 +96,16 @@ 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 GetTowerInfoRequest { // The identifying public key of the watchtower to retrieve information for. bytes pubkey = 1; diff --git a/lnrpc/wtclientrpc/wtclient.swagger.json b/lnrpc/wtclientrpc/wtclient.swagger.json index 36dc87981..bef43e944 100644 --- a/lnrpc/wtclientrpc/wtclient.swagger.json +++ b/lnrpc/wtclientrpc/wtclient.swagger.json @@ -194,6 +194,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 +316,15 @@ "wtclientrpcAddTowerResponse": { "type": "object" }, + "wtclientrpcDeactivateTowerResponse": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "A string describing the action that took place." + } + } + }, "wtclientrpcListTowersResponse": { "type": "object", "properties": { diff --git a/lnrpc/wtclientrpc/wtclient.yaml b/lnrpc/wtclientrpc/wtclient.yaml index 011a43ba1..6f02969dd 100644 --- a/lnrpc/wtclientrpc/wtclient.yaml +++ b/lnrpc/wtclientrpc/wtclient.yaml @@ -8,6 +8,8 @@ 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.ListTowers get: "/v2/watchtower/client" - selector: wtclientrpc.WatchtowerClient.GetTowerInfo diff --git a/lnrpc/wtclientrpc/wtclient_grpc.pb.go b/lnrpc/wtclientrpc/wtclient_grpc.pb.go index cbae01fee..86347ebf3 100644 --- a/lnrpc/wtclientrpc/wtclient_grpc.pb.go +++ b/lnrpc/wtclientrpc/wtclient_grpc.pb.go @@ -30,6 +30,11 @@ 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 towers` // ListTowers returns the list of watchtowers registered with the client. ListTowers(ctx context.Context, in *ListTowersRequest, opts ...grpc.CallOption) (*ListTowersResponse, error) @@ -70,6 +75,15 @@ 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) 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 +136,11 @@ 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 towers` // ListTowers returns the list of watchtowers registered with the client. ListTowers(context.Context, *ListTowersRequest) (*ListTowersResponse, error) @@ -147,6 +166,9 @@ 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) ListTowers(context.Context, *ListTowersRequest) (*ListTowersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListTowers not implemented") } @@ -208,6 +230,24 @@ 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_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 +335,10 @@ var WatchtowerClient_ServiceDesc = grpc.ServiceDesc{ MethodName: "RemoveTower", Handler: _WatchtowerClient_RemoveTower_Handler, }, + { + MethodName: "DeactivateTower", + Handler: _WatchtowerClient_DeactivateTower_Handler, + }, { MethodName: "ListTowers", Handler: _WatchtowerClient_ListTowers_Handler, From 5cb8c8df7ef2a000dde7d2776bd499d75abc9eae Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 28 Nov 2023 18:28:52 +0200 Subject: [PATCH 07/18] wtdb: rename CSessionInactive to CSessionTerminal Since we will now change this to mean that the session should not ever be activated again. --- watchtower/wtdb/client_db.go | 2 +- watchtower/wtdb/client_db_test.go | 6 +++--- watchtower/wtdb/client_session.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/watchtower/wtdb/client_db.go b/watchtower/wtdb/client_db.go index 5ed986ff6..6ebfd273b 100644 --- a/watchtower/wtdb/client_db.go +++ b/watchtower/wtdb/client_db.go @@ -578,7 +578,7 @@ func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error { return ErrTowerUnackedUpdates } err := markSessionStatus( - sessions, session, CSessionInactive, + sessions, session, CSessionTerminal, ) if err != nil { return err diff --git a/watchtower/wtdb/client_db_test.go b/watchtower/wtdb/client_db_test.go index 464a6a7d9..4619695d0 100644 --- a/watchtower/wtdb/client_db_test.go +++ b/watchtower/wtdb/client_db_test.go @@ -146,10 +146,10 @@ func (h *clientDBHarness) removeTower(pubKey *btcec.PublicKey, addr net.Addr, } for _, session := range h.listSessions(&tower.ID) { - require.Equal(h.t, wtdb.CSessionInactive, + require.Equal(h.t, wtdb.CSessionTerminal, session.Status, "expected status for session "+ "%v to be %v, got %v", session.ID, - wtdb.CSessionInactive, session.Status) + wtdb.CSessionTerminal, session.Status) } } } @@ -617,7 +617,7 @@ func testTowerStatusChange(h *clientDBHarness) { // to inactive. h.removeTower(tower.IdentityKey, nil, true, nil) assertTowerStatus(wtdb.TowerStatusInactive) - assertSessionStatus(wtdb.CSessionInactive) + assertSessionStatus(wtdb.CSessionTerminal) // Re-adding the tower in some way should re-active it and its session. h.createTower(towerAddr, nil) diff --git a/watchtower/wtdb/client_session.go b/watchtower/wtdb/client_session.go index e44331094..6b2890333 100644 --- a/watchtower/wtdb/client_session.go +++ b/watchtower/wtdb/client_session.go @@ -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 From cbf08940ca2ab2b0c8a7a1c4dd75206400d9d5d8 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 27 Nov 2023 16:30:53 +0200 Subject: [PATCH 08/18] wtdb: let DeleteCommitedUpdates move session to terminal In this commit, we adjust the DeleteCommitmentUpdate method so that it marks a session as Terminal (if there are updates to delete) since once we have deleted a commitment update from a session - the session is no longer useable. --- watchtower/wtdb/client_db.go | 43 ++++++++++++++++++++++++------- watchtower/wtdb/client_db_test.go | 38 +++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/watchtower/wtdb/client_db.go b/watchtower/wtdb/client_db.go index 6ebfd273b..850ef5d17 100644 --- a/watchtower/wtdb/client_db.go +++ b/watchtower/wtdb/client_db.go @@ -1860,9 +1860,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) { @@ -1893,10 +1894,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 } @@ -1916,11 +1921,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) } @@ -2068,7 +2078,6 @@ func (c *ClientDB) CommitUpdate(id *SessionID, lastApplied = session.TowerLastApplied return nil - }, func() { lastApplied = 0 }) @@ -2334,6 +2343,20 @@ func (c *ClientDB) DeleteCommittedUpdates(id *SessionID) error { return err } + 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) diff --git a/watchtower/wtdb/client_db_test.go b/watchtower/wtdb/client_db_test.go index 4619695d0..489c6c016 100644 --- a/watchtower/wtdb/client_db_test.go +++ b/watchtower/wtdb/client_db_test.go @@ -1002,6 +1002,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. From 41582c0b8bfc04337be33a3dad50c0590f43c10c Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 27 Nov 2023 16:43:32 +0200 Subject: [PATCH 09/18] wtdb: create and remove tower should no longer change session status --- watchtower/wtclient/interface.go | 7 ++--- watchtower/wtdb/client_db.go | 51 +++++-------------------------- watchtower/wtdb/client_db_test.go | 26 +++++----------- 3 files changed, 18 insertions(+), 66 deletions(-) diff --git a/watchtower/wtclient/interface.go b/watchtower/wtclient/interface.go index df5ee8257..ffc6ea694 100644 --- a/watchtower/wtclient/interface.go +++ b/watchtower/wtclient/interface.go @@ -26,10 +26,9 @@ 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 diff --git a/watchtower/wtdb/client_db.go b/watchtower/wtdb/client_db.go index 850ef5d17..a88cd49c4 100644 --- a/watchtower/wtdb/client_db.go +++ b/watchtower/wtdb/client_db.go @@ -394,37 +394,6 @@ func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error) { // 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 @@ -435,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() @@ -468,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 { @@ -570,19 +540,12 @@ func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error { return err } - // 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. + // 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, CSessionTerminal, - ) - if err != nil { - return err - } } return nil diff --git a/watchtower/wtdb/client_db_test.go b/watchtower/wtdb/client_db_test.go index 489c6c016..01ea0d8d8 100644 --- a/watchtower/wtdb/client_db_test.go +++ b/watchtower/wtdb/client_db_test.go @@ -145,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.CSessionTerminal, - session.Status, "expected status for session "+ - "%v to be %v, got %v", session.ID, - wtdb.CSessionTerminal, session.Status) - } + require.EqualValues( + h.t, wtdb.TowerStatusInactive, tower.Status, + ) } } @@ -551,20 +548,13 @@ 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{ - IdentityKey: pk, - Address: addr1, - }, nil) } // testTowerStatusChange tests that the Tower status is updated accordingly @@ -613,11 +603,11 @@ func testTowerStatusChange(h *clientDBHarness) { assertTowerStatus(wtdb.TowerStatusActive) assertSessionStatus(wtdb.CSessionActive) - // Removing the tower should change its status and its session status - // to inactive. + // 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.CSessionTerminal) + assertSessionStatus(wtdb.CSessionActive) // Re-adding the tower in some way should re-active it and its session. h.createTower(towerAddr, nil) From 05c03400a97c5b81d406b84bcaeb51bb0c88e095 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 29 Nov 2023 09:14:30 +0200 Subject: [PATCH 10/18] wtdb: add TerminateSession method --- watchtower/wtclient/interface.go | 4 ++ watchtower/wtdb/client_db.go | 47 ++++++++++++++++++++ watchtower/wtdb/client_db_test.go | 71 +++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/watchtower/wtclient/interface.go b/watchtower/wtclient/interface.go index ffc6ea694..1051da0d5 100644 --- a/watchtower/wtclient/interface.go +++ b/watchtower/wtclient/interface.go @@ -33,6 +33,10 @@ type DB interface { // 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) diff --git a/watchtower/wtdb/client_db.go b/watchtower/wtdb/client_db.go index a88cd49c4..9731dfef8 100644 --- a/watchtower/wtdb/client_db.go +++ b/watchtower/wtdb/client_db.go @@ -2259,6 +2259,53 @@ func (c *ClientDB) GetDBQueue(namespace []byte) Queue[*BackupID] { ) } +// 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 { diff --git a/watchtower/wtdb/client_db_test.go b/watchtower/wtdb/client_db_test.go index 01ea0d8d8..dd6479b74 100644 --- a/watchtower/wtdb/client_db_test.go +++ b/watchtower/wtdb/client_db_test.go @@ -173,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() @@ -557,6 +575,55 @@ func testRemoveTower(h *clientDBHarness) { h.removeTower(pk, nil, true, nil) } +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) { @@ -1258,6 +1325,10 @@ func TestClientDB(t *testing.T) { name: "test tower status change", run: testTowerStatusChange, }, + { + name: "terminate session", + run: testTerminateSession, + }, } for _, database := range dbs { From 24702ede14a2aa2f5066b61c9e242b5403c03b92 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 5 Dec 2023 13:12:38 +0200 Subject: [PATCH 11/18] wtclient: add TerminateSession method --- watchtower/wtclient/client.go | 96 ++++++++++++++++++--- watchtower/wtclient/client_test.go | 133 +++++++++++++++++++++++++++-- watchtower/wtclient/manager.go | 21 +++++ 3 files changed, 231 insertions(+), 19 deletions(-) diff --git a/watchtower/wtclient/client.go b/watchtower/wtclient/client.go index 04faa1231..e8fec5d4a 100644 --- a/watchtower/wtclient/client.go +++ b/watchtower/wtclient/client.go @@ -148,6 +148,19 @@ type deactivateTowerMsg struct { 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 @@ -181,9 +194,10 @@ type client struct { statTicker *time.Ticker stats *clientStats - newTowers chan *newTowerMsg - staleTowers chan *staleTowerMsg - deactivateTowers chan *deactivateTowerMsg + newTowers chan *newTowerMsg + staleTowers chan *staleTowerMsg + deactivateTowers chan *deactivateTowerMsg + terminateSessions chan *terminateSessMsg wg sync.WaitGroup quit chan struct{} @@ -209,16 +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), - deactivateTowers: make(chan *deactivateTowerMsg), - 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() @@ -718,6 +733,10 @@ func (c *client) backupDispatcher() { 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 } @@ -807,6 +826,10 @@ func (c *client) backupDispatcher() { 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 } @@ -1074,6 +1097,53 @@ 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 diff --git a/watchtower/wtclient/client_test.go b/watchtower/wtclient/client_test.go index 0e5352f88..38d9acd9f 100644 --- a/watchtower/wtclient/client_test.go +++ b/watchtower/wtclient/client_test.go @@ -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 } @@ -2770,6 +2774,123 @@ var clientTests = []clientTest{ 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 diff --git a/watchtower/wtclient/manager.go b/watchtower/wtclient/manager.go index 17a351c15..f1cae979f 100644 --- a/watchtower/wtclient/manager.go +++ b/watchtower/wtclient/manager.go @@ -43,6 +43,10 @@ type ClientManager interface { // 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 @@ -436,6 +440,23 @@ 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. From 5d16491c13626c5c44c96fe70a9c82b14077a3cd Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 29 Nov 2023 11:06:07 +0200 Subject: [PATCH 12/18] lnrpc+lncli: add TerminateSession command --- cmd/lncli/wtclient.go | 48 +- lnrpc/wtclientrpc/watchtowerclient.pb.json.go | 25 + lnrpc/wtclientrpc/wtclient.go | 31 + lnrpc/wtclientrpc/wtclient.pb.go | 567 +++++++++++------- lnrpc/wtclientrpc/wtclient.pb.gw.go | 99 +++ lnrpc/wtclientrpc/wtclient.proto | 17 + lnrpc/wtclientrpc/wtclient.swagger.json | 42 ++ lnrpc/wtclientrpc/wtclient.yaml | 2 + lnrpc/wtclientrpc/wtclient_grpc.pb.go | 42 ++ 9 files changed, 657 insertions(+), 216 deletions(-) diff --git a/cmd/lncli/wtclient.go b/cmd/lncli/wtclient.go index 488e828b9..ac844ef45 100644 --- a/cmd/lncli/wtclient.go +++ b/cmd/lncli/wtclient.go @@ -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{ { @@ -25,6 +26,7 @@ func wtclientCommands() []cli.Command { getTowerCommand, statsCommand, policyCommand, + sessionCommands, }, }, } @@ -372,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 +} diff --git a/lnrpc/wtclientrpc/watchtowerclient.pb.json.go b/lnrpc/wtclientrpc/watchtowerclient.pb.json.go index 338c65331..f9773c5d9 100644 --- a/lnrpc/wtclientrpc/watchtowerclient.pb.json.go +++ b/lnrpc/wtclientrpc/watchtowerclient.pb.json.go @@ -96,6 +96,31 @@ func RegisterWatchtowerClientJSONCallbacks(registry map[string]func(ctx context. 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)) { diff --git a/lnrpc/wtclientrpc/wtclient.go b/lnrpc/wtclientrpc/wtclient.go index 045a01b57..8e9de23b7 100644 --- a/lnrpc/wtclientrpc/wtclient.go +++ b/lnrpc/wtclientrpc/wtclient.go @@ -48,6 +48,10 @@ var ( Entity: "offchain", Action: "write", }}, + "/wtclientrpc.WatchtowerClient/TerminateSession": {{ + Entity: "offchain", + Action: "write", + }}, "/wtclientrpc.WatchtowerClient/ListTowers": {{ Entity: "offchain", Action: "read", @@ -281,6 +285,33 @@ func (c *WatchtowerClient) DeactivateTower(_ context.Context, }, 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) { diff --git a/lnrpc/wtclientrpc/wtclient.pb.go b/lnrpc/wtclientrpc/wtclient.pb.go index 32f124fee..e9bd28610 100644 --- a/lnrpc/wtclientrpc/wtclient.pb.go +++ b/lnrpc/wtclientrpc/wtclient.pb.go @@ -360,6 +360,102 @@ func (x *DeactivateTowerResponse) GetStatus() string { 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 @@ -377,7 +473,7 @@ type GetTowerInfoRequest struct { func (x *GetTowerInfoRequest) Reset() { *x = GetTowerInfoRequest{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[6] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -390,7 +486,7 @@ func (x *GetTowerInfoRequest) String() string { func (*GetTowerInfoRequest) ProtoMessage() {} func (x *GetTowerInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[6] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -403,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{6} + return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{8} } func (x *GetTowerInfoRequest) GetPubkey() []byte { @@ -454,7 +550,7 @@ type TowerSession struct { func (x *TowerSession) Reset() { *x = TowerSession{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[7] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -467,7 +563,7 @@ func (x *TowerSession) String() string { func (*TowerSession) ProtoMessage() {} func (x *TowerSession) ProtoReflect() protoreflect.Message { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[7] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -480,7 +576,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{7} + return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{9} } func (x *TowerSession) GetNumBackups() uint32 { @@ -553,7 +649,7 @@ type Tower struct { func (x *Tower) Reset() { *x = Tower{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -566,7 +662,7 @@ func (x *Tower) String() string { func (*Tower) ProtoMessage() {} func (x *Tower) ProtoReflect() protoreflect.Message { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -579,7 +675,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{8} + return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{10} } func (x *Tower) GetPubkey() []byte { @@ -645,7 +741,7 @@ type TowerSessionInfo struct { func (x *TowerSessionInfo) Reset() { *x = TowerSessionInfo{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -658,7 +754,7 @@ func (x *TowerSessionInfo) String() string { func (*TowerSessionInfo) ProtoMessage() {} func (x *TowerSessionInfo) ProtoReflect() protoreflect.Message { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -671,7 +767,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{9} + return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{11} } func (x *TowerSessionInfo) GetActiveSessionCandidate() bool { @@ -717,7 +813,7 @@ type ListTowersRequest struct { func (x *ListTowersRequest) Reset() { *x = ListTowersRequest{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -730,7 +826,7 @@ func (x *ListTowersRequest) String() string { func (*ListTowersRequest) ProtoMessage() {} func (x *ListTowersRequest) ProtoReflect() protoreflect.Message { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -743,7 +839,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{10} + return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{12} } func (x *ListTowersRequest) GetIncludeSessions() bool { @@ -772,7 +868,7 @@ type ListTowersResponse struct { func (x *ListTowersResponse) Reset() { *x = ListTowersResponse{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -785,7 +881,7 @@ func (x *ListTowersResponse) String() string { func (*ListTowersResponse) ProtoMessage() {} func (x *ListTowersResponse) ProtoReflect() protoreflect.Message { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -798,7 +894,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{11} + return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{13} } func (x *ListTowersResponse) GetTowers() []*Tower { @@ -817,7 +913,7 @@ type StatsRequest struct { func (x *StatsRequest) Reset() { *x = StatsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -830,7 +926,7 @@ func (x *StatsRequest) String() string { func (*StatsRequest) ProtoMessage() {} func (x *StatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -843,7 +939,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{12} + return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{14} } type StatsResponse struct { @@ -869,7 +965,7 @@ type StatsResponse struct { func (x *StatsResponse) Reset() { *x = StatsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[13] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -882,7 +978,7 @@ func (x *StatsResponse) String() string { func (*StatsResponse) ProtoMessage() {} func (x *StatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[13] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -895,7 +991,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{13} + return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{15} } func (x *StatsResponse) GetNumBackups() uint32 { @@ -945,7 +1041,7 @@ type PolicyRequest struct { func (x *PolicyRequest) Reset() { *x = PolicyRequest{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[14] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -958,7 +1054,7 @@ func (x *PolicyRequest) String() string { func (*PolicyRequest) ProtoMessage() {} func (x *PolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[14] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -971,7 +1067,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{14} + return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{16} } func (x *PolicyRequest) GetPolicyType() PolicyType { @@ -1003,7 +1099,7 @@ type PolicyResponse struct { func (x *PolicyResponse) Reset() { *x = PolicyResponse{} if protoimpl.UnsafeEnabled { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[15] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1016,7 +1112,7 @@ func (x *PolicyResponse) String() string { func (*PolicyResponse) ProtoMessage() {} func (x *PolicyResponse) ProtoReflect() protoreflect.Message { - mi := &file_wtclientrpc_wtclient_proto_msgTypes[15] + mi := &file_wtclientrpc_wtclient_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1029,7 +1125,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{15} + return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{17} } func (x *PolicyResponse) GetMaxUpdates() uint32 { @@ -1077,147 +1173,160 @@ var file_wtclientrpc_wtclient_proto_rawDesc = []byte{ 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, 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, + 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, 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, 0xa3, 0x04, 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, 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, + 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, 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 ( @@ -1233,49 +1342,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, 16) +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 - (*DeactivateTowerRequest)(nil), // 5: wtclientrpc.DeactivateTowerRequest - (*DeactivateTowerResponse)(nil), // 6: wtclientrpc.DeactivateTowerResponse - (*GetTowerInfoRequest)(nil), // 7: wtclientrpc.GetTowerInfoRequest - (*TowerSession)(nil), // 8: wtclientrpc.TowerSession - (*Tower)(nil), // 9: wtclientrpc.Tower - (*TowerSessionInfo)(nil), // 10: wtclientrpc.TowerSessionInfo - (*ListTowersRequest)(nil), // 11: wtclientrpc.ListTowersRequest - (*ListTowersResponse)(nil), // 12: wtclientrpc.ListTowersResponse - (*StatsRequest)(nil), // 13: wtclientrpc.StatsRequest - (*StatsResponse)(nil), // 14: wtclientrpc.StatsResponse - (*PolicyRequest)(nil), // 15: wtclientrpc.PolicyRequest - (*PolicyResponse)(nil), // 16: 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{ - 8, // 0: wtclientrpc.Tower.sessions:type_name -> wtclientrpc.TowerSession - 10, // 1: wtclientrpc.Tower.session_info:type_name -> wtclientrpc.TowerSessionInfo - 8, // 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 - 9, // 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 5, // 8: wtclientrpc.WatchtowerClient.DeactivateTower:input_type -> wtclientrpc.DeactivateTowerRequest - 11, // 9: wtclientrpc.WatchtowerClient.ListTowers:input_type -> wtclientrpc.ListTowersRequest - 7, // 10: wtclientrpc.WatchtowerClient.GetTowerInfo:input_type -> wtclientrpc.GetTowerInfoRequest - 13, // 11: wtclientrpc.WatchtowerClient.Stats:input_type -> wtclientrpc.StatsRequest - 15, // 12: wtclientrpc.WatchtowerClient.Policy:input_type -> wtclientrpc.PolicyRequest - 2, // 13: wtclientrpc.WatchtowerClient.AddTower:output_type -> wtclientrpc.AddTowerResponse - 4, // 14: wtclientrpc.WatchtowerClient.RemoveTower:output_type -> wtclientrpc.RemoveTowerResponse - 6, // 15: wtclientrpc.WatchtowerClient.DeactivateTower:output_type -> wtclientrpc.DeactivateTowerResponse - 12, // 16: wtclientrpc.WatchtowerClient.ListTowers:output_type -> wtclientrpc.ListTowersResponse - 9, // 17: wtclientrpc.WatchtowerClient.GetTowerInfo:output_type -> wtclientrpc.Tower - 14, // 18: wtclientrpc.WatchtowerClient.Stats:output_type -> wtclientrpc.StatsResponse - 16, // 19: wtclientrpc.WatchtowerClient.Policy:output_type -> wtclientrpc.PolicyResponse - 13, // [13:20] is the sub-list for method output_type - 6, // [6:13] is the sub-list for method input_type + 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 @@ -1360,7 +1473,7 @@ func file_wtclientrpc_wtclient_proto_init() { } } file_wtclientrpc_wtclient_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetTowerInfoRequest); i { + switch v := v.(*TerminateSessionRequest); i { case 0: return &v.state case 1: @@ -1372,7 +1485,7 @@ func file_wtclientrpc_wtclient_proto_init() { } } file_wtclientrpc_wtclient_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TowerSession); i { + switch v := v.(*TerminateSessionResponse); i { case 0: return &v.state case 1: @@ -1384,7 +1497,7 @@ func file_wtclientrpc_wtclient_proto_init() { } } file_wtclientrpc_wtclient_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Tower); i { + switch v := v.(*GetTowerInfoRequest); i { case 0: return &v.state case 1: @@ -1396,7 +1509,7 @@ func file_wtclientrpc_wtclient_proto_init() { } } file_wtclientrpc_wtclient_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TowerSessionInfo); i { + switch v := v.(*TowerSession); i { case 0: return &v.state case 1: @@ -1408,7 +1521,7 @@ func file_wtclientrpc_wtclient_proto_init() { } } file_wtclientrpc_wtclient_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListTowersRequest); i { + switch v := v.(*Tower); i { case 0: return &v.state case 1: @@ -1420,7 +1533,7 @@ func file_wtclientrpc_wtclient_proto_init() { } } file_wtclientrpc_wtclient_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListTowersResponse); i { + switch v := v.(*TowerSessionInfo); i { case 0: return &v.state case 1: @@ -1432,7 +1545,7 @@ func file_wtclientrpc_wtclient_proto_init() { } } file_wtclientrpc_wtclient_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StatsRequest); i { + switch v := v.(*ListTowersRequest); i { case 0: return &v.state case 1: @@ -1444,7 +1557,7 @@ func file_wtclientrpc_wtclient_proto_init() { } } file_wtclientrpc_wtclient_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StatsResponse); i { + switch v := v.(*ListTowersResponse); i { case 0: return &v.state case 1: @@ -1456,7 +1569,7 @@ func file_wtclientrpc_wtclient_proto_init() { } } file_wtclientrpc_wtclient_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PolicyRequest); i { + switch v := v.(*StatsRequest); i { case 0: return &v.state case 1: @@ -1468,6 +1581,30 @@ func file_wtclientrpc_wtclient_proto_init() { } } 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 @@ -1486,7 +1623,7 @@ func file_wtclientrpc_wtclient_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_wtclientrpc_wtclient_proto_rawDesc, NumEnums: 1, - NumMessages: 16, + NumMessages: 18, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/wtclientrpc/wtclient.pb.gw.go b/lnrpc/wtclientrpc/wtclient.pb.gw.go index f3d3e70de..b441437fa 100644 --- a/lnrpc/wtclientrpc/wtclient.pb.gw.go +++ b/lnrpc/wtclientrpc/wtclient.pb.gw.go @@ -187,6 +187,58 @@ func local_request_WatchtowerClient_DeactivateTower_0(ctx context.Context, marsh } +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)} ) @@ -422,6 +474,29 @@ func RegisterWatchtowerClientHandlerServer(ctx context.Context, mux *runtime.Ser }) + 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() @@ -615,6 +690,26 @@ func RegisterWatchtowerClientHandlerClient(ctx context.Context, mux *runtime.Ser }) + 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() @@ -705,6 +800,8 @@ var ( 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"}, "")) @@ -721,6 +818,8 @@ var ( forward_WatchtowerClient_DeactivateTower_0 = runtime.ForwardResponseMessage + forward_WatchtowerClient_TerminateSession_0 = runtime.ForwardResponseMessage + forward_WatchtowerClient_ListTowers_0 = runtime.ForwardResponseMessage forward_WatchtowerClient_GetTowerInfo_0 = runtime.ForwardResponseMessage diff --git a/lnrpc/wtclientrpc/wtclient.proto b/lnrpc/wtclientrpc/wtclient.proto index b0bf53cf0..3272d0951 100644 --- a/lnrpc/wtclientrpc/wtclient.proto +++ b/lnrpc/wtclientrpc/wtclient.proto @@ -49,6 +49,13 @@ service WatchtowerClient { 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. */ @@ -106,6 +113,16 @@ message DeactivateTowerResponse { 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; diff --git a/lnrpc/wtclientrpc/wtclient.swagger.json b/lnrpc/wtclientrpc/wtclient.swagger.json index bef43e944..676f183bc 100644 --- a/lnrpc/wtclientrpc/wtclient.swagger.json +++ b/lnrpc/wtclientrpc/wtclient.swagger.json @@ -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.", @@ -400,6 +433,15 @@ } } }, + "wtclientrpcTerminateSessionResponse": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "A string describing the action that took place." + } + } + }, "wtclientrpcTower": { "type": "object", "properties": { diff --git a/lnrpc/wtclientrpc/wtclient.yaml b/lnrpc/wtclientrpc/wtclient.yaml index 6f02969dd..55850a304 100644 --- a/lnrpc/wtclientrpc/wtclient.yaml +++ b/lnrpc/wtclientrpc/wtclient.yaml @@ -10,6 +10,8 @@ http: 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 diff --git a/lnrpc/wtclientrpc/wtclient_grpc.pb.go b/lnrpc/wtclientrpc/wtclient_grpc.pb.go index 86347ebf3..d22ef01fb 100644 --- a/lnrpc/wtclientrpc/wtclient_grpc.pb.go +++ b/lnrpc/wtclientrpc/wtclient_grpc.pb.go @@ -35,6 +35,10 @@ type WatchtowerClientClient interface { // 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) @@ -84,6 +88,15 @@ func (c *watchtowerClientClient) DeactivateTower(ctx context.Context, in *Deacti 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...) @@ -141,6 +154,10 @@ type WatchtowerClientServer interface { // 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) @@ -169,6 +186,9 @@ func (UnimplementedWatchtowerClientServer) RemoveTower(context.Context, *RemoveT 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") } @@ -248,6 +268,24 @@ func _WatchtowerClient_DeactivateTower_Handler(srv interface{}, ctx context.Cont 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 { @@ -339,6 +377,10 @@ var WatchtowerClient_ServiceDesc = grpc.ServiceDesc{ MethodName: "DeactivateTower", Handler: _WatchtowerClient_DeactivateTower_Handler, }, + { + MethodName: "TerminateSession", + Handler: _WatchtowerClient_TerminateSession_Handler, + }, { MethodName: "ListTowers", Handler: _WatchtowerClient_ListTowers_Handler, From a9648cbdd1e7ea749796e9caa413a67da5894c01 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 9 Feb 2024 12:48:11 +0200 Subject: [PATCH 13/18] wtclient: load all active towers into memory Load an active tower into memory regardless of whether or not it has any sessions. --- watchtower/wtclient/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/watchtower/wtclient/client.go b/watchtower/wtclient/client.go index e8fec5d4a..167a98268 100644 --- a/watchtower/wtclient/client.go +++ b/watchtower/wtclient/client.go @@ -324,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 From 1c7a7543c7b2b07e6d9b739039e031d9789c420a Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 9 Feb 2024 15:10:32 +0200 Subject: [PATCH 14/18] lnrpc: add session ID to tower session info This will be used in the itest added in an upcoming commit so that we can test the TerminateSession command. --- lnrpc/wtclientrpc/wtclient.go | 1 + lnrpc/wtclientrpc/wtclient.pb.go | 12 +++++++++++- lnrpc/wtclientrpc/wtclient.proto | 5 +++++ lnrpc/wtclientrpc/wtclient.swagger.json | 5 +++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lnrpc/wtclientrpc/wtclient.go b/lnrpc/wtclientrpc/wtclient.go index 8e9de23b7..459ce3240 100644 --- a/lnrpc/wtclientrpc/wtclient.go +++ b/lnrpc/wtclientrpc/wtclient.go @@ -550,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), diff --git a/lnrpc/wtclientrpc/wtclient.pb.go b/lnrpc/wtclientrpc/wtclient.pb.go index e9bd28610..828575366 100644 --- a/lnrpc/wtclientrpc/wtclient.pb.go +++ b/lnrpc/wtclientrpc/wtclient.pb.go @@ -545,6 +545,8 @@ 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() { @@ -615,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 @@ -1189,7 +1198,7 @@ var file_wtclientrpc_wtclient_proto_rawDesc = []byte{ 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, + 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, @@ -1204,6 +1213,7 @@ var file_wtclientrpc_wtclient_proto_rawDesc = []byte{ 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, diff --git a/lnrpc/wtclientrpc/wtclient.proto b/lnrpc/wtclientrpc/wtclient.proto index 3272d0951..edc79b8d1 100644 --- a/lnrpc/wtclientrpc/wtclient.proto +++ b/lnrpc/wtclientrpc/wtclient.proto @@ -163,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 { diff --git a/lnrpc/wtclientrpc/wtclient.swagger.json b/lnrpc/wtclientrpc/wtclient.swagger.json index 676f183bc..22467258a 100644 --- a/lnrpc/wtclientrpc/wtclient.swagger.json +++ b/lnrpc/wtclientrpc/wtclient.swagger.json @@ -509,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." } } }, From d4424fbcfaf8107f35199fefd9879d44593824af Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 9 Feb 2024 12:45:36 +0200 Subject: [PATCH 15/18] itest: refactor watchtower related tests --- itest/list_on_test.go | 12 +- itest/lnd_revocation_test.go | 303 ----------------------------- itest/lnd_watchtower_test.go | 357 ++++++++++++++++++++++++++++++++--- 3 files changed, 335 insertions(+), 337 deletions(-) diff --git a/itest/list_on_test.go b/itest/list_on_test.go index e8cf80c5f..c7e76a52f 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -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", diff --git a/itest/lnd_revocation_test.go b/itest/lnd_revocation_test.go index d02e60d7e..d94fa7c43 100644 --- a/itest/lnd_revocation_test.go +++ b/itest/lnd_revocation_test.go @@ -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) -} diff --git a/itest/lnd_watchtower_test.go b/itest/lnd_watchtower_test.go index 3432848d8..efd14503b 100644 --- a/itest/lnd_watchtower_test.go +++ b/itest/lnd_watchtower_test.go @@ -2,21 +2,38 @@ 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) + }) +} + +// testTowerClientSessionDeletion tests that sessions are correctly deleted +// when they are deemed closable. +func testTowerClientSessionDeletion(ht *lntest.HarnessTest) { const ( chanAmt = funding.MaxBtcFundingAmount paymentAmt = 10_000 @@ -28,24 +45,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 +58,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 +66,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, }) @@ -125,7 +125,7 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) { err = wait.NoError(func() error { info := dave.RPC.GetTowerInfo( &wtclientrpc.GetTowerInfoRequest{ - Pubkey: wallisInfo.Pubkey, + Pubkey: wallisPk, IncludeSessions: true, }, ) @@ -170,3 +170,312 @@ func testWatchtowerSessionManagement(ht *lntest.HarnessTest) { // ensure that the session deleting logic is run. assertNumBackups(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 +} From 59a4bfbc59a235e367b3106879806a6dcae68f11 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 9 Feb 2024 13:24:19 +0200 Subject: [PATCH 16/18] itest+refactor: extract various tower test helpers --- itest/lnd_watchtower_test.go | 131 ++++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 55 deletions(-) diff --git a/itest/lnd_watchtower_test.go b/itest/lnd_watchtower_test.go index efd14503b..92336c863 100644 --- a/itest/lnd_watchtower_test.go +++ b/itest/lnd_watchtower_test.go @@ -36,7 +36,6 @@ func testWatchtower(ht *lntest.HarnessTest) { func testTowerClientSessionDeletion(ht *lntest.HarnessTest) { const ( chanAmt = funding.MaxBtcFundingAmount - paymentAmt = 10_000 numInvoices = 5 maxUpdates = numInvoices * 2 externalIP = "1.2.3.4" @@ -98,61 +97,10 @@ func testTowerClientSessionDeletion(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: wallisPk, - 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,7 +116,7 @@ func testTowerClientSessionDeletion(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 @@ -479,3 +427,76 @@ func setUpNewTower(ht *lntest.HarnessTest, name, externalIP string) ([]byte, 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) +} From 4ab17525ce47ba8ec4f3758790afced22a072ae8 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 9 Feb 2024 12:47:35 +0200 Subject: [PATCH 17/18] itest+lntest: add wtclient session & tower management test --- itest/lnd_watchtower_test.go | 194 +++++++++++++++++++++++++++++++++++ lntest/rpc/watchtower.go | 32 ++++++ 2 files changed, 226 insertions(+) diff --git a/itest/lnd_watchtower_test.go b/itest/lnd_watchtower_test.go index 92336c863..bfd1425a5 100644 --- a/itest/lnd_watchtower_test.go +++ b/itest/lnd_watchtower_test.go @@ -29,6 +29,200 @@ func testWatchtower(ht *lntest.HarnessTest) { tt := ht.Subtest(t) testTowerClientSessionDeletion(tt) }) + + ht.Run("tower and session activation", func(t *testing.T) { + tt := ht.Subtest(t) + testTowerClientTowerAndSessionManagement(tt) + }) +} + +// testTowerClientTowerAndSessionManagement tests the various control commands +// that a user has over the client's set of active towers and sessions. +func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) { + const ( + chanAmt = funding.MaxBtcFundingAmount + externalIP = "1.2.3.4" + externalIP2 = "1.2.3.5" + sessionCloseRange = 1 + ) + + // Set up Wallis the watchtower who will be used by Dave to watch over + // his channel commitment transactions. + wallisPk, wallisListener, _ := setUpNewTower(ht, "Wallis", externalIP) + + // Dave will be the tower client. + daveArgs := []string{ + "--wtclient.active", + fmt.Sprintf( + "--wtclient.session-close-range=%d", sessionCloseRange, + ), + } + dave := ht.NewNode("Dave", daveArgs) + + addWallisReq := &wtclientrpc.AddTowerRequest{ + Pubkey: wallisPk, + Address: wallisListener, + } + dave.RPC.AddTower(addWallisReq) + + assertNumSessions := func(towerPk []byte, expectedNum int) { + err := wait.NoError(func() error { + info := dave.RPC.GetTowerInfo( + &wtclientrpc.GetTowerInfoRequest{ + Pubkey: towerPk, + IncludeSessions: true, + }, + ) + + var numSessions uint32 + for _, sessionType := range info.SessionInfo { + numSessions += sessionType.NumSessions + } + if numSessions == uint32(expectedNum) { + return nil + } + + return fmt.Errorf("expected %d sessions, got %d", + expectedNum, numSessions) + }, defaultTimeout) + require.NoError(ht, err) + } + + // Assert that there are a few sessions between Dave and Wallis. There + // should be one per client. There are currently 3 types of clients, so + // we expect 3 sessions. + assertNumSessions(wallisPk, 3) + + // Before we make a channel, we'll load up Dave with some coins sent + // directly from the miner. + ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) + + // Connect Dave and Alice. + ht.ConnectNodes(dave, ht.Alice) + + // Open a channel between Dave and Alice. + params := lntest.OpenChannelParams{ + Amt: chanAmt, + } + chanPoint := ht.OpenChannel(dave, ht.Alice, params) + + // Show that the Wallis tower is currently seen as an active session + // candidate. + info := dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{ + Pubkey: wallisPk, + }) + require.GreaterOrEqual(ht, len(info.SessionInfo), 1) + require.True(ht, info.SessionInfo[0].ActiveSessionCandidate) + + // Make some back-ups and assert that they are added to a session with + // the tower. + generateBackups(ht, dave, ht.Alice, 4) + + // Assert that one of the sessions now has 4 backups. + assertNumBackups(ht, dave.RPC, wallisPk, 4, false) + + // Now, deactivate the tower and show that it is no longer considered + // an active session candidate. + dave.RPC.DeactivateTower(&wtclientrpc.DeactivateTowerRequest{ + Pubkey: wallisPk, + }) + info = dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{ + Pubkey: wallisPk, + }) + require.GreaterOrEqual(ht, len(info.SessionInfo), 1) + require.False(ht, info.SessionInfo[0].ActiveSessionCandidate) + + // Back up a few more states. + generateBackups(ht, dave, ht.Alice, 4) + + // These should _not_ be on the tower. Therefore, the number of + // back-ups on the tower should be the same as before. + assertNumBackups(ht, dave.RPC, wallisPk, 4, false) + + // Add new tower and connect Dave to it. + wilmaPk, wilmaListener, _ := setUpNewTower(ht, "Wilma", externalIP2) + dave.RPC.AddTower(&wtclientrpc.AddTowerRequest{ + Pubkey: wilmaPk, + Address: wilmaListener, + }) + assertNumSessions(wilmaPk, 3) + + // The updates from before should now appear on the new watchtower. + assertNumBackups(ht, dave.RPC, wilmaPk, 4, false) + + // Reactivate the Wallis tower and then deactivate the Wilma one. + dave.RPC.AddTower(addWallisReq) + dave.RPC.DeactivateTower(&wtclientrpc.DeactivateTowerRequest{ + Pubkey: wilmaPk, + }) + + // Generate some more back-ups. + generateBackups(ht, dave, ht.Alice, 4) + + // Assert that they get added to the first tower (Wallis) and that the + // number of sessions with Wallis has not changed - in other words, the + // previously used session was re-used. + assertNumBackups(ht, dave.RPC, wallisPk, 8, false) + assertNumSessions(wallisPk, 3) + + findSession := func(towerPk []byte, numBackups uint32) []byte { + info := dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{ + Pubkey: towerPk, + IncludeSessions: true, + }) + + for _, sessionType := range info.SessionInfo { + for _, session := range sessionType.Sessions { + if session.NumBackups == numBackups { + return session.Id + } + } + } + ht.Fatalf("session with %d backups not found", numBackups) + + return nil + } + + // Now we will test the termination of a session. + // First, we need to figure out the ID of the session that has been used + // for back-ups. + sessionID := findSession(wallisPk, 8) + + // Now, terminate the session. + dave.RPC.TerminateSession(&wtclientrpc.TerminateSessionRequest{ + SessionId: sessionID, + }) + + // This should force the client to negotiate a new session. The old + // session still remains in our session list since the channel for which + // it has updates for is still open. + assertNumSessions(wallisPk, 4) + + // Any new back-ups should now be backed up on a different session. + generateBackups(ht, dave, ht.Alice, 2) + assertNumBackups(ht, dave.RPC, wallisPk, 10, false) + findSession(wallisPk, 2) + + // Close the channel. + ht.CloseChannelAssertPending(dave, chanPoint, false) + + // Mine enough blocks to surpass the session close range buffer. + ht.MineBlocksAndAssertNumTxes(sessionCloseRange+6, 1) + + // The session that was previously terminated now gets deleted since + // the channel for which it has updates has now been closed. All the + // remaining sessions are not yet closable since they are not yet + // exhausted and are all still active. + assertNumSessions(wallisPk, 3) + + // For the sake of completion, we call RemoveTower here for both towers + // to show that this should never error. + dave.RPC.RemoveTower(&wtclientrpc.RemoveTowerRequest{ + Pubkey: wallisPk, + }) + dave.RPC.RemoveTower(&wtclientrpc.RemoveTowerRequest{ + Pubkey: wilmaPk, + }) } // testTowerClientSessionDeletion tests that sessions are correctly deleted diff --git a/lntest/rpc/watchtower.go b/lntest/rpc/watchtower.go index d5512e7a4..13913114c 100644 --- a/lntest/rpc/watchtower.go +++ b/lntest/rpc/watchtower.go @@ -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 { From a3ccae9a55709cfd1ae337fba7387d57ea8b1620 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 30 Nov 2023 12:17:08 +0200 Subject: [PATCH 18/18] docs: update release notes --- docs/release-notes/release-notes-0.18.0.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/release-notes/release-notes-0.18.0.md b/docs/release-notes/release-notes-0.18.0.md index 1323c1f0e..5ccc18f3d 100644 --- a/docs/release-notes/release-notes-0.18.0.md +++ b/docs/release-notes/release-notes-0.18.0.md @@ -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