Merge pull request #6972 from ellemouton/wtclientTowerDb
watchtower: add towerID-to-sessionID index
This commit is contained in:
14 files changed
+999
-409
No files matched your search
@@ -102,6 +102,14 @@ crash](https://github.com/lightningnetwork/lnd/pull/7019).
|
|||||||
* [The `tlv` package now allows decoding records larger than 65535 bytes. The
|
* [The `tlv` package now allows decoding records larger than 65535 bytes. The
|
||||||
caller is expected to know that doing so with untrusted input is
|
caller is expected to know that doing so with untrusted input is
|
||||||
unsafe.](https://github.com/lightningnetwork/lnd/pull/6779)
|
unsafe.](https://github.com/lightningnetwork/lnd/pull/6779)
|
||||||
|
|
||||||
|
## Watchtowers
|
||||||
|
|
||||||
|
* [Create a towerID-to-sessionID index in the wtclient DB to improve the
|
||||||
|
speed of listing sessions for a particular tower ID](
|
||||||
|
https://github.com/lightningnetwork/lnd/pull/6972). This PR also ensures a
|
||||||
|
closer coupling of Towers and Sessions and ensures that a session cannot be
|
||||||
|
added if the tower it is referring to does not exist.
|
||||||
|
|
||||||
* [Create a helper function to wait for peer to come
|
* [Create a helper function to wait for peer to come
|
||||||
online](https://github.com/lightningnetwork/lnd/pull/6931).
|
online](https://github.com/lightningnetwork/lnd/pull/6931).
|
||||||
|
|||||||
@@ -287,26 +287,33 @@ func New(config *Config) (*TowerClient, error) {
|
|||||||
}
|
}
|
||||||
plog := build.NewPrefixLog(prefix, log)
|
plog := build.NewPrefixLog(prefix, log)
|
||||||
|
|
||||||
// Next, load all candidate sessions and towers from the database into
|
// Next, load all candidate towers and sessions from the database into
|
||||||
// the client. We will use any of these session if their policies match
|
// the client. We will use any of these sessions if their policies match
|
||||||
// the current policy of the client, otherwise they will be ignored and
|
// the current policy of the client, otherwise they will be ignored and
|
||||||
// new sessions will be requested.
|
// new sessions will be requested.
|
||||||
isAnchorClient := cfg.Policy.IsAnchorChannel()
|
isAnchorClient := cfg.Policy.IsAnchorChannel()
|
||||||
activeSessionFilter := genActiveSessionFilter(isAnchorClient)
|
activeSessionFilter := genActiveSessionFilter(isAnchorClient)
|
||||||
candidateSessions, err := getClientSessions(
|
candidateTowers := newTowerListIterator()
|
||||||
cfg.DB, cfg.SecretKeyRing, nil, activeSessionFilter,
|
perActiveTower := func(tower *wtdb.Tower) {
|
||||||
|
// If the tower has already been marked as active, then there is
|
||||||
|
// no need to add it to the iterator again.
|
||||||
|
if candidateTowers.IsActive(tower.ID) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Using private watchtower %s, offering policy %s",
|
||||||
|
tower, cfg.Policy)
|
||||||
|
|
||||||
|
// Add the tower to the set of candidate towers.
|
||||||
|
candidateTowers.AddCandidate(tower)
|
||||||
|
}
|
||||||
|
candidateSessions, err := getTowerAndSessionCandidates(
|
||||||
|
cfg.DB, cfg.SecretKeyRing, activeSessionFilter, perActiveTower,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var candidateTowers []*wtdb.Tower
|
|
||||||
for _, s := range candidateSessions {
|
|
||||||
plog.Infof("Using private watchtower %s, offering policy %s",
|
|
||||||
s.Tower, cfg.Policy)
|
|
||||||
candidateTowers = append(candidateTowers, s.Tower)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the sweep pkscripts that have been generated for all previously
|
// Load the sweep pkscripts that have been generated for all previously
|
||||||
// registered channels.
|
// registered channels.
|
||||||
chanSummaries, err := cfg.DB.FetchChanSummaries()
|
chanSummaries, err := cfg.DB.FetchChanSummaries()
|
||||||
@@ -318,7 +325,7 @@ func New(config *Config) (*TowerClient, error) {
|
|||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
log: plog,
|
log: plog,
|
||||||
pipeline: newTaskPipeline(plog),
|
pipeline: newTaskPipeline(plog),
|
||||||
candidateTowers: newTowerListIterator(candidateTowers...),
|
candidateTowers: candidateTowers,
|
||||||
candidateSessions: candidateSessions,
|
candidateSessions: candidateSessions,
|
||||||
activeSessions: make(sessionQueueSet),
|
activeSessions: make(sessionQueueSet),
|
||||||
summaries: chanSummaries,
|
summaries: chanSummaries,
|
||||||
@@ -349,13 +356,62 @@ func New(config *Config) (*TowerClient, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getTowerAndSessionCandidates loads all the towers from the DB and then
|
||||||
|
// fetches the sessions for each of tower. Sessions are only collected if they
|
||||||
|
// pass the sessionFilter check. If a tower has a session that does pass the
|
||||||
|
// sessionFilter check then the perActiveTower call-back will be called on that
|
||||||
|
// tower.
|
||||||
|
func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing,
|
||||||
|
sessionFilter func(*wtdb.ClientSession) bool,
|
||||||
|
perActiveTower func(tower *wtdb.Tower)) (
|
||||||
|
map[wtdb.SessionID]*wtdb.ClientSession, error) {
|
||||||
|
|
||||||
|
towers, err := db.ListTowers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
candidateSessions := make(map[wtdb.SessionID]*wtdb.ClientSession)
|
||||||
|
for _, tower := range towers {
|
||||||
|
sessions, err := db.ListClientSessions(&tower.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range sessions {
|
||||||
|
towerKeyDesc, err := keyRing.DeriveKey(
|
||||||
|
keychain.KeyLocator{
|
||||||
|
Family: keychain.KeyFamilyTowerSession,
|
||||||
|
Index: s.KeyIndex,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.SessionKeyECDH = keychain.NewPubKeyECDH(
|
||||||
|
towerKeyDesc, keyRing,
|
||||||
|
)
|
||||||
|
|
||||||
|
if !sessionFilter(s) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the session to the set of candidate sessions.
|
||||||
|
candidateSessions[s.ID] = s
|
||||||
|
perActiveTower(tower)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidateSessions, nil
|
||||||
|
}
|
||||||
|
|
||||||
// getClientSessions retrieves the client sessions for a particular tower if
|
// getClientSessions retrieves the client sessions for a particular tower if
|
||||||
// specified, otherwise all client sessions for all towers are retrieved. An
|
// specified, otherwise all client sessions for all towers are retrieved. An
|
||||||
// optional filter can be provided to filter out any undesired client sessions.
|
// optional filter can be provided to filter out any undesired client sessions.
|
||||||
//
|
//
|
||||||
// NOTE: This method should only be used when deserialization of a
|
// NOTE: This method should only be used when deserialization of a
|
||||||
// ClientSession's Tower and SessionPrivKey fields is desired, otherwise, the
|
// ClientSession's SessionPrivKey field is desired, otherwise, the existing
|
||||||
// existing ListClientSessions method should be used.
|
// ListClientSessions method should be used.
|
||||||
func getClientSessions(db DB, keyRing ECDHKeyRing, forTower *wtdb.TowerID,
|
func getClientSessions(db DB, keyRing ECDHKeyRing, forTower *wtdb.TowerID,
|
||||||
passesFilter func(*wtdb.ClientSession) bool) (
|
passesFilter func(*wtdb.ClientSession) bool) (
|
||||||
map[wtdb.SessionID]*wtdb.ClientSession, error) {
|
map[wtdb.SessionID]*wtdb.ClientSession, error) {
|
||||||
@@ -371,12 +427,6 @@ func getClientSessions(db DB, keyRing ECDHKeyRing, forTower *wtdb.TowerID,
|
|||||||
// requests. This prevents us from having to store the private keys on
|
// requests. This prevents us from having to store the private keys on
|
||||||
// disk.
|
// disk.
|
||||||
for _, s := range sessions {
|
for _, s := range sessions {
|
||||||
tower, err := db.LoadTowerByID(s.TowerID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.Tower = tower
|
|
||||||
|
|
||||||
towerKeyDesc, err := keyRing.DeriveKey(keychain.KeyLocator{
|
towerKeyDesc, err := keyRing.DeriveKey(keychain.KeyLocator{
|
||||||
Family: keychain.KeyFamilyTowerSession,
|
Family: keychain.KeyFamilyTowerSession,
|
||||||
Index: s.KeyIndex,
|
Index: s.KeyIndex,
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ type DB interface {
|
|||||||
// still be able to accept state updates. An optional tower ID can be
|
// still be able to accept state updates. An optional tower ID can be
|
||||||
// used to filter out any client sessions in the response that do not
|
// used to filter out any client sessions in the response that do not
|
||||||
// correspond to this tower.
|
// correspond to this tower.
|
||||||
ListClientSessions(*wtdb.TowerID) (map[wtdb.SessionID]*wtdb.ClientSession, error)
|
ListClientSessions(*wtdb.TowerID) (
|
||||||
|
map[wtdb.SessionID]*wtdb.ClientSession, error)
|
||||||
|
|
||||||
// FetchChanSummaries loads a mapping from all registered channels to
|
// FetchChanSummaries loads a mapping from all registered channels to
|
||||||
// their channel summaries.
|
// their channel summaries.
|
||||||
@@ -96,8 +97,8 @@ type DB interface {
|
|||||||
AckUpdate(id *wtdb.SessionID, seqNum, lastApplied uint16) error
|
AckUpdate(id *wtdb.SessionID, seqNum, lastApplied uint16) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthDialer connects to a remote node using an authenticated transport, such as
|
// AuthDialer connects to a remote node using an authenticated transport, such
|
||||||
// brontide. The dialer argument is used to specify a resolver, which allows
|
// as brontide. The dialer argument is used to specify a resolver, which allows
|
||||||
// this method to be used over Tor or clear net connections.
|
// this method to be used over Tor or clear net connections.
|
||||||
type AuthDialer func(localKey keychain.SingleKeyECDH,
|
type AuthDialer func(localKey keychain.SingleKeyECDH,
|
||||||
netAddr *lnwire.NetAddress,
|
netAddr *lnwire.NetAddress,
|
||||||
|
|||||||
+172
-39
@@ -48,6 +48,12 @@ var (
|
|||||||
// tower-pubkey -> tower-id.
|
// tower-pubkey -> tower-id.
|
||||||
cTowerIndexBkt = []byte("client-tower-index-bucket")
|
cTowerIndexBkt = []byte("client-tower-index-bucket")
|
||||||
|
|
||||||
|
// cTowerToSessionIndexBkt is a top-level bucket storing:
|
||||||
|
// tower-id -> session-id -> 1
|
||||||
|
cTowerToSessionIndexBkt = []byte(
|
||||||
|
"client-tower-to-session-index-bucket",
|
||||||
|
)
|
||||||
|
|
||||||
// ErrTowerNotFound signals that the target tower was not found in the
|
// ErrTowerNotFound signals that the target tower was not found in the
|
||||||
// database.
|
// database.
|
||||||
ErrTowerNotFound = errors.New("tower not found")
|
ErrTowerNotFound = errors.New("tower not found")
|
||||||
@@ -113,7 +119,8 @@ var (
|
|||||||
// NewBoltBackendCreator returns a function that creates a new bbolt backend for
|
// NewBoltBackendCreator returns a function that creates a new bbolt backend for
|
||||||
// the watchtower database.
|
// the watchtower database.
|
||||||
func NewBoltBackendCreator(active bool, dbPath,
|
func NewBoltBackendCreator(active bool, dbPath,
|
||||||
dbFileName string) func(boltCfg *kvdb.BoltConfig) (kvdb.Backend, error) {
|
dbFileName string) func(boltCfg *kvdb.BoltConfig) (kvdb.Backend,
|
||||||
|
error) {
|
||||||
|
|
||||||
// If the watchtower client isn't active, we return a function that
|
// If the watchtower client isn't active, we return a function that
|
||||||
// always returns a nil DB to make sure we don't create empty database
|
// always returns a nil DB to make sure we don't create empty database
|
||||||
@@ -195,6 +202,7 @@ func initClientDBBuckets(tx kvdb.RwTx) error {
|
|||||||
cSessionBkt,
|
cSessionBkt,
|
||||||
cTowerBkt,
|
cTowerBkt,
|
||||||
cTowerIndexBkt,
|
cTowerIndexBkt,
|
||||||
|
cTowerToSessionIndexBkt,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
@@ -259,6 +267,13 @@ func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error) {
|
|||||||
return ErrUninitializedDB
|
return ErrUninitializedDB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
towerToSessionIndex := tx.ReadWriteBucket(
|
||||||
|
cTowerToSessionIndexBkt,
|
||||||
|
)
|
||||||
|
if towerToSessionIndex == nil {
|
||||||
|
return ErrUninitializedDB
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the tower index already knows of this pubkey.
|
// Check if the tower index already knows of this pubkey.
|
||||||
towerIDBytes := towerIndex.Get(towerPubKey[:])
|
towerIDBytes := towerIndex.Get(towerPubKey[:])
|
||||||
if len(towerIDBytes) == 8 {
|
if len(towerIDBytes) == 8 {
|
||||||
@@ -278,27 +293,32 @@ func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error) {
|
|||||||
// If there are any client sessions that correspond to
|
// If there are any client sessions that correspond to
|
||||||
// this tower, we'll mark them as active to ensure we
|
// this tower, we'll mark them as active to ensure we
|
||||||
// load them upon restarts.
|
// load them upon restarts.
|
||||||
//
|
towerSessIndex := towerToSessionIndex.NestedReadBucket(
|
||||||
// TODO(wilmer): with an index of tower -> sessions we
|
tower.ID.Bytes(),
|
||||||
// can avoid the linear lookup.
|
)
|
||||||
|
if towerSessIndex == nil {
|
||||||
|
return ErrTowerNotFound
|
||||||
|
}
|
||||||
|
|
||||||
sessions := tx.ReadWriteBucket(cSessionBkt)
|
sessions := tx.ReadWriteBucket(cSessionBkt)
|
||||||
if sessions == nil {
|
if sessions == nil {
|
||||||
return ErrUninitializedDB
|
return ErrUninitializedDB
|
||||||
}
|
}
|
||||||
towerID := TowerIDFromBytes(towerIDBytes)
|
|
||||||
towerSessions, err := listClientSessions(
|
err = towerSessIndex.ForEach(func(k, _ []byte) error {
|
||||||
sessions, &towerID,
|
session, err := getClientSessionBody(
|
||||||
)
|
sessions, k,
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, session := range towerSessions {
|
|
||||||
err := markSessionStatus(
|
|
||||||
sessions, session, CSessionActive,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return markSessionStatus(
|
||||||
|
sessions, session, CSessionActive,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No such tower exists, create a new tower id for our
|
// No such tower exists, create a new tower id for our
|
||||||
@@ -320,6 +340,13 @@ func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new bucket for this tower in the
|
||||||
|
// tower-to-sessions index.
|
||||||
|
_, err = towerToSessionIndex.CreateBucket(towerIDBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the new or updated tower under its tower id.
|
// Store the new or updated tower under its tower id.
|
||||||
@@ -348,11 +375,19 @@ func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error {
|
|||||||
if towers == nil {
|
if towers == nil {
|
||||||
return ErrUninitializedDB
|
return ErrUninitializedDB
|
||||||
}
|
}
|
||||||
|
|
||||||
towerIndex := tx.ReadWriteBucket(cTowerIndexBkt)
|
towerIndex := tx.ReadWriteBucket(cTowerIndexBkt)
|
||||||
if towerIndex == nil {
|
if towerIndex == nil {
|
||||||
return ErrUninitializedDB
|
return ErrUninitializedDB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
towersToSessionsIndex := tx.ReadWriteBucket(
|
||||||
|
cTowerToSessionIndexBkt,
|
||||||
|
)
|
||||||
|
if towersToSessionsIndex == nil {
|
||||||
|
return ErrUninitializedDB
|
||||||
|
}
|
||||||
|
|
||||||
// Don't return an error if the watchtower doesn't exist to act
|
// Don't return an error if the watchtower doesn't exist to act
|
||||||
// as a NOP.
|
// as a NOP.
|
||||||
pubKeyBytes := pubKey.SerializeCompressed()
|
pubKeyBytes := pubKey.SerializeCompressed()
|
||||||
@@ -380,15 +415,14 @@ func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error {
|
|||||||
|
|
||||||
// Otherwise, we should attempt to mark the tower's sessions as
|
// Otherwise, we should attempt to mark the tower's sessions as
|
||||||
// inactive.
|
// inactive.
|
||||||
//
|
|
||||||
// TODO(wilmer): with an index of tower -> sessions we can avoid
|
|
||||||
// the linear lookup.
|
|
||||||
sessions := tx.ReadWriteBucket(cSessionBkt)
|
sessions := tx.ReadWriteBucket(cSessionBkt)
|
||||||
if sessions == nil {
|
if sessions == nil {
|
||||||
return ErrUninitializedDB
|
return ErrUninitializedDB
|
||||||
}
|
}
|
||||||
towerID := TowerIDFromBytes(towerIDBytes)
|
towerID := TowerIDFromBytes(towerIDBytes)
|
||||||
towerSessions, err := listClientSessions(sessions, &towerID)
|
towerSessions, err := listTowerSessions(
|
||||||
|
towerID, sessions, towers, towersToSessionsIndex,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -399,7 +433,14 @@ func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error {
|
|||||||
if err := towerIndex.Delete(pubKeyBytes); err != nil {
|
if err := towerIndex.Delete(pubKeyBytes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return towers.Delete(towerIDBytes)
|
|
||||||
|
if err := towers.Delete(towerIDBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return towersToSessionsIndex.DeleteNestedBucket(
|
||||||
|
towerIDBytes,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll mark its sessions as inactive as long as they don't
|
// We'll mark its sessions as inactive as long as they don't
|
||||||
@@ -573,14 +614,34 @@ func (c *ClientDB) CreateClientSession(session *ClientSession) error {
|
|||||||
return ErrUninitializedDB
|
return ErrUninitializedDB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
towers := tx.ReadBucket(cTowerBkt)
|
||||||
|
if towers == nil {
|
||||||
|
return ErrUninitializedDB
|
||||||
|
}
|
||||||
|
|
||||||
|
towerToSessionIndex := tx.ReadWriteBucket(
|
||||||
|
cTowerToSessionIndexBkt,
|
||||||
|
)
|
||||||
|
if towerToSessionIndex == nil {
|
||||||
|
return ErrUninitializedDB
|
||||||
|
}
|
||||||
|
|
||||||
// Check that client session with this session id doesn't
|
// Check that client session with this session id doesn't
|
||||||
// already exist.
|
// already exist.
|
||||||
existingSessionBytes := sessions.NestedReadWriteBucket(session.ID[:])
|
existingSessionBytes := sessions.NestedReadWriteBucket(
|
||||||
|
session.ID[:],
|
||||||
|
)
|
||||||
if existingSessionBytes != nil {
|
if existingSessionBytes != nil {
|
||||||
return ErrClientSessionAlreadyExists
|
return ErrClientSessionAlreadyExists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that a tower with the given ID actually exists in the
|
||||||
|
// DB.
|
||||||
towerID := session.TowerID
|
towerID := session.TowerID
|
||||||
|
if _, err := getTower(towers, towerID.Bytes()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
blobType := session.Policy.BlobType
|
blobType := session.Policy.BlobType
|
||||||
|
|
||||||
// Check that this tower has a reserved key index.
|
// Check that this tower has a reserved key index.
|
||||||
@@ -609,6 +670,19 @@ func (c *ClientDB) CreateClientSession(session *ClientSession) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the new entry to the towerID-to-SessionID index.
|
||||||
|
indexBkt := towerToSessionIndex.NestedReadWriteBucket(
|
||||||
|
towerID.Bytes(),
|
||||||
|
)
|
||||||
|
if indexBkt == nil {
|
||||||
|
return ErrTowerNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
err = indexBkt.Put(session.ID[:], []byte{1})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Finally, write the client session's body in the sessions
|
// Finally, write the client session's body in the sessions
|
||||||
// bucket.
|
// bucket.
|
||||||
return putClientSessionBody(sessions, session)
|
return putClientSessionBody(sessions, session)
|
||||||
@@ -662,15 +736,41 @@ func getSessionKeyIndex(keyIndexes kvdb.RwBucket, towerID TowerID,
|
|||||||
// ListClientSessions returns the set of all client sessions known to the db. An
|
// ListClientSessions returns the set of all client sessions known to the db. An
|
||||||
// optional tower ID can be used to filter out any client sessions in the
|
// optional tower ID can be used to filter out any client sessions in the
|
||||||
// response that do not correspond to this tower.
|
// response that do not correspond to this tower.
|
||||||
func (c *ClientDB) ListClientSessions(id *TowerID) (map[SessionID]*ClientSession, error) {
|
func (c *ClientDB) ListClientSessions(id *TowerID) (
|
||||||
|
map[SessionID]*ClientSession, error) {
|
||||||
|
|
||||||
var clientSessions map[SessionID]*ClientSession
|
var clientSessions map[SessionID]*ClientSession
|
||||||
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
|
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
|
||||||
sessions := tx.ReadBucket(cSessionBkt)
|
sessions := tx.ReadBucket(cSessionBkt)
|
||||||
if sessions == nil {
|
if sessions == nil {
|
||||||
return ErrUninitializedDB
|
return ErrUninitializedDB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
towers := tx.ReadBucket(cTowerBkt)
|
||||||
|
if towers == nil {
|
||||||
|
return ErrUninitializedDB
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
clientSessions, err = listClientSessions(sessions, id)
|
|
||||||
|
// If no tower ID is specified, then fetch all the sessions
|
||||||
|
// known to the db.
|
||||||
|
if id == nil {
|
||||||
|
clientSessions, err = listClientAllSessions(
|
||||||
|
sessions, towers,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, fetch the sessions for the given tower.
|
||||||
|
towerToSessionIndex := tx.ReadBucket(cTowerToSessionIndexBkt)
|
||||||
|
if towerToSessionIndex == nil {
|
||||||
|
return ErrUninitializedDB
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSessions, err = listTowerSessions(
|
||||||
|
*id, sessions, towers, towerToSessionIndex,
|
||||||
|
)
|
||||||
return err
|
return err
|
||||||
}, func() {
|
}, func() {
|
||||||
clientSessions = nil
|
clientSessions = nil
|
||||||
@@ -682,11 +782,9 @@ func (c *ClientDB) ListClientSessions(id *TowerID) (map[SessionID]*ClientSession
|
|||||||
return clientSessions, nil
|
return clientSessions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// listClientSessions returns the set of all client sessions known to the db. An
|
// listClientAllSessions returns the set of all client sessions known to the db.
|
||||||
// optional tower ID can be used to filter out any client sessions in the
|
func listClientAllSessions(sessions,
|
||||||
// response that do not correspond to this tower.
|
towers kvdb.RBucket) (map[SessionID]*ClientSession, error) {
|
||||||
func listClientSessions(sessions kvdb.RBucket,
|
|
||||||
id *TowerID) (map[SessionID]*ClientSession, error) {
|
|
||||||
|
|
||||||
clientSessions := make(map[SessionID]*ClientSession)
|
clientSessions := make(map[SessionID]*ClientSession)
|
||||||
err := sessions.ForEach(func(k, _ []byte) error {
|
err := sessions.ForEach(func(k, _ []byte) error {
|
||||||
@@ -694,19 +792,45 @@ func listClientSessions(sessions kvdb.RBucket,
|
|||||||
// the CommittedUpdates and AckedUpdates on startup to resume
|
// the CommittedUpdates and AckedUpdates on startup to resume
|
||||||
// committed updates and compute the highest known commit height
|
// committed updates and compute the highest known commit height
|
||||||
// for each channel.
|
// for each channel.
|
||||||
session, err := getClientSession(sessions, k)
|
session, err := getClientSession(sessions, towers, k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out any sessions that don't correspond to the given
|
clientSessions[session.ID] = session
|
||||||
// tower if one was set.
|
|
||||||
if id != nil && session.TowerID != *id {
|
return nil
|
||||||
return nil
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientSessions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listTowerSessions returns the set of all client sessions known to the db
|
||||||
|
// that are associated with the given tower id.
|
||||||
|
func listTowerSessions(id TowerID, sessionsBkt, towersBkt,
|
||||||
|
towerToSessionIndex kvdb.RBucket) (map[SessionID]*ClientSession,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
towerIndexBkt := towerToSessionIndex.NestedReadBucket(id.Bytes())
|
||||||
|
if towerIndexBkt == nil {
|
||||||
|
return nil, ErrTowerNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSessions := make(map[SessionID]*ClientSession)
|
||||||
|
err := towerIndexBkt.ForEach(func(k, _ []byte) error {
|
||||||
|
// We'll load the full client session since the client will need
|
||||||
|
// the CommittedUpdates and AckedUpdates on startup to resume
|
||||||
|
// committed updates and compute the highest known commit height
|
||||||
|
// for each channel.
|
||||||
|
session, err := getClientSession(sessionsBkt, towersBkt, k)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
clientSessions[session.ID] = session
|
clientSessions[session.ID] = session
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -951,7 +1075,9 @@ func (c *ClientDB) AckUpdate(id *SessionID, seqNum uint16,
|
|||||||
|
|
||||||
// If the commits sub-bucket doesn't exist, there can't possibly
|
// If the commits sub-bucket doesn't exist, there can't possibly
|
||||||
// be a corresponding committed update to remove.
|
// be a corresponding committed update to remove.
|
||||||
sessionCommits := sessionBkt.NestedReadWriteBucket(cSessionCommits)
|
sessionCommits := sessionBkt.NestedReadWriteBucket(
|
||||||
|
cSessionCommits,
|
||||||
|
)
|
||||||
if sessionCommits == nil {
|
if sessionCommits == nil {
|
||||||
return ErrCommittedUpdateNotFound
|
return ErrCommittedUpdateNotFound
|
||||||
}
|
}
|
||||||
@@ -1004,8 +1130,8 @@ func (c *ClientDB) AckUpdate(id *SessionID, seqNum uint16,
|
|||||||
|
|
||||||
// getClientSessionBody loads the body of a ClientSession from the sessions
|
// getClientSessionBody loads the body of a ClientSession from the sessions
|
||||||
// bucket corresponding to the serialized session id. This does not deserialize
|
// bucket corresponding to the serialized session id. This does not deserialize
|
||||||
// the CommittedUpdates or AckUpdates associated with the session. If the caller
|
// the CommittedUpdates, AckUpdates or the Tower associated with the session.
|
||||||
// requires this info, use getClientSession.
|
// If the caller requires this info, use getClientSession.
|
||||||
func getClientSessionBody(sessions kvdb.RBucket,
|
func getClientSessionBody(sessions kvdb.RBucket,
|
||||||
idBytes []byte) (*ClientSession, error) {
|
idBytes []byte) (*ClientSession, error) {
|
||||||
|
|
||||||
@@ -1032,9 +1158,9 @@ func getClientSessionBody(sessions kvdb.RBucket,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getClientSession loads the full ClientSession associated with the serialized
|
// getClientSession loads the full ClientSession associated with the serialized
|
||||||
// session id. This method populates the CommittedUpdates and AckUpdates in
|
// session id. This method populates the CommittedUpdates, AckUpdates and Tower
|
||||||
// addition to the ClientSession's body.
|
// in addition to the ClientSession's body.
|
||||||
func getClientSession(sessions kvdb.RBucket,
|
func getClientSession(sessions, towers kvdb.RBucket,
|
||||||
idBytes []byte) (*ClientSession, error) {
|
idBytes []byte) (*ClientSession, error) {
|
||||||
|
|
||||||
session, err := getClientSessionBody(sessions, idBytes)
|
session, err := getClientSessionBody(sessions, idBytes)
|
||||||
@@ -1042,6 +1168,12 @@ func getClientSession(sessions kvdb.RBucket,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch the tower associated with this session.
|
||||||
|
tower, err := getTower(towers, session.TowerID.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch the committed updates for this session.
|
// Fetch the committed updates for this session.
|
||||||
commitedUpdates, err := getClientSessionCommits(sessions, idBytes)
|
commitedUpdates, err := getClientSessionCommits(sessions, idBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1054,6 +1186,7 @@ func getClientSession(sessions kvdb.RBucket,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.Tower = tower
|
||||||
session.CommittedUpdates = commitedUpdates
|
session.CommittedUpdates = commitedUpdates
|
||||||
session.AckedUpdates = ackedUpdates
|
session.AckedUpdates = ackedUpdates
|
||||||
|
|
||||||
|
|||||||
+143
-227
@@ -1,11 +1,9 @@
|
|||||||
package wtdb_test
|
package wtdb_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
@@ -16,8 +14,12 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtmock"
|
"github.com/lightningnetwork/lnd/watchtower/wtmock"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// pseudoAddr is a fake network address to be used for testing purposes.
|
||||||
|
var pseudoAddr = &net.TCPAddr{IP: []byte{0x01, 0x00, 0x00, 0x00}, Port: 9911}
|
||||||
|
|
||||||
// clientDBInit is a closure used to initialize a wtclient.DB instance.
|
// clientDBInit is a closure used to initialize a wtclient.DB instance.
|
||||||
type clientDBInit func(t *testing.T) wtclient.DB
|
type clientDBInit func(t *testing.T) wtclient.DB
|
||||||
|
|
||||||
@@ -37,23 +39,22 @@ func newClientDBHarness(t *testing.T, init clientDBInit) *clientDBHarness {
|
|||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *clientDBHarness) insertSession(session *wtdb.ClientSession, expErr error) {
|
func (h *clientDBHarness) insertSession(session *wtdb.ClientSession,
|
||||||
|
expErr error) {
|
||||||
|
|
||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
err := h.db.CreateClientSession(session)
|
err := h.db.CreateClientSession(session)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected create client session error: %v, got: %v",
|
|
||||||
expErr, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *clientDBHarness) listSessions(id *wtdb.TowerID) map[wtdb.SessionID]*wtdb.ClientSession {
|
func (h *clientDBHarness) listSessions(
|
||||||
|
id *wtdb.TowerID) map[wtdb.SessionID]*wtdb.ClientSession {
|
||||||
|
|
||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
sessions, err := h.db.ListClientSessions(id)
|
sessions, err := h.db.ListClientSessions(id)
|
||||||
if err != nil {
|
require.NoError(h.t, err, "unable to list client sessions")
|
||||||
h.t.Fatalf("unable to list client sessions: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessions
|
return sessions
|
||||||
}
|
}
|
||||||
@@ -64,13 +65,8 @@ func (h *clientDBHarness) nextKeyIndex(id wtdb.TowerID,
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
index, err := h.db.NextSessionKeyIndex(id, blobType)
|
index, err := h.db.NextSessionKeyIndex(id, blobType)
|
||||||
if err != nil {
|
require.NoError(h.t, err, "unable to create next session key index")
|
||||||
h.t.Fatalf("unable to create next session key index: %v", err)
|
require.NotZero(h.t, index, "next key index should never be 0")
|
||||||
}
|
|
||||||
|
|
||||||
if index == 0 {
|
|
||||||
h.t.Fatalf("next key index should never be 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
@@ -81,20 +77,11 @@ func (h *clientDBHarness) createTower(lnAddr *lnwire.NetAddress,
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
tower, err := h.db.CreateTower(lnAddr)
|
tower, err := h.db.CreateTower(lnAddr)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected create tower error: %v, got: %v", expErr, err)
|
require.NotZero(h.t, tower.ID, "tower id should never be 0")
|
||||||
}
|
|
||||||
|
|
||||||
if tower.ID == 0 {
|
|
||||||
h.t.Fatalf("tower id should never be 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, session := range h.listSessions(&tower.ID) {
|
for _, session := range h.listSessions(&tower.ID) {
|
||||||
if session.Status != wtdb.CSessionActive {
|
require.Equal(h.t, wtdb.CSessionActive, session.Status)
|
||||||
h.t.Fatalf("expected status for session %v to be %v, "+
|
|
||||||
"got %v", session.ID, wtdb.CSessionActive,
|
|
||||||
session.Status)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tower
|
return tower
|
||||||
@@ -105,68 +92,64 @@ func (h *clientDBHarness) removeTower(pubKey *btcec.PublicKey, addr net.Addr,
|
|||||||
|
|
||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
if err := h.db.RemoveTower(pubKey, addr); err != expErr {
|
err := h.db.RemoveTower(pubKey, addr)
|
||||||
h.t.Fatalf("expected remove tower error: %v, got %v", expErr, err)
|
require.ErrorIs(h.t, err, expErr)
|
||||||
}
|
|
||||||
if expErr != nil {
|
if expErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pubKeyStr := pubKey.SerializeCompressed()
|
||||||
|
|
||||||
if addr != nil {
|
if addr != nil {
|
||||||
tower, err := h.db.LoadTower(pubKey)
|
tower, err := h.db.LoadTower(pubKey)
|
||||||
if err != nil {
|
require.NoErrorf(h.t, err, "expected tower %x to still exist",
|
||||||
h.t.Fatalf("expected tower %x to still exist",
|
pubKeyStr)
|
||||||
pubKey.SerializeCompressed())
|
|
||||||
}
|
|
||||||
|
|
||||||
removedAddr := addr.String()
|
removedAddr := addr.String()
|
||||||
for _, towerAddr := range tower.Addresses {
|
for _, towerAddr := range tower.Addresses {
|
||||||
if towerAddr.String() == removedAddr {
|
require.NotEqualf(h.t, removedAddr, towerAddr,
|
||||||
h.t.Fatalf("address %v not removed for tower %x",
|
"address %v not removed for tower %x",
|
||||||
removedAddr, pubKey.SerializeCompressed())
|
removedAddr, pubKeyStr)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tower, err := h.db.LoadTower(pubKey)
|
tower, err := h.db.LoadTower(pubKey)
|
||||||
if hasSessions && err != nil {
|
if hasSessions {
|
||||||
h.t.Fatalf("expected tower %x with sessions to still "+
|
require.NoError(h.t, err, "expected tower %x with "+
|
||||||
"exist", pubKey.SerializeCompressed())
|
"sessions to still exist", pubKeyStr)
|
||||||
}
|
} else {
|
||||||
if !hasSessions && err == nil {
|
require.Errorf(h.t, err, "expected tower %x with no "+
|
||||||
h.t.Fatalf("expected tower %x with no sessions to not "+
|
"sessions to not exist", pubKeyStr)
|
||||||
"exist", pubKey.SerializeCompressed())
|
|
||||||
}
|
|
||||||
if !hasSessions {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, session := range h.listSessions(&tower.ID) {
|
for _, session := range h.listSessions(&tower.ID) {
|
||||||
if session.Status != wtdb.CSessionInactive {
|
require.Equal(h.t, wtdb.CSessionInactive,
|
||||||
h.t.Fatalf("expected status for session %v to "+
|
session.Status, "expected status for session "+
|
||||||
"be %v, got %v", session.ID,
|
"%v to be %v, got %v", session.ID,
|
||||||
wtdb.CSessionInactive, session.Status)
|
wtdb.CSessionInactive, session.Status)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *clientDBHarness) loadTower(pubKey *btcec.PublicKey, expErr error) *wtdb.Tower {
|
func (h *clientDBHarness) loadTower(pubKey *btcec.PublicKey,
|
||||||
|
expErr error) *wtdb.Tower {
|
||||||
|
|
||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
tower, err := h.db.LoadTower(pubKey)
|
tower, err := h.db.LoadTower(pubKey)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected load tower error: %v, got: %v", expErr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tower
|
return tower
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *clientDBHarness) loadTowerByID(id wtdb.TowerID, expErr error) *wtdb.Tower {
|
func (h *clientDBHarness) loadTowerByID(id wtdb.TowerID,
|
||||||
|
expErr error) *wtdb.Tower {
|
||||||
|
|
||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
tower, err := h.db.LoadTowerByID(id)
|
tower, err := h.db.LoadTowerByID(id)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected load tower error: %v, got: %v", expErr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tower
|
return tower
|
||||||
}
|
}
|
||||||
@@ -175,9 +158,7 @@ func (h *clientDBHarness) fetchChanSummaries() map[lnwire.ChannelID]wtdb.ClientC
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
summaries, err := h.db.FetchChanSummaries()
|
summaries, err := h.db.FetchChanSummaries()
|
||||||
if err != nil {
|
require.NoError(h.t, err)
|
||||||
h.t.Fatalf("unable to fetch chan summaries: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return summaries
|
return summaries
|
||||||
}
|
}
|
||||||
@@ -188,10 +169,7 @@ func (h *clientDBHarness) registerChan(chanID lnwire.ChannelID,
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
err := h.db.RegisterChannel(chanID, sweepPkScript)
|
err := h.db.RegisterChannel(chanID, sweepPkScript)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected register channel error: %v, got: %v",
|
|
||||||
expErr, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *clientDBHarness) commitUpdate(id *wtdb.SessionID,
|
func (h *clientDBHarness) commitUpdate(id *wtdb.SessionID,
|
||||||
@@ -200,10 +178,7 @@ func (h *clientDBHarness) commitUpdate(id *wtdb.SessionID,
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
lastApplied, err := h.db.CommitUpdate(id, update)
|
lastApplied, err := h.db.CommitUpdate(id, update)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected commit update error: %v, got: %v",
|
|
||||||
expErr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastApplied
|
return lastApplied
|
||||||
}
|
}
|
||||||
@@ -214,10 +189,22 @@ func (h *clientDBHarness) ackUpdate(id *wtdb.SessionID, seqNum uint16,
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
err := h.db.AckUpdate(id, seqNum, lastApplied)
|
err := h.db.AckUpdate(id, seqNum, lastApplied)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected commit update error: %v, got: %v",
|
}
|
||||||
expErr, err)
|
|
||||||
}
|
// newTower is a helper function that creates a new tower with a randomly
|
||||||
|
// generated public key and inserts it into the client DB.
|
||||||
|
func (h *clientDBHarness) newTower() *wtdb.Tower {
|
||||||
|
h.t.Helper()
|
||||||
|
|
||||||
|
pk, err := randPubKey()
|
||||||
|
require.NoError(h.t, err)
|
||||||
|
|
||||||
|
// Insert a random tower into the database.
|
||||||
|
return h.createTower(&lnwire.NetAddress{
|
||||||
|
IdentityKey: pk,
|
||||||
|
Address: pseudoAddr,
|
||||||
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testCreateClientSession asserts various conditions regarding the creation of
|
// testCreateClientSession asserts various conditions regarding the creation of
|
||||||
@@ -228,10 +215,12 @@ func (h *clientDBHarness) ackUpdate(id *wtdb.SessionID, seqNum uint16,
|
|||||||
func testCreateClientSession(h *clientDBHarness) {
|
func testCreateClientSession(h *clientDBHarness) {
|
||||||
const blobType = blob.TypeAltruistAnchorCommit
|
const blobType = blob.TypeAltruistAnchorCommit
|
||||||
|
|
||||||
|
tower := h.newTower()
|
||||||
|
|
||||||
// Create a test client session to insert.
|
// Create a test client session to insert.
|
||||||
session := &wtdb.ClientSession{
|
session := &wtdb.ClientSession{
|
||||||
ClientSessionBody: wtdb.ClientSessionBody{
|
ClientSessionBody: wtdb.ClientSessionBody{
|
||||||
TowerID: wtdb.TowerID(3),
|
TowerID: tower.ID,
|
||||||
Policy: wtpolicy.Policy{
|
Policy: wtpolicy.Policy{
|
||||||
TxPolicy: wtpolicy.TxPolicy{
|
TxPolicy: wtpolicy.TxPolicy{
|
||||||
BlobType: blobType,
|
BlobType: blobType,
|
||||||
@@ -245,9 +234,9 @@ func testCreateClientSession(h *clientDBHarness) {
|
|||||||
|
|
||||||
// First, assert that this session is not already present in the
|
// First, assert that this session is not already present in the
|
||||||
// database.
|
// database.
|
||||||
if _, ok := h.listSessions(nil)[session.ID]; ok {
|
_, ok := h.listSessions(nil)[session.ID]
|
||||||
h.t.Fatalf("session for id %x should not exist yet", session.ID)
|
require.Falsef(h.t, ok, "session for id %x should not exist yet",
|
||||||
}
|
session.ID)
|
||||||
|
|
||||||
// Attempting to insert the client session without reserving a session
|
// Attempting to insert the client session without reserving a session
|
||||||
// key index should fail.
|
// key index should fail.
|
||||||
@@ -264,10 +253,8 @@ func testCreateClientSession(h *clientDBHarness) {
|
|||||||
// successfully created, it should return the same index to maintain
|
// successfully created, it should return the same index to maintain
|
||||||
// idempotency across restarts.
|
// idempotency across restarts.
|
||||||
keyIndex2 := h.nextKeyIndex(session.TowerID, blobType)
|
keyIndex2 := h.nextKeyIndex(session.TowerID, blobType)
|
||||||
if keyIndex != keyIndex2 {
|
require.Equalf(h.t, keyIndex, keyIndex2, "next key index should "+
|
||||||
h.t.Fatalf("next key index should be idempotent: want: %v, "+
|
"be idempotent: want: %v, got %v", keyIndex, keyIndex2)
|
||||||
"got %v", keyIndex, keyIndex2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, set the client session's key index so that it is proper and
|
// Now, set the client session's key index so that it is proper and
|
||||||
// insert it. This should succeed.
|
// insert it. This should succeed.
|
||||||
@@ -275,9 +262,8 @@ func testCreateClientSession(h *clientDBHarness) {
|
|||||||
h.insertSession(session, nil)
|
h.insertSession(session, nil)
|
||||||
|
|
||||||
// Verify that the session now exists in the database.
|
// Verify that the session now exists in the database.
|
||||||
if _, ok := h.listSessions(nil)[session.ID]; !ok {
|
_, ok = h.listSessions(nil)[session.ID]
|
||||||
h.t.Fatalf("session for id %x should exist now", session.ID)
|
require.Truef(h.t, ok, "session for id %x should exist now", session.ID)
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to insert the session again, which should fail due to the
|
// Attempt to insert the session again, which should fail due to the
|
||||||
// session already existing.
|
// session already existing.
|
||||||
@@ -286,9 +272,8 @@ func testCreateClientSession(h *clientDBHarness) {
|
|||||||
// Finally, assert that reserving another key index succeeds with a
|
// Finally, assert that reserving another key index succeeds with a
|
||||||
// different key index, now that the first one has been finalized.
|
// different key index, now that the first one has been finalized.
|
||||||
keyIndex3 := h.nextKeyIndex(session.TowerID, blobType)
|
keyIndex3 := h.nextKeyIndex(session.TowerID, blobType)
|
||||||
if keyIndex == keyIndex3 {
|
require.NotEqualf(h.t, keyIndex, keyIndex3, "key index still "+
|
||||||
h.t.Fatalf("key index still reserved after creating session")
|
"reserved after creating session")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// testFilterClientSessions asserts that we can correctly filter client sessions
|
// testFilterClientSessions asserts that we can correctly filter client sessions
|
||||||
@@ -300,15 +285,12 @@ func testFilterClientSessions(h *clientDBHarness) {
|
|||||||
const blobType = blob.TypeAltruistCommit
|
const blobType = blob.TypeAltruistCommit
|
||||||
towerSessions := make(map[wtdb.TowerID][]wtdb.SessionID)
|
towerSessions := make(map[wtdb.TowerID][]wtdb.SessionID)
|
||||||
for i := 0; i < numSessions; i++ {
|
for i := 0; i < numSessions; i++ {
|
||||||
towerID := wtdb.TowerID(1)
|
tower := h.newTower()
|
||||||
if i == numSessions-1 {
|
keyIndex := h.nextKeyIndex(tower.ID, blobType)
|
||||||
towerID = wtdb.TowerID(2)
|
|
||||||
}
|
|
||||||
keyIndex := h.nextKeyIndex(towerID, blobType)
|
|
||||||
sessionID := wtdb.SessionID([33]byte{byte(i)})
|
sessionID := wtdb.SessionID([33]byte{byte(i)})
|
||||||
h.insertSession(&wtdb.ClientSession{
|
h.insertSession(&wtdb.ClientSession{
|
||||||
ClientSessionBody: wtdb.ClientSessionBody{
|
ClientSessionBody: wtdb.ClientSessionBody{
|
||||||
TowerID: towerID,
|
TowerID: tower.ID,
|
||||||
Policy: wtpolicy.Policy{
|
Policy: wtpolicy.Policy{
|
||||||
TxPolicy: wtpolicy.TxPolicy{
|
TxPolicy: wtpolicy.TxPolicy{
|
||||||
BlobType: blobType,
|
BlobType: blobType,
|
||||||
@@ -320,22 +302,21 @@ func testFilterClientSessions(h *clientDBHarness) {
|
|||||||
},
|
},
|
||||||
ID: sessionID,
|
ID: sessionID,
|
||||||
}, nil)
|
}, nil)
|
||||||
towerSessions[towerID] = append(towerSessions[towerID], sessionID)
|
towerSessions[tower.ID] = append(
|
||||||
|
towerSessions[tower.ID], sessionID,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should see the expected sessions for each tower when filtering
|
// We should see the expected sessions for each tower when filtering
|
||||||
// them.
|
// them.
|
||||||
for towerID, expectedSessions := range towerSessions {
|
for towerID, expectedSessions := range towerSessions {
|
||||||
sessions := h.listSessions(&towerID)
|
sessions := h.listSessions(&towerID)
|
||||||
if len(sessions) != len(expectedSessions) {
|
require.Len(h.t, sessions, len(expectedSessions))
|
||||||
h.t.Fatalf("expected %v sessions for tower %v, got %v",
|
|
||||||
len(expectedSessions), towerID, len(sessions))
|
|
||||||
}
|
|
||||||
for _, expectedSession := range expectedSessions {
|
for _, expectedSession := range expectedSessions {
|
||||||
if _, ok := sessions[expectedSession]; !ok {
|
_, ok := sessions[expectedSession]
|
||||||
h.t.Fatalf("expected session %v for tower %v",
|
require.Truef(h.t, ok, "expected session %v for "+
|
||||||
expectedSession, towerID)
|
"tower %v", expectedSession, towerID)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,49 +328,31 @@ func testCreateTower(h *clientDBHarness) {
|
|||||||
// Test that loading a tower with an arbitrary tower id fails.
|
// Test that loading a tower with an arbitrary tower id fails.
|
||||||
h.loadTowerByID(20, wtdb.ErrTowerNotFound)
|
h.loadTowerByID(20, wtdb.ErrTowerNotFound)
|
||||||
|
|
||||||
pk, err := randPubKey()
|
tower := h.newTower()
|
||||||
if err != nil {
|
require.Len(h.t, tower.LNAddrs(), 1)
|
||||||
h.t.Fatalf("unable to generate pubkey: %v", err)
|
towerAddr := tower.LNAddrs()[0]
|
||||||
}
|
|
||||||
|
|
||||||
addr1 := &net.TCPAddr{IP: []byte{0x01, 0x00, 0x00, 0x00}, Port: 9911}
|
|
||||||
lnAddr := &lnwire.NetAddress{
|
|
||||||
IdentityKey: pk,
|
|
||||||
Address: addr1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert a random tower into the database.
|
|
||||||
tower := h.createTower(lnAddr, nil)
|
|
||||||
|
|
||||||
// Load the tower from the database and assert that it matches the tower
|
// Load the tower from the database and assert that it matches the tower
|
||||||
// we created.
|
// we created.
|
||||||
tower2 := h.loadTowerByID(tower.ID, nil)
|
tower2 := h.loadTowerByID(tower.ID, nil)
|
||||||
if !reflect.DeepEqual(tower, tower2) {
|
require.Equal(h.t, tower, tower2)
|
||||||
h.t.Fatalf("loaded tower mismatch, want: %v, got: %v",
|
|
||||||
tower, tower2)
|
tower2 = h.loadTower(tower.IdentityKey, nil)
|
||||||
}
|
require.Equal(h.t, tower, tower2)
|
||||||
tower2 = h.loadTower(pk, err)
|
|
||||||
if !reflect.DeepEqual(tower, tower2) {
|
|
||||||
h.t.Fatalf("loaded tower mismatch, want: %v, got: %v",
|
|
||||||
tower, tower2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert the address again into the database. Since the address is the
|
// Insert the address again into the database. Since the address is the
|
||||||
// same, this should result in an unmodified tower record.
|
// same, this should result in an unmodified tower record.
|
||||||
towerDupAddr := h.createTower(lnAddr, nil)
|
towerDupAddr := h.createTower(towerAddr, nil)
|
||||||
if len(towerDupAddr.Addresses) != 1 {
|
require.Lenf(h.t, towerDupAddr.Addresses, 1, "duplicate address "+
|
||||||
h.t.Fatalf("duplicate address should be deduped")
|
"should be deduped")
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(tower, towerDupAddr) {
|
require.Equal(h.t, tower, towerDupAddr)
|
||||||
h.t.Fatalf("mismatch towers, want: %v, got: %v",
|
|
||||||
tower, towerDupAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a new address for this tower.
|
// Generate a new address for this tower.
|
||||||
addr2 := &net.TCPAddr{IP: []byte{0x02, 0x00, 0x00, 0x00}, Port: 9911}
|
addr2 := &net.TCPAddr{IP: []byte{0x02, 0x00, 0x00, 0x00}, Port: 9911}
|
||||||
|
|
||||||
lnAddr2 := &lnwire.NetAddress{
|
lnAddr2 := &lnwire.NetAddress{
|
||||||
IdentityKey: pk,
|
IdentityKey: tower.IdentityKey,
|
||||||
Address: addr2,
|
Address: addr2,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,26 +363,18 @@ func testCreateTower(h *clientDBHarness) {
|
|||||||
// Load the tower from the database, and assert that it matches the
|
// Load the tower from the database, and assert that it matches the
|
||||||
// tower returned from creation.
|
// tower returned from creation.
|
||||||
towerNewAddr2 := h.loadTowerByID(tower.ID, nil)
|
towerNewAddr2 := h.loadTowerByID(tower.ID, nil)
|
||||||
if !reflect.DeepEqual(towerNewAddr, towerNewAddr2) {
|
require.Equal(h.t, towerNewAddr, towerNewAddr2)
|
||||||
h.t.Fatalf("loaded tower mismatch, want: %v, got: %v",
|
|
||||||
towerNewAddr, towerNewAddr2)
|
towerNewAddr2 = h.loadTower(tower.IdentityKey, nil)
|
||||||
}
|
require.Equal(h.t, towerNewAddr, towerNewAddr2)
|
||||||
towerNewAddr2 = h.loadTower(pk, nil)
|
|
||||||
if !reflect.DeepEqual(towerNewAddr, towerNewAddr2) {
|
|
||||||
h.t.Fatalf("loaded tower mismatch, want: %v, got: %v",
|
|
||||||
towerNewAddr, towerNewAddr2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that there are now two addresses on the tower object.
|
// Assert that there are now two addresses on the tower object.
|
||||||
if len(towerNewAddr.Addresses) != 2 {
|
require.Lenf(h.t, towerNewAddr.Addresses, 2, "new address should be "+
|
||||||
h.t.Fatalf("new address should be added")
|
"added")
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, assert that the new address was prepended since it is deemed
|
// Finally, assert that the new address was prepended since it is deemed
|
||||||
// fresher.
|
// fresher.
|
||||||
if !reflect.DeepEqual(tower.Addresses, towerNewAddr.Addresses[1:]) {
|
require.Equal(h.t, tower.Addresses, towerNewAddr.Addresses[1:])
|
||||||
h.t.Fatalf("new address should be prepended")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// testRemoveTower asserts the behavior of removing Tower objects as a whole and
|
// testRemoveTower asserts the behavior of removing Tower objects as a whole and
|
||||||
@@ -427,9 +382,7 @@ func testCreateTower(h *clientDBHarness) {
|
|||||||
func testRemoveTower(h *clientDBHarness) {
|
func testRemoveTower(h *clientDBHarness) {
|
||||||
// Generate a random public key we'll use for our tower.
|
// Generate a random public key we'll use for our tower.
|
||||||
pk, err := randPubKey()
|
pk, err := randPubKey()
|
||||||
if err != nil {
|
require.NoError(h.t, err)
|
||||||
h.t.Fatalf("unable to generate pubkey: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removing a tower that does not exist within the database should
|
// Removing a tower that does not exist within the database should
|
||||||
// result in a NOP.
|
// result in a NOP.
|
||||||
@@ -507,28 +460,23 @@ func testRemoveTower(h *clientDBHarness) {
|
|||||||
func testChanSummaries(h *clientDBHarness) {
|
func testChanSummaries(h *clientDBHarness) {
|
||||||
// First, assert that this channel is not already registered.
|
// First, assert that this channel is not already registered.
|
||||||
var chanID lnwire.ChannelID
|
var chanID lnwire.ChannelID
|
||||||
if _, ok := h.fetchChanSummaries()[chanID]; ok {
|
_, ok := h.fetchChanSummaries()[chanID]
|
||||||
h.t.Fatalf("pkscript for channel %x should not exist yet",
|
require.Falsef(h.t, ok, "pkscript for channel %x should not exist yet",
|
||||||
chanID)
|
chanID)
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a random sweep pkscript and register it for this channel.
|
// Generate a random sweep pkscript and register it for this channel.
|
||||||
expPkScript := make([]byte, 22)
|
expPkScript := make([]byte, 22)
|
||||||
if _, err := io.ReadFull(crand.Reader, expPkScript); err != nil {
|
_, err := io.ReadFull(crand.Reader, expPkScript)
|
||||||
h.t.Fatalf("unable to generate pkscript: %v", err)
|
require.NoError(h.t, err)
|
||||||
}
|
|
||||||
h.registerChan(chanID, expPkScript, nil)
|
h.registerChan(chanID, expPkScript, nil)
|
||||||
|
|
||||||
// Assert that the channel exists and that its sweep pkscript matches
|
// Assert that the channel exists and that its sweep pkscript matches
|
||||||
// the one we registered.
|
// the one we registered.
|
||||||
summary, ok := h.fetchChanSummaries()[chanID]
|
summary, ok := h.fetchChanSummaries()[chanID]
|
||||||
if !ok {
|
require.Truef(h.t, ok, "pkscript for channel %x should not exist yet",
|
||||||
h.t.Fatalf("pkscript for channel %x should not exist yet",
|
chanID)
|
||||||
chanID)
|
require.Equal(h.t, expPkScript, summary.SweepPkScript)
|
||||||
} else if bytes.Compare(expPkScript, summary.SweepPkScript) != 0 {
|
|
||||||
h.t.Fatalf("pkscript mismatch, want: %x, got: %x",
|
|
||||||
expPkScript, summary.SweepPkScript)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, assert that re-registering the same channel produces a
|
// Finally, assert that re-registering the same channel produces a
|
||||||
// failure.
|
// failure.
|
||||||
@@ -538,9 +486,11 @@ func testChanSummaries(h *clientDBHarness) {
|
|||||||
// testCommitUpdate tests the behavior of CommitUpdate, ensuring that they can
|
// testCommitUpdate tests the behavior of CommitUpdate, ensuring that they can
|
||||||
func testCommitUpdate(h *clientDBHarness) {
|
func testCommitUpdate(h *clientDBHarness) {
|
||||||
const blobType = blob.TypeAltruistCommit
|
const blobType = blob.TypeAltruistCommit
|
||||||
|
|
||||||
|
tower := h.newTower()
|
||||||
session := &wtdb.ClientSession{
|
session := &wtdb.ClientSession{
|
||||||
ClientSessionBody: wtdb.ClientSessionBody{
|
ClientSessionBody: wtdb.ClientSessionBody{
|
||||||
TowerID: wtdb.TowerID(3),
|
TowerID: tower.ID,
|
||||||
Policy: wtpolicy.Policy{
|
Policy: wtpolicy.Policy{
|
||||||
TxPolicy: wtpolicy.TxPolicy{
|
TxPolicy: wtpolicy.TxPolicy{
|
||||||
BlobType: blobType,
|
BlobType: blobType,
|
||||||
@@ -565,10 +515,7 @@ func testCommitUpdate(h *clientDBHarness) {
|
|||||||
// succeed. The lastApplied value should be 0 since we have not received
|
// succeed. The lastApplied value should be 0 since we have not received
|
||||||
// an ack from the tower.
|
// an ack from the tower.
|
||||||
lastApplied := h.commitUpdate(&session.ID, update1, nil)
|
lastApplied := h.commitUpdate(&session.ID, update1, nil)
|
||||||
if lastApplied != 0 {
|
require.Zero(h.t, lastApplied)
|
||||||
h.t.Fatalf("last applied mismatch, want: 0, got: %v",
|
|
||||||
lastApplied)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the committed update appears in the client session's
|
// Assert that the committed update appears in the client session's
|
||||||
// CommittedUpdates map when loaded from disk and that there are no
|
// CommittedUpdates map when loaded from disk and that there are no
|
||||||
@@ -584,10 +531,7 @@ func testCommitUpdate(h *clientDBHarness) {
|
|||||||
// the on-disk update's hint). The lastApplied value should remain
|
// the on-disk update's hint). The lastApplied value should remain
|
||||||
// unchanged.
|
// unchanged.
|
||||||
lastApplied2 := h.commitUpdate(&session.ID, update1, nil)
|
lastApplied2 := h.commitUpdate(&session.ID, update1, nil)
|
||||||
if lastApplied2 != lastApplied {
|
require.Equal(h.t, lastApplied, lastApplied2)
|
||||||
h.t.Fatalf("last applied should not have changed, got %v",
|
|
||||||
lastApplied2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the loaded ClientSession is the same as before.
|
// Assert that the loaded ClientSession is the same as before.
|
||||||
dbSession = h.listSessions(nil)[session.ID]
|
dbSession = h.listSessions(nil)[session.ID]
|
||||||
@@ -605,10 +549,7 @@ func testCommitUpdate(h *clientDBHarness) {
|
|||||||
// which should succeed.
|
// which should succeed.
|
||||||
update2.SeqNum = 2
|
update2.SeqNum = 2
|
||||||
lastApplied3 := h.commitUpdate(&session.ID, update2, nil)
|
lastApplied3 := h.commitUpdate(&session.ID, update2, nil)
|
||||||
if lastApplied3 != lastApplied {
|
require.Equal(h.t, lastApplied, lastApplied3)
|
||||||
h.t.Fatalf("last applied should not have changed, got %v",
|
|
||||||
lastApplied3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that both updates now appear as committed on the ClientSession
|
// Check that both updates now appear as committed on the ClientSession
|
||||||
// loaded from disk.
|
// loaded from disk.
|
||||||
@@ -638,10 +579,12 @@ func testCommitUpdate(h *clientDBHarness) {
|
|||||||
func testAckUpdate(h *clientDBHarness) {
|
func testAckUpdate(h *clientDBHarness) {
|
||||||
const blobType = blob.TypeAltruistCommit
|
const blobType = blob.TypeAltruistCommit
|
||||||
|
|
||||||
|
tower := h.newTower()
|
||||||
|
|
||||||
// Create a new session that the updates in this will be tied to.
|
// Create a new session that the updates in this will be tied to.
|
||||||
session := &wtdb.ClientSession{
|
session := &wtdb.ClientSession{
|
||||||
ClientSessionBody: wtdb.ClientSessionBody{
|
ClientSessionBody: wtdb.ClientSessionBody{
|
||||||
TowerID: wtdb.TowerID(3),
|
TowerID: tower.ID,
|
||||||
Policy: wtpolicy.Policy{
|
Policy: wtpolicy.Policy{
|
||||||
TxPolicy: wtpolicy.TxPolicy{
|
TxPolicy: wtpolicy.TxPolicy{
|
||||||
BlobType: blobType,
|
BlobType: blobType,
|
||||||
@@ -668,10 +611,7 @@ func testAckUpdate(h *clientDBHarness) {
|
|||||||
// Commit to a random update at seqnum 1.
|
// Commit to a random update at seqnum 1.
|
||||||
update1 := randCommittedUpdate(h.t, 1)
|
update1 := randCommittedUpdate(h.t, 1)
|
||||||
lastApplied := h.commitUpdate(&session.ID, update1, nil)
|
lastApplied := h.commitUpdate(&session.ID, update1, nil)
|
||||||
if lastApplied != 0 {
|
require.Zero(h.t, lastApplied)
|
||||||
h.t.Fatalf("last applied mismatch, want: 0, got: %v",
|
|
||||||
lastApplied)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acking seqnum 1 should succeed.
|
// Acking seqnum 1 should succeed.
|
||||||
h.ackUpdate(&session.ID, 1, 1, nil)
|
h.ackUpdate(&session.ID, 1, 1, nil)
|
||||||
@@ -699,10 +639,7 @@ func testAckUpdate(h *clientDBHarness) {
|
|||||||
// ack.
|
// ack.
|
||||||
update2 := randCommittedUpdate(h.t, 2)
|
update2 := randCommittedUpdate(h.t, 2)
|
||||||
lastApplied = h.commitUpdate(&session.ID, update2, nil)
|
lastApplied = h.commitUpdate(&session.ID, update2, nil)
|
||||||
if lastApplied != 1 {
|
require.EqualValues(h.t, 1, lastApplied)
|
||||||
h.t.Fatalf("last applied mismatch, want: 1, got: %v",
|
|
||||||
lastApplied)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ack seqnum 2.
|
// Ack seqnum 2.
|
||||||
h.ackUpdate(&session.ID, 2, 2, nil)
|
h.ackUpdate(&session.ID, 2, 2, nil)
|
||||||
@@ -740,10 +677,7 @@ func checkCommittedUpdates(t *testing.T, session *wtdb.ClientSession,
|
|||||||
expUpdates = make([]wtdb.CommittedUpdate, 0)
|
expUpdates = make([]wtdb.CommittedUpdate, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(session.CommittedUpdates, expUpdates) {
|
require.Equal(t, expUpdates, session.CommittedUpdates)
|
||||||
t.Fatalf("committed updates mismatch, want: %v, got: %v",
|
|
||||||
expUpdates, session.CommittedUpdates)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkAckedUpdates asserts that the AckedUpdates on a session match the
|
// checkAckedUpdates asserts that the AckedUpdates on a session match the
|
||||||
@@ -758,10 +692,7 @@ func checkAckedUpdates(t *testing.T, session *wtdb.ClientSession,
|
|||||||
expUpdates = make(map[uint16]wtdb.BackupID)
|
expUpdates = make(map[uint16]wtdb.BackupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(session.AckedUpdates, expUpdates) {
|
require.Equal(t, expUpdates, session.AckedUpdates)
|
||||||
t.Fatalf("acked updates mismatch, want: %v, got: %v",
|
|
||||||
expUpdates, session.AckedUpdates)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestClientDB asserts the behavior of a fresh client db, a reopened client db,
|
// TestClientDB asserts the behavior of a fresh client db, a reopened client db,
|
||||||
@@ -779,14 +710,10 @@ func TestClientDB(t *testing.T) {
|
|||||||
bdb, err := wtdb.NewBoltBackendCreator(
|
bdb, err := wtdb.NewBoltBackendCreator(
|
||||||
true, t.TempDir(), "wtclient.db",
|
true, t.TempDir(), "wtclient.db",
|
||||||
)(dbCfg)
|
)(dbCfg)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := wtdb.OpenClientDB(bdb)
|
db, err := wtdb.OpenClientDB(bdb)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
db.Close()
|
db.Close()
|
||||||
@@ -803,27 +730,19 @@ func TestClientDB(t *testing.T) {
|
|||||||
bdb, err := wtdb.NewBoltBackendCreator(
|
bdb, err := wtdb.NewBoltBackendCreator(
|
||||||
true, path, "wtclient.db",
|
true, path, "wtclient.db",
|
||||||
)(dbCfg)
|
)(dbCfg)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := wtdb.OpenClientDB(bdb)
|
db, err := wtdb.OpenClientDB(bdb)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
db.Close()
|
db.Close()
|
||||||
|
|
||||||
bdb, err = wtdb.NewBoltBackendCreator(
|
bdb, err = wtdb.NewBoltBackendCreator(
|
||||||
true, path, "wtclient.db",
|
true, path, "wtclient.db",
|
||||||
)(dbCfg)
|
)(dbCfg)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err = wtdb.OpenClientDB(bdb)
|
db, err = wtdb.OpenClientDB(bdb)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to reopen db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
db.Close()
|
db.Close()
|
||||||
@@ -893,19 +812,16 @@ func TestClientDB(t *testing.T) {
|
|||||||
// randCommittedUpdate generates a random committed update.
|
// randCommittedUpdate generates a random committed update.
|
||||||
func randCommittedUpdate(t *testing.T, seqNum uint16) *wtdb.CommittedUpdate {
|
func randCommittedUpdate(t *testing.T, seqNum uint16) *wtdb.CommittedUpdate {
|
||||||
var chanID lnwire.ChannelID
|
var chanID lnwire.ChannelID
|
||||||
if _, err := io.ReadFull(crand.Reader, chanID[:]); err != nil {
|
_, err := io.ReadFull(crand.Reader, chanID[:])
|
||||||
t.Fatalf("unable to generate chan id: %v", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
var hint blob.BreachHint
|
var hint blob.BreachHint
|
||||||
if _, err := io.ReadFull(crand.Reader, hint[:]); err != nil {
|
_, err = io.ReadFull(crand.Reader, hint[:])
|
||||||
t.Fatalf("unable to generate breach hint: %v", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
encBlob := make([]byte, blob.Size(blob.FlagCommitOutputs.Type()))
|
encBlob := make([]byte, blob.Size(blob.FlagCommitOutputs.Type()))
|
||||||
if _, err := io.ReadFull(crand.Reader, encBlob); err != nil {
|
_, err = io.ReadFull(crand.Reader, encBlob)
|
||||||
t.Fatalf("unable to generate encrypted blob: %v", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
return &wtdb.CommittedUpdate{
|
return &wtdb.CommittedUpdate{
|
||||||
SeqNum: seqNum,
|
SeqNum: seqNum,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/lightningnetwork/lnd/tor"
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func randPubKey() (*btcec.PublicKey, error) {
|
func randPubKey() (*btcec.PublicKey, error) {
|
||||||
@@ -134,10 +135,7 @@ func TestCodec(tt *testing.T) {
|
|||||||
// Ensure encoding the object succeeds.
|
// Ensure encoding the object succeeds.
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
err := obj.Encode(&b)
|
err := obj.Encode(&b)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to encode: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj2 dbObject
|
var obj2 dbObject
|
||||||
switch obj.(type) {
|
switch obj.(type) {
|
||||||
@@ -162,17 +160,10 @@ func TestCodec(tt *testing.T) {
|
|||||||
|
|
||||||
// Ensure decoding the object succeeds.
|
// Ensure decoding the object succeeds.
|
||||||
err = obj2.Decode(bytes.NewReader(b.Bytes()))
|
err = obj2.Decode(bytes.NewReader(b.Bytes()))
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to decode: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert the original and decoded object match.
|
// Assert the original and decoded object match.
|
||||||
if !reflect.DeepEqual(obj, obj2) {
|
require.Equal(t, obj, obj2)
|
||||||
t.Fatalf("encode/decode mismatch, want: %v, "+
|
|
||||||
"got: %v", obj, obj2)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -180,16 +171,10 @@ func TestCodec(tt *testing.T) {
|
|||||||
customTypeGen := map[string]func([]reflect.Value, *rand.Rand){
|
customTypeGen := map[string]func([]reflect.Value, *rand.Rand){
|
||||||
"Tower": func(v []reflect.Value, r *rand.Rand) {
|
"Tower": func(v []reflect.Value, r *rand.Rand) {
|
||||||
pk, err := randPubKey()
|
pk, err := randPubKey()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to generate pubkey: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := randAddrs(r)
|
addrs, err := randAddrs(r)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to generate addrs: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := wtdb.Tower{
|
obj := wtdb.Tower{
|
||||||
IdentityKey: pk,
|
IdentityKey: pk,
|
||||||
@@ -260,10 +245,7 @@ func TestCodec(tt *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := quick.Check(test.scenario, config)
|
err := quick.Check(test.scenario, config)
|
||||||
if err != nil {
|
require.NoError(h, err)
|
||||||
t.Fatalf("fuzz checks for msg=%s failed: %v",
|
|
||||||
test.name, err)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package wtdb
|
|||||||
import (
|
import (
|
||||||
"github.com/btcsuite/btclog"
|
"github.com/btcsuite/btclog"
|
||||||
"github.com/lightningnetwork/lnd/build"
|
"github.com/lightningnetwork/lnd/build"
|
||||||
|
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// log is a logger that is initialized with no output filters. This
|
// log is a logger that is initialized with no output filters. This
|
||||||
@@ -26,6 +27,7 @@ func DisableLog() {
|
|||||||
// using btclog.
|
// using btclog.
|
||||||
func UseLogger(logger btclog.Logger) {
|
func UseLogger(logger btclog.Logger) {
|
||||||
log = logger
|
log = logger
|
||||||
|
migration1.UseLogger(logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logClosure is used to provide a closure over expensive logging operations so
|
// logClosure is used to provide a closure over expensive logging operations so
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package migration1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// cSessionBkt is a top-level bucket storing:
|
||||||
|
// session-id => cSessionBody -> encoded ClientSessionBody
|
||||||
|
// => cSessionCommits => seqnum -> encoded CommittedUpdate
|
||||||
|
// => cSessionAcks => seqnum -> encoded BackupID
|
||||||
|
cSessionBkt = []byte("client-session-bucket")
|
||||||
|
|
||||||
|
// cSessionBody is a sub-bucket of cSessionBkt storing only the body of
|
||||||
|
// the ClientSession.
|
||||||
|
cSessionBody = []byte("client-session-body")
|
||||||
|
|
||||||
|
// cTowerIDToSessionIDIndexBkt is a top-level bucket storing:
|
||||||
|
// tower-id -> session-id -> 1
|
||||||
|
cTowerIDToSessionIDIndexBkt = []byte(
|
||||||
|
"client-tower-to-session-index-bucket",
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrUninitializedDB signals that top-level buckets for the database
|
||||||
|
// have not been initialized.
|
||||||
|
ErrUninitializedDB = errors.New("db not initialized")
|
||||||
|
|
||||||
|
// ErrClientSessionNotFound signals that the requested client session
|
||||||
|
// was not found in the database.
|
||||||
|
ErrClientSessionNotFound = errors.New("client session not found")
|
||||||
|
|
||||||
|
// ErrCorruptClientSession signals that the client session's on-disk
|
||||||
|
// structure deviates from what is expected.
|
||||||
|
ErrCorruptClientSession = errors.New("client session corrupted")
|
||||||
|
)
|
||||||
|
|
||||||
|
// MigrateTowerToSessionIndex constructs a new towerID-to-sessionID for the
|
||||||
|
// watchtower client DB.
|
||||||
|
func MigrateTowerToSessionIndex(tx kvdb.RwTx) error {
|
||||||
|
log.Infof("Migrating the tower client db to add a " +
|
||||||
|
"towerID-to-sessionID index")
|
||||||
|
|
||||||
|
// First, we collect all the entries we want to add to the index.
|
||||||
|
entries, err := getIndexEntries(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then we create a new top-level bucket for the index.
|
||||||
|
indexBkt, err := tx.CreateTopLevelBucket(cTowerIDToSessionIDIndexBkt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we add all the collected entries to the index.
|
||||||
|
for towerID, sessions := range entries {
|
||||||
|
// Create a sub-bucket using the tower ID.
|
||||||
|
towerBkt, err := indexBkt.CreateBucketIfNotExists(
|
||||||
|
towerID.Bytes(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for sessionID := range sessions {
|
||||||
|
err := addIndex(towerBkt, sessionID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addIndex adds a new towerID-sessionID pair to the given bucket. The
|
||||||
|
// session ID is used as a key within the bucket and a value of []byte{1} is
|
||||||
|
// used for each session ID key.
|
||||||
|
func addIndex(towerBkt kvdb.RwBucket, sessionID SessionID) error {
|
||||||
|
session := towerBkt.Get(sessionID[:])
|
||||||
|
if session != nil {
|
||||||
|
return fmt.Errorf("session %x duplicated", sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return towerBkt.Put(sessionID[:], []byte{1})
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIndexEntries collects all the towerID-sessionID entries that need to be
|
||||||
|
// added to the new index.
|
||||||
|
func getIndexEntries(tx kvdb.RwTx) (map[TowerID]map[SessionID]bool, error) {
|
||||||
|
sessions := tx.ReadBucket(cSessionBkt)
|
||||||
|
if sessions == nil {
|
||||||
|
return nil, ErrUninitializedDB
|
||||||
|
}
|
||||||
|
|
||||||
|
index := make(map[TowerID]map[SessionID]bool)
|
||||||
|
err := sessions.ForEach(func(k, _ []byte) error {
|
||||||
|
session, err := getClientSession(sessions, k)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if index[session.TowerID] == nil {
|
||||||
|
index[session.TowerID] = make(map[SessionID]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
index[session.TowerID][session.ID] = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return index, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClientSession fetches the session with the given ID from the db.
|
||||||
|
func getClientSession(sessions kvdb.RBucket, idBytes []byte) (*ClientSession,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
sessionBkt := sessions.NestedReadBucket(idBytes)
|
||||||
|
if sessionBkt == nil {
|
||||||
|
return nil, ErrClientSessionNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should never have a sessionBkt without also having its body.
|
||||||
|
sessionBody := sessionBkt.Get(cSessionBody)
|
||||||
|
if sessionBody == nil {
|
||||||
|
return nil, ErrCorruptClientSession
|
||||||
|
}
|
||||||
|
|
||||||
|
var session ClientSession
|
||||||
|
copy(session.ID[:], idBytes)
|
||||||
|
|
||||||
|
err := session.Decode(bytes.NewReader(sessionBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &session, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package migration1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb/migtest"
|
||||||
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
s1 = &ClientSessionBody{
|
||||||
|
TowerID: TowerID(1),
|
||||||
|
}
|
||||||
|
s2 = &ClientSessionBody{
|
||||||
|
TowerID: TowerID(3),
|
||||||
|
}
|
||||||
|
s3 = &ClientSessionBody{
|
||||||
|
TowerID: TowerID(6),
|
||||||
|
}
|
||||||
|
|
||||||
|
// pre is the expected data in the DB before the migration.
|
||||||
|
pre = map[string]interface{}{
|
||||||
|
sessionIDString("1"): map[string]interface{}{
|
||||||
|
string(cSessionBody): clientSessionString(s1),
|
||||||
|
},
|
||||||
|
sessionIDString("2"): map[string]interface{}{
|
||||||
|
string(cSessionBody): clientSessionString(s3),
|
||||||
|
},
|
||||||
|
sessionIDString("3"): map[string]interface{}{
|
||||||
|
string(cSessionBody): clientSessionString(s1),
|
||||||
|
},
|
||||||
|
sessionIDString("4"): map[string]interface{}{
|
||||||
|
string(cSessionBody): clientSessionString(s1),
|
||||||
|
},
|
||||||
|
sessionIDString("5"): map[string]interface{}{
|
||||||
|
string(cSessionBody): clientSessionString(s2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// preFailNoSessionBody should fail the migration due to there being a
|
||||||
|
// session without an associated session body.
|
||||||
|
preFailNoSessionBody = map[string]interface{}{
|
||||||
|
sessionIDString("1"): map[string]interface{}{
|
||||||
|
string(cSessionBody): clientSessionString(s1),
|
||||||
|
},
|
||||||
|
sessionIDString("2"): map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// post is the expected data after migration.
|
||||||
|
post = map[string]interface{}{
|
||||||
|
towerIDString(1): map[string]interface{}{
|
||||||
|
sessionIDString("1"): string([]byte{1}),
|
||||||
|
sessionIDString("3"): string([]byte{1}),
|
||||||
|
sessionIDString("4"): string([]byte{1}),
|
||||||
|
},
|
||||||
|
towerIDString(3): map[string]interface{}{
|
||||||
|
sessionIDString("5"): string([]byte{1}),
|
||||||
|
},
|
||||||
|
towerIDString(6): map[string]interface{}{
|
||||||
|
sessionIDString("2"): string([]byte{1}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMigrateTowerToSessionIndex tests that the TestMigrateTowerToSessionIndex
|
||||||
|
// function correctly adds a new towerID-to-sessionID index to the tower client
|
||||||
|
// db.
|
||||||
|
func TestMigrateTowerToSessionIndex(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
shouldFail bool
|
||||||
|
pre map[string]interface{}
|
||||||
|
post map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "migration ok",
|
||||||
|
shouldFail: false,
|
||||||
|
pre: pre,
|
||||||
|
post: post,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail due to corrupt db",
|
||||||
|
shouldFail: true,
|
||||||
|
pre: preFailNoSessionBody,
|
||||||
|
post: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no sessions",
|
||||||
|
shouldFail: false,
|
||||||
|
pre: nil,
|
||||||
|
post: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
// Before the migration we have a sessions bucket.
|
||||||
|
before := func(tx kvdb.RwTx) error {
|
||||||
|
return migtest.RestoreDB(
|
||||||
|
tx, cSessionBkt, test.pre,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// After the migration, we should have an untouched
|
||||||
|
// sessions bucket and a new index bucket.
|
||||||
|
after := func(tx kvdb.RwTx) error {
|
||||||
|
if err := migtest.VerifyDB(
|
||||||
|
tx, cSessionBkt, test.pre,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we expect our migration to fail, we don't
|
||||||
|
// expect an index bucket.
|
||||||
|
if test.shouldFail {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return migtest.VerifyDB(
|
||||||
|
tx, cTowerIDToSessionIDIndexBkt,
|
||||||
|
test.post,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
migtest.ApplyMigration(
|
||||||
|
t, before, after, MigrateTowerToSessionIndex,
|
||||||
|
test.shouldFail,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionIDString(id string) string {
|
||||||
|
var sessID SessionID
|
||||||
|
copy(sessID[:], id)
|
||||||
|
return string(sessID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientSessionString(s *ClientSessionBody) string {
|
||||||
|
var b bytes.Buffer
|
||||||
|
err := s.Encode(&b)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func towerIDString(id int) string {
|
||||||
|
towerID := TowerID(id)
|
||||||
|
return string(towerID.Bytes())
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package migration1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
|
"github.com/lightningnetwork/lnd/watchtower/blob"
|
||||||
|
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionIDSize is 33-bytes; it is a serialized, compressed public key.
|
||||||
|
const SessionIDSize = 33
|
||||||
|
|
||||||
|
// UnknownElementType is an alias for channeldb.UnknownElementType.
|
||||||
|
type UnknownElementType = channeldb.UnknownElementType
|
||||||
|
|
||||||
|
// SessionID is created from the remote public key of a client, and serves as a
|
||||||
|
// unique identifier and authentication for sending state updates.
|
||||||
|
type SessionID [SessionIDSize]byte
|
||||||
|
|
||||||
|
// TowerID is a unique 64-bit identifier allocated to each unique watchtower.
|
||||||
|
// This allows the client to conserve on-disk space by not needing to always
|
||||||
|
// reference towers by their pubkey.
|
||||||
|
type TowerID uint64
|
||||||
|
|
||||||
|
// Bytes encodes a TowerID into an 8-byte slice in big-endian byte order.
|
||||||
|
func (id TowerID) Bytes() []byte {
|
||||||
|
var buf [8]byte
|
||||||
|
binary.BigEndian.PutUint64(buf[:], uint64(id))
|
||||||
|
return buf[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientSession encapsulates a SessionInfo returned from a successful
|
||||||
|
// session negotiation, and also records the tower and ephemeral secret used for
|
||||||
|
// communicating with the tower.
|
||||||
|
type ClientSession struct {
|
||||||
|
// ID is the client's public key used when authenticating with the
|
||||||
|
// tower.
|
||||||
|
ID SessionID
|
||||||
|
ClientSessionBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSessionStatus is a bit-field representing the possible statuses of
|
||||||
|
// ClientSessions.
|
||||||
|
type CSessionStatus uint8
|
||||||
|
|
||||||
|
type ClientSessionBody struct {
|
||||||
|
// SeqNum is the next unallocated sequence number that can be sent to
|
||||||
|
// the tower.
|
||||||
|
SeqNum uint16
|
||||||
|
|
||||||
|
// TowerLastApplied the last last-applied the tower has echoed back.
|
||||||
|
TowerLastApplied uint16
|
||||||
|
|
||||||
|
// TowerID is the unique, db-assigned identifier that references the
|
||||||
|
// Tower with which the session is negotiated.
|
||||||
|
TowerID TowerID
|
||||||
|
|
||||||
|
// KeyIndex is the index of key locator used to derive the client's
|
||||||
|
// session key so that it can authenticate with the tower to update its
|
||||||
|
// session. In order to rederive the private key, the key locator should
|
||||||
|
// use the keychain.KeyFamilyTowerSession key family.
|
||||||
|
KeyIndex uint32
|
||||||
|
|
||||||
|
// Policy holds the negotiated session parameters.
|
||||||
|
Policy wtpolicy.Policy
|
||||||
|
|
||||||
|
// Status indicates the current state of the ClientSession.
|
||||||
|
Status CSessionStatus
|
||||||
|
|
||||||
|
// RewardPkScript is the pkscript that the tower's reward will be
|
||||||
|
// deposited to if a sweep transaction confirms and the sessions
|
||||||
|
// specifies a reward output.
|
||||||
|
RewardPkScript []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes a ClientSessionBody to the passed io.Writer.
|
||||||
|
func (s *ClientSessionBody) Encode(w io.Writer) error {
|
||||||
|
return WriteElements(w,
|
||||||
|
s.SeqNum,
|
||||||
|
s.TowerLastApplied,
|
||||||
|
uint64(s.TowerID),
|
||||||
|
s.KeyIndex,
|
||||||
|
uint8(s.Status),
|
||||||
|
s.Policy,
|
||||||
|
s.RewardPkScript,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads a ClientSessionBody from the passed io.Reader.
|
||||||
|
func (s *ClientSessionBody) Decode(r io.Reader) error {
|
||||||
|
var (
|
||||||
|
towerID uint64
|
||||||
|
status uint8
|
||||||
|
)
|
||||||
|
err := ReadElements(r,
|
||||||
|
&s.SeqNum,
|
||||||
|
&s.TowerLastApplied,
|
||||||
|
&towerID,
|
||||||
|
&s.KeyIndex,
|
||||||
|
&status,
|
||||||
|
&s.Policy,
|
||||||
|
&s.RewardPkScript,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.TowerID = TowerID(towerID)
|
||||||
|
s.Status = CSessionStatus(status)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteElements serializes a variadic list of elements into the given
|
||||||
|
// io.Writer.
|
||||||
|
func WriteElements(w io.Writer, elements ...interface{}) error {
|
||||||
|
for _, element := range elements {
|
||||||
|
if err := WriteElement(w, element); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteElement serializes a single element into the provided io.Writer.
|
||||||
|
func WriteElement(w io.Writer, element interface{}) error {
|
||||||
|
err := channeldb.WriteElement(w, element)
|
||||||
|
switch {
|
||||||
|
// Known to channeldb codec.
|
||||||
|
case err == nil:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Fail if error is not UnknownElementType.
|
||||||
|
default:
|
||||||
|
if _, ok := err.(UnknownElementType); !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process any wtdb-specific extensions to the codec.
|
||||||
|
switch e := element.(type) {
|
||||||
|
case SessionID:
|
||||||
|
if _, err := w.Write(e[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case blob.BreachHint:
|
||||||
|
if _, err := w.Write(e[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case wtpolicy.Policy:
|
||||||
|
return channeldb.WriteElements(w,
|
||||||
|
uint16(e.BlobType),
|
||||||
|
e.MaxUpdates,
|
||||||
|
e.RewardBase,
|
||||||
|
e.RewardRate,
|
||||||
|
uint64(e.SweepFeeRate),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type is still unknown to wtdb extensions, fail.
|
||||||
|
default:
|
||||||
|
return channeldb.NewUnknownElementType(
|
||||||
|
"WriteElement", element,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadElements deserializes the provided io.Reader into a variadic list of
|
||||||
|
// target elements.
|
||||||
|
func ReadElements(r io.Reader, elements ...interface{}) error {
|
||||||
|
for _, element := range elements {
|
||||||
|
if err := ReadElement(r, element); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadElement deserializes a single element from the provided io.Reader.
|
||||||
|
func ReadElement(r io.Reader, element interface{}) error {
|
||||||
|
err := channeldb.ReadElement(r, element)
|
||||||
|
switch {
|
||||||
|
// Known to channeldb codec.
|
||||||
|
case err == nil:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Fail if error is not UnknownElementType.
|
||||||
|
default:
|
||||||
|
if _, ok := err.(UnknownElementType); !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process any wtdb-specific extensions to the codec.
|
||||||
|
switch e := element.(type) {
|
||||||
|
case *SessionID:
|
||||||
|
if _, err := io.ReadFull(r, e[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case *blob.BreachHint:
|
||||||
|
if _, err := io.ReadFull(r, e[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case *wtpolicy.Policy:
|
||||||
|
var (
|
||||||
|
blobType uint16
|
||||||
|
sweepFeeRate uint64
|
||||||
|
)
|
||||||
|
err := channeldb.ReadElements(r,
|
||||||
|
&blobType,
|
||||||
|
&e.MaxUpdates,
|
||||||
|
&e.RewardBase,
|
||||||
|
&e.RewardRate,
|
||||||
|
&sweepFeeRate,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.BlobType = blob.Type(blobType)
|
||||||
|
e.SweepFeeRate = chainfee.SatPerKWeight(sweepFeeRate)
|
||||||
|
|
||||||
|
// Type is still unknown to wtdb extensions, fail.
|
||||||
|
default:
|
||||||
|
return channeldb.NewUnknownElementType(
|
||||||
|
"ReadElement", element,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package migration1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btclog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// log is a logger that is initialized as disabled. This means the package will
|
||||||
|
// not perform any logging by default until a logger is set.
|
||||||
|
var log = btclog.Disabled
|
||||||
|
|
||||||
|
// UseLogger uses a specified Logger to output package logging info.
|
||||||
|
func UseLogger(logger btclog.Logger) {
|
||||||
|
log = logger
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package wtdb_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
@@ -14,6 +13,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtmock"
|
"github.com/lightningnetwork/lnd/watchtower/wtmock"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -48,10 +48,7 @@ func (h *towerDBHarness) insertSession(s *wtdb.SessionInfo, expErr error) {
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
err := h.db.InsertSessionInfo(s)
|
err := h.db.InsertSessionInfo(s)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected insert session error: %v, got : %v",
|
|
||||||
expErr, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSession retrieves the session identified by id, asserting that the call
|
// getSession retrieves the session identified by id, asserting that the call
|
||||||
@@ -62,10 +59,7 @@ func (h *towerDBHarness) getSession(id *wtdb.SessionID,
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
session, err := h.db.GetSessionInfo(id)
|
session, err := h.db.GetSessionInfo(id)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected get session error: %v, got: %v",
|
|
||||||
expErr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
@@ -79,10 +73,7 @@ func (h *towerDBHarness) insertUpdate(s *wtdb.SessionStateUpdate,
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
lastApplied, err := h.db.InsertStateUpdate(s)
|
lastApplied, err := h.db.InsertStateUpdate(s)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected insert update error: %v, got: %v",
|
|
||||||
expErr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastApplied
|
return lastApplied
|
||||||
}
|
}
|
||||||
@@ -93,10 +84,7 @@ func (h *towerDBHarness) deleteSession(id wtdb.SessionID, expErr error) {
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
err := h.db.DeleteSession(id)
|
err := h.db.DeleteSession(id)
|
||||||
if err != expErr {
|
require.ErrorIs(h.t, err, expErr)
|
||||||
h.t.Fatalf("expected deletion error: %v, got: %v",
|
|
||||||
expErr, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// queryMatches queries that database for the passed breach hint, returning all
|
// queryMatches queries that database for the passed breach hint, returning all
|
||||||
@@ -105,9 +93,7 @@ func (h *towerDBHarness) queryMatches(hint blob.BreachHint) []wtdb.Match {
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
matches, err := h.db.QueryMatches([]blob.BreachHint{hint})
|
matches, err := h.db.QueryMatches([]blob.BreachHint{hint})
|
||||||
if err != nil {
|
require.NoError(h.t, err)
|
||||||
h.t.Fatalf("unable to query matches: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
@@ -119,14 +105,10 @@ func (h *towerDBHarness) hasUpdate(hint blob.BreachHint) wtdb.Match {
|
|||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
matches := h.queryMatches(hint)
|
matches := h.queryMatches(hint)
|
||||||
if len(matches) != 1 {
|
require.Len(h.t, matches, 1)
|
||||||
h.t.Fatalf("expected 1 match, found: %d", len(matches))
|
|
||||||
}
|
|
||||||
|
|
||||||
match := matches[0]
|
match := matches[0]
|
||||||
if match.Hint != hint {
|
require.Equal(h.t, hint, match.Hint)
|
||||||
h.t.Fatalf("expected hint: %x, got: %x", hint, match.Hint)
|
|
||||||
}
|
|
||||||
|
|
||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
@@ -158,11 +140,7 @@ func testInsertSession(h *towerDBHarness) {
|
|||||||
h.insertSession(session, nil)
|
h.insertSession(session, nil)
|
||||||
|
|
||||||
session2 := h.getSession(&id, nil)
|
session2 := h.getSession(&id, nil)
|
||||||
|
require.Equal(h.t, session, session2)
|
||||||
if !reflect.DeepEqual(session, session2) {
|
|
||||||
h.t.Fatalf("expected session: %v, got %v",
|
|
||||||
session, session2)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.insertSession(session, nil)
|
h.insertSession(session, nil)
|
||||||
|
|
||||||
@@ -211,28 +189,21 @@ func testMultipleMatches(h *towerDBHarness) {
|
|||||||
|
|
||||||
// Query the db for matches on the chosen hint.
|
// Query the db for matches on the chosen hint.
|
||||||
matches := h.queryMatches(hint)
|
matches := h.queryMatches(hint)
|
||||||
if len(matches) != numUpdates {
|
require.Len(h.t, matches, numUpdates)
|
||||||
h.t.Fatalf("num updates mismatch, want: %d, got: %d",
|
|
||||||
numUpdates, len(matches))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the hints are what we asked for, and compute the set of
|
// Assert that the hints are what we asked for, and compute the set of
|
||||||
// sessions returned.
|
// sessions returned.
|
||||||
sessions := make(map[wtdb.SessionID]struct{})
|
sessions := make(map[wtdb.SessionID]struct{})
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
if match.Hint != hint {
|
require.Equal(h.t, hint, match.Hint)
|
||||||
h.t.Fatalf("hint mismatch, want: %v, got: %v",
|
|
||||||
hint, match.Hint)
|
|
||||||
}
|
|
||||||
sessions[match.ID] = struct{}{}
|
sessions[match.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert that the sessions returned match the session ids of the
|
// Assert that the sessions returned match the session ids of the
|
||||||
// sessions we initially created.
|
// sessions we initially created.
|
||||||
for i := 0; i < numUpdates; i++ {
|
for i := 0; i < numUpdates; i++ {
|
||||||
if _, ok := sessions[*id(i)]; !ok {
|
_, ok := sessions[*id(i)]
|
||||||
h.t.Fatalf("match for session %v not found", *id(i))
|
require.Truef(h.t, ok, "match for session %v not found", *id(i))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,33 +213,22 @@ func testMultipleMatches(h *towerDBHarness) {
|
|||||||
func testLookoutTip(h *towerDBHarness) {
|
func testLookoutTip(h *towerDBHarness) {
|
||||||
// Retrieve lookout tip on fresh db.
|
// Retrieve lookout tip on fresh db.
|
||||||
epoch, err := h.db.GetLookoutTip()
|
epoch, err := h.db.GetLookoutTip()
|
||||||
if err != nil {
|
require.NoError(h.t, err)
|
||||||
h.t.Fatalf("unable to fetch lookout tip: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the epoch is nil.
|
// Assert that the epoch is nil.
|
||||||
if epoch != nil {
|
require.Nil(h.t, epoch)
|
||||||
h.t.Fatalf("lookout tip should not be set, found: %v", epoch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a closure that inserts an epoch, retrieves it, and asserts
|
// Create a closure that inserts an epoch, retrieves it, and asserts
|
||||||
// that the returned epoch matches what was inserted.
|
// that the returned epoch matches what was inserted.
|
||||||
setAndCheck := func(i int) {
|
setAndCheck := func(i int) {
|
||||||
expEpoch := epochFromInt(1)
|
expEpoch := epochFromInt(1)
|
||||||
err = h.db.SetLookoutTip(expEpoch)
|
err = h.db.SetLookoutTip(expEpoch)
|
||||||
if err != nil {
|
require.NoError(h.t, err)
|
||||||
h.t.Fatalf("unable to set lookout tip: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
epoch, err = h.db.GetLookoutTip()
|
epoch, err = h.db.GetLookoutTip()
|
||||||
if err != nil {
|
require.NoError(h.t, err)
|
||||||
h.t.Fatalf("unable to fetch lookout tip: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(epoch, expEpoch) {
|
require.Equal(h.t, expEpoch, epoch)
|
||||||
h.t.Fatalf("lookout tip mismatch, want: %v, got: %v",
|
|
||||||
expEpoch, epoch)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set and assert the lookout tip.
|
// Set and assert the lookout tip.
|
||||||
@@ -348,15 +308,10 @@ func testDeleteSession(h *towerDBHarness) {
|
|||||||
|
|
||||||
// Assert that only one update is still present.
|
// Assert that only one update is still present.
|
||||||
matches := h.queryMatches(hint)
|
matches := h.queryMatches(hint)
|
||||||
if len(matches) != 1 {
|
require.Len(h.t, matches, 1)
|
||||||
h.t.Fatalf("expected one update, found: %d", len(matches))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the update belongs to the first session.
|
// Assert that the update belongs to the first session.
|
||||||
if matches[0].ID != *id0 {
|
require.Equal(h.t, *id0, matches[0].ID)
|
||||||
h.t.Fatalf("expected match for %v, instead is for: %v",
|
|
||||||
*id0, matches[0].ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, remove the first session added.
|
// Finally, remove the first session added.
|
||||||
h.deleteSession(*id0, nil)
|
h.deleteSession(*id0, nil)
|
||||||
@@ -366,9 +321,7 @@ func testDeleteSession(h *towerDBHarness) {
|
|||||||
|
|
||||||
// No matches should exist for this hint.
|
// No matches should exist for this hint.
|
||||||
matches = h.queryMatches(hint)
|
matches = h.queryMatches(hint)
|
||||||
if len(matches) != 0 {
|
require.Zero(h.t, len(matches))
|
||||||
h.t.Fatalf("expected zero updates, found: %d", len(matches))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type stateUpdateTest struct {
|
type stateUpdateTest struct {
|
||||||
@@ -403,10 +356,9 @@ func runStateUpdateTest(test stateUpdateTest) func(*towerDBHarness) {
|
|||||||
*expSession = *test.session
|
*expSession = *test.session
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(test.updates) != len(test.updateErrs) {
|
require.Lenf(h.t, test.updates, len(test.updateErrs),
|
||||||
h.t.Fatalf("malformed test case, num updates " +
|
"malformed test case, num updates should match num "+
|
||||||
"should match num errors")
|
"errors")
|
||||||
}
|
|
||||||
|
|
||||||
// Send any updates provided in the test.
|
// Send any updates provided in the test.
|
||||||
for i, update := range test.updates {
|
for i, update := range test.updates {
|
||||||
@@ -430,10 +382,7 @@ func runStateUpdateTest(test stateUpdateTest) func(*towerDBHarness) {
|
|||||||
expSession.ClientLastApplied = update.LastApplied
|
expSession.ClientLastApplied = update.LastApplied
|
||||||
|
|
||||||
match := h.hasUpdate(update.Hint)
|
match := h.hasUpdate(update.Hint)
|
||||||
if !reflect.DeepEqual(match.SessionInfo, expSession) {
|
require.Equal(h.t, expSession, match.SessionInfo)
|
||||||
h.t.Fatalf("expected session: %v, got: %v",
|
|
||||||
expSession, match.SessionInfo)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -640,14 +589,10 @@ func TestTowerDB(t *testing.T) {
|
|||||||
bdb, err := wtdb.NewBoltBackendCreator(
|
bdb, err := wtdb.NewBoltBackendCreator(
|
||||||
true, path, "watchtower.db",
|
true, path, "watchtower.db",
|
||||||
)(dbCfg)
|
)(dbCfg)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := wtdb.OpenTowerDB(bdb)
|
db, err := wtdb.OpenTowerDB(bdb)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
db.Close()
|
db.Close()
|
||||||
@@ -664,14 +609,10 @@ func TestTowerDB(t *testing.T) {
|
|||||||
bdb, err := wtdb.NewBoltBackendCreator(
|
bdb, err := wtdb.NewBoltBackendCreator(
|
||||||
true, path, "watchtower.db",
|
true, path, "watchtower.db",
|
||||||
)(dbCfg)
|
)(dbCfg)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := wtdb.OpenTowerDB(bdb)
|
db, err := wtdb.OpenTowerDB(bdb)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
db.Close()
|
db.Close()
|
||||||
|
|
||||||
// Open the db again, ensuring we test a
|
// Open the db again, ensuring we test a
|
||||||
@@ -680,14 +621,10 @@ func TestTowerDB(t *testing.T) {
|
|||||||
bdb, err = wtdb.NewBoltBackendCreator(
|
bdb, err = wtdb.NewBoltBackendCreator(
|
||||||
true, path, "watchtower.db",
|
true, path, "watchtower.db",
|
||||||
)(dbCfg)
|
)(dbCfg)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err = wtdb.OpenTowerDB(bdb)
|
db, err = wtdb.OpenTowerDB(bdb)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to open db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
db.Close()
|
db.Close()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package wtdb
|
|||||||
import (
|
import (
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
|
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// migration is a function which takes a prior outdated version of the database
|
// migration is a function which takes a prior outdated version of the database
|
||||||
@@ -24,7 +25,11 @@ var towerDBVersions = []version{}
|
|||||||
// clientDBVersions stores all versions and migrations of the client database.
|
// clientDBVersions stores all versions and migrations of the client database.
|
||||||
// This list will be used when opening the database to determine if any
|
// This list will be used when opening the database to determine if any
|
||||||
// migrations must be applied.
|
// migrations must be applied.
|
||||||
var clientDBVersions = []version{}
|
var clientDBVersions = []version{
|
||||||
|
{
|
||||||
|
migration: migration1.MigrateTowerToSessionIndex,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// getLatestDBVersion returns the last known database version.
|
// getLatestDBVersion returns the last known database version.
|
||||||
func getLatestDBVersion(versions []version) uint32 {
|
func getLatestDBVersion(versions []version) uint32 {
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ func (m *ClientDB) listClientSessions(
|
|||||||
if tower != nil && *tower != session.TowerID {
|
if tower != nil && *tower != session.TowerID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
session.Tower = m.towers[session.TowerID]
|
||||||
sessions[session.ID] = &session
|
sessions[session.ID] = &session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user