mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
474 lines
14 KiB
Go
474 lines
14 KiB
Go
package aliasmgr
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
|
"github.com/lightningnetwork/lnd/kvdb"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
)
|
|
|
|
var (
|
|
// aliasBucket stores aliases as keys and their base SCIDs as values.
|
|
// This is used to populate the maps that the Manager uses. The keys
|
|
// are alias SCIDs and the values are their respective base SCIDs. This
|
|
// is used instead of the other way around (base -> alias...) because
|
|
// updating an alias would require fetching all the existing aliases,
|
|
// adding another one, and then flushing the write to disk. This is
|
|
// inefficient compared to N 1:1 mappings at the cost of marginally
|
|
// more disk space.
|
|
aliasBucket = []byte("alias-bucket")
|
|
|
|
// confirmedBucket stores whether or not a given base SCID should no
|
|
// longer have entries in the ToBase maps. The key is the SCID that is
|
|
// confirmed with 6 confirmations and is public, and the value is
|
|
// empty.
|
|
confirmedBucket = []byte("base-bucket")
|
|
|
|
// aliasAllocBucket is a root-level bucket that stores the last alias
|
|
// that was allocated. It is used to allocate a new alias when
|
|
// requested.
|
|
aliasAllocBucket = []byte("alias-alloc-bucket")
|
|
|
|
// lastAliasKey is a key in the aliasAllocBucket whose value is the
|
|
// last allocated alias ShortChannelID. This will be updated upon calls
|
|
// to RequestAlias.
|
|
lastAliasKey = []byte("last-alias-key")
|
|
|
|
// invoiceAliasBucket is a root-level bucket that stores the alias
|
|
// SCIDs that our peers send us in the funding_locked TLV. The keys are
|
|
// the ChannelID generated from the FundingOutpoint and the values are
|
|
// the remote peer's alias SCID.
|
|
invoiceAliasBucket = []byte("invoice-alias-bucket")
|
|
|
|
// byteOrder denotes the byte order of database (de)-serialization
|
|
// operations.
|
|
byteOrder = binary.BigEndian
|
|
|
|
// startBlockHeight is the starting block height of the alias range.
|
|
startingBlockHeight = 16_000_000
|
|
|
|
// endBlockHeight is the ending block height of the alias range.
|
|
endBlockHeight = 16_250_000
|
|
|
|
// startingAlias is the first alias ShortChannelID that will get
|
|
// assigned by RequestAlias. The starting BlockHeight is chosen so that
|
|
// legitimate SCIDs in integration tests aren't mistaken for an alias.
|
|
startingAlias = lnwire.ShortChannelID{
|
|
BlockHeight: uint32(startingBlockHeight),
|
|
TxIndex: 0,
|
|
TxPosition: 0,
|
|
}
|
|
|
|
// errNoBase is returned when a base SCID isn't found.
|
|
errNoBase = fmt.Errorf("no base found")
|
|
|
|
// errNoPeerAlias is returned when the peer's alias for a given
|
|
// channel is not found.
|
|
errNoPeerAlias = fmt.Errorf("no peer alias found")
|
|
)
|
|
|
|
// Manager is a struct that handles aliases for LND. It has an underlying
|
|
// database that can allocate aliases for channels, stores the peer's last
|
|
// alias for use in our hop hints, and contains mappings that both the Switch
|
|
// and Gossiper use.
|
|
type Manager struct {
|
|
backend kvdb.Backend
|
|
|
|
// baseToSet is a mapping from the "base" SCID to the set of aliases
|
|
// for this channel. This mapping includes all channels that
|
|
// negotiated the option-scid-alias feature bit.
|
|
baseToSet map[lnwire.ShortChannelID][]lnwire.ShortChannelID
|
|
|
|
// aliasToBase is a mapping that maps all aliases for a given channel
|
|
// to its base SCID. This is only used for channels that have
|
|
// negotiated option-scid-alias feature bit.
|
|
aliasToBase map[lnwire.ShortChannelID]lnwire.ShortChannelID
|
|
|
|
sync.RWMutex
|
|
}
|
|
|
|
// NewManager initializes an alias Manager from the passed database backend.
|
|
func NewManager(db kvdb.Backend) (*Manager, error) {
|
|
m := &Manager{backend: db}
|
|
m.baseToSet = make(
|
|
map[lnwire.ShortChannelID][]lnwire.ShortChannelID,
|
|
)
|
|
m.aliasToBase = make(
|
|
map[lnwire.ShortChannelID]lnwire.ShortChannelID,
|
|
)
|
|
|
|
err := m.populateMaps()
|
|
return m, err
|
|
}
|
|
|
|
// populateMaps reads the database state and populates the maps.
|
|
func (m *Manager) populateMaps() error {
|
|
// This map tracks the base SCIDs that are confirmed and don't need to
|
|
// have entries in the *ToBase mappings as they won't be used in the
|
|
// gossiper.
|
|
baseConfMap := make(map[lnwire.ShortChannelID]struct{})
|
|
|
|
// This map caches what is found in the database and is used to
|
|
// populate the Manager's actual maps.
|
|
aliasMap := make(map[lnwire.ShortChannelID]lnwire.ShortChannelID)
|
|
|
|
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
|
|
baseConfBucket, err := tx.CreateTopLevelBucket(confirmedBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = baseConfBucket.ForEach(func(k, v []byte) error {
|
|
// The key will the base SCID and the value will be
|
|
// empty. Existence in the bucket means the SCID is
|
|
// confirmed.
|
|
baseScid := lnwire.NewShortChanIDFromInt(
|
|
byteOrder.Uint64(k),
|
|
)
|
|
baseConfMap[baseScid] = struct{}{}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
aliasToBaseBucket, err := tx.CreateTopLevelBucket(aliasBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = aliasToBaseBucket.ForEach(func(k, v []byte) error {
|
|
// The key will be the alias SCID and the value will be
|
|
// the base SCID.
|
|
aliasScid := lnwire.NewShortChanIDFromInt(
|
|
byteOrder.Uint64(k),
|
|
)
|
|
baseScid := lnwire.NewShortChanIDFromInt(
|
|
byteOrder.Uint64(v),
|
|
)
|
|
aliasMap[aliasScid] = baseScid
|
|
return nil
|
|
})
|
|
return err
|
|
}, func() {
|
|
baseConfMap = make(map[lnwire.ShortChannelID]struct{})
|
|
aliasMap = make(
|
|
map[lnwire.ShortChannelID]lnwire.ShortChannelID,
|
|
)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Populate the baseToSet map regardless if the baseSCID is marked as
|
|
// public with 6 confirmations.
|
|
for aliasSCID, baseSCID := range aliasMap {
|
|
m.baseToSet[baseSCID] = append(m.baseToSet[baseSCID], aliasSCID)
|
|
|
|
// Skip if baseSCID is in the baseConfMap.
|
|
if _, ok := baseConfMap[baseSCID]; ok {
|
|
continue
|
|
}
|
|
|
|
m.aliasToBase[aliasSCID] = baseSCID
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddLocalAlias adds a database mapping from the passed alias to the passed
|
|
// base SCID. The gossip boolean marks whether or not to create a mapping
|
|
// that the gossiper will use. It is set to false for the upgrade path where
|
|
// the feature-bit is toggled on and there are existing channels.
|
|
func (m *Manager) AddLocalAlias(alias, baseScid lnwire.ShortChannelID,
|
|
gossip bool) error {
|
|
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
|
|
// If the caller does not want to allow the alias to be used
|
|
// for a channel update, we'll mark it in the baseConfBucket.
|
|
if !gossip {
|
|
var baseGossipBytes [8]byte
|
|
byteOrder.PutUint64(
|
|
baseGossipBytes[:], baseScid.ToUint64(),
|
|
)
|
|
|
|
confBucket, err := tx.CreateTopLevelBucket(
|
|
confirmedBucket,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = confBucket.Put(baseGossipBytes[:], []byte{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
aliasToBaseBucket, err := tx.CreateTopLevelBucket(aliasBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
aliasBytes [8]byte
|
|
baseBytes [8]byte
|
|
)
|
|
|
|
byteOrder.PutUint64(aliasBytes[:], alias.ToUint64())
|
|
byteOrder.PutUint64(baseBytes[:], baseScid.ToUint64())
|
|
return aliasToBaseBucket.Put(aliasBytes[:], baseBytes[:])
|
|
}, func() {})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the aliasToBase and baseToSet maps.
|
|
m.baseToSet[baseScid] = append(m.baseToSet[baseScid], alias)
|
|
|
|
// Only store the gossiper map if gossip is true.
|
|
if gossip {
|
|
m.aliasToBase[alias] = baseScid
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAliases fetches the set of aliases stored under a given base SCID from
|
|
// write-through caches.
|
|
func (m *Manager) GetAliases(base lnwire.ShortChannelID) []lnwire.ShortChannelID {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
|
|
aliasSet, ok := m.baseToSet[base]
|
|
if ok {
|
|
// Copy the found alias slice.
|
|
setCopy := make([]lnwire.ShortChannelID, len(aliasSet))
|
|
copy(setCopy, aliasSet)
|
|
return setCopy
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FindBaseSCID finds the base SCID for a given alias. This is used in the
|
|
// gossiper to find the correct SCID to lookup in the graph database.
|
|
func (m *Manager) FindBaseSCID(
|
|
alias lnwire.ShortChannelID) (lnwire.ShortChannelID, error) {
|
|
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
|
|
base, ok := m.aliasToBase[alias]
|
|
if ok {
|
|
return base, nil
|
|
}
|
|
|
|
return lnwire.ShortChannelID{}, errNoBase
|
|
}
|
|
|
|
// DeleteSixConfs removes a mapping for the gossiper once six confirmations
|
|
// have been reached and the channel is public. At this point, only the
|
|
// confirmed SCID should be used.
|
|
func (m *Manager) DeleteSixConfs(baseScid lnwire.ShortChannelID) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
|
|
baseConfBucket, err := tx.CreateTopLevelBucket(confirmedBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var baseBytes [8]byte
|
|
byteOrder.PutUint64(baseBytes[:], baseScid.ToUint64())
|
|
return baseConfBucket.Put(baseBytes[:], []byte{})
|
|
}, func() {})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Now that the database state has been updated, we'll delete all of
|
|
// the aliasToBase mappings for this SCID.
|
|
for alias, base := range m.aliasToBase {
|
|
if base.ToUint64() == baseScid.ToUint64() {
|
|
delete(m.aliasToBase, alias)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PutPeerAlias stores the peer's alias SCID once we learn of it in the
|
|
// funding_locked message.
|
|
func (m *Manager) PutPeerAlias(chanID lnwire.ChannelID,
|
|
alias lnwire.ShortChannelID) error {
|
|
|
|
return kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(invoiceAliasBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var scratch [8]byte
|
|
byteOrder.PutUint64(scratch[:], alias.ToUint64())
|
|
return bucket.Put(chanID[:], scratch[:])
|
|
}, func() {})
|
|
}
|
|
|
|
// GetPeerAlias retrieves a peer's alias SCID by the channel's ChanID.
|
|
func (m *Manager) GetPeerAlias(chanID lnwire.ChannelID) (
|
|
lnwire.ShortChannelID, error) {
|
|
|
|
var alias lnwire.ShortChannelID
|
|
|
|
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(invoiceAliasBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
aliasBytes := bucket.Get(chanID[:])
|
|
if aliasBytes == nil {
|
|
return nil
|
|
}
|
|
|
|
alias = lnwire.NewShortChanIDFromInt(
|
|
byteOrder.Uint64(aliasBytes),
|
|
)
|
|
return nil
|
|
}, func() {})
|
|
|
|
if alias == hop.Source {
|
|
return alias, errNoPeerAlias
|
|
}
|
|
|
|
return alias, err
|
|
}
|
|
|
|
// RequestAlias returns a new ALIAS ShortChannelID to the caller by allocating
|
|
// the next un-allocated ShortChannelID. The starting ShortChannelID is
|
|
// 16000000:0:0 and the ending ShortChannelID is 16250000:16777215:65535. This
|
|
// gives roughly 2^58 possible ALIAS ShortChannelIDs which ensures this space
|
|
// won't get exhausted.
|
|
func (m *Manager) RequestAlias() (lnwire.ShortChannelID, error) {
|
|
var nextAlias lnwire.ShortChannelID
|
|
|
|
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(aliasAllocBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lastBytes := bucket.Get(lastAliasKey)
|
|
if lastBytes == nil {
|
|
// If the key does not exist, then we can write the
|
|
// startingAlias to it.
|
|
nextAlias = startingAlias
|
|
|
|
var scratch [8]byte
|
|
byteOrder.PutUint64(scratch[:], nextAlias.ToUint64())
|
|
return bucket.Put(lastAliasKey, scratch[:])
|
|
}
|
|
|
|
// Otherwise the key does exist so we can convert the retrieved
|
|
// lastAlias to a ShortChannelID and use it to assign the next
|
|
// ShortChannelID. This next ShortChannelID will then be
|
|
// persisted in the database.
|
|
lastScid := lnwire.NewShortChanIDFromInt(
|
|
byteOrder.Uint64(lastBytes),
|
|
)
|
|
nextAlias = getNextScid(lastScid)
|
|
|
|
var scratch [8]byte
|
|
byteOrder.PutUint64(scratch[:], nextAlias.ToUint64())
|
|
return bucket.Put(lastAliasKey, scratch[:])
|
|
}, func() {
|
|
nextAlias = lnwire.ShortChannelID{}
|
|
})
|
|
if err != nil {
|
|
return nextAlias, err
|
|
}
|
|
|
|
return nextAlias, nil
|
|
}
|
|
|
|
// ListAliases returns a carbon copy of baseToSet. This is used by the rpc
|
|
// layer.
|
|
func (m *Manager) ListAliases() map[lnwire.ShortChannelID][]lnwire.ShortChannelID {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
|
|
baseCopy := make(map[lnwire.ShortChannelID][]lnwire.ShortChannelID)
|
|
|
|
for k, v := range m.baseToSet {
|
|
setCopy := make([]lnwire.ShortChannelID, len(v))
|
|
copy(setCopy, v)
|
|
baseCopy[k] = setCopy
|
|
}
|
|
|
|
return baseCopy
|
|
}
|
|
|
|
// getNextScid is a utility function that returns the next SCID for a given
|
|
// alias SCID. The BlockHeight ranges from [16000000, 16250000], the TxIndex
|
|
// ranges from [1, 16777215], and the TxPosition ranges from [1, 65535].
|
|
func getNextScid(last lnwire.ShortChannelID) lnwire.ShortChannelID {
|
|
var (
|
|
next lnwire.ShortChannelID
|
|
incrementIdx bool
|
|
incrementHeight bool
|
|
)
|
|
|
|
// If the TxPosition is 65535, then it goes to 0 and we need to
|
|
// increment the TxIndex.
|
|
if last.TxPosition == 65535 {
|
|
incrementIdx = true
|
|
}
|
|
|
|
// If the TxIndex is 16777215 and we need to increment it, then it goes
|
|
// to 0 and we need to increment the BlockHeight.
|
|
if last.TxIndex == 16777215 && incrementIdx {
|
|
incrementIdx = false
|
|
incrementHeight = true
|
|
}
|
|
|
|
switch {
|
|
// If we increment the TxIndex, then TxPosition goes to 0.
|
|
case incrementIdx:
|
|
next.BlockHeight = last.BlockHeight
|
|
next.TxIndex = last.TxIndex + 1
|
|
next.TxPosition = 0
|
|
|
|
// If we increment the BlockHeight, then the Tx fields go to 0.
|
|
case incrementHeight:
|
|
next.BlockHeight = last.BlockHeight + 1
|
|
next.TxIndex = 0
|
|
next.TxPosition = 0
|
|
|
|
// Otherwise, we only need to increment the TxPosition.
|
|
default:
|
|
next.BlockHeight = last.BlockHeight
|
|
next.TxIndex = last.TxIndex
|
|
next.TxPosition = last.TxPosition + 1
|
|
}
|
|
|
|
return next
|
|
}
|
|
|
|
// IsAlias returns true if the passed SCID is an alias. The function determines
|
|
// this by looking at the BlockHeight. If the BlockHeight is greater than
|
|
// startingBlockHeight and less than endBlockHeight, then it is an alias
|
|
// assigned by RequestAlias. These bounds only apply to aliases we generate.
|
|
// Our peers are free to use any range they choose.
|
|
func IsAlias(scid lnwire.ShortChannelID) bool {
|
|
return scid.BlockHeight >= uint32(startingBlockHeight) &&
|
|
scid.BlockHeight < uint32(endBlockHeight)
|
|
}
|