mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
Merge pull request #5955 from Crypt-iQ/zero-conf-alias-patch
multi: implement option_scid_alias, zero-conf w/ channel_type
This commit is contained in:
commit
d9c79d874e
456
aliasmgr/aliasmgr.go
Normal file
456
aliasmgr/aliasmgr.go
Normal file
@ -0,0 +1,456 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
146
aliasmgr/aliasmgr_test.go
Normal file
146
aliasmgr/aliasmgr_test.go
Normal file
@ -0,0 +1,146 @@
|
||||
package aliasmgr
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestAliasStorePeerAlias tests that putting and retrieving a peer's alias
|
||||
// works properly.
|
||||
func TestAliasStorePeerAlias(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the backend database and use this to create the aliasStore.
|
||||
dbDir, err := ioutil.TempDir("", "aliasStore")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(dbDir)
|
||||
|
||||
dbPath := filepath.Join(dbDir, "testdb")
|
||||
db, err := kvdb.Create(
|
||||
kvdb.BoltBackendName, dbPath, true, kvdb.DefaultDBTimeout,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
aliasStore, err := NewManager(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
var chanID1 [32]byte
|
||||
_, err = rand.Read(chanID1[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test that we can put the (chanID, alias) mapping in the database.
|
||||
// Also check that we retrieve exactly what we put in.
|
||||
err = aliasStore.PutPeerAlias(chanID1, startingAlias)
|
||||
require.NoError(t, err)
|
||||
|
||||
storedAlias, err := aliasStore.GetPeerAlias(chanID1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, startingAlias, storedAlias)
|
||||
}
|
||||
|
||||
// TestAliasStoreRequest tests that the aliasStore delivers the expected SCID.
|
||||
func TestAliasStoreRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the backend database and use this to create the aliasStore.
|
||||
dbDir, err := ioutil.TempDir("", "aliasStore")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(dbDir)
|
||||
|
||||
dbPath := filepath.Join(dbDir, "testdb")
|
||||
db, err := kvdb.Create(
|
||||
kvdb.BoltBackendName, dbPath, true, kvdb.DefaultDBTimeout,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
aliasStore, err := NewManager(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We'll assert that the very first alias we receive is startingAlias.
|
||||
alias1, err := aliasStore.RequestAlias()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, startingAlias, alias1)
|
||||
|
||||
// The next alias should be the result of passing in startingAlias to
|
||||
// getNextScid.
|
||||
nextAlias := getNextScid(alias1)
|
||||
alias2, err := aliasStore.RequestAlias()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, nextAlias, alias2)
|
||||
}
|
||||
|
||||
// TestGetNextScid tests that given a current lnwire.ShortChannelID,
|
||||
// getNextScid returns the expected alias to use next.
|
||||
func TestGetNextScid(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
current lnwire.ShortChannelID
|
||||
expected lnwire.ShortChannelID
|
||||
}{
|
||||
{
|
||||
name: "starting alias",
|
||||
current: startingAlias,
|
||||
expected: lnwire.ShortChannelID{
|
||||
BlockHeight: uint32(startingBlockHeight),
|
||||
TxIndex: 0,
|
||||
TxPosition: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "txposition rollover",
|
||||
current: lnwire.ShortChannelID{
|
||||
BlockHeight: 16_100_000,
|
||||
TxIndex: 15,
|
||||
TxPosition: 65535,
|
||||
},
|
||||
expected: lnwire.ShortChannelID{
|
||||
BlockHeight: 16_100_000,
|
||||
TxIndex: 16,
|
||||
TxPosition: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "txindex max no rollover",
|
||||
current: lnwire.ShortChannelID{
|
||||
BlockHeight: 16_100_000,
|
||||
TxIndex: 16777215,
|
||||
TxPosition: 15,
|
||||
},
|
||||
expected: lnwire.ShortChannelID{
|
||||
BlockHeight: 16_100_000,
|
||||
TxIndex: 16777215,
|
||||
TxPosition: 16,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "txindex rollover",
|
||||
current: lnwire.ShortChannelID{
|
||||
BlockHeight: 16_100_000,
|
||||
TxIndex: 16777215,
|
||||
TxPosition: 65535,
|
||||
},
|
||||
expected: lnwire.ShortChannelID{
|
||||
BlockHeight: 16_100_001,
|
||||
TxIndex: 0,
|
||||
TxPosition: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
nextScid := getNextScid(test.current)
|
||||
require.Equal(t, test.expected, nextScid)
|
||||
})
|
||||
}
|
||||
}
|
@ -184,13 +184,15 @@ func TestMultipleAcceptClients(t *testing.T) {
|
||||
queries = map[*lnwire.OpenChannel]*ChannelAcceptResponse{
|
||||
chan1: NewChannelAcceptResponse(
|
||||
true, nil, testUpfront, 1, 2, 3, 4, 5, 6,
|
||||
false,
|
||||
),
|
||||
chan2: NewChannelAcceptResponse(
|
||||
false, errChannelRejected, nil, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
0, 0, 0, false,
|
||||
),
|
||||
chan3: NewChannelAcceptResponse(
|
||||
false, customError, nil, 0, 0, 0, 0, 0, 0,
|
||||
false,
|
||||
),
|
||||
}
|
||||
|
||||
@ -245,7 +247,7 @@ func TestInvalidResponse(t *testing.T) {
|
||||
PendingChannelID: chan1,
|
||||
}: NewChannelAcceptResponse(
|
||||
false, errChannelRejected, nil, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0, false,
|
||||
),
|
||||
}
|
||||
|
||||
@ -288,7 +290,7 @@ func TestInvalidReserve(t *testing.T) {
|
||||
DustLimit: dustLimit,
|
||||
}: NewChannelAcceptResponse(
|
||||
false, errChannelRejected, nil, 0, 0,
|
||||
0, reserve, 0, 0,
|
||||
0, reserve, 0, 0, false,
|
||||
),
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ func (c *ChainedAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptRespon
|
||||
|
||||
return NewChannelAcceptResponse(
|
||||
false, errChannelRejected, nil, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0, false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,10 @@ type ChannelAcceptResponse struct {
|
||||
// MinAcceptDepth is the minimum depth that the initiator of the
|
||||
// channel should wait before considering the channel open.
|
||||
MinAcceptDepth uint16
|
||||
|
||||
// ZeroConf indicates that the fundee wishes to send min_depth = 0 and
|
||||
// request a zero-conf channel with the counter-party.
|
||||
ZeroConf bool
|
||||
}
|
||||
|
||||
// NewChannelAcceptResponse is a constructor for a channel accept response,
|
||||
@ -72,7 +76,7 @@ type ChannelAcceptResponse struct {
|
||||
func NewChannelAcceptResponse(accept bool, acceptErr error,
|
||||
upfrontShutdown lnwire.DeliveryAddress, csvDelay, htlcLimit,
|
||||
minDepth uint16, reserve btcutil.Amount, inFlight,
|
||||
minHtlcIn lnwire.MilliSatoshi) *ChannelAcceptResponse {
|
||||
minHtlcIn lnwire.MilliSatoshi, zeroConf bool) *ChannelAcceptResponse {
|
||||
|
||||
resp := &ChannelAcceptResponse{
|
||||
UpfrontShutdown: upfrontShutdown,
|
||||
@ -82,6 +86,7 @@ func NewChannelAcceptResponse(accept bool, acceptErr error,
|
||||
HtlcLimit: htlcLimit,
|
||||
MinHtlcIn: minHtlcIn,
|
||||
MinAcceptDepth: minDepth,
|
||||
ZeroConf: zeroConf,
|
||||
}
|
||||
|
||||
// If we want to accept the channel, we return a response with a nil
|
||||
|
@ -20,6 +20,10 @@ const (
|
||||
fieldUpfrontShutdown = "upfront shutdown"
|
||||
)
|
||||
|
||||
var (
|
||||
errZeroConf = fmt.Errorf("zero-conf set with non-zero min-depth")
|
||||
)
|
||||
|
||||
// fieldMismatchError returns a merge error for a named field when we get two
|
||||
// channel acceptor responses which have different values set.
|
||||
func fieldMismatchError(name string, current, newValue interface{}) error {
|
||||
@ -27,6 +31,13 @@ func fieldMismatchError(name string, current, newValue interface{}) error {
|
||||
name, current, newValue)
|
||||
}
|
||||
|
||||
// mergeBool merges two boolean values.
|
||||
func mergeBool(current, newValue bool) bool {
|
||||
// If either is true, return true. It is not possible to have different
|
||||
// "non-zero" values like the other cases.
|
||||
return current || newValue
|
||||
}
|
||||
|
||||
// mergeInt64 merges two int64 values, failing if they have different non-zero
|
||||
// values.
|
||||
func mergeInt64(name string, current, newValue int64) (int64, error) {
|
||||
@ -117,6 +128,13 @@ func mergeResponse(current,
|
||||
}
|
||||
current.MinAcceptDepth = uint16(minDepth)
|
||||
|
||||
current.ZeroConf = mergeBool(current.ZeroConf, newValue.ZeroConf)
|
||||
|
||||
// Assert that if zero-conf is set, min-depth is zero.
|
||||
if current.ZeroConf && current.MinAcceptDepth != 0 {
|
||||
return current, errZeroConf
|
||||
}
|
||||
|
||||
reserve, err := mergeInt64(
|
||||
fieldReserve, int64(current.Reserve), int64(newValue.Reserve),
|
||||
)
|
||||
|
@ -167,6 +167,18 @@ func TestMergeResponse(t *testing.T) {
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
// Test the case where one response has ZeroConf set
|
||||
// and another has a non-zero min depth set.
|
||||
name: "zero conf conflict",
|
||||
current: ChannelAcceptResponse{
|
||||
ZeroConf: true,
|
||||
},
|
||||
new: ChannelAcceptResponse{
|
||||
MinAcceptDepth: 5,
|
||||
},
|
||||
err: errZeroConf,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -107,7 +107,7 @@ func (r *RPCAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptResponse {
|
||||
// Create a rejection response which we can use for the cases where we
|
||||
// reject the channel.
|
||||
rejectChannel := NewChannelAcceptResponse(
|
||||
false, errChannelRejected, nil, 0, 0, 0, 0, 0, 0,
|
||||
false, errChannelRejected, nil, 0, 0, 0, 0, 0, 0, false,
|
||||
)
|
||||
|
||||
// Send the request to the newRequests channel.
|
||||
@ -216,6 +216,7 @@ func (r *RPCAcceptor) receiveResponses(errChan chan error,
|
||||
MaxHtlcCount: resp.MaxHtlcCount,
|
||||
MinHtlcIn: resp.MinHtlcIn,
|
||||
MinAcceptDepth: resp.MinAcceptDepth,
|
||||
ZeroConf: resp.ZeroConf,
|
||||
}
|
||||
|
||||
// We have received a decision for one of our channel
|
||||
@ -348,6 +349,7 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
|
||||
btcutil.Amount(resp.ReserveSat),
|
||||
lnwire.MilliSatoshi(resp.InFlightMaxMsat),
|
||||
lnwire.MilliSatoshi(resp.MinHtlcIn),
|
||||
resp.ZeroConf,
|
||||
)
|
||||
|
||||
// Delete the channel from the acceptRequests map.
|
||||
|
@ -177,7 +177,25 @@ func NewSingle(channel *channeldb.OpenChannel,
|
||||
// to the channel ID so we can use that as height hint on restore.
|
||||
chanID := channel.ShortChanID()
|
||||
if chanID.BlockHeight == 0 {
|
||||
chanID.BlockHeight = channel.FundingBroadcastHeight
|
||||
chanID.BlockHeight = channel.BroadcastHeight()
|
||||
}
|
||||
|
||||
// If this is a zero-conf channel, we'll need to have separate logic
|
||||
// depending on whether it's confirmed or not. This is because the
|
||||
// ShortChanID is an alias.
|
||||
if channel.IsZeroConf() {
|
||||
// If the channel is confirmed, we'll use the confirmed SCID.
|
||||
if channel.ZeroConfConfirmed() {
|
||||
chanID = channel.ZeroConfRealScid()
|
||||
} else {
|
||||
// Else if the zero-conf channel is unconfirmed, we'll
|
||||
// need to use the broadcast height and zero out the
|
||||
// TxIndex and TxPosition fields. This is so
|
||||
// openChannelShell works properly.
|
||||
chanID.BlockHeight = channel.BroadcastHeight()
|
||||
chanID.TxIndex = 0
|
||||
chanID.TxPosition = 0
|
||||
}
|
||||
}
|
||||
|
||||
single := Single{
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
@ -53,6 +54,14 @@ var (
|
||||
//
|
||||
outpointBucket = []byte("outpoint-bucket")
|
||||
|
||||
// chanIDBucket stores all of the 32-byte channel ID's we know about.
|
||||
// These could be derived from outpointBucket, but it is more
|
||||
// convenient to have these in their own bucket.
|
||||
//
|
||||
// chanID -> tlv stream.
|
||||
//
|
||||
chanIDBucket = []byte("chan-id-bucket")
|
||||
|
||||
// historicalChannelBucket stores all channels that have seen their
|
||||
// commitment tx confirm. All information from their previous open state
|
||||
// is retained.
|
||||
@ -190,6 +199,10 @@ const (
|
||||
// A tlv type used to serialize and deserialize the
|
||||
// `InitialRemoteBalance` field.
|
||||
initialRemoteBalanceType tlv.Type = 3
|
||||
|
||||
// A tlv type definition used to serialize and deserialize the
|
||||
// confirmed ShortChannelID for a zero-conf channel.
|
||||
realScidType tlv.Type = 4
|
||||
)
|
||||
|
||||
// indexStatus is an enum-like type that describes what state the
|
||||
@ -211,7 +224,7 @@ const (
|
||||
// fee negotiation, channel closing, the format of HTLCs, etc. Structure-wise,
|
||||
// a ChannelType is a bit field, with each bit denoting a modification from the
|
||||
// base channel type of single funder.
|
||||
type ChannelType uint8
|
||||
type ChannelType uint64
|
||||
|
||||
const (
|
||||
// NOTE: iota isn't used here for this enum needs to be stable
|
||||
@ -254,6 +267,17 @@ const (
|
||||
// period of time, constraining every output that pays to the channel
|
||||
// initiator with an additional CLTV of the lease maturity.
|
||||
LeaseExpirationBit ChannelType = 1 << 6
|
||||
|
||||
// ZeroConfBit indicates that the channel is a zero-conf channel.
|
||||
ZeroConfBit ChannelType = 1 << 7
|
||||
|
||||
// ScidAliasChanBit indicates that the channel has negotiated the
|
||||
// scid-alias channel type.
|
||||
ScidAliasChanBit ChannelType = 1 << 8
|
||||
|
||||
// ScidAliasFeatureBit indicates that the scid-alias feature bit was
|
||||
// negotiated during the lifetime of this channel.
|
||||
ScidAliasFeatureBit ChannelType = 1 << 9
|
||||
)
|
||||
|
||||
// IsSingleFunder returns true if the channel type if one of the known single
|
||||
@ -303,6 +327,22 @@ func (c ChannelType) HasLeaseExpiration() bool {
|
||||
return c&LeaseExpirationBit == LeaseExpirationBit
|
||||
}
|
||||
|
||||
// HasZeroConf returns true if the channel is a zero-conf channel.
|
||||
func (c ChannelType) HasZeroConf() bool {
|
||||
return c&ZeroConfBit == ZeroConfBit
|
||||
}
|
||||
|
||||
// HasScidAliasChan returns true if the scid-alias channel type was negotiated.
|
||||
func (c ChannelType) HasScidAliasChan() bool {
|
||||
return c&ScidAliasChanBit == ScidAliasChanBit
|
||||
}
|
||||
|
||||
// HasScidAliasFeature returns true if the scid-alias feature bit was
|
||||
// negotiated during the lifetime of this channel.
|
||||
func (c ChannelType) HasScidAliasFeature() bool {
|
||||
return c&ScidAliasFeatureBit == ScidAliasFeatureBit
|
||||
}
|
||||
|
||||
// ChannelConstraints represents a set of constraints meant to allow a node to
|
||||
// limit their exposure, enact flow control and ensure that all HTLCs are
|
||||
// economically relevant. This struct will be mirrored for both sides of the
|
||||
@ -476,7 +516,7 @@ type ChannelCommitment struct {
|
||||
|
||||
// ChannelStatus is a bit vector used to indicate whether an OpenChannel is in
|
||||
// the default usable state, or a state where it shouldn't be used.
|
||||
type ChannelStatus uint8
|
||||
type ChannelStatus uint64
|
||||
|
||||
var (
|
||||
// ChanStatusDefault is the normal state of an open channel.
|
||||
@ -604,6 +644,9 @@ type OpenChannel struct {
|
||||
// ShortChannelID encodes the exact location in the chain in which the
|
||||
// channel was initially confirmed. This includes: the block height,
|
||||
// transaction index, and the output within the target transaction.
|
||||
//
|
||||
// If IsZeroConf(), then this will the "base" (very first) ALIAS scid
|
||||
// and the confirmed SCID will be stored in ConfirmedScid.
|
||||
ShortChannelID lnwire.ShortChannelID
|
||||
|
||||
// IsPending indicates whether a channel's funding transaction has been
|
||||
@ -739,6 +782,11 @@ type OpenChannel struct {
|
||||
// have private key isolation from lnd.
|
||||
RevocationKeyLocator keychain.KeyLocator
|
||||
|
||||
// confirmedScid is the confirmed ShortChannelID for a zero-conf
|
||||
// channel. If the channel is unconfirmed, then this will be the
|
||||
// default ShortChannelID. This is only set for zero-conf channels.
|
||||
confirmedScid lnwire.ShortChannelID
|
||||
|
||||
// TODO(roasbeef): eww
|
||||
Db *ChannelStateDB
|
||||
|
||||
@ -755,6 +803,50 @@ func (c *OpenChannel) ShortChanID() lnwire.ShortChannelID {
|
||||
return c.ShortChannelID
|
||||
}
|
||||
|
||||
// ZeroConfRealScid returns the zero-conf channel's confirmed scid. This should
|
||||
// only be called if IsZeroConf returns true.
|
||||
func (c *OpenChannel) ZeroConfRealScid() lnwire.ShortChannelID {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c.confirmedScid
|
||||
}
|
||||
|
||||
// ZeroConfConfirmed returns whether the zero-conf channel has confirmed. This
|
||||
// should only be called if IsZeroConf returns true.
|
||||
func (c *OpenChannel) ZeroConfConfirmed() bool {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c.confirmedScid != hop.Source
|
||||
}
|
||||
|
||||
// IsZeroConf returns whether the option_zeroconf channel type was negotiated.
|
||||
func (c *OpenChannel) IsZeroConf() bool {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c.ChanType.HasZeroConf()
|
||||
}
|
||||
|
||||
// IsOptionScidAlias returns whether the option_scid_alias channel type was
|
||||
// negotiated.
|
||||
func (c *OpenChannel) IsOptionScidAlias() bool {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c.ChanType.HasScidAliasChan()
|
||||
}
|
||||
|
||||
// NegotiatedAliasFeature returns whether the option-scid-alias feature bit was
|
||||
// negotiated.
|
||||
func (c *OpenChannel) NegotiatedAliasFeature() bool {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c.ChanType.HasScidAliasFeature()
|
||||
}
|
||||
|
||||
// ChanStatus returns the current ChannelStatus of this channel.
|
||||
func (c *OpenChannel) ChanStatus() ChannelStatus {
|
||||
c.RLock()
|
||||
@ -801,13 +893,25 @@ func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool {
|
||||
return c.chanStatus&status == status
|
||||
}
|
||||
|
||||
// RefreshShortChanID updates the in-memory channel state using the latest
|
||||
// value observed on disk.
|
||||
//
|
||||
// TODO: the name of this function should be changed to reflect the fact that
|
||||
// it is not only refreshing the short channel id but all the channel state.
|
||||
// maybe Refresh/Reload?
|
||||
func (c *OpenChannel) RefreshShortChanID() error {
|
||||
// BroadcastHeight returns the height at which the funding tx was broadcast.
|
||||
func (c *OpenChannel) BroadcastHeight() uint32 {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c.FundingBroadcastHeight
|
||||
}
|
||||
|
||||
// SetBroadcastHeight sets the FundingBroadcastHeight.
|
||||
func (c *OpenChannel) SetBroadcastHeight(height uint32) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.FundingBroadcastHeight = height
|
||||
}
|
||||
|
||||
// Refresh updates the in-memory channel state using the latest state observed
|
||||
// on disk.
|
||||
func (c *OpenChannel) Refresh() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
@ -825,6 +929,19 @@ func (c *OpenChannel) RefreshShortChanID() error {
|
||||
return fmt.Errorf("unable to fetch chan info: %v", err)
|
||||
}
|
||||
|
||||
// Also populate the channel's commitment states for both sides
|
||||
// of the channel.
|
||||
if err := fetchChanCommitments(chanBucket, c); err != nil {
|
||||
return fmt.Errorf("unable to fetch chan commitments: "+
|
||||
"%v", err)
|
||||
}
|
||||
|
||||
// Also retrieve the current revocation state.
|
||||
if err := fetchChanRevocationState(chanBucket, c); err != nil {
|
||||
return fmt.Errorf("unable to fetch chan revocations: "+
|
||||
"%v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, func() {})
|
||||
if err != nil {
|
||||
@ -931,6 +1048,7 @@ func fetchChanBucketRw(tx kvdb.RwTx, nodeKey *btcec.PublicKey,
|
||||
func (c *OpenChannel) fullSync(tx kvdb.RwTx) error {
|
||||
// Fetch the outpoint bucket and check if the outpoint already exists.
|
||||
opBucket := tx.ReadWriteBucket(outpointBucket)
|
||||
cidBucket := tx.ReadWriteBucket(chanIDBucket)
|
||||
|
||||
var chanPointBuf bytes.Buffer
|
||||
if err := writeOutpoint(&chanPointBuf, &c.FundingOutpoint); err != nil {
|
||||
@ -942,6 +1060,11 @@ func (c *OpenChannel) fullSync(tx kvdb.RwTx) error {
|
||||
return ErrChanAlreadyExists
|
||||
}
|
||||
|
||||
cid := lnwire.NewChanIDFromOutPoint(&c.FundingOutpoint)
|
||||
if cidBucket.Get(cid[:]) != nil {
|
||||
return ErrChanAlreadyExists
|
||||
}
|
||||
|
||||
status := uint8(outpointOpen)
|
||||
|
||||
// Write the status of this outpoint as the first entry in a tlv
|
||||
@ -962,6 +1085,10 @@ func (c *OpenChannel) fullSync(tx kvdb.RwTx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cidBucket.Put(cid[:], []byte{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// First fetch the top level bucket which stores all data related to
|
||||
// current, active channels.
|
||||
openChanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
|
||||
@ -1035,6 +1162,71 @@ func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkRealScid marks the zero-conf channel's confirmed ShortChannelID. This
|
||||
// should only be done if IsZeroConf returns true.
|
||||
func (c *OpenChannel) MarkRealScid(realScid lnwire.ShortChannelID) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
|
||||
chanBucket, err := fetchChanBucketRw(
|
||||
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
channel, err := fetchOpenChannel(
|
||||
chanBucket, &c.FundingOutpoint,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
channel.confirmedScid = realScid
|
||||
|
||||
return putOpenChannel(chanBucket, channel)
|
||||
}, func() {}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.confirmedScid = realScid
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkScidAliasNegotiated adds ScidAliasFeatureBit to ChanType in-memory and
|
||||
// in the database.
|
||||
func (c *OpenChannel) MarkScidAliasNegotiated() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
|
||||
chanBucket, err := fetchChanBucketRw(
|
||||
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
channel, err := fetchOpenChannel(
|
||||
chanBucket, &c.FundingOutpoint,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
channel.ChanType |= ScidAliasFeatureBit
|
||||
return putOpenChannel(chanBucket, channel)
|
||||
}, func() {}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.ChanType |= ScidAliasFeatureBit
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkDataLoss marks sets the channel status to LocalDataLoss and stores the
|
||||
// passed commitPoint for use to retrieve funds in case the remote force closes
|
||||
// the channel.
|
||||
@ -1101,6 +1293,22 @@ func (c *OpenChannel) MarkBorked() error {
|
||||
return c.putChanStatus(ChanStatusBorked)
|
||||
}
|
||||
|
||||
// SecondCommitmentPoint returns the second per-commitment-point for use in the
|
||||
// funding_locked message.
|
||||
func (c *OpenChannel) SecondCommitmentPoint() (*btcec.PublicKey, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
// Since we start at commitment height = 0, the second per commitment
|
||||
// point is actually at the 1st index.
|
||||
revocation, err := c.RevocationProducer.AtIndex(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return input.ComputeCommitmentPoint(revocation[:]), nil
|
||||
}
|
||||
|
||||
// ChanSyncMsg returns the ChannelReestablish message that should be sent upon
|
||||
// reconnection with the remote peer that we're maintaining this channel with.
|
||||
// The information contained within this message is necessary to re-sync our
|
||||
@ -3095,7 +3303,24 @@ func (c *OpenChannel) AbsoluteThawHeight() (uint32, error) {
|
||||
return 0, errors.New("cannot use relative thaw " +
|
||||
"height for unconfirmed channel")
|
||||
}
|
||||
return c.ShortChannelID.BlockHeight + c.ThawHeight, nil
|
||||
|
||||
// For non-zero-conf channels, this is the base height to use.
|
||||
blockHeightBase := c.ShortChannelID.BlockHeight
|
||||
|
||||
// If this is a zero-conf channel, the ShortChannelID will be
|
||||
// an alias.
|
||||
if c.IsZeroConf() {
|
||||
if !c.ZeroConfConfirmed() {
|
||||
return 0, errors.New("cannot use relative " +
|
||||
"height for unconfirmed zero-conf " +
|
||||
"channel")
|
||||
}
|
||||
|
||||
// Use the confirmed SCID's BlockHeight.
|
||||
blockHeightBase = c.confirmedScid.BlockHeight
|
||||
}
|
||||
|
||||
return blockHeightBase + c.ThawHeight, nil
|
||||
}
|
||||
|
||||
return c.ThawHeight, nil
|
||||
@ -3325,6 +3550,7 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
|
||||
tlv.MakePrimitiveRecord(
|
||||
initialRemoteBalanceType, &remoteBalance,
|
||||
),
|
||||
MakeScidRecord(realScidType, &channel.confirmedScid),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -3541,6 +3767,7 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
|
||||
tlv.MakePrimitiveRecord(
|
||||
initialRemoteBalanceType, &remoteBalance,
|
||||
),
|
||||
MakeScidRecord(realScidType, &channel.confirmedScid),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -3744,3 +3971,12 @@ func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
||||
func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record {
|
||||
return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator)
|
||||
}
|
||||
|
||||
// MakeScidRecord creates a Record out of a ShortChannelID using the passed
|
||||
// Type and the EShortChannelID and DShortChannelID functions. The size will
|
||||
// always be 8 for the ShortChannelID.
|
||||
func MakeScidRecord(typ tlv.Type, scid *lnwire.ShortChannelID) tlv.Record {
|
||||
return tlv.MakeStaticRecord(
|
||||
typ, scid, 8, lnwire.EShortChannelID, lnwire.DShortChannelID,
|
||||
)
|
||||
}
|
||||
|
@ -1148,10 +1148,10 @@ func TestFetchWaitingCloseChannels(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRefreshShortChanID asserts that RefreshShortChanID updates the in-memory
|
||||
// state of another OpenChannel to reflect a preceding call to MarkOpen on a
|
||||
// different OpenChannel.
|
||||
func TestRefreshShortChanID(t *testing.T) {
|
||||
// TestRefresh asserts that Refresh updates the in-memory state of another
|
||||
// OpenChannel to reflect a preceding call to MarkOpen on a different
|
||||
// OpenChannel.
|
||||
func TestRefresh(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fullDB, cleanUp, err := MakeTestDB()
|
||||
@ -1209,8 +1209,8 @@ func TestRefreshShortChanID(t *testing.T) {
|
||||
state.Packager.(*ChannelPackager).source)
|
||||
}
|
||||
|
||||
// Now, refresh the short channel ID of the pending channel.
|
||||
err = pendingChannel.RefreshShortChanID()
|
||||
// Now, refresh the state of the pending channel.
|
||||
err = pendingChannel.Refresh()
|
||||
require.NoError(t, err, "unable to refresh short_chan_id")
|
||||
|
||||
// This should result in both OpenChannel's now having the same
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/shachain"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
// writeOutpoint writes an outpoint to the passed writer using the minimal
|
||||
@ -85,7 +86,8 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
|
||||
return binary.Write(w, byteOrder, false)
|
||||
case ChannelType:
|
||||
if err := binary.Write(w, byteOrder, e); err != nil {
|
||||
var buf [8]byte
|
||||
if err := tlv.WriteVarInt(w, uint64(e), &buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -194,7 +196,8 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
}
|
||||
|
||||
case ChannelStatus:
|
||||
if err := binary.Write(w, byteOrder, e); err != nil {
|
||||
var buf [8]byte
|
||||
if err := tlv.WriteVarInt(w, uint64(e), &buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -270,10 +273,14 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
}
|
||||
|
||||
case *ChannelType:
|
||||
if err := binary.Read(r, byteOrder, e); err != nil {
|
||||
var buf [8]byte
|
||||
ctype, err := tlv.ReadVarInt(r, &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*e = ChannelType(ctype)
|
||||
|
||||
case *chainhash.Hash:
|
||||
if _, err := io.ReadFull(r, e[:]); err != nil {
|
||||
return err
|
||||
@ -419,10 +426,14 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
*e = msg
|
||||
|
||||
case *ChannelStatus:
|
||||
if err := binary.Read(r, byteOrder, e); err != nil {
|
||||
var buf [8]byte
|
||||
status, err := tlv.ReadVarInt(r, &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*e = ChannelStatus(status)
|
||||
|
||||
case *ClosureType:
|
||||
if err := binary.Read(r, byteOrder, e); err != nil {
|
||||
return err
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration25"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration26"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration27"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration29"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
@ -226,6 +227,14 @@ var (
|
||||
number: 27,
|
||||
migration: migration27.MigrateHistoricalBalances,
|
||||
},
|
||||
{
|
||||
number: 28,
|
||||
migration: mig.CreateTLB(chanIDBucket),
|
||||
},
|
||||
{
|
||||
number: 29,
|
||||
migration: migration29.MigrateChanID,
|
||||
},
|
||||
}
|
||||
|
||||
// Big endian is the preferred byte order, due to cursor scans over
|
||||
@ -352,6 +361,7 @@ var dbTopLevelBuckets = [][]byte{
|
||||
metaBucket,
|
||||
closeSummaryBucket,
|
||||
outpointBucket,
|
||||
chanIDBucket,
|
||||
historicalChannelBucket,
|
||||
}
|
||||
|
||||
|
@ -1692,8 +1692,9 @@ func (c *ChannelGraph) PruneTip() (*chainhash.Hash, uint32, error) {
|
||||
// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is
|
||||
// true, then when we mark these edges as zombies, we'll set up the keys such
|
||||
// that we require the node that failed to send the fresh update to be the one
|
||||
// that resurrects the channel from its zombie state.
|
||||
func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning bool,
|
||||
// that resurrects the channel from its zombie state. The markZombie bool
|
||||
// denotes whether or not to mark the channel as a zombie.
|
||||
func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool,
|
||||
chanIDs ...uint64) error {
|
||||
|
||||
// TODO(roasbeef): possibly delete from node bucket if node has no more
|
||||
@ -1730,7 +1731,7 @@ func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning bool,
|
||||
byteOrder.PutUint64(rawChanID[:], chanID)
|
||||
err := c.delChannelEdge(
|
||||
edges, edgeIndex, chanIndex, zombieIndex, nodes,
|
||||
rawChanID[:], true, strictZombiePruning,
|
||||
rawChanID[:], markZombie, strictZombiePruning,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -2167,8 +2168,18 @@ func (c *ChannelGraph) FilterChannelRange(startHeight,
|
||||
|
||||
// We'll now iterate through the database, and find each
|
||||
// channel ID that resides within the specified range.
|
||||
for k, _ := cursor.Seek(chanIDStart[:]); k != nil &&
|
||||
bytes.Compare(k, chanIDEnd[:]) <= 0; k, _ = cursor.Next() {
|
||||
for k, v := cursor.Seek(chanIDStart[:]); k != nil &&
|
||||
bytes.Compare(k, chanIDEnd[:]) <= 0; k, v = cursor.Next() {
|
||||
// Don't send alias SCIDs during gossip sync.
|
||||
edgeReader := bytes.NewReader(v)
|
||||
edgeInfo, err := deserializeChanEdgeInfo(edgeReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if edgeInfo.AuthProof == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// This channel ID rests within the target range, so
|
||||
// we'll add it to our returned set.
|
||||
|
@ -378,7 +378,7 @@ func TestEdgeInsertionDeletion(t *testing.T) {
|
||||
|
||||
// Next, attempt to delete the edge from the database, again this
|
||||
// should proceed without any issues.
|
||||
if err := graph.DeleteChannelEdges(false, chanID); err != nil {
|
||||
if err := graph.DeleteChannelEdges(false, true, chanID); err != nil {
|
||||
t.Fatalf("unable to delete edge: %v", err)
|
||||
}
|
||||
assertNoEdge(t, graph, chanID)
|
||||
@ -398,7 +398,7 @@ func TestEdgeInsertionDeletion(t *testing.T) {
|
||||
|
||||
// Finally, attempt to delete a (now) non-existent edge within the
|
||||
// database, this should result in an error.
|
||||
err = graph.DeleteChannelEdges(false, chanID)
|
||||
err = graph.DeleteChannelEdges(false, true, chanID)
|
||||
if err != ErrEdgeNotFound {
|
||||
t.Fatalf("deleting a non-existent edge should fail!")
|
||||
}
|
||||
@ -1993,7 +1993,7 @@ func TestFilterKnownChanIDs(t *testing.T) {
|
||||
if err := graph.AddChannelEdge(&channel); err != nil {
|
||||
t.Fatalf("unable to create channel edge: %v", err)
|
||||
}
|
||||
err := graph.DeleteChannelEdges(false, channel.ChannelID)
|
||||
err := graph.DeleteChannelEdges(false, true, channel.ChannelID)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mark edge zombie: %v", err)
|
||||
}
|
||||
@ -2251,7 +2251,7 @@ func TestFetchChanInfos(t *testing.T) {
|
||||
if err := graph.AddChannelEdge(&zombieChan); err != nil {
|
||||
t.Fatalf("unable to create channel edge: %v", err)
|
||||
}
|
||||
err = graph.DeleteChannelEdges(false, zombieChan.ChannelID)
|
||||
err = graph.DeleteChannelEdges(false, true, zombieChan.ChannelID)
|
||||
require.NoError(t, err, "unable to delete and mark edge zombie")
|
||||
edgeQuery = append(edgeQuery, zombieChanID.ToUint64())
|
||||
|
||||
@ -2789,7 +2789,9 @@ func TestNodeIsPublic(t *testing.T) {
|
||||
// graph. This will make Alice be seen as a private node as it no longer
|
||||
// has any advertised edges.
|
||||
for _, graph := range graphs {
|
||||
err := graph.DeleteChannelEdges(false, aliceBobEdge.ChannelID)
|
||||
err := graph.DeleteChannelEdges(
|
||||
false, true, aliceBobEdge.ChannelID,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to remove edge: %v", err)
|
||||
}
|
||||
@ -2806,7 +2808,9 @@ func TestNodeIsPublic(t *testing.T) {
|
||||
// completely remove the edge as it is not possible for her to know of
|
||||
// it without it being advertised.
|
||||
for i, graph := range graphs {
|
||||
err := graph.DeleteChannelEdges(false, bobCarolEdge.ChannelID)
|
||||
err := graph.DeleteChannelEdges(
|
||||
false, true, bobCarolEdge.ChannelID,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to remove edge: %v", err)
|
||||
}
|
||||
@ -2900,7 +2904,9 @@ func TestDisabledChannelIDs(t *testing.T) {
|
||||
}
|
||||
|
||||
// Delete the channel edge and ensure it is removed from the disabled list.
|
||||
if err = graph.DeleteChannelEdges(false, edgeInfo.ChannelID); err != nil {
|
||||
if err = graph.DeleteChannelEdges(
|
||||
false, true, edgeInfo.ChannelID,
|
||||
); err != nil {
|
||||
t.Fatalf("unable to delete channel edge: %v", err)
|
||||
}
|
||||
disabledChanIds, err = graph.DisabledChannelIDs()
|
||||
@ -3111,7 +3117,7 @@ func TestGraphZombieIndex(t *testing.T) {
|
||||
|
||||
// If we delete the edge and mark it as a zombie, then we should expect
|
||||
// to see it within the index.
|
||||
err = graph.DeleteChannelEdges(false, edge.ChannelID)
|
||||
err = graph.DeleteChannelEdges(false, true, edge.ChannelID)
|
||||
require.NoError(t, err, "unable to mark edge as zombie")
|
||||
isZombie, pubKey1, pubKey2 := graph.IsZombieEdge(edge.ChannelID)
|
||||
if !isZombie {
|
||||
|
66
channeldb/migration29/codec.go
Normal file
66
channeldb/migration29/codec.go
Normal file
@ -0,0 +1,66 @@
|
||||
package migration29
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
byteOrder = binary.BigEndian
|
||||
)
|
||||
|
||||
// ChannelID is a series of 32-bytes that uniquely identifies all channels
|
||||
// within the network. The ChannelID is computed using the outpoint of the
|
||||
// funding transaction (the txid, and output index). Given a funding output the
|
||||
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
|
||||
// txid and the big-endian serialization of the output index, truncated to
|
||||
// 2 bytes.
|
||||
type ChannelID [32]byte
|
||||
|
||||
// String returns the string representation of the ChannelID. This is just the
|
||||
// hex string encoding of the ChannelID itself.
|
||||
func (c ChannelID) String() string {
|
||||
return hex.EncodeToString(c[:])
|
||||
}
|
||||
|
||||
// NewChanIDFromOutPoint converts a target OutPoint into a ChannelID that is
|
||||
// usable within the network. In order to convert the OutPoint into a ChannelID,
|
||||
// we XOR the lower 2-bytes of the txid within the OutPoint with the big-endian
|
||||
// serialization of the Index of the OutPoint, truncated to 2-bytes.
|
||||
func NewChanIDFromOutPoint(op *wire.OutPoint) ChannelID {
|
||||
// First we'll copy the txid of the outpoint into our channel ID slice.
|
||||
var cid ChannelID
|
||||
copy(cid[:], op.Hash[:])
|
||||
|
||||
// With the txid copied over, we'll now XOR the lower 2-bytes of the
|
||||
// partial channelID with big-endian serialization of output index.
|
||||
xorTxid(&cid, uint16(op.Index))
|
||||
|
||||
return cid
|
||||
}
|
||||
|
||||
// xorTxid performs the transformation needed to transform an OutPoint into a
|
||||
// ChannelID. To do this, we expect the cid parameter to contain the txid
|
||||
// unaltered and the outputIndex to be the output index
|
||||
func xorTxid(cid *ChannelID, outputIndex uint16) {
|
||||
var buf [2]byte
|
||||
binary.BigEndian.PutUint16(buf[:], outputIndex)
|
||||
|
||||
cid[30] ^= buf[0]
|
||||
cid[31] ^= buf[1]
|
||||
}
|
||||
|
||||
// readOutpoint reads an outpoint from the passed reader.
|
||||
func readOutpoint(r io.Reader, o *wire.OutPoint) error {
|
||||
if _, err := io.ReadFull(r, o.Hash[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Read(r, byteOrder, &o.Index); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
12
channeldb/migration29/log.go
Normal file
12
channeldb/migration29/log.go
Normal file
@ -0,0 +1,12 @@
|
||||
package migration29
|
||||
|
||||
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 specific Logger to output package logging info.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
72
channeldb/migration29/migration.go
Normal file
72
channeldb/migration29/migration.go
Normal file
@ -0,0 +1,72 @@
|
||||
package migration29
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
)
|
||||
|
||||
var (
|
||||
// outpointBucket is the bucket that stores the set of outpoints we
|
||||
// know about.
|
||||
outpointBucket = []byte("outpoint-bucket")
|
||||
|
||||
// chanIDBucket is the bucket that stores the set of ChannelID's we
|
||||
// know about.
|
||||
chanIDBucket = []byte("chan-id-bucket")
|
||||
)
|
||||
|
||||
// MigrateChanID populates the ChannelID index by using the set of outpoints
|
||||
// retrieved from the outpoint bucket.
|
||||
func MigrateChanID(tx kvdb.RwTx) error {
|
||||
log.Info("Populating ChannelID index")
|
||||
|
||||
// First we'll retrieve the set of outpoints we know about.
|
||||
ops, err := fetchOutPoints(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return populateChanIDIndex(tx, ops)
|
||||
}
|
||||
|
||||
// fetchOutPoints loops through the outpointBucket and returns each stored
|
||||
// outpoint.
|
||||
func fetchOutPoints(tx kvdb.RwTx) ([]*wire.OutPoint, error) {
|
||||
var ops []*wire.OutPoint
|
||||
|
||||
bucket := tx.ReadBucket(outpointBucket)
|
||||
|
||||
err := bucket.ForEach(func(k, _ []byte) error {
|
||||
var op wire.OutPoint
|
||||
r := bytes.NewReader(k)
|
||||
if err := readOutpoint(r, &op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ops = append(ops, &op)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ops, nil
|
||||
}
|
||||
|
||||
// populateChanIDIndex uses the set of retrieved outpoints and populates the
|
||||
// ChannelID index.
|
||||
func populateChanIDIndex(tx kvdb.RwTx, ops []*wire.OutPoint) error {
|
||||
bucket := tx.ReadWriteBucket(chanIDBucket)
|
||||
|
||||
for _, op := range ops {
|
||||
chanID := NewChanIDFromOutPoint(op)
|
||||
|
||||
if err := bucket.Put(chanID[:], []byte{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
67
channeldb/migration29/migration_test.go
Normal file
67
channeldb/migration29/migration_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
package migration29
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb/migtest"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
)
|
||||
|
||||
var (
|
||||
hexStr = migtest.Hex
|
||||
|
||||
outpoint1 = hexStr("81b637d8fcd2c6da6859e6963113a1170de793e4b725b84d1e0b4cf99ec58ce90fb463ad")
|
||||
outpoint2 = hexStr("81b637d8fcd2c6da6859e6963113a1170de793e4b725b84d1e0b4cf99ec58ce952d6c6c7")
|
||||
|
||||
chanID1 = hexStr("81b637d8fcd2c6da6859e6963113a1170de793e4b725b84d1e0b4cf99ec5ef44")
|
||||
chanID2 = hexStr("81b637d8fcd2c6da6859e6963113a1170de793e4b725b84d1e0b4cf99ec54a2e")
|
||||
|
||||
// These tlv streams are used to populate the outpoint bucket at the
|
||||
// start of the test.
|
||||
tlvOutpointOpen = hexStr("000100")
|
||||
tlvOutpointClosed = hexStr("000101")
|
||||
|
||||
// outpointData is used to populate the outpoint bucket.
|
||||
outpointData = map[string]interface{}{
|
||||
outpoint1: tlvOutpointOpen,
|
||||
outpoint2: tlvOutpointClosed,
|
||||
}
|
||||
|
||||
// chanIDBefore is the ChannelID bucket before the migration.
|
||||
chanIDBefore = map[string]interface{}{}
|
||||
|
||||
// post is the expected data in the ChannelID bucket after the
|
||||
// migration.
|
||||
post = map[string]interface{}{
|
||||
chanID1: "",
|
||||
chanID2: "",
|
||||
}
|
||||
)
|
||||
|
||||
// TestMigrateChannelIDIndex asserts that the ChannelID index is properly
|
||||
// populated.
|
||||
func TestMigrateChannelIDIndex(t *testing.T) {
|
||||
// Prime the database with the populated outpoint bucket. We create the
|
||||
// ChannelID bucket since the prior migration creates it anyways.
|
||||
before := func(tx kvdb.RwTx) error {
|
||||
err := migtest.RestoreDB(tx, outpointBucket, outpointData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return migtest.RestoreDB(tx, chanIDBucket, chanIDBefore)
|
||||
}
|
||||
|
||||
// After the migration, ensure that the ChannelID bucket is properly
|
||||
// populated.
|
||||
after := func(tx kvdb.RwTx) error {
|
||||
err := migtest.VerifyDB(tx, outpointBucket, outpointData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return migtest.VerifyDB(tx, chanIDBucket, post)
|
||||
}
|
||||
|
||||
migtest.ApplyMigration(t, before, after, MigrateChanID, false)
|
||||
}
|
@ -243,7 +243,7 @@ func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) e
|
||||
// funding broadcast height to a reasonable value that we
|
||||
// determined earlier.
|
||||
case channel.ShortChanID().BlockHeight == 0:
|
||||
channel.FundingBroadcastHeight = firstChanHeight
|
||||
channel.SetBroadcastHeight(firstChanHeight)
|
||||
|
||||
// Fallback case 2: It is extremely unlikely at this point that
|
||||
// a channel we are trying to restore has a coinbase funding TX.
|
||||
@ -255,7 +255,7 @@ func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) e
|
||||
// unconfirmed one here.
|
||||
case channel.ShortChannelID.TxIndex == 0:
|
||||
broadcastHeight := channel.ShortChannelID.BlockHeight
|
||||
channel.FundingBroadcastHeight = broadcastHeight
|
||||
channel.SetBroadcastHeight(broadcastHeight)
|
||||
channel.ShortChannelID.BlockHeight = 0
|
||||
}
|
||||
}
|
||||
|
@ -210,6 +210,16 @@ var openChannelCommand = cli.Command{
|
||||
"propose to the remote peer (%q, %q)",
|
||||
channelTypeTweakless, channelTypeAnchors),
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "zero_conf",
|
||||
Usage: "(optional) whether a zero-conf channel open " +
|
||||
"should be attempted.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "scid_alias",
|
||||
Usage: "(optional) whether a scid-alias channel type" +
|
||||
" should be negotiated.",
|
||||
},
|
||||
},
|
||||
Action: actionDecorator(openChannel),
|
||||
}
|
||||
@ -249,6 +259,8 @@ func openChannel(ctx *cli.Context) error {
|
||||
CloseAddress: ctx.String("close_address"),
|
||||
RemoteMaxValueInFlightMsat: ctx.Uint64("remote_max_value_in_flight_msat"),
|
||||
MaxLocalCsv: uint32(ctx.Uint64("max_local_csv")),
|
||||
ZeroConf: ctx.Bool("zero_conf"),
|
||||
ScidAlias: ctx.Bool("scid_alias"),
|
||||
}
|
||||
|
||||
switch {
|
||||
|
@ -266,10 +266,25 @@ func (c *chainWatcher) Start() error {
|
||||
|
||||
// As a height hint, we'll try to use the opening height, but if the
|
||||
// channel isn't yet open, then we'll use the height it was broadcast
|
||||
// at.
|
||||
// at. This may be an unconfirmed zero-conf channel.
|
||||
heightHint := c.cfg.chanState.ShortChanID().BlockHeight
|
||||
if heightHint == 0 {
|
||||
heightHint = chanState.FundingBroadcastHeight
|
||||
heightHint = chanState.BroadcastHeight()
|
||||
}
|
||||
|
||||
// Since no zero-conf state is stored in a channel backup, the below
|
||||
// logic will not be triggered for restored, zero-conf channels. Set
|
||||
// the height hint for zero-conf channels.
|
||||
if chanState.IsZeroConf() {
|
||||
if chanState.ZeroConfConfirmed() {
|
||||
// If the zero-conf channel is confirmed, we'll use the
|
||||
// confirmed SCID's block height.
|
||||
heightHint = chanState.ZeroConfRealScid().BlockHeight
|
||||
} else {
|
||||
// The zero-conf channel is unconfirmed. We'll need to
|
||||
// use the FundingBroadcastHeight.
|
||||
heightHint = chanState.BroadcastHeight()
|
||||
}
|
||||
}
|
||||
|
||||
localKey := chanState.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed()
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
@ -72,6 +73,7 @@ var (
|
||||
type optionalMsgFields struct {
|
||||
capacity *btcutil.Amount
|
||||
channelPoint *wire.OutPoint
|
||||
remoteAlias *lnwire.ShortChannelID
|
||||
}
|
||||
|
||||
// apply applies the optional fields within the functional options.
|
||||
@ -102,6 +104,18 @@ func ChannelPoint(op wire.OutPoint) OptionalMsgField {
|
||||
}
|
||||
}
|
||||
|
||||
// RemoteAlias is an optional field that lets the gossiper know that a locally
|
||||
// sent channel update is actually an update for the peer that should replace
|
||||
// the ShortChannelID field with the remote's alias. This is only used for
|
||||
// channels with peers where the option-scid-alias feature bit was negotiated.
|
||||
// The channel update will be added to the graph under the original SCID, but
|
||||
// will be modified and re-signed with this alias.
|
||||
func RemoteAlias(alias *lnwire.ShortChannelID) OptionalMsgField {
|
||||
return func(f *optionalMsgFields) {
|
||||
f.remoteAlias = alias
|
||||
}
|
||||
}
|
||||
|
||||
// networkMsg couples a routing related wire message with the peer that
|
||||
// originally sent it.
|
||||
type networkMsg struct {
|
||||
@ -277,12 +291,40 @@ type Config struct {
|
||||
// how often we should allow a new update for a specific channel and
|
||||
// direction.
|
||||
ChannelUpdateInterval time.Duration
|
||||
|
||||
// IsAlias returns true if a given ShortChannelID is an alias for
|
||||
// option_scid_alias channels.
|
||||
IsAlias func(scid lnwire.ShortChannelID) bool
|
||||
|
||||
// SignAliasUpdate is used to re-sign a channel update using the
|
||||
// remote's alias if the option-scid-alias feature bit was negotiated.
|
||||
SignAliasUpdate func(u *lnwire.ChannelUpdate) (*ecdsa.Signature,
|
||||
error)
|
||||
|
||||
// FindBaseByAlias finds the SCID stored in the graph by an alias SCID.
|
||||
// This is used for channels that have negotiated the option-scid-alias
|
||||
// feature bit.
|
||||
FindBaseByAlias func(alias lnwire.ShortChannelID) (
|
||||
lnwire.ShortChannelID, error)
|
||||
|
||||
// GetAlias allows the gossiper to look up the peer's alias for a given
|
||||
// ChannelID. This is used to sign updates for them if the channel has
|
||||
// no AuthProof and the option-scid-alias feature bit was negotiated.
|
||||
GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
|
||||
}
|
||||
|
||||
// processedNetworkMsg is a wrapper around networkMsg and a boolean. It is
|
||||
// used to let the caller of the lru.Cache know if a message has already been
|
||||
// processed or not.
|
||||
type processedNetworkMsg struct {
|
||||
processed bool
|
||||
msg *networkMsg
|
||||
}
|
||||
|
||||
// cachedNetworkMsg is a wrapper around a network message that can be used with
|
||||
// *lru.Cache.
|
||||
type cachedNetworkMsg struct {
|
||||
msgs []*networkMsg
|
||||
msgs []*processedNetworkMsg
|
||||
}
|
||||
|
||||
// Size returns the "size" of an entry. We return the number of items as we
|
||||
@ -596,11 +638,11 @@ func (d *AuthenticatedGossiper) resendFutureMessages(height uint32) {
|
||||
log.Debugf("Resending %d network messages at height %d",
|
||||
len(msgs), height)
|
||||
|
||||
for _, msg := range msgs {
|
||||
for _, pMsg := range msgs {
|
||||
select {
|
||||
case d.networkMsgs <- msg:
|
||||
case d.networkMsgs <- pMsg.msg:
|
||||
case <-d.quit:
|
||||
msg.err <- ErrGossiperShuttingDown
|
||||
pMsg.msg.err <- ErrGossiperShuttingDown
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1523,6 +1565,37 @@ func (d *AuthenticatedGossiper) processChanPolicyUpdate(
|
||||
// avoid directly giving away their existence. Instead, we'll
|
||||
// send the update directly to the remote party.
|
||||
if edgeInfo.Info.AuthProof == nil {
|
||||
// If AuthProof is nil and an alias was found for this
|
||||
// ChannelID (meaning the option-scid-alias feature was
|
||||
// negotiated), we'll replace the ShortChannelID in the
|
||||
// update with the peer's alias. We do this after
|
||||
// updateChannel so that the alias isn't persisted to
|
||||
// the database.
|
||||
op := &edgeInfo.Info.ChannelPoint
|
||||
chanID := lnwire.NewChanIDFromOutPoint(op)
|
||||
|
||||
var defaultAlias lnwire.ShortChannelID
|
||||
foundAlias, _ := d.cfg.GetAlias(chanID)
|
||||
if foundAlias != defaultAlias {
|
||||
chanUpdate.ShortChannelID = foundAlias
|
||||
|
||||
sig, err := d.cfg.SignAliasUpdate(chanUpdate)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to sign alias "+
|
||||
"update: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
lnSig, err := lnwire.NewSigFromSignature(sig)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to create sig: %v",
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
chanUpdate.Signature = lnSig
|
||||
}
|
||||
|
||||
remotePubKey := remotePubFromChanInfo(
|
||||
edgeInfo.Info, chanUpdate.ChannelFlags,
|
||||
)
|
||||
@ -1703,7 +1776,7 @@ func (d *AuthenticatedGossiper) isPremature(chanID lnwire.ShortChannelID,
|
||||
// Init an empty cached message and overwrite it if there are cached
|
||||
// messages found.
|
||||
cachedMsgs := &cachedNetworkMsg{
|
||||
msgs: make([]*networkMsg, 0),
|
||||
msgs: make([]*processedNetworkMsg, 0),
|
||||
}
|
||||
|
||||
result, err := d.futureMsgs.Get(msgHeight)
|
||||
@ -1723,8 +1796,11 @@ func (d *AuthenticatedGossiper) isPremature(chanID lnwire.ShortChannelID,
|
||||
err: make(chan error, 1),
|
||||
}
|
||||
|
||||
// The processed boolean is unused in the futureMsgs case.
|
||||
pMsg := &processedNetworkMsg{msg: copied}
|
||||
|
||||
// Add the network message.
|
||||
cachedMsgs.msgs = append(cachedMsgs.msgs, copied)
|
||||
cachedMsgs.msgs = append(cachedMsgs.msgs, pMsg)
|
||||
_, err = d.futureMsgs.Put(msgHeight, cachedMsgs)
|
||||
if err != nil {
|
||||
log.Errorf("Adding future message got error: %v", err)
|
||||
@ -1826,7 +1902,8 @@ func (d *AuthenticatedGossiper) processZombieUpdate(
|
||||
// With the signature valid, we'll proceed to mark the
|
||||
// edge as live and wait for the channel announcement to
|
||||
// come through again.
|
||||
err = d.cfg.Router.MarkEdgeLive(msg.ShortChannelID)
|
||||
baseScid := lnwire.NewShortChanIDFromInt(chanInfo.ChannelID)
|
||||
err = d.cfg.Router.MarkEdgeLive(baseScid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to remove edge with "+
|
||||
"chan_id=%v from zombie index: %v",
|
||||
@ -2147,6 +2224,24 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// If this is a remote ChannelAnnouncement with an alias SCID, we'll
|
||||
// reject the announcement. Since the router accepts alias SCIDs,
|
||||
// not erroring out would be a DoS vector.
|
||||
if nMsg.isRemote && d.cfg.IsAlias(ann.ShortChannelID) {
|
||||
err := fmt.Errorf("ignoring remote alias channel=%v",
|
||||
ann.ShortChannelID)
|
||||
log.Errorf(err.Error())
|
||||
|
||||
key := newRejectCacheKey(
|
||||
ann.ShortChannelID.ToUint64(),
|
||||
sourceToPub(nMsg.source),
|
||||
)
|
||||
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
||||
|
||||
nMsg.err <- err
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// If the advertised inclusionary block is beyond our knowledge of the
|
||||
// chain tip, then we'll ignore it for now.
|
||||
d.Lock()
|
||||
@ -2293,7 +2388,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
||||
// If we earlier received any ChannelUpdates for this channel, we can
|
||||
// now process them, as the channel is added to the graph.
|
||||
shortChanID := ann.ShortChannelID.ToUint64()
|
||||
var channelUpdates []*networkMsg
|
||||
var channelUpdates []*processedNetworkMsg
|
||||
|
||||
earlyChanUpdates, err := d.prematureChannelUpdates.Get(shortChanID)
|
||||
if err == nil {
|
||||
@ -2308,6 +2403,16 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
||||
// ensure we don't block here, as we can handle only one announcement
|
||||
// at a time.
|
||||
for _, cu := range channelUpdates {
|
||||
// Skip if already processed.
|
||||
if cu.processed {
|
||||
continue
|
||||
}
|
||||
|
||||
// Mark the ChannelUpdate as processed. This ensures that a
|
||||
// subsequent announcement in the option-scid-alias case does
|
||||
// not re-use an old ChannelUpdate.
|
||||
cu.processed = true
|
||||
|
||||
d.wg.Add(1)
|
||||
go func(updMsg *networkMsg) {
|
||||
defer d.wg.Done()
|
||||
@ -2333,7 +2438,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
||||
log.Errorf("Unsupported message type found "+
|
||||
"among ChannelUpdates: %T", msg)
|
||||
}
|
||||
}(cu)
|
||||
}(cu.msg)
|
||||
}
|
||||
|
||||
// Channel announcement was successfully processed and now it might be
|
||||
@ -2380,9 +2485,13 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
|
||||
// If the advertised inclusionary block is beyond our knowledge of the
|
||||
// chain tip, then we'll put the announcement in limbo to be fully
|
||||
// verified once we advance forward in the chain.
|
||||
// verified once we advance forward in the chain. If the update has an
|
||||
// alias SCID, we'll skip the isPremature check. This is necessary
|
||||
// since aliases start at block height 16_000_000.
|
||||
d.Lock()
|
||||
if nMsg.isRemote && d.isPremature(upd.ShortChannelID, 0, nMsg) {
|
||||
if nMsg.isRemote && !d.cfg.IsAlias(upd.ShortChannelID) &&
|
||||
d.isPremature(upd.ShortChannelID, 0, nMsg) {
|
||||
|
||||
log.Warnf("Update announcement for short_chan_id(%v), is "+
|
||||
"premature: advertises height %v, only height %v is "+
|
||||
"known", shortChanID, blockHeight, d.bestHeight)
|
||||
@ -2396,8 +2505,21 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
// whether this update is stale or is for a zombie channel in order to
|
||||
// quickly reject it.
|
||||
timestamp := time.Unix(int64(upd.Timestamp), 0)
|
||||
|
||||
// Fetch the SCID we should be using to lock the channelMtx and make
|
||||
// graph queries with.
|
||||
graphScid, err := d.cfg.FindBaseByAlias(upd.ShortChannelID)
|
||||
if err != nil {
|
||||
// Fallback and set the graphScid to the peer-provided SCID.
|
||||
// This will occur for non-option-scid-alias channels and for
|
||||
// public option-scid-alias channels after 6 confirmations.
|
||||
// Once public option-scid-alias channels have 6 confs, we'll
|
||||
// ignore ChannelUpdates with one of their aliases.
|
||||
graphScid = upd.ShortChannelID
|
||||
}
|
||||
|
||||
if d.cfg.Router.IsStaleEdgePolicy(
|
||||
upd.ShortChannelID, timestamp, upd.ChannelFlags,
|
||||
graphScid, timestamp, upd.ChannelFlags,
|
||||
) {
|
||||
|
||||
log.Debugf("Ignored stale edge policy: peer=%v, source=%x, "+
|
||||
@ -2418,11 +2540,10 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
// access the database. This ensures the state we read from the
|
||||
// database has not changed between this point and when we call
|
||||
// UpdateEdge() later.
|
||||
d.channelMtx.Lock(upd.ShortChannelID.ToUint64())
|
||||
defer d.channelMtx.Unlock(upd.ShortChannelID.ToUint64())
|
||||
chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID(
|
||||
upd.ShortChannelID,
|
||||
)
|
||||
d.channelMtx.Lock(graphScid.ToUint64())
|
||||
defer d.channelMtx.Unlock(graphScid.ToUint64())
|
||||
|
||||
chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID(graphScid)
|
||||
switch err {
|
||||
// No error, break.
|
||||
case nil:
|
||||
@ -2458,6 +2579,13 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
// ChannelAnnouncement for since we reject them. Because of
|
||||
// this, we temporarily add it to a map, and reprocess it after
|
||||
// our own ChannelAnnouncement has been processed.
|
||||
//
|
||||
// The shortChanID may be an alias, but it is fine to use here
|
||||
// since we don't have an edge in the graph and if the peer is
|
||||
// not buggy, we should be able to use it once the gossiper
|
||||
// receives the local announcement.
|
||||
pMsg := &processedNetworkMsg{msg: nMsg}
|
||||
|
||||
earlyMsgs, err := d.prematureChannelUpdates.Get(shortChanID)
|
||||
switch {
|
||||
// Nothing in the cache yet, we can just directly insert this
|
||||
@ -2465,14 +2593,14 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
case err == cache.ErrElementNotFound:
|
||||
_, _ = d.prematureChannelUpdates.Put(
|
||||
shortChanID, &cachedNetworkMsg{
|
||||
msgs: []*networkMsg{nMsg},
|
||||
msgs: []*processedNetworkMsg{pMsg},
|
||||
})
|
||||
|
||||
// There's already something in the cache, so we'll combine the
|
||||
// set of messages into a single value.
|
||||
default:
|
||||
msgs := earlyMsgs.(*cachedNetworkMsg).msgs
|
||||
msgs = append(msgs, nMsg)
|
||||
msgs = append(msgs, pMsg)
|
||||
_, _ = d.prematureChannelUpdates.Put(
|
||||
shortChanID, &cachedNetworkMsg{
|
||||
msgs: msgs,
|
||||
@ -2555,8 +2683,16 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
// maximum burst of 10. If we haven't seen an update
|
||||
// for this channel before, we'll need to initialize a
|
||||
// rate limiter for each direction.
|
||||
//
|
||||
// Since the edge exists in the graph, we'll create a
|
||||
// rate limiter for chanInfo.ChannelID rather then the
|
||||
// SCID the peer sent. This is because there may be
|
||||
// multiple aliases for a channel and we may otherwise
|
||||
// rate-limit only a single alias of the channel,
|
||||
// instead of the whole channel.
|
||||
baseScid := chanInfo.ChannelID
|
||||
d.Lock()
|
||||
rls, ok := d.chanUpdateRateLimiter[shortChanID]
|
||||
rls, ok := d.chanUpdateRateLimiter[baseScid]
|
||||
if !ok {
|
||||
r := rate.Every(d.cfg.ChannelUpdateInterval)
|
||||
b := d.cfg.MaxChannelUpdateBurst
|
||||
@ -2564,7 +2700,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
rate.NewLimiter(r, b),
|
||||
rate.NewLimiter(r, b),
|
||||
}
|
||||
d.chanUpdateRateLimiter[shortChanID] = rls
|
||||
d.chanUpdateRateLimiter[baseScid] = rls
|
||||
}
|
||||
d.Unlock()
|
||||
|
||||
@ -2578,9 +2714,16 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
}
|
||||
}
|
||||
|
||||
// We'll use chanInfo.ChannelID rather than the peer-supplied
|
||||
// ShortChannelID in the ChannelUpdate to avoid the router having to
|
||||
// lookup the stored SCID. If we're sending the update, we'll always
|
||||
// use the SCID stored in the database rather than a potentially
|
||||
// different alias. This might mean that SigBytes is incorrect as it
|
||||
// signs a different SCID than the database SCID, but since there will
|
||||
// only be a difference if AuthProof == nil, this is fine.
|
||||
update := &channeldb.ChannelEdgePolicy{
|
||||
SigBytes: upd.Signature.ToSignatureBytes(),
|
||||
ChannelID: shortChanID,
|
||||
ChannelID: chanInfo.ChannelID,
|
||||
LastUpdate: timestamp,
|
||||
MessageFlags: upd.MessageFlags,
|
||||
ChannelFlags: upd.ChannelFlags,
|
||||
@ -2601,8 +2744,10 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
|
||||
log.Debug(err)
|
||||
} else {
|
||||
// Since we know the stored SCID in the graph, we'll
|
||||
// cache that SCID.
|
||||
key := newRejectCacheKey(
|
||||
upd.ShortChannelID.ToUint64(),
|
||||
chanInfo.ChannelID,
|
||||
sourceToPub(nMsg.source),
|
||||
)
|
||||
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
||||
@ -2620,6 +2765,35 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
// to be given the update, so we'll try sending the update directly to
|
||||
// the remote peer.
|
||||
if !nMsg.isRemote && chanInfo.AuthProof == nil {
|
||||
if nMsg.optionalMsgFields != nil {
|
||||
remoteAlias := nMsg.optionalMsgFields.remoteAlias
|
||||
if remoteAlias != nil {
|
||||
// The remoteAlias field was specified, meaning
|
||||
// that we should replace the SCID in the
|
||||
// update with the remote's alias. We'll also
|
||||
// need to re-sign the channel update. This is
|
||||
// required for option-scid-alias feature-bit
|
||||
// negotiated channels.
|
||||
upd.ShortChannelID = *remoteAlias
|
||||
|
||||
sig, err := d.cfg.SignAliasUpdate(upd)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
nMsg.err <- err
|
||||
return nil, false
|
||||
}
|
||||
|
||||
lnSig, err := lnwire.NewSigFromSignature(sig)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
nMsg.err <- err
|
||||
return nil, false
|
||||
}
|
||||
|
||||
upd.Signature = lnSig
|
||||
}
|
||||
}
|
||||
|
||||
// Get our peer's public key.
|
||||
remotePubKey := remotePubFromChanInfo(
|
||||
chanInfo, upd.ChannelFlags,
|
||||
@ -2645,9 +2819,10 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
|
||||
// Channel update announcement was successfully processed and now it
|
||||
// can be broadcast to the rest of the network. However, we'll only
|
||||
// broadcast the channel update announcement if it has an attached
|
||||
// authentication proof.
|
||||
// authentication proof. We also won't broadcast the update if it
|
||||
// contains an alias because the network would reject this.
|
||||
var announcements []networkMsg
|
||||
if chanInfo.AuthProof != nil {
|
||||
if chanInfo.AuthProof != nil && !d.cfg.IsAlias(upd.ShortChannelID) {
|
||||
announcements = append(announcements, networkMsg{
|
||||
peer: nMsg.peer,
|
||||
source: nMsg.source,
|
||||
@ -2704,7 +2879,6 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
|
||||
chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID(
|
||||
ann.ShortChannelID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
proof := channeldb.NewWaitingProof(nMsg.isRemote, ann)
|
||||
err := d.cfg.WaitingProofStore.Add(proof)
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
@ -734,6 +735,27 @@ func createTestCtx(startHeight uint32) (*testCtx, func(), error) {
|
||||
}
|
||||
|
||||
broadcastedMessage := make(chan msgWithSenders, 10)
|
||||
|
||||
isAlias := func(lnwire.ShortChannelID) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
signAliasUpdate := func(*lnwire.ChannelUpdate) (*ecdsa.Signature,
|
||||
error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
findBaseByAlias := func(lnwire.ShortChannelID) (lnwire.ShortChannelID,
|
||||
error) {
|
||||
|
||||
return lnwire.ShortChannelID{}, fmt.Errorf("no base scid")
|
||||
}
|
||||
|
||||
getAlias := func(lnwire.ChannelID) (lnwire.ShortChannelID, error) {
|
||||
return lnwire.ShortChannelID{}, fmt.Errorf("no peer alias")
|
||||
}
|
||||
|
||||
gossiper := New(Config{
|
||||
Notifier: notifier,
|
||||
Broadcast: func(senders map[route.Vertex]struct{},
|
||||
@ -778,6 +800,10 @@ func createTestCtx(startHeight uint32) (*testCtx, func(), error) {
|
||||
MinimumBatchSize: 10,
|
||||
MaxChannelUpdateBurst: DefaultMaxChannelUpdateBurst,
|
||||
ChannelUpdateInterval: DefaultChannelUpdateInterval,
|
||||
IsAlias: isAlias,
|
||||
SignAliasUpdate: signAliasUpdate,
|
||||
FindBaseByAlias: findBaseByAlias,
|
||||
GetAlias: getAlias,
|
||||
}, selfKeyDesc)
|
||||
|
||||
if err := gossiper.Start(); err != nil {
|
||||
@ -1387,6 +1413,27 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) {
|
||||
// NotifyWhenOffline methods. This should trigger a new attempt to send
|
||||
// the message to the peer.
|
||||
ctx.gossiper.Stop()
|
||||
|
||||
isAlias := func(lnwire.ShortChannelID) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
signAliasUpdate := func(*lnwire.ChannelUpdate) (*ecdsa.Signature,
|
||||
error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
findBaseByAlias := func(lnwire.ShortChannelID) (lnwire.ShortChannelID,
|
||||
error) {
|
||||
|
||||
return lnwire.ShortChannelID{}, fmt.Errorf("no base scid")
|
||||
}
|
||||
|
||||
getAlias := func(lnwire.ChannelID) (lnwire.ShortChannelID, error) {
|
||||
return lnwire.ShortChannelID{}, fmt.Errorf("no peer alias")
|
||||
}
|
||||
|
||||
gossiper := New(Config{
|
||||
Notifier: ctx.gossiper.cfg.Notifier,
|
||||
Broadcast: ctx.gossiper.cfg.Broadcast,
|
||||
@ -1405,6 +1452,10 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) {
|
||||
NumActiveSyncers: 3,
|
||||
MinimumBatchSize: 10,
|
||||
SubBatchDelay: time.Second * 5,
|
||||
IsAlias: isAlias,
|
||||
SignAliasUpdate: signAliasUpdate,
|
||||
FindBaseByAlias: findBaseByAlias,
|
||||
GetAlias: getAlias,
|
||||
}, &keychain.KeyDescriptor{
|
||||
PubKey: ctx.gossiper.selfKey,
|
||||
KeyLocator: ctx.gossiper.selfKeyLoc,
|
||||
|
@ -1,5 +1,10 @@
|
||||
# Release Notes
|
||||
|
||||
## Protocol Extensions
|
||||
|
||||
### Zero-Conf Channel Opens
|
||||
* [Introduces support for zero-conf channel opens and non-zero-conf option_scid_alias channels.](https://github.com/lightningnetwork/lnd/pull/5955)
|
||||
|
||||
## Build system
|
||||
|
||||
* [Add the release build directory to the `.gitignore` file to avoid the release
|
||||
|
@ -71,4 +71,12 @@ var defaultSetDesc = setDesc{
|
||||
SetInit: {}, // I
|
||||
SetNodeAnn: {}, // N
|
||||
},
|
||||
lnwire.ScidAliasOptional: {
|
||||
SetInit: {}, // I
|
||||
SetNodeAnn: {}, // N
|
||||
},
|
||||
lnwire.ZeroConfOptional: {
|
||||
SetInit: {}, // I
|
||||
SetNodeAnn: {}, // N
|
||||
},
|
||||
}
|
||||
|
@ -72,6 +72,12 @@ var deps = depDesc{
|
||||
lnwire.KeysendOptional: {
|
||||
lnwire.TLVOnionPayloadOptional: {},
|
||||
},
|
||||
lnwire.ScidAliasOptional: {
|
||||
lnwire.ExplicitChannelTypeOptional: {},
|
||||
},
|
||||
lnwire.ZeroConfOptional: {
|
||||
lnwire.ScidAliasOptional: {},
|
||||
},
|
||||
}
|
||||
|
||||
// ValidateDeps asserts that a feature vector sets all features and their
|
||||
|
@ -31,6 +31,15 @@ type Config struct {
|
||||
// NoKeysend unsets any bits signaling support for accepting keysend
|
||||
// payments.
|
||||
NoKeysend bool
|
||||
|
||||
// NoOptionScidAlias unsets any bits signalling support for
|
||||
// option_scid_alias. This also implicitly disables zero-conf channels.
|
||||
NoOptionScidAlias bool
|
||||
|
||||
// NoZeroConf unsets any bits signalling support for zero-conf
|
||||
// channels. This should be used instead of NoOptionScidAlias to still
|
||||
// keep option-scid-alias support.
|
||||
NoZeroConf bool
|
||||
}
|
||||
|
||||
// Manager is responsible for generating feature vectors for different requested
|
||||
@ -125,6 +134,14 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
|
||||
raw.Unset(lnwire.KeysendOptional)
|
||||
raw.Unset(lnwire.KeysendRequired)
|
||||
}
|
||||
if cfg.NoOptionScidAlias {
|
||||
raw.Unset(lnwire.ScidAliasOptional)
|
||||
raw.Unset(lnwire.ScidAliasRequired)
|
||||
}
|
||||
if cfg.NoZeroConf {
|
||||
raw.Unset(lnwire.ZeroConfOptional)
|
||||
raw.Unset(lnwire.ZeroConfRequired)
|
||||
}
|
||||
|
||||
// Ensure that all of our feature sets properly set any
|
||||
// dependent features.
|
||||
|
@ -24,10 +24,12 @@ var (
|
||||
// negotiateCommitmentType negotiates the commitment type of a newly opened
|
||||
// channel. If a channelType is provided, explicit negotiation for said type
|
||||
// will be attempted if the set of both local and remote features support it.
|
||||
// Otherwise, implicit negotiation will be attempted.
|
||||
func negotiateCommitmentType(channelType *lnwire.ChannelType,
|
||||
local, remote *lnwire.FeatureVector, mustBeExplicit bool,
|
||||
) (bool, *lnwire.ChannelType, lnwallet.CommitmentType, error) {
|
||||
// Otherwise, implicit negotiation will be attempted. Two booleans are
|
||||
// returned letting the caller know if the option-scid-alias or zero-conf
|
||||
// channel types were negotiated.
|
||||
func negotiateCommitmentType(channelType *lnwire.ChannelType, local,
|
||||
remote *lnwire.FeatureVector, mustBeExplicit bool) (bool,
|
||||
*lnwire.ChannelType, lnwallet.CommitmentType, error) {
|
||||
|
||||
if channelType != nil {
|
||||
// If the peer does know explicit negotiation, let's attempt
|
||||
@ -57,12 +59,127 @@ func negotiateCommitmentType(channelType *lnwire.ChannelType,
|
||||
// specific channel type. Since the channel type is comprised of a set of even
|
||||
// feature bits, we also make sure each feature is supported by both peers. An
|
||||
// error is returned if either peer does not support said channel type.
|
||||
func explicitNegotiateCommitmentType(channelType lnwire.ChannelType,
|
||||
local, remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
|
||||
func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local,
|
||||
remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
|
||||
|
||||
channelFeatures := lnwire.RawFeatureVector(channelType)
|
||||
|
||||
switch {
|
||||
// Lease script enforcement + anchors zero fee + static remote key +
|
||||
// zero conf + scid alias features only.
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.ScidAliasRequired,
|
||||
lnwire.ScriptEnforcedLeaseRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
):
|
||||
if !hasFeatures(
|
||||
local, remote,
|
||||
lnwire.ZeroConfOptional,
|
||||
lnwire.ScriptEnforcedLeaseOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
) {
|
||||
|
||||
return 0, errUnsupportedChannelType
|
||||
}
|
||||
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
|
||||
|
||||
// Anchors zero fee + static remote key + zero conf + scid alias
|
||||
// features only.
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.ScidAliasRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
):
|
||||
if !hasFeatures(
|
||||
local, remote,
|
||||
lnwire.ZeroConfOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
) {
|
||||
|
||||
return 0, errUnsupportedChannelType
|
||||
}
|
||||
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
|
||||
|
||||
// Lease script enforcement + anchors zero fee + static remote key +
|
||||
// zero conf features only.
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.ScriptEnforcedLeaseRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
):
|
||||
if !hasFeatures(
|
||||
local, remote,
|
||||
lnwire.ZeroConfOptional,
|
||||
lnwire.ScriptEnforcedLeaseOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
) {
|
||||
|
||||
return 0, errUnsupportedChannelType
|
||||
}
|
||||
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
|
||||
|
||||
// Anchors zero fee + static remote key + zero conf features only.
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
):
|
||||
if !hasFeatures(
|
||||
local, remote,
|
||||
lnwire.ZeroConfOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
) {
|
||||
|
||||
return 0, errUnsupportedChannelType
|
||||
}
|
||||
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
|
||||
|
||||
// Lease script enforcement + anchors zero fee + static remote key +
|
||||
// option-scid-alias features only.
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.ScidAliasRequired,
|
||||
lnwire.ScriptEnforcedLeaseRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
):
|
||||
if !hasFeatures(
|
||||
local, remote,
|
||||
lnwire.ScidAliasOptional,
|
||||
lnwire.ScriptEnforcedLeaseOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
) {
|
||||
|
||||
return 0, errUnsupportedChannelType
|
||||
}
|
||||
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
|
||||
|
||||
// Anchors zero fee + static remote key + option-scid-alias features
|
||||
// only.
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.ScidAliasRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
):
|
||||
if !hasFeatures(
|
||||
local, remote,
|
||||
lnwire.ScidAliasOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
) {
|
||||
|
||||
return 0, errUnsupportedChannelType
|
||||
}
|
||||
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
|
||||
|
||||
// Lease script enforcement + anchors zero fee + static remote key
|
||||
// features only.
|
||||
case channelFeatures.OnlyContains(
|
||||
|
@ -21,6 +21,8 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
|
||||
remoteFeatures *lnwire.RawFeatureVector
|
||||
expectsCommitType lnwallet.CommitmentType
|
||||
expectsChanType lnwire.ChannelType
|
||||
zeroConf bool
|
||||
scidAlias bool
|
||||
expectsErr error
|
||||
}{
|
||||
{
|
||||
@ -81,6 +83,134 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
|
||||
),
|
||||
expectsErr: errUnsupportedChannelType,
|
||||
},
|
||||
{
|
||||
name: "explicit zero-conf script enforced",
|
||||
channelFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.ScriptEnforcedLeaseRequired,
|
||||
),
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ZeroConfOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ScriptEnforcedLeaseOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ZeroConfOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ScriptEnforcedLeaseOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
expectsCommitType: lnwallet.CommitmentTypeScriptEnforcedLease,
|
||||
expectsChanType: lnwire.ChannelType(
|
||||
*lnwire.NewRawFeatureVector(
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.ScriptEnforcedLeaseRequired,
|
||||
),
|
||||
),
|
||||
zeroConf: true,
|
||||
expectsErr: nil,
|
||||
},
|
||||
{
|
||||
name: "explicit zero-conf anchors",
|
||||
channelFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
),
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ZeroConfOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ZeroConfOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
expectsCommitType: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx,
|
||||
expectsChanType: lnwire.ChannelType(
|
||||
*lnwire.NewRawFeatureVector(
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
),
|
||||
),
|
||||
zeroConf: true,
|
||||
expectsErr: nil,
|
||||
},
|
||||
{
|
||||
name: "explicit scid-alias script enforced",
|
||||
channelFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ScidAliasRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.ScriptEnforcedLeaseRequired,
|
||||
),
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ScidAliasOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ScriptEnforcedLeaseOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ScidAliasOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ScriptEnforcedLeaseOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
expectsCommitType: lnwallet.CommitmentTypeScriptEnforcedLease,
|
||||
expectsChanType: lnwire.ChannelType(
|
||||
*lnwire.NewRawFeatureVector(
|
||||
lnwire.ScidAliasRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.ScriptEnforcedLeaseRequired,
|
||||
),
|
||||
),
|
||||
scidAlias: true,
|
||||
expectsErr: nil,
|
||||
},
|
||||
{
|
||||
name: "explicit scid-alias anchors",
|
||||
channelFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ScidAliasRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
),
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ScidAliasOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.ScidAliasOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
expectsCommitType: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx,
|
||||
expectsChanType: lnwire.ChannelType(
|
||||
*lnwire.NewRawFeatureVector(
|
||||
lnwire.ScidAliasRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
),
|
||||
),
|
||||
scidAlias: true,
|
||||
expectsErr: nil,
|
||||
},
|
||||
{
|
||||
name: "explicit anchors",
|
||||
channelFeatures: lnwire.NewRawFeatureVector(
|
||||
@ -212,16 +342,50 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
|
||||
*testCase.channelFeatures,
|
||||
)
|
||||
}
|
||||
_, localChanType, localCommitType, err := negotiateCommitmentType(
|
||||
|
||||
_, lChan, lCommit, err := negotiateCommitmentType(
|
||||
channelType, localFeatures, remoteFeatures,
|
||||
testCase.mustBeExplicit,
|
||||
)
|
||||
|
||||
var (
|
||||
localZc bool
|
||||
localScid bool
|
||||
remoteZc bool
|
||||
remoteScid bool
|
||||
)
|
||||
|
||||
if lChan != nil {
|
||||
localFv := lnwire.RawFeatureVector(*lChan)
|
||||
localZc = localFv.IsSet(
|
||||
lnwire.ZeroConfRequired,
|
||||
)
|
||||
localScid = localFv.IsSet(
|
||||
lnwire.ScidAliasRequired,
|
||||
)
|
||||
}
|
||||
|
||||
require.Equal(t, testCase.zeroConf, localZc)
|
||||
require.Equal(t, testCase.scidAlias, localScid)
|
||||
require.Equal(t, testCase.expectsErr, err)
|
||||
|
||||
_, remoteChanType, remoteCommitType, err := negotiateCommitmentType(
|
||||
_, rChan, rCommit, err := negotiateCommitmentType(
|
||||
channelType, remoteFeatures, localFeatures,
|
||||
testCase.mustBeExplicit,
|
||||
)
|
||||
|
||||
if rChan != nil {
|
||||
remoteFv := lnwire.RawFeatureVector(*rChan)
|
||||
remoteZc = remoteFv.IsSet(
|
||||
lnwire.ZeroConfRequired,
|
||||
)
|
||||
remoteScid = remoteFv.IsSet(
|
||||
lnwire.ScidAliasRequired,
|
||||
)
|
||||
}
|
||||
|
||||
require.Equal(t, testCase.zeroConf, remoteZc)
|
||||
require.Equal(t, testCase.scidAlias, remoteScid)
|
||||
require.Equal(t, testCase.expectsErr, err)
|
||||
|
||||
if testCase.expectsErr != nil {
|
||||
@ -229,20 +393,20 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
|
||||
}
|
||||
|
||||
require.Equal(
|
||||
t, testCase.expectsCommitType, localCommitType,
|
||||
t, testCase.expectsCommitType, lCommit,
|
||||
testCase.name,
|
||||
)
|
||||
require.Equal(
|
||||
t, testCase.expectsCommitType, remoteCommitType,
|
||||
t, testCase.expectsCommitType, rCommit,
|
||||
testCase.name,
|
||||
)
|
||||
|
||||
require.Equal(
|
||||
t, testCase.expectsChanType, *localChanType,
|
||||
t, testCase.expectsChanType, *lChan,
|
||||
testCase.name,
|
||||
)
|
||||
require.Equal(
|
||||
t, testCase.expectsChanType, *remoteChanType,
|
||||
t, testCase.expectsChanType, *rChan,
|
||||
testCase.name,
|
||||
)
|
||||
})
|
||||
|
@ -19,3 +19,31 @@ type Controller interface {
|
||||
// represents a pending channel in the Controller implementation.
|
||||
IsPendingChannel([32]byte, lnpeer.Peer) bool
|
||||
}
|
||||
|
||||
// aliasHandler is an interface that abstracts the managing of aliases.
|
||||
type aliasHandler interface {
|
||||
// RequestAlias lets the funding manager request a unique SCID alias to
|
||||
// use in the funding_locked message.
|
||||
RequestAlias() (lnwire.ShortChannelID, error)
|
||||
|
||||
// PutPeerAlias lets the funding manager store the received alias SCID
|
||||
// in the funding_locked message.
|
||||
PutPeerAlias(lnwire.ChannelID, lnwire.ShortChannelID) error
|
||||
|
||||
// GetPeerAlias lets the funding manager lookup the received alias SCID
|
||||
// from the funding_locked message. This is not the same as GetAliases
|
||||
// which retrieves OUR aliases for a given channel.
|
||||
GetPeerAlias(lnwire.ChannelID) (lnwire.ShortChannelID, error)
|
||||
|
||||
// AddLocalAlias persists an alias to an underlying alias store.
|
||||
AddLocalAlias(lnwire.ShortChannelID, lnwire.ShortChannelID, bool) error
|
||||
|
||||
// GetAliases returns the set of aliases given the main SCID of a
|
||||
// channel. This SCID will be an alias for zero-conf channels and will
|
||||
// be the confirmed SCID otherwise.
|
||||
GetAliases(lnwire.ShortChannelID) []lnwire.ShortChannelID
|
||||
|
||||
// DeleteSixConfs removes the passed SCID from one of the underlying
|
||||
// alias store's indices.
|
||||
DeleteSixConfs(lnwire.ShortChannelID) error
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -115,8 +115,48 @@ var (
|
||||
testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey}
|
||||
|
||||
fundingNetParams = chainreg.BitcoinTestNetParams
|
||||
|
||||
alias = lnwire.ShortChannelID{
|
||||
BlockHeight: 16_000_000,
|
||||
TxIndex: 0,
|
||||
TxPosition: 0,
|
||||
}
|
||||
)
|
||||
|
||||
type mockAliasMgr struct{}
|
||||
|
||||
func (m *mockAliasMgr) RequestAlias() (lnwire.ShortChannelID, error) {
|
||||
return alias, nil
|
||||
}
|
||||
|
||||
func (m *mockAliasMgr) PutPeerAlias(lnwire.ChannelID,
|
||||
lnwire.ShortChannelID) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAliasMgr) GetPeerAlias(lnwire.ChannelID) (lnwire.ShortChannelID,
|
||||
error) {
|
||||
|
||||
return alias, nil
|
||||
}
|
||||
|
||||
func (m *mockAliasMgr) AddLocalAlias(lnwire.ShortChannelID,
|
||||
lnwire.ShortChannelID, bool) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAliasMgr) GetAliases(
|
||||
lnwire.ShortChannelID) []lnwire.ShortChannelID {
|
||||
|
||||
return []lnwire.ShortChannelID{alias}
|
||||
}
|
||||
|
||||
func (m *mockAliasMgr) DeleteSixConfs(lnwire.ShortChannelID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockNotifier struct {
|
||||
oneConfChannel chan *chainntnfs.TxConfirmation
|
||||
sixConfChannel chan *chainntnfs.TxConfirmation
|
||||
@ -199,6 +239,8 @@ type testNode struct {
|
||||
mockChanEvent *mockChanEvent
|
||||
testDir string
|
||||
shutdownChannel chan struct{}
|
||||
reportScidChan chan struct{}
|
||||
localFeatures []lnwire.FeatureBit
|
||||
remoteFeatures []lnwire.FeatureBit
|
||||
|
||||
remotePeer *testNode
|
||||
@ -234,7 +276,9 @@ func (n *testNode) QuitSignal() <-chan struct{} {
|
||||
}
|
||||
|
||||
func (n *testNode) LocalFeatures() *lnwire.FeatureVector {
|
||||
return lnwire.NewFeatureVector(nil, nil)
|
||||
return lnwire.NewFeatureVector(
|
||||
lnwire.NewRawFeatureVector(n.localFeatures...), nil,
|
||||
)
|
||||
}
|
||||
|
||||
func (n *testNode) RemoteFeatures() *lnwire.FeatureVector {
|
||||
@ -307,10 +351,13 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
||||
epochChan: make(chan *chainntnfs.BlockEpoch, 2),
|
||||
}
|
||||
|
||||
aliasMgr := &mockAliasMgr{}
|
||||
|
||||
sentMessages := make(chan lnwire.Message)
|
||||
sentAnnouncements := make(chan lnwire.Message)
|
||||
publTxChan := make(chan *wire.MsgTx, 1)
|
||||
shutdownChan := make(chan struct{})
|
||||
reportScidChan := make(chan struct{})
|
||||
|
||||
wc := &mock.WalletController{
|
||||
RootKey: alicePrivKey,
|
||||
@ -382,14 +429,16 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
||||
return lnwire.NodeAnnouncement{}, nil
|
||||
},
|
||||
TempChanIDSeed: chanIDSeed,
|
||||
FindChannel: func(chanID lnwire.ChannelID) (
|
||||
*channeldb.OpenChannel, error) {
|
||||
dbChannels, err := cdb.FetchAllChannels()
|
||||
FindChannel: func(node *btcec.PublicKey,
|
||||
chanID lnwire.ChannelID) (*channeldb.OpenChannel,
|
||||
error) {
|
||||
|
||||
nodeChans, err := cdb.FetchOpenChannels(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, channel := range dbChannels {
|
||||
for _, channel := range nodeChans {
|
||||
if chanID.IsChanPoint(&channel.FundingOutpoint) {
|
||||
return channel, nil
|
||||
}
|
||||
@ -432,6 +481,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
||||
return nil
|
||||
},
|
||||
ReportShortChanID: func(wire.OutPoint) error {
|
||||
reportScidChan <- struct{}{}
|
||||
return nil
|
||||
},
|
||||
PublishTransaction: func(txn *wire.MsgTx, _ string) error {
|
||||
@ -450,6 +500,12 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
||||
OpenChannelPredicate: chainedAcceptor,
|
||||
NotifyPendingOpenChannelEvent: evt.NotifyPendingOpenChannelEvent,
|
||||
RegisteredChains: chainreg.NewChainRegistry(),
|
||||
DeleteAliasEdge: func(scid lnwire.ShortChannelID) (
|
||||
*channeldb.ChannelEdgePolicy, error) {
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
AliasManager: aliasMgr,
|
||||
}
|
||||
|
||||
for _, op := range options {
|
||||
@ -473,6 +529,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
||||
mockChanEvent: evt,
|
||||
testDir: tempTestDir,
|
||||
shutdownChannel: shutdownChan,
|
||||
reportScidChan: reportScidChan,
|
||||
addr: addr,
|
||||
}
|
||||
|
||||
@ -542,6 +599,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
|
||||
},
|
||||
DefaultMinHtlcIn: 5,
|
||||
RequiredRemoteMaxValue: oldCfg.RequiredRemoteMaxValue,
|
||||
ReportShortChanID: oldCfg.ReportShortChanID,
|
||||
PublishTransaction: func(txn *wire.MsgTx, _ string) error {
|
||||
publishChan <- txn
|
||||
return nil
|
||||
@ -552,6 +610,8 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
|
||||
ZombieSweeperInterval: oldCfg.ZombieSweeperInterval,
|
||||
ReservationTimeout: oldCfg.ReservationTimeout,
|
||||
OpenChannelPredicate: chainedAcceptor,
|
||||
DeleteAliasEdge: oldCfg.DeleteAliasEdge,
|
||||
AliasManager: oldCfg.AliasManager,
|
||||
})
|
||||
require.NoError(t, err, "failed recreating aliceFundingManager")
|
||||
|
||||
@ -639,7 +699,7 @@ func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
||||
|
||||
publ := fundChannel(
|
||||
t, alice, bob, localFundingAmt, pushAmt, false, numConfs,
|
||||
updateChan, announceChan,
|
||||
updateChan, announceChan, nil,
|
||||
)
|
||||
fundingOutPoint := &wire.OutPoint{
|
||||
Hash: publ.TxHash(),
|
||||
@ -652,7 +712,8 @@ func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
||||
// transaction is confirmed on-chain. Returns the funding tx.
|
||||
func fundChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
||||
pushAmt btcutil.Amount, subtractFees bool, numConfs uint32,
|
||||
updateChan chan *lnrpc.OpenStatusUpdate, announceChan bool) *wire.MsgTx {
|
||||
updateChan chan *lnrpc.OpenStatusUpdate, announceChan bool,
|
||||
chanType *lnwire.ChannelType) *wire.MsgTx {
|
||||
|
||||
// Create a funding request and start the workflow.
|
||||
errChan := make(chan error, 1)
|
||||
@ -665,6 +726,7 @@ func fundChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
||||
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
|
||||
FundingFeePerKw: 1000,
|
||||
Private: !announceChan,
|
||||
ChannelType: chanType,
|
||||
Updates: updateChan,
|
||||
Err: errChan,
|
||||
}
|
||||
@ -3231,7 +3293,7 @@ func TestFundingManagerFundAll(t *testing.T) {
|
||||
pushAmt := btcutil.Amount(0)
|
||||
fundingTx := fundChannel(
|
||||
t, alice, bob, test.spendAmt, pushAmt, true, 1,
|
||||
updateChan, true,
|
||||
updateChan, true, nil,
|
||||
)
|
||||
|
||||
// Check whether the expected change output is present.
|
||||
@ -3662,3 +3724,128 @@ func testUpfrontFailure(t *testing.T, pkscript []byte, expectErr bool) {
|
||||
require.True(t, ok, "did not receive AcceptChannel")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFundingManagerZeroConf tests that the fundingmanager properly handles
|
||||
// the whole flow for zero-conf channels.
|
||||
func TestFundingManagerZeroConf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
alice, bob := setupFundingManagers(t)
|
||||
defer tearDownFundingManagers(t, alice, bob)
|
||||
|
||||
// Alice and Bob will have the same set of feature bits in our test.
|
||||
featureBits := []lnwire.FeatureBit{
|
||||
lnwire.ZeroConfOptional,
|
||||
lnwire.ScidAliasOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
}
|
||||
alice.localFeatures = featureBits
|
||||
alice.remoteFeatures = featureBits
|
||||
bob.localFeatures = featureBits
|
||||
bob.remoteFeatures = featureBits
|
||||
|
||||
fundingAmt := btcutil.Amount(500000)
|
||||
pushAmt := btcutil.Amount(0)
|
||||
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
||||
|
||||
// Construct the zero-conf ChannelType for use in open_channel.
|
||||
channelTypeBits := []lnwire.FeatureBit{
|
||||
lnwire.ZeroConfRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
}
|
||||
channelType := lnwire.ChannelType(
|
||||
*lnwire.NewRawFeatureVector(channelTypeBits...),
|
||||
)
|
||||
|
||||
// Call fundChannel with the zero-conf ChannelType.
|
||||
fundingTx := fundChannel(
|
||||
t, alice, bob, fundingAmt, pushAmt, false, 1, updateChan, true,
|
||||
&channelType,
|
||||
)
|
||||
fundingOp := &wire.OutPoint{
|
||||
Hash: fundingTx.TxHash(),
|
||||
Index: 0,
|
||||
}
|
||||
|
||||
// Assert that Bob's funding_locked message has an AliasScid.
|
||||
bobFundingLocked := assertFundingMsgSent(
|
||||
t, bob.msgChan, "FundingLocked",
|
||||
).(*lnwire.FundingLocked)
|
||||
require.NotNil(t, bobFundingLocked.AliasScid)
|
||||
require.Equal(t, *bobFundingLocked.AliasScid, alias)
|
||||
|
||||
// Do the same for Alice as well.
|
||||
aliceFundingLocked := assertFundingMsgSent(
|
||||
t, alice.msgChan, "FundingLocked",
|
||||
).(*lnwire.FundingLocked)
|
||||
require.NotNil(t, aliceFundingLocked.AliasScid)
|
||||
require.Equal(t, *aliceFundingLocked.AliasScid, alias)
|
||||
|
||||
// Exchange the funding_locked messages.
|
||||
alice.fundingMgr.ProcessFundingMsg(bobFundingLocked, bob)
|
||||
bob.fundingMgr.ProcessFundingMsg(aliceFundingLocked, alice)
|
||||
|
||||
// We'll assert that they both create new links.
|
||||
assertHandleFundingLocked(t, alice, bob)
|
||||
|
||||
// We'll now assert that both sides send ChannelAnnouncement and
|
||||
// ChannelUpdate messages.
|
||||
assertChannelAnnouncements(t, alice, bob, fundingAmt, nil, nil)
|
||||
|
||||
// We'll now wait for the OpenStatusUpdate_ChanOpen update.
|
||||
waitForOpenUpdate(t, updateChan)
|
||||
|
||||
// Assert that both Alice & Bob are in the addedToRouterGraph state.
|
||||
assertAddedToRouterGraph(t, alice, bob, fundingOp)
|
||||
|
||||
// We'll now restart Alice's funding manager and assert that the tx
|
||||
// is rebroadcast.
|
||||
recreateAliceFundingManager(t, alice)
|
||||
|
||||
select {
|
||||
case <-alice.publTxChan:
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("timed out waiting for alice to rebroadcast tx")
|
||||
}
|
||||
|
||||
// We'll now confirm the funding transaction.
|
||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||
Tx: fundingTx,
|
||||
}
|
||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||
Tx: fundingTx,
|
||||
}
|
||||
|
||||
assertChannelAnnouncements(t, alice, bob, fundingAmt, nil, nil)
|
||||
|
||||
// Both Alice and Bob should send on reportScidChan.
|
||||
select {
|
||||
case <-alice.reportScidChan:
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("did not call ReportShortChanID in time")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-bob.reportScidChan:
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("did not call ReportShortChanID in time")
|
||||
}
|
||||
|
||||
// Send along the 6-confirmation channel so that announcement sigs can
|
||||
// be exchanged.
|
||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||
Tx: fundingTx,
|
||||
}
|
||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||
Tx: fundingTx,
|
||||
}
|
||||
|
||||
assertAnnouncementSignatures(t, alice, bob)
|
||||
|
||||
// Assert that the channel state is deleted from the fundingmanager's
|
||||
// datastore.
|
||||
assertNoChannelState(t, alice, bob, fundingOp)
|
||||
}
|
||||
|
@ -527,11 +527,18 @@ func (f *interceptedForward) FailWithCode(code lnwire.FailCode) error {
|
||||
}
|
||||
|
||||
case lnwire.CodeTemporaryChannelFailure:
|
||||
update, err := f.htlcSwitch.cfg.FetchLastChannelUpdate(
|
||||
f.packet.incomingChanID,
|
||||
update := f.htlcSwitch.failAliasUpdate(
|
||||
f.packet.incomingChanID, true,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
if update == nil {
|
||||
// Fallback to the original, non-alias behavior.
|
||||
var err error
|
||||
update, err = f.htlcSwitch.cfg.FetchLastChannelUpdate(
|
||||
f.packet.incomingChanID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
failureMsg = lnwire.NewTemporaryChannelFailure(update)
|
||||
|
@ -69,6 +69,36 @@ type dustHandler interface {
|
||||
getDustClosure() dustClosure
|
||||
}
|
||||
|
||||
// scidAliasHandler is an interface that the ChannelLink implements so it can
|
||||
// properly handle option_scid_alias channels.
|
||||
type scidAliasHandler interface {
|
||||
// attachFailAliasUpdate allows the link to properly fail incoming
|
||||
// HTLCs on option_scid_alias channels.
|
||||
attachFailAliasUpdate(failClosure func(
|
||||
sid lnwire.ShortChannelID,
|
||||
incoming bool) *lnwire.ChannelUpdate)
|
||||
|
||||
// getAliases fetches the link's underlying aliases. This is used by
|
||||
// the Switch to determine whether to forward an HTLC and where to
|
||||
// forward an HTLC.
|
||||
getAliases() []lnwire.ShortChannelID
|
||||
|
||||
// isZeroConf returns whether or not the underlying channel is a
|
||||
// zero-conf channel.
|
||||
isZeroConf() bool
|
||||
|
||||
// negotiatedAliasFeature returns whether the option-scid-alias feature
|
||||
// bit was negotiated.
|
||||
negotiatedAliasFeature() bool
|
||||
|
||||
// confirmedScid returns the confirmed SCID for a zero-conf channel.
|
||||
confirmedScid() lnwire.ShortChannelID
|
||||
|
||||
// zeroConfConfirmed returns whether or not the zero-conf channel has
|
||||
// confirmed.
|
||||
zeroConfConfirmed() bool
|
||||
}
|
||||
|
||||
// ChannelUpdateHandler is an interface that provides methods that allow
|
||||
// sending lnwire.Message to the underlying link as well as querying state.
|
||||
type ChannelUpdateHandler interface {
|
||||
@ -138,6 +168,13 @@ type ChannelLink interface {
|
||||
// Embed the dustHandler interface.
|
||||
dustHandler
|
||||
|
||||
// Embed the scidAliasHandler interface.
|
||||
scidAliasHandler
|
||||
|
||||
// IsUnadvertised returns true if the underlying channel is
|
||||
// unadvertised.
|
||||
IsUnadvertised() bool
|
||||
|
||||
// ChannelPoint returns the channel outpoint for the channel link.
|
||||
ChannelPoint() *wire.OutPoint
|
||||
|
||||
@ -165,7 +202,7 @@ type ChannelLink interface {
|
||||
CheckHtlcForward(payHash [32]byte, incomingAmt lnwire.MilliSatoshi,
|
||||
amtToForward lnwire.MilliSatoshi,
|
||||
incomingTimeout, outgoingTimeout uint32,
|
||||
heightNow uint32) *LinkError
|
||||
heightNow uint32, scid lnwire.ShortChannelID) *LinkError
|
||||
|
||||
// CheckHtlcTransit should return a nil error if the passed HTLC details
|
||||
// satisfy the current channel policy. Otherwise, a LinkError with a
|
||||
|
@ -294,6 +294,15 @@ type ChannelLinkConfig struct {
|
||||
// HtlcNotifier is an instance of a htlcNotifier which we will pipe htlc
|
||||
// events through.
|
||||
HtlcNotifier htlcNotifier
|
||||
|
||||
// FailAliasUpdate is a function used to fail an HTLC for an
|
||||
// option_scid_alias channel.
|
||||
FailAliasUpdate func(sid lnwire.ShortChannelID,
|
||||
incoming bool) *lnwire.ChannelUpdate
|
||||
|
||||
// GetAliases is used by the link and switch to fetch the set of
|
||||
// aliases for a given link.
|
||||
GetAliases func(base lnwire.ShortChannelID) []lnwire.ShortChannelID
|
||||
}
|
||||
|
||||
// shutdownReq contains an error channel that will be used by the channelLink
|
||||
@ -581,6 +590,12 @@ func (l *channelLink) markReestablished() {
|
||||
atomic.StoreInt32(&l.reestablished, 1)
|
||||
}
|
||||
|
||||
// IsUnadvertised returns true if the underlying channel is unadvertised.
|
||||
func (l *channelLink) IsUnadvertised() bool {
|
||||
state := l.channel.State()
|
||||
return state.ChannelFlags&lnwire.FFAnnounceChannel == 0
|
||||
}
|
||||
|
||||
// sampleNetworkFee samples the current fee rate on the network to get into the
|
||||
// chain in a timely manner. The returned value is expressed in fee-per-kw, as
|
||||
// this is the native rate used when computing the fee for commitment
|
||||
@ -629,14 +644,33 @@ func shouldAdjustCommitFee(netFee, chanFee,
|
||||
}
|
||||
}
|
||||
|
||||
// createFailureWithUpdate retrieves this link's last channel update message and
|
||||
// passes it into the callback. It expects a fully populated failure message.
|
||||
func (l *channelLink) createFailureWithUpdate(
|
||||
cb func(update *lnwire.ChannelUpdate) lnwire.FailureMessage) lnwire.FailureMessage {
|
||||
// failCb is used to cut down on the argument verbosity.
|
||||
type failCb func(update *lnwire.ChannelUpdate) lnwire.FailureMessage
|
||||
|
||||
update, err := l.cfg.FetchLastChannelUpdate(l.ShortChanID())
|
||||
if err != nil {
|
||||
return &lnwire.FailTemporaryNodeFailure{}
|
||||
// createFailureWithUpdate creates a ChannelUpdate when failing an incoming or
|
||||
// outgoing HTLC. It may return a FailureMessage that references a channel's
|
||||
// alias. If the channel does not have an alias, then the regular channel
|
||||
// update from disk will be returned.
|
||||
func (l *channelLink) createFailureWithUpdate(incoming bool,
|
||||
outgoingScid lnwire.ShortChannelID, cb failCb) lnwire.FailureMessage {
|
||||
|
||||
// Determine which SCID to use in case we need to use aliases in the
|
||||
// ChannelUpdate.
|
||||
scid := outgoingScid
|
||||
if incoming {
|
||||
scid = l.ShortChanID()
|
||||
}
|
||||
|
||||
// Try using the FailAliasUpdate function. If it returns nil, fallback
|
||||
// to the non-alias behavior.
|
||||
update := l.cfg.FailAliasUpdate(scid, incoming)
|
||||
if update == nil {
|
||||
// Fallback to the non-alias behavior.
|
||||
var err error
|
||||
update, err = l.cfg.FetchLastChannelUpdate(l.ShortChanID())
|
||||
if err != nil {
|
||||
return &lnwire.FailTemporaryNodeFailure{}
|
||||
}
|
||||
}
|
||||
|
||||
return cb(update)
|
||||
@ -697,6 +731,28 @@ func (l *channelLink) syncChanStates() error {
|
||||
fundingLockedMsg := lnwire.NewFundingLocked(
|
||||
l.ChanID(), nextRevocation,
|
||||
)
|
||||
|
||||
// For channels that negotiated the option-scid-alias
|
||||
// feature bit, ensure that we send over the alias in
|
||||
// the funding_locked message. We'll send the first
|
||||
// alias we find for the channel since it does not
|
||||
// matter which alias we send. We'll error out if no
|
||||
// aliases are found.
|
||||
if l.negotiatedAliasFeature() {
|
||||
aliases := l.getAliases()
|
||||
if len(aliases) == 0 {
|
||||
// This shouldn't happen since we
|
||||
// always add at least one alias before
|
||||
// the channel reaches the link.
|
||||
return fmt.Errorf("no aliases found")
|
||||
}
|
||||
|
||||
// getAliases returns a copy of the alias slice
|
||||
// so it is ok to use a pointer to the first
|
||||
// entry.
|
||||
fundingLockedMsg.AliasScid = &aliases[0]
|
||||
}
|
||||
|
||||
err = l.cfg.Peer.SendMessage(false, fundingLockedMsg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to re-send "+
|
||||
@ -2243,36 +2299,14 @@ func (l *channelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) {
|
||||
// Refresh the channel state's short channel ID by loading it from disk.
|
||||
// This ensures that the channel state accurately reflects the updated
|
||||
// short channel ID.
|
||||
err := l.channel.State().RefreshShortChanID()
|
||||
err := l.channel.State().Refresh()
|
||||
if err != nil {
|
||||
l.log.Errorf("unable to refresh short_chan_id for chan_id=%v: "+
|
||||
"%v", chanID, err)
|
||||
return hop.Source, err
|
||||
}
|
||||
|
||||
sid := l.channel.ShortChanID()
|
||||
|
||||
l.log.Infof("updating to short_chan_id=%v for chan_id=%v", sid, chanID)
|
||||
|
||||
l.Lock()
|
||||
l.shortChanID = sid
|
||||
l.Unlock()
|
||||
|
||||
go func() {
|
||||
err := l.cfg.UpdateContractSignals(&contractcourt.ContractSignals{
|
||||
ShortChanID: sid,
|
||||
})
|
||||
if err != nil {
|
||||
l.log.Errorf("unable to update signals")
|
||||
}
|
||||
}()
|
||||
|
||||
// Now that the short channel ID has been properly updated, we can begin
|
||||
// garbage collecting any forwarding packages we create.
|
||||
l.wg.Add(1)
|
||||
go l.fwdPkgGarbager()
|
||||
|
||||
return sid, nil
|
||||
return hop.Source, nil
|
||||
}
|
||||
|
||||
// ChanID returns the channel ID for the channel link. The channel ID is a more
|
||||
@ -2362,6 +2396,58 @@ func dustHelper(chantype channeldb.ChannelType, localDustLimit,
|
||||
return isDust
|
||||
}
|
||||
|
||||
// zeroConfConfirmed returns whether or not the zero-conf channel has
|
||||
// confirmed on-chain.
|
||||
//
|
||||
// Part of the scidAliasHandler interface.
|
||||
func (l *channelLink) zeroConfConfirmed() bool {
|
||||
return l.channel.State().ZeroConfConfirmed()
|
||||
}
|
||||
|
||||
// confirmedScid returns the confirmed SCID for a zero-conf channel. This
|
||||
// should not be called for non-zero-conf channels.
|
||||
//
|
||||
// Part of the scidAliasHandler interface.
|
||||
func (l *channelLink) confirmedScid() lnwire.ShortChannelID {
|
||||
return l.channel.State().ZeroConfRealScid()
|
||||
}
|
||||
|
||||
// isZeroConf returns whether or not the underlying channel is a zero-conf
|
||||
// channel.
|
||||
//
|
||||
// Part of the scidAliasHandler interface.
|
||||
func (l *channelLink) isZeroConf() bool {
|
||||
return l.channel.State().IsZeroConf()
|
||||
}
|
||||
|
||||
// negotiatedAliasFeature returns whether or not the underlying channel has
|
||||
// negotiated the option-scid-alias feature bit. This will be true for both
|
||||
// option-scid-alias and zero-conf channel-types. It will also be true for
|
||||
// channels with the feature bit but without the above channel-types.
|
||||
//
|
||||
// Part of the scidAliasFeature interface.
|
||||
func (l *channelLink) negotiatedAliasFeature() bool {
|
||||
return l.channel.State().NegotiatedAliasFeature()
|
||||
}
|
||||
|
||||
// getAliases returns the set of aliases for the underlying channel.
|
||||
//
|
||||
// Part of the scidAliasHandler interface.
|
||||
func (l *channelLink) getAliases() []lnwire.ShortChannelID {
|
||||
return l.cfg.GetAliases(l.ShortChanID())
|
||||
}
|
||||
|
||||
// attachFailAliasUpdate sets the link's FailAliasUpdate function.
|
||||
//
|
||||
// Part of the scidAliasHandler interface.
|
||||
func (l *channelLink) attachFailAliasUpdate(closure func(
|
||||
sid lnwire.ShortChannelID, incoming bool) *lnwire.ChannelUpdate) {
|
||||
|
||||
l.Lock()
|
||||
l.cfg.FailAliasUpdate = closure
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// AttachMailBox updates the current mailbox used by this link, and hooks up
|
||||
// the mailbox's message and packet outboxes to the link's upstream and
|
||||
// downstream chans, respectively.
|
||||
@ -2405,7 +2491,7 @@ func (l *channelLink) UpdateForwardingPolicy(newPolicy ForwardingPolicy) {
|
||||
func (l *channelLink) CheckHtlcForward(payHash [32]byte,
|
||||
incomingHtlcAmt, amtToForward lnwire.MilliSatoshi,
|
||||
incomingTimeout, outgoingTimeout uint32,
|
||||
heightNow uint32) *LinkError {
|
||||
heightNow uint32, originalScid lnwire.ShortChannelID) *LinkError {
|
||||
|
||||
l.RLock()
|
||||
policy := l.cfg.FwrdingPolicy
|
||||
@ -2414,6 +2500,7 @@ func (l *channelLink) CheckHtlcForward(payHash [32]byte,
|
||||
// First check whether the outgoing htlc satisfies the channel policy.
|
||||
err := l.canSendHtlc(
|
||||
policy, payHash, amtToForward, outgoingTimeout, heightNow,
|
||||
originalScid,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -2437,13 +2524,10 @@ func (l *channelLink) CheckHtlcForward(payHash [32]byte,
|
||||
|
||||
// As part of the returned error, we'll send our latest routing
|
||||
// policy so the sending node obtains the most up to date data.
|
||||
failure := l.createFailureWithUpdate(
|
||||
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewFeeInsufficient(
|
||||
amtToForward, *upd,
|
||||
)
|
||||
},
|
||||
)
|
||||
cb := func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewFeeInsufficient(amtToForward, *upd)
|
||||
}
|
||||
failure := l.createFailureWithUpdate(false, originalScid, cb)
|
||||
return NewLinkError(failure)
|
||||
}
|
||||
|
||||
@ -2459,13 +2543,12 @@ func (l *channelLink) CheckHtlcForward(payHash [32]byte,
|
||||
|
||||
// Grab the latest routing policy so the sending node is up to
|
||||
// date with our current policy.
|
||||
failure := l.createFailureWithUpdate(
|
||||
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewIncorrectCltvExpiry(
|
||||
incomingTimeout, *upd,
|
||||
)
|
||||
},
|
||||
)
|
||||
cb := func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewIncorrectCltvExpiry(
|
||||
incomingTimeout, *upd,
|
||||
)
|
||||
}
|
||||
failure := l.createFailureWithUpdate(false, originalScid, cb)
|
||||
return NewLinkError(failure)
|
||||
}
|
||||
|
||||
@ -2485,8 +2568,11 @@ func (l *channelLink) CheckHtlcTransit(payHash [32]byte,
|
||||
policy := l.cfg.FwrdingPolicy
|
||||
l.RUnlock()
|
||||
|
||||
// We pass in hop.Source here as this is only used in the Switch when
|
||||
// trying to send over a local link. This causes the fallback mechanism
|
||||
// to occur.
|
||||
return l.canSendHtlc(
|
||||
policy, payHash, amt, timeout, heightNow,
|
||||
policy, payHash, amt, timeout, heightNow, hop.Source,
|
||||
)
|
||||
}
|
||||
|
||||
@ -2494,7 +2580,7 @@ func (l *channelLink) CheckHtlcTransit(payHash [32]byte,
|
||||
// the channel's amount and time lock constraints.
|
||||
func (l *channelLink) canSendHtlc(policy ForwardingPolicy,
|
||||
payHash [32]byte, amt lnwire.MilliSatoshi, timeout uint32,
|
||||
heightNow uint32) *LinkError {
|
||||
heightNow uint32, originalScid lnwire.ShortChannelID) *LinkError {
|
||||
|
||||
// As our first sanity check, we'll ensure that the passed HTLC isn't
|
||||
// too small for the next hop. If so, then we'll cancel the HTLC
|
||||
@ -2506,13 +2592,10 @@ func (l *channelLink) canSendHtlc(policy ForwardingPolicy,
|
||||
|
||||
// As part of the returned error, we'll send our latest routing
|
||||
// policy so the sending node obtains the most up to date data.
|
||||
failure := l.createFailureWithUpdate(
|
||||
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewAmountBelowMinimum(
|
||||
amt, *upd,
|
||||
)
|
||||
},
|
||||
)
|
||||
cb := func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewAmountBelowMinimum(amt, *upd)
|
||||
}
|
||||
failure := l.createFailureWithUpdate(false, originalScid, cb)
|
||||
return NewLinkError(failure)
|
||||
}
|
||||
|
||||
@ -2524,11 +2607,10 @@ func (l *channelLink) canSendHtlc(policy ForwardingPolicy,
|
||||
|
||||
// As part of the returned error, we'll send our latest routing
|
||||
// policy so the sending node obtains the most up-to-date data.
|
||||
failure := l.createFailureWithUpdate(
|
||||
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewTemporaryChannelFailure(upd)
|
||||
},
|
||||
)
|
||||
cb := func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewTemporaryChannelFailure(upd)
|
||||
}
|
||||
failure := l.createFailureWithUpdate(false, originalScid, cb)
|
||||
return NewDetailedLinkError(failure, OutgoingFailureHTLCExceedsMax)
|
||||
}
|
||||
|
||||
@ -2539,11 +2621,11 @@ func (l *channelLink) canSendHtlc(policy ForwardingPolicy,
|
||||
l.log.Warnf("htlc(%x) has an expiry that's too soon: "+
|
||||
"outgoing_expiry=%v, best_height=%v", payHash[:],
|
||||
timeout, heightNow)
|
||||
failure := l.createFailureWithUpdate(
|
||||
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewExpiryTooSoon(*upd)
|
||||
},
|
||||
)
|
||||
|
||||
cb := func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewExpiryTooSoon(*upd)
|
||||
}
|
||||
failure := l.createFailureWithUpdate(false, originalScid, cb)
|
||||
return NewLinkError(failure)
|
||||
}
|
||||
|
||||
@ -2560,11 +2642,10 @@ func (l *channelLink) canSendHtlc(policy ForwardingPolicy,
|
||||
if amt > l.Bandwidth() {
|
||||
l.log.Warnf("insufficient bandwidth to route htlc: %v is "+
|
||||
"larger than %v", amt, l.Bandwidth())
|
||||
failure := l.createFailureWithUpdate(
|
||||
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewTemporaryChannelFailure(upd)
|
||||
},
|
||||
)
|
||||
cb := func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewTemporaryChannelFailure(upd)
|
||||
}
|
||||
failure := l.createFailureWithUpdate(false, originalScid, cb)
|
||||
return NewDetailedLinkError(
|
||||
failure, OutgoingFailureInsufficientBalance,
|
||||
)
|
||||
@ -3009,12 +3090,12 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
||||
l.log.Errorf("unable to encode the "+
|
||||
"remaining route %v", err)
|
||||
|
||||
cb := func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewTemporaryChannelFailure(upd)
|
||||
}
|
||||
|
||||
failure := l.createFailureWithUpdate(
|
||||
func(upd *lnwire.ChannelUpdate) lnwire.FailureMessage {
|
||||
return lnwire.NewTemporaryChannelFailure(
|
||||
upd,
|
||||
)
|
||||
},
|
||||
true, hop.Source, cb,
|
||||
)
|
||||
|
||||
l.sendHTLCError(
|
||||
|
@ -1874,6 +1874,12 @@ func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) (
|
||||
return nil
|
||||
}
|
||||
|
||||
getAliases := func(
|
||||
base lnwire.ShortChannelID) []lnwire.ShortChannelID {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Instantiate with a long interval, so that we can precisely control
|
||||
// the firing via force feeding.
|
||||
bticker := ticker.NewForce(time.Hour)
|
||||
@ -1917,6 +1923,7 @@ func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) (
|
||||
NotifyActiveChannel: func(wire.OutPoint) {},
|
||||
NotifyInactiveChannel: func(wire.OutPoint) {},
|
||||
HtlcNotifier: aliceSwitch.cfg.HtlcNotifier,
|
||||
GetAliases: getAliases,
|
||||
}
|
||||
|
||||
aliceLink := NewChannelLink(aliceCfg, aliceLc.channel)
|
||||
@ -4325,6 +4332,12 @@ func (h *persistentLinkHarness) restartLink(
|
||||
return nil
|
||||
}
|
||||
|
||||
getAliases := func(
|
||||
base lnwire.ShortChannelID) []lnwire.ShortChannelID {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Instantiate with a long interval, so that we can precisely control
|
||||
// the firing via force feeding.
|
||||
bticker := ticker.NewForce(time.Hour)
|
||||
@ -4371,6 +4384,7 @@ func (h *persistentLinkHarness) restartLink(
|
||||
NotifyInactiveChannel: func(wire.OutPoint) {},
|
||||
HtlcNotifier: aliceSwitch.cfg.HtlcNotifier,
|
||||
SyncStates: syncStates,
|
||||
GetAliases: getAliases,
|
||||
}
|
||||
|
||||
aliceLink := NewChannelLink(aliceCfg, aliceChannel)
|
||||
@ -5571,6 +5585,12 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
return &lnwire.ChannelUpdate{}, nil
|
||||
}
|
||||
|
||||
failAliasUpdate := func(sid lnwire.ShortChannelID,
|
||||
incoming bool) *lnwire.ChannelUpdate {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
testChannel, _, fCleanUp, err := createTestChannel(
|
||||
alicePrivKey, bobPrivKey, 100000, 100000,
|
||||
1000, 1000, lnwire.ShortChannelID{},
|
||||
@ -5596,11 +5616,13 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
channel: testChannel.channel,
|
||||
}
|
||||
|
||||
link.attachFailAliasUpdate(failAliasUpdate)
|
||||
|
||||
var hash [32]byte
|
||||
|
||||
t.Run("satisfied", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
200, 150, 0)
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
if result != nil {
|
||||
t.Fatalf("expected policy to be satisfied")
|
||||
}
|
||||
@ -5608,7 +5630,7 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("below minhtlc", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 100, 50,
|
||||
200, 150, 0)
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
if _, ok := result.WireMessage().(*lnwire.FailAmountBelowMinimum); !ok {
|
||||
t.Fatalf("expected FailAmountBelowMinimum failure code")
|
||||
}
|
||||
@ -5616,7 +5638,7 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("above maxhtlc", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1200,
|
||||
200, 150, 0)
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
if _, ok := result.WireMessage().(*lnwire.FailTemporaryChannelFailure); !ok {
|
||||
t.Fatalf("expected FailTemporaryChannelFailure failure code")
|
||||
}
|
||||
@ -5624,7 +5646,7 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("insufficient fee", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1005, 1000,
|
||||
200, 150, 0)
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
if _, ok := result.WireMessage().(*lnwire.FailFeeInsufficient); !ok {
|
||||
t.Fatalf("expected FailFeeInsufficient failure code")
|
||||
}
|
||||
@ -5632,7 +5654,7 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("expiry too soon", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
200, 150, 190)
|
||||
200, 150, 190, lnwire.ShortChannelID{})
|
||||
if _, ok := result.WireMessage().(*lnwire.FailExpiryTooSoon); !ok {
|
||||
t.Fatalf("expected FailExpiryTooSoon failure code")
|
||||
}
|
||||
@ -5640,7 +5662,7 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("incorrect cltv expiry", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
200, 190, 0)
|
||||
200, 190, 0, lnwire.ShortChannelID{})
|
||||
if _, ok := result.WireMessage().(*lnwire.FailIncorrectCltvExpiry); !ok {
|
||||
t.Fatalf("expected FailIncorrectCltvExpiry failure code")
|
||||
}
|
||||
@ -5650,7 +5672,7 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
t.Run("cltv expiry too far in the future", func(t *testing.T) {
|
||||
// Check that expiry isn't too far in the future.
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
10200, 10100, 0)
|
||||
10200, 10100, 0, lnwire.ShortChannelID{})
|
||||
if _, ok := result.WireMessage().(*lnwire.FailExpiryTooFar); !ok {
|
||||
t.Fatalf("expected FailExpiryTooFar failure code")
|
||||
}
|
||||
|
@ -91,10 +91,6 @@ type mailBoxConfig struct {
|
||||
// belongs to.
|
||||
shortChanID lnwire.ShortChannelID
|
||||
|
||||
// fetchUpdate retrieves the most recent channel update for the channel
|
||||
// this mailbox belongs to.
|
||||
fetchUpdate func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error)
|
||||
|
||||
// forwardPackets send a varidic number of htlcPackets to the switch to
|
||||
// be routed. A quit channel should be provided so that the call can
|
||||
// properly exit during shutdown.
|
||||
@ -107,6 +103,11 @@ type mailBoxConfig struct {
|
||||
// have not been yet been delivered. The computed deadline will expiry
|
||||
// this long after the Adds are added via AddPacket.
|
||||
expiry time.Duration
|
||||
|
||||
// failMailboxUpdate is used to fail an expired HTLC and use the
|
||||
// correct SCID if the underlying channel uses aliases.
|
||||
failMailboxUpdate func(outScid,
|
||||
mailboxScid lnwire.ShortChannelID) lnwire.FailureMessage
|
||||
}
|
||||
|
||||
// memoryMailBox is an implementation of the MailBox struct backed by purely
|
||||
@ -710,13 +711,9 @@ func (m *memoryMailBox) FailAdd(pkt *htlcPacket) {
|
||||
// Create a temporary channel failure which we will send back to our
|
||||
// peer if this is a forward, or report to the user if the failed
|
||||
// payment was locally initiated.
|
||||
var failure lnwire.FailureMessage
|
||||
update, err := m.cfg.fetchUpdate(m.cfg.shortChanID)
|
||||
if err != nil {
|
||||
failure = &lnwire.FailTemporaryNodeFailure{}
|
||||
} else {
|
||||
failure = lnwire.NewTemporaryChannelFailure(update)
|
||||
}
|
||||
failure := m.cfg.failMailboxUpdate(
|
||||
pkt.originalOutgoingChanID, m.cfg.shortChanID,
|
||||
)
|
||||
|
||||
// If the payment was locally initiated (which is indicated by a nil
|
||||
// obfuscator), we do not need to encrypt it back to the sender.
|
||||
@ -817,10 +814,6 @@ type mailOrchConfig struct {
|
||||
// properly exit during shutdown.
|
||||
forwardPackets func(chan struct{}, ...*htlcPacket) error
|
||||
|
||||
// fetchUpdate retrieves the most recent channel update for the channel
|
||||
// this mailbox belongs to.
|
||||
fetchUpdate func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error)
|
||||
|
||||
// clock is a time source for the generated mailboxes.
|
||||
clock clock.Clock
|
||||
|
||||
@ -828,6 +821,11 @@ type mailOrchConfig struct {
|
||||
// have not been yet been delivered. The computed deadline will expiry
|
||||
// this long after the Adds are added to a mailbox via AddPacket.
|
||||
expiry time.Duration
|
||||
|
||||
// failMailboxUpdate is used to fail an expired HTLC and use the
|
||||
// correct SCID if the underlying channel uses aliases.
|
||||
failMailboxUpdate func(outScid,
|
||||
mailboxScid lnwire.ShortChannelID) lnwire.FailureMessage
|
||||
}
|
||||
|
||||
// newMailOrchestrator initializes a fresh mailOrchestrator.
|
||||
@ -881,11 +879,11 @@ func (mo *mailOrchestrator) exclusiveGetOrCreateMailBox(
|
||||
mailbox, ok := mo.mailboxes[chanID]
|
||||
if !ok {
|
||||
mailbox = newMemoryMailBox(&mailBoxConfig{
|
||||
shortChanID: shortChanID,
|
||||
fetchUpdate: mo.cfg.fetchUpdate,
|
||||
forwardPackets: mo.cfg.forwardPackets,
|
||||
clock: mo.cfg.clock,
|
||||
expiry: mo.cfg.expiry,
|
||||
shortChanID: shortChanID,
|
||||
forwardPackets: mo.cfg.forwardPackets,
|
||||
clock: mo.cfg.clock,
|
||||
expiry: mo.cfg.expiry,
|
||||
failMailboxUpdate: mo.cfg.failMailboxUpdate,
|
||||
})
|
||||
mailbox.Start()
|
||||
mo.mailboxes[chanID] = mailbox
|
||||
|
@ -201,17 +201,18 @@ func newMailboxContext(t *testing.T, startTime time.Time,
|
||||
clock: clock.NewTestClock(startTime),
|
||||
forwards: make(chan *htlcPacket, 1),
|
||||
}
|
||||
ctx.mailbox = newMemoryMailBox(&mailBoxConfig{
|
||||
fetchUpdate: func(sid lnwire.ShortChannelID) (
|
||||
*lnwire.ChannelUpdate, error) {
|
||||
|
||||
return &lnwire.ChannelUpdate{
|
||||
ShortChannelID: sid,
|
||||
}, nil
|
||||
},
|
||||
forwardPackets: ctx.forward,
|
||||
clock: ctx.clock,
|
||||
expiry: expiry,
|
||||
failMailboxUpdate := func(outScid,
|
||||
mboxScid lnwire.ShortChannelID) lnwire.FailureMessage {
|
||||
|
||||
return &lnwire.FailTemporaryNodeFailure{}
|
||||
}
|
||||
|
||||
ctx.mailbox = newMemoryMailBox(&mailBoxConfig{
|
||||
failMailboxUpdate: failMailboxUpdate,
|
||||
forwardPackets: ctx.forward,
|
||||
clock: ctx.clock,
|
||||
expiry: expiry,
|
||||
})
|
||||
ctx.mailbox.Start()
|
||||
|
||||
@ -660,15 +661,15 @@ func testMailBoxDust(t *testing.T, chantype channeldb.ChannelType) {
|
||||
func TestMailOrchestrator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
failMailboxUpdate := func(outScid,
|
||||
mboxScid lnwire.ShortChannelID) lnwire.FailureMessage {
|
||||
|
||||
return &lnwire.FailTemporaryNodeFailure{}
|
||||
}
|
||||
|
||||
// First, we'll create a new instance of our orchestrator.
|
||||
mo := newMailOrchestrator(&mailOrchConfig{
|
||||
fetchUpdate: func(sid lnwire.ShortChannelID) (
|
||||
*lnwire.ChannelUpdate, error) {
|
||||
|
||||
return &lnwire.ChannelUpdate{
|
||||
ShortChannelID: sid,
|
||||
}, nil
|
||||
},
|
||||
failMailboxUpdate: failMailboxUpdate,
|
||||
forwardPackets: func(_ chan struct{},
|
||||
pkts ...*htlcPacket) error {
|
||||
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/go-errors/errors"
|
||||
@ -33,6 +34,10 @@ import (
|
||||
"github.com/lightningnetwork/lnd/ticker"
|
||||
)
|
||||
|
||||
func isAlias(scid lnwire.ShortChannelID) bool {
|
||||
return scid.BlockHeight >= 16_000_000 && scid.BlockHeight < 16_250_000
|
||||
}
|
||||
|
||||
type mockPreimageCache struct {
|
||||
sync.Mutex
|
||||
preimageMap map[lntypes.Hash]lntypes.Preimage
|
||||
@ -180,6 +185,12 @@ func initSwitchWithDB(startingHeight uint32, db *channeldb.DB) (*Switch, error)
|
||||
}
|
||||
}
|
||||
|
||||
signAliasUpdate := func(u *lnwire.ChannelUpdate) (*ecdsa.Signature,
|
||||
error) {
|
||||
|
||||
return testSig, nil
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
DB: db,
|
||||
FetchAllOpenChannels: db.ChannelStateDB().FetchAllOpenChannels,
|
||||
@ -188,21 +199,27 @@ func initSwitchWithDB(startingHeight uint32, db *channeldb.DB) (*Switch, error)
|
||||
FwdingLog: &mockForwardingLog{
|
||||
events: make(map[time.Time]channeldb.ForwardingEvent),
|
||||
},
|
||||
FetchLastChannelUpdate: func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) {
|
||||
return &lnwire.ChannelUpdate{}, nil
|
||||
FetchLastChannelUpdate: func(scid lnwire.ShortChannelID) (
|
||||
*lnwire.ChannelUpdate, error) {
|
||||
|
||||
return &lnwire.ChannelUpdate{
|
||||
ShortChannelID: scid,
|
||||
}, nil
|
||||
},
|
||||
Notifier: &mock.ChainNotifier{
|
||||
SpendChan: make(chan *chainntnfs.SpendDetail),
|
||||
EpochChan: make(chan *chainntnfs.BlockEpoch),
|
||||
ConfChan: make(chan *chainntnfs.TxConfirmation),
|
||||
},
|
||||
FwdEventTicker: ticker.NewForce(DefaultFwdEventInterval),
|
||||
LogEventTicker: ticker.NewForce(DefaultLogInterval),
|
||||
AckEventTicker: ticker.NewForce(DefaultAckInterval),
|
||||
HtlcNotifier: &mockHTLCNotifier{},
|
||||
Clock: clock.NewDefaultClock(),
|
||||
HTLCExpiry: time.Hour,
|
||||
DustThreshold: DefaultDustThreshold,
|
||||
FwdEventTicker: ticker.NewForce(DefaultFwdEventInterval),
|
||||
LogEventTicker: ticker.NewForce(DefaultLogInterval),
|
||||
AckEventTicker: ticker.NewForce(DefaultAckInterval),
|
||||
HtlcNotifier: &mockHTLCNotifier{},
|
||||
Clock: clock.NewDefaultClock(),
|
||||
HTLCExpiry: time.Hour,
|
||||
DustThreshold: DefaultDustThreshold,
|
||||
SignAliasUpdate: signAliasUpdate,
|
||||
IsAlias: isAlias,
|
||||
}
|
||||
|
||||
return New(cfg, startingHeight)
|
||||
@ -658,6 +675,11 @@ type mockChannelLink struct {
|
||||
|
||||
shortChanID lnwire.ShortChannelID
|
||||
|
||||
// Only used for zero-conf channels.
|
||||
realScid lnwire.ShortChannelID
|
||||
|
||||
aliases []lnwire.ShortChannelID
|
||||
|
||||
chanID lnwire.ChannelID
|
||||
|
||||
peer lnpeer.Peer
|
||||
@ -668,11 +690,22 @@ type mockChannelLink struct {
|
||||
|
||||
eligible bool
|
||||
|
||||
unadvertised bool
|
||||
|
||||
zeroConf bool
|
||||
|
||||
optionFeature bool
|
||||
|
||||
htlcID uint64
|
||||
|
||||
checkHtlcTransitResult *LinkError
|
||||
|
||||
checkHtlcForwardResult *LinkError
|
||||
|
||||
failAliasUpdate func(sid lnwire.ShortChannelID,
|
||||
incoming bool) *lnwire.ChannelUpdate
|
||||
|
||||
confirmedZC bool
|
||||
}
|
||||
|
||||
// completeCircuit is a helper method for adding the finalized payment circuit
|
||||
@ -712,16 +745,39 @@ func (f *mockChannelLink) deleteCircuit(pkt *htlcPacket) error {
|
||||
}
|
||||
|
||||
func newMockChannelLink(htlcSwitch *Switch, chanID lnwire.ChannelID,
|
||||
shortChanID lnwire.ShortChannelID, peer lnpeer.Peer, eligible bool,
|
||||
shortChanID, realScid lnwire.ShortChannelID, peer lnpeer.Peer,
|
||||
eligible, unadvertised, zeroConf, optionFeature bool,
|
||||
) *mockChannelLink {
|
||||
|
||||
return &mockChannelLink{
|
||||
htlcSwitch: htlcSwitch,
|
||||
chanID: chanID,
|
||||
shortChanID: shortChanID,
|
||||
peer: peer,
|
||||
eligible: eligible,
|
||||
aliases := make([]lnwire.ShortChannelID, 0)
|
||||
var realConfirmed bool
|
||||
|
||||
if zeroConf {
|
||||
aliases = append(aliases, shortChanID)
|
||||
}
|
||||
|
||||
if realScid != hop.Source {
|
||||
realConfirmed = true
|
||||
}
|
||||
|
||||
return &mockChannelLink{
|
||||
htlcSwitch: htlcSwitch,
|
||||
chanID: chanID,
|
||||
shortChanID: shortChanID,
|
||||
realScid: realScid,
|
||||
peer: peer,
|
||||
eligible: eligible,
|
||||
unadvertised: unadvertised,
|
||||
zeroConf: zeroConf,
|
||||
optionFeature: optionFeature,
|
||||
aliases: aliases,
|
||||
confirmedZC: realConfirmed,
|
||||
}
|
||||
}
|
||||
|
||||
// addAlias is not part of any interface method.
|
||||
func (f *mockChannelLink) addAlias(alias lnwire.ShortChannelID) {
|
||||
f.aliases = append(f.aliases, alias)
|
||||
}
|
||||
|
||||
func (f *mockChannelLink) handleSwitchPacket(pkt *htlcPacket) error {
|
||||
@ -750,7 +806,8 @@ func (f *mockChannelLink) HandleChannelUpdate(lnwire.Message) {
|
||||
func (f *mockChannelLink) UpdateForwardingPolicy(_ ForwardingPolicy) {
|
||||
}
|
||||
func (f *mockChannelLink) CheckHtlcForward([32]byte, lnwire.MilliSatoshi,
|
||||
lnwire.MilliSatoshi, uint32, uint32, uint32) *LinkError {
|
||||
lnwire.MilliSatoshi, uint32, uint32, uint32,
|
||||
lnwire.ShortChannelID) *LinkError {
|
||||
|
||||
return f.checkHtlcForwardResult
|
||||
}
|
||||
@ -772,6 +829,32 @@ func (f *mockChannelLink) AttachMailBox(mailBox MailBox) {
|
||||
mailBox.SetDustClosure(f.getDustClosure())
|
||||
}
|
||||
|
||||
func (f *mockChannelLink) attachFailAliasUpdate(closure func(
|
||||
sid lnwire.ShortChannelID, incoming bool) *lnwire.ChannelUpdate) {
|
||||
|
||||
f.failAliasUpdate = closure
|
||||
}
|
||||
|
||||
func (f *mockChannelLink) getAliases() []lnwire.ShortChannelID {
|
||||
return f.aliases
|
||||
}
|
||||
|
||||
func (f *mockChannelLink) isZeroConf() bool {
|
||||
return f.zeroConf
|
||||
}
|
||||
|
||||
func (f *mockChannelLink) negotiatedAliasFeature() bool {
|
||||
return f.optionFeature
|
||||
}
|
||||
|
||||
func (f *mockChannelLink) confirmedScid() lnwire.ShortChannelID {
|
||||
return f.realScid
|
||||
}
|
||||
|
||||
func (f *mockChannelLink) zeroConfConfirmed() bool {
|
||||
return f.confirmedZC
|
||||
}
|
||||
|
||||
func (f *mockChannelLink) Start() error {
|
||||
f.mailBox.ResetMessages()
|
||||
f.mailBox.ResetPackets()
|
||||
@ -788,6 +871,7 @@ func (f *mockChannelLink) EligibleToForward() bool { return
|
||||
func (f *mockChannelLink) MayAddOutgoingHtlc(lnwire.MilliSatoshi) error { return nil }
|
||||
func (f *mockChannelLink) ShutdownIfChannelClean() error { return nil }
|
||||
func (f *mockChannelLink) setLiveShortChanID(sid lnwire.ShortChannelID) { f.shortChanID = sid }
|
||||
func (f *mockChannelLink) IsUnadvertised() bool { return f.unadvertised }
|
||||
func (f *mockChannelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) {
|
||||
f.eligible = true
|
||||
return f.shortChanID, nil
|
||||
|
@ -96,6 +96,13 @@ type htlcPacket struct {
|
||||
// customRecords are user-defined records in the custom type range that
|
||||
// were included in the payload.
|
||||
customRecords record.CustomSet
|
||||
|
||||
// originalOutgoingChanID is used when sending back failure messages.
|
||||
// It is only used for forwarded Adds on option_scid_alias channels.
|
||||
// This is to avoid possible confusion if a payer uses the public SCID
|
||||
// but receives a channel_update with the alias SCID. Instead, the
|
||||
// payer should receive a channel_update with the public SCID.
|
||||
originalOutgoingChanID lnwire.ShortChannelID
|
||||
}
|
||||
|
||||
// inKey returns the circuit key used to identify the incoming htlc.
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
@ -200,6 +201,16 @@ type Config struct {
|
||||
// DustThreshold is the threshold in milli-satoshis after which we'll
|
||||
// fail incoming or outgoing dust payments for a particular channel.
|
||||
DustThreshold lnwire.MilliSatoshi
|
||||
|
||||
// SignAliasUpdate is used when sending FailureMessages backwards for
|
||||
// option_scid_alias channels. This avoids a potential privacy leak by
|
||||
// replacing the public, confirmed SCID with the alias in the
|
||||
// ChannelUpdate.
|
||||
SignAliasUpdate func(u *lnwire.ChannelUpdate) (*ecdsa.Signature,
|
||||
error)
|
||||
|
||||
// IsAlias returns whether or not a given SCID is an alias.
|
||||
IsAlias func(scid lnwire.ShortChannelID) bool
|
||||
}
|
||||
|
||||
// Switch is the central messaging bus for all incoming/outgoing HTLCs.
|
||||
@ -247,8 +258,7 @@ type Switch struct {
|
||||
indexMtx sync.RWMutex
|
||||
|
||||
// pendingLinkIndex holds links that have not had their final, live
|
||||
// short_chan_id assigned. These links can be transitioned into the
|
||||
// primary linkIndex by using UpdateShortChanID to load their live id.
|
||||
// short_chan_id assigned.
|
||||
pendingLinkIndex map[lnwire.ChannelID]ChannelLink
|
||||
|
||||
// links is a map of channel id and channel link which manages
|
||||
@ -311,6 +321,21 @@ type Switch struct {
|
||||
// contractcourt. This is used so the Switch can properly forward them,
|
||||
// even on restarts.
|
||||
resMsgStore *resolutionStore
|
||||
|
||||
// aliasToReal is a map used for option-scid-alias feature-bit links.
|
||||
// The alias SCID is the key and the real, confirmed SCID is the value.
|
||||
// If the channel is unconfirmed, there will not be a mapping for it.
|
||||
// Since channels can have multiple aliases, this map is essentially a
|
||||
// N->1 mapping for a channel. This MUST be accessed with the indexMtx.
|
||||
aliasToReal map[lnwire.ShortChannelID]lnwire.ShortChannelID
|
||||
|
||||
// baseIndex is a map used for option-scid-alias feature-bit links.
|
||||
// The value is the SCID of the link's ShortChannelID. This value may
|
||||
// be an alias for zero-conf channels or a confirmed SCID for
|
||||
// non-zero-conf channels with the option-scid-alias feature-bit. The
|
||||
// key includes the value itself and also any other aliases. This MUST
|
||||
// be accessed with the indexMtx.
|
||||
baseIndex map[lnwire.ShortChannelID]lnwire.ShortChannelID
|
||||
}
|
||||
|
||||
// New creates the new instance of htlc switch.
|
||||
@ -345,11 +370,14 @@ func New(cfg Config, currentHeight uint32) (*Switch, error) {
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
s.aliasToReal = make(map[lnwire.ShortChannelID]lnwire.ShortChannelID)
|
||||
s.baseIndex = make(map[lnwire.ShortChannelID]lnwire.ShortChannelID)
|
||||
|
||||
s.mailOrchestrator = newMailOrchestrator(&mailOrchConfig{
|
||||
fetchUpdate: s.cfg.FetchLastChannelUpdate,
|
||||
forwardPackets: s.ForwardPackets,
|
||||
clock: s.cfg.Clock,
|
||||
expiry: s.cfg.HTLCExpiry,
|
||||
forwardPackets: s.ForwardPackets,
|
||||
clock: s.cfg.Clock,
|
||||
expiry: s.cfg.HTLCExpiry,
|
||||
failMailboxUpdate: s.failMailboxUpdate,
|
||||
})
|
||||
|
||||
return s, nil
|
||||
@ -725,14 +753,28 @@ func (s *Switch) ForwardPackets(linkQuit chan struct{},
|
||||
// failures.
|
||||
if len(failedPackets) > 0 {
|
||||
var failure lnwire.FailureMessage
|
||||
update, err := s.cfg.FetchLastChannelUpdate(
|
||||
failedPackets[0].incomingChanID,
|
||||
)
|
||||
if err != nil {
|
||||
failure = &lnwire.FailTemporaryNodeFailure{}
|
||||
incomingID := failedPackets[0].incomingChanID
|
||||
|
||||
// If the incoming channel is an option_scid_alias channel,
|
||||
// then we'll need to replace the SCID in the ChannelUpdate.
|
||||
update := s.failAliasUpdate(incomingID, true)
|
||||
if update == nil {
|
||||
// Fallback to the original non-option behavior.
|
||||
update, err := s.cfg.FetchLastChannelUpdate(
|
||||
incomingID,
|
||||
)
|
||||
if err != nil {
|
||||
failure = &lnwire.FailTemporaryNodeFailure{}
|
||||
} else {
|
||||
failure = lnwire.NewTemporaryChannelFailure(
|
||||
update,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// This is an option_scid_alias channel.
|
||||
failure = lnwire.NewTemporaryChannelFailure(update)
|
||||
}
|
||||
|
||||
linkError := NewDetailedLinkError(
|
||||
failure, OutgoingFailureIncompleteForward,
|
||||
)
|
||||
@ -804,10 +846,29 @@ func (s *Switch) getLocalLink(pkt *htlcPacket, htlc *lnwire.UpdateAddHTLC) (
|
||||
// Try to find links by node destination.
|
||||
s.indexMtx.RLock()
|
||||
link, err := s.getLinkByShortID(pkt.outgoingChanID)
|
||||
s.indexMtx.RUnlock()
|
||||
defer s.indexMtx.RUnlock()
|
||||
if err != nil {
|
||||
log.Errorf("Link %v not found", pkt.outgoingChanID)
|
||||
return nil, NewLinkError(&lnwire.FailUnknownNextPeer{})
|
||||
// If the link was not found for the outgoingChanID, an outside
|
||||
// subsystem may be using the confirmed SCID of a zero-conf
|
||||
// channel. In this case, we'll consult the Switch maps to see
|
||||
// if an alias exists and use the alias to lookup the link.
|
||||
// This extra step is a consequence of not updating the Switch
|
||||
// forwardingIndex when a zero-conf channel is confirmed. We
|
||||
// don't need to change the outgoingChanID since the link will
|
||||
// do that upon receiving the packet.
|
||||
baseScid, ok := s.baseIndex[pkt.outgoingChanID]
|
||||
if !ok {
|
||||
log.Errorf("Link %v not found", pkt.outgoingChanID)
|
||||
return nil, NewLinkError(&lnwire.FailUnknownNextPeer{})
|
||||
}
|
||||
|
||||
// The base SCID was found, so we'll use that to fetch the
|
||||
// link.
|
||||
link, err = s.getLinkByShortID(baseScid)
|
||||
if err != nil {
|
||||
log.Errorf("Link %v not found", baseScid)
|
||||
return nil, NewLinkError(&lnwire.FailUnknownNextPeer{})
|
||||
}
|
||||
}
|
||||
|
||||
if !link.EligibleToForward() {
|
||||
@ -1043,8 +1104,11 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
||||
// same incoming and outgoing channel. If our node does not
|
||||
// allow forwards of this nature, we fail the htlc early. This
|
||||
// check is in place to disallow inefficiently routed htlcs from
|
||||
// locking up our balance.
|
||||
linkErr := checkCircularForward(
|
||||
// locking up our balance. With channels where the
|
||||
// option-scid-alias feature was negotiated, we also have to be
|
||||
// sure that the IDs aren't the same since one or both could be
|
||||
// an alias.
|
||||
linkErr := s.checkCircularForward(
|
||||
packet.incomingChanID, packet.outgoingChanID,
|
||||
s.cfg.AllowCircularRoute, htlc.PaymentHash,
|
||||
)
|
||||
@ -1053,7 +1117,7 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
||||
}
|
||||
|
||||
s.indexMtx.RLock()
|
||||
targetLink, err := s.getLinkByShortID(packet.outgoingChanID)
|
||||
targetLink, err := s.getLinkByMapping(packet)
|
||||
if err != nil {
|
||||
s.indexMtx.RUnlock()
|
||||
|
||||
@ -1101,6 +1165,7 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
||||
htlc.PaymentHash, packet.incomingAmount,
|
||||
packet.amount, packet.incomingTimeout,
|
||||
packet.outgoingTimeout, currentHeight,
|
||||
packet.originalOutgoingChanID,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1306,12 +1371,51 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
||||
// checkCircularForward checks whether a forward is circular (arrives and
|
||||
// departs on the same link) and returns a link error if the switch is
|
||||
// configured to disallow this behaviour.
|
||||
func checkCircularForward(incoming, outgoing lnwire.ShortChannelID,
|
||||
func (s *Switch) checkCircularForward(incoming, outgoing lnwire.ShortChannelID,
|
||||
allowCircular bool, paymentHash lntypes.Hash) *LinkError {
|
||||
|
||||
// If the route is not circular we do not need to perform any further
|
||||
// checks.
|
||||
if incoming != outgoing {
|
||||
// If they are equal, we can skip the alias mapping checks.
|
||||
if incoming == outgoing {
|
||||
// The switch may be configured to allow circular routes, so
|
||||
// just log and return nil.
|
||||
if allowCircular {
|
||||
log.Debugf("allowing circular route over link: %v "+
|
||||
"(payment hash: %x)", incoming, paymentHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise, we'll return a temporary channel failure.
|
||||
return NewDetailedLinkError(
|
||||
lnwire.NewTemporaryChannelFailure(nil),
|
||||
OutgoingFailureCircularRoute,
|
||||
)
|
||||
}
|
||||
|
||||
// We'll fetch the "base" SCID from the baseIndex for the incoming and
|
||||
// outgoing SCIDs. If either one does not have a base SCID, then the
|
||||
// two channels are not equal since one will be a channel that does not
|
||||
// need a mapping and SCID equality was checked above. If the "base"
|
||||
// SCIDs are equal, then this is a circular route. Otherwise, it isn't.
|
||||
s.indexMtx.RLock()
|
||||
incomingBaseScid, ok := s.baseIndex[incoming]
|
||||
if !ok {
|
||||
// This channel does not use baseIndex, bail out.
|
||||
s.indexMtx.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
outgoingBaseScid, ok := s.baseIndex[outgoing]
|
||||
if !ok {
|
||||
// This channel does not use baseIndex, bail out.
|
||||
s.indexMtx.RUnlock()
|
||||
return nil
|
||||
}
|
||||
s.indexMtx.RUnlock()
|
||||
|
||||
// Check base SCID equality.
|
||||
if incomingBaseScid != outgoingBaseScid {
|
||||
// The base SCIDs are not equal so these are not the same
|
||||
// channel.
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -2170,6 +2274,9 @@ func (s *Switch) AddLink(link ChannelLink) error {
|
||||
mailbox := s.mailOrchestrator.GetOrCreateMailBox(chanID, shortChanID)
|
||||
link.AttachMailBox(mailbox)
|
||||
|
||||
// Attach the Switch's failAliasUpdate function to the link.
|
||||
link.attachFailAliasUpdate(s.failAliasUpdate)
|
||||
|
||||
if err := link.Start(); err != nil {
|
||||
s.removeLink(chanID)
|
||||
return err
|
||||
@ -2196,12 +2303,14 @@ func (s *Switch) AddLink(link ChannelLink) error {
|
||||
// addLiveLink adds a link to all associated forwarding index, this makes it a
|
||||
// candidate for forwarding HTLCs.
|
||||
func (s *Switch) addLiveLink(link ChannelLink) {
|
||||
linkScid := link.ShortChanID()
|
||||
|
||||
// We'll add the link to the linkIndex which lets us quickly
|
||||
// look up a channel when we need to close or register it, and
|
||||
// the forwarding index which'll be used when forwarding HTLC's
|
||||
// in the multi-hop setting.
|
||||
s.linkIndex[link.ChanID()] = link
|
||||
s.forwardingIndex[link.ShortChanID()] = link
|
||||
s.forwardingIndex[linkScid] = link
|
||||
|
||||
// Next we'll add the link to the interface index so we can
|
||||
// quickly look up all the channels for a particular node.
|
||||
@ -2210,6 +2319,42 @@ func (s *Switch) addLiveLink(link ChannelLink) {
|
||||
s.interfaceIndex[peerPub] = make(map[lnwire.ChannelID]ChannelLink)
|
||||
}
|
||||
s.interfaceIndex[peerPub][link.ChanID()] = link
|
||||
|
||||
aliases := link.getAliases()
|
||||
if link.isZeroConf() {
|
||||
if link.zeroConfConfirmed() {
|
||||
// Since the zero-conf channel has confirmed, we can
|
||||
// populate the aliasToReal mapping.
|
||||
confirmedScid := link.confirmedScid()
|
||||
|
||||
for _, alias := range aliases {
|
||||
s.aliasToReal[alias] = confirmedScid
|
||||
}
|
||||
|
||||
// Add the confirmed SCID as a key in the baseIndex.
|
||||
s.baseIndex[confirmedScid] = linkScid
|
||||
}
|
||||
|
||||
// Now we populate the baseIndex which will be used to fetch
|
||||
// the link given any of the channel's alias SCIDs or the real
|
||||
// SCID. The link's SCID is an alias, so we don't need to
|
||||
// special-case it like the option-scid-alias feature-bit case
|
||||
// further down.
|
||||
for _, alias := range aliases {
|
||||
s.baseIndex[alias] = linkScid
|
||||
}
|
||||
} else if link.negotiatedAliasFeature() {
|
||||
// The link's SCID is the confirmed SCID for non-zero-conf
|
||||
// option-scid-alias feature bit channels.
|
||||
for _, alias := range aliases {
|
||||
s.aliasToReal[alias] = linkScid
|
||||
s.baseIndex[alias] = linkScid
|
||||
}
|
||||
|
||||
// Since the link's SCID is confirmed, it was not included in
|
||||
// the baseIndex above as a key. Add it now.
|
||||
s.baseIndex[linkScid] = linkScid
|
||||
}
|
||||
}
|
||||
|
||||
// GetLink is used to initiate the handling of the get link command. The
|
||||
@ -2245,7 +2390,21 @@ func (s *Switch) GetLinkByShortID(chanID lnwire.ShortChannelID) (ChannelLink,
|
||||
s.indexMtx.RLock()
|
||||
defer s.indexMtx.RUnlock()
|
||||
|
||||
return s.getLinkByShortID(chanID)
|
||||
link, err := s.getLinkByShortID(chanID)
|
||||
if err != nil {
|
||||
// If we failed to find the link under the passed-in SCID, we
|
||||
// consult the Switch's baseIndex map to see if the confirmed
|
||||
// SCID was used for a zero-conf channel.
|
||||
aliasID, ok := s.baseIndex[chanID]
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// An alias was found, use it to lookup if a link exists.
|
||||
return s.getLinkByShortID(aliasID)
|
||||
}
|
||||
|
||||
return link, nil
|
||||
}
|
||||
|
||||
// getLinkByShortID attempts to return the link which possesses the target
|
||||
@ -2261,6 +2420,93 @@ func (s *Switch) getLinkByShortID(chanID lnwire.ShortChannelID) (ChannelLink, er
|
||||
return link, nil
|
||||
}
|
||||
|
||||
// getLinkByMapping attempts to fetch the link via the htlcPacket's
|
||||
// outgoingChanID, possibly using a mapping. If it finds the link via mapping,
|
||||
// the outgoingChanID will be changed so that an error can be properly
|
||||
// attributed when looping over linkErrs in handlePacketForward.
|
||||
//
|
||||
// * If the outgoingChanID is an alias, we'll fetch the link regardless if it's
|
||||
// public or not.
|
||||
//
|
||||
// * If the outgoingChanID is a confirmed SCID, we'll need to do more checks.
|
||||
// - If there is no entry found in baseIndex, fetch the link. This channel
|
||||
// did not have the option-scid-alias feature negotiated (which includes
|
||||
// zero-conf and option-scid-alias channel-types).
|
||||
// - If there is an entry found, fetch the link from forwardingIndex and
|
||||
// fail if this is a private link.
|
||||
//
|
||||
// NOTE: This MUST be called with the indexMtx read lock held.
|
||||
func (s *Switch) getLinkByMapping(pkt *htlcPacket) (ChannelLink, error) {
|
||||
// Determine if this ShortChannelID is an alias or a confirmed SCID.
|
||||
chanID := pkt.outgoingChanID
|
||||
aliasID := s.cfg.IsAlias(chanID)
|
||||
|
||||
// Set the originalOutgoingChanID so the proper channel_update can be
|
||||
// sent back if the option-scid-alias feature bit was negotiated.
|
||||
pkt.originalOutgoingChanID = chanID
|
||||
|
||||
if aliasID {
|
||||
// Since outgoingChanID is an alias, we'll fetch the link via
|
||||
// baseIndex.
|
||||
baseScid, ok := s.baseIndex[chanID]
|
||||
if !ok {
|
||||
// No mapping exists, bail.
|
||||
return nil, ErrChannelLinkNotFound
|
||||
}
|
||||
|
||||
// A mapping exists, so use baseScid to find the link in the
|
||||
// forwardingIndex.
|
||||
link, ok := s.forwardingIndex[baseScid]
|
||||
if !ok {
|
||||
// Link not found, bail.
|
||||
return nil, ErrChannelLinkNotFound
|
||||
}
|
||||
|
||||
// Change the packet's outgoingChanID field so that errors are
|
||||
// properly attributed.
|
||||
pkt.outgoingChanID = baseScid
|
||||
|
||||
// Return the link without checking if it's private or not.
|
||||
return link, nil
|
||||
}
|
||||
|
||||
// The outgoingChanID is a confirmed SCID. Attempt to fetch the base
|
||||
// SCID from baseIndex.
|
||||
baseScid, ok := s.baseIndex[chanID]
|
||||
if !ok {
|
||||
// outgoingChanID is not a key in base index meaning this
|
||||
// channel did not have the option-scid-alias feature bit
|
||||
// negotiated. We'll fetch the link and return it.
|
||||
link, ok := s.forwardingIndex[chanID]
|
||||
if !ok {
|
||||
// The link wasn't found, bail out.
|
||||
return nil, ErrChannelLinkNotFound
|
||||
}
|
||||
|
||||
return link, nil
|
||||
}
|
||||
|
||||
// Fetch the link whose internal SCID is baseScid.
|
||||
link, ok := s.forwardingIndex[baseScid]
|
||||
if !ok {
|
||||
// Link wasn't found, bail out.
|
||||
return nil, ErrChannelLinkNotFound
|
||||
}
|
||||
|
||||
// If the link is unadvertised, we fail since the real SCID was used to
|
||||
// forward over it and this is a channel where the option-scid-alias
|
||||
// feature bit was negotiated.
|
||||
if link.IsUnadvertised() {
|
||||
return nil, ErrChannelLinkNotFound
|
||||
}
|
||||
|
||||
// The link is public so the confirmed SCID can be used to forward over
|
||||
// it. We'll also replace pkt's outgoingChanID field so errors can
|
||||
// properly be attributed in the calling function.
|
||||
pkt.outgoingChanID = baseScid
|
||||
return link, nil
|
||||
}
|
||||
|
||||
// HasActiveLink returns true if the given channel ID has a link in the link
|
||||
// index AND the link is eligible to forward.
|
||||
func (s *Switch) HasActiveLink(chanID lnwire.ChannelID) bool {
|
||||
@ -2357,50 +2603,38 @@ func (s *Switch) removeLink(chanID lnwire.ChannelID) ChannelLink {
|
||||
return link
|
||||
}
|
||||
|
||||
// UpdateShortChanID updates the short chan ID for an existing channel. This is
|
||||
// required in the case of a re-org and re-confirmation or a channel, or in the
|
||||
// case that a link was added to the switch before its short chan ID was known.
|
||||
// UpdateShortChanID locates the link with the passed-in chanID and updates the
|
||||
// underlying channel state. This is only used in zero-conf channels to allow
|
||||
// the confirmed SCID to be updated.
|
||||
func (s *Switch) UpdateShortChanID(chanID lnwire.ChannelID) error {
|
||||
s.indexMtx.Lock()
|
||||
defer s.indexMtx.Unlock()
|
||||
|
||||
// Locate the target link in the pending link index. If no such link
|
||||
// exists, then we will ignore the request.
|
||||
link, ok := s.pendingLinkIndex[chanID]
|
||||
// Locate the target link in the link index. If no such link exists,
|
||||
// then we will ignore the request.
|
||||
link, ok := s.linkIndex[chanID]
|
||||
if !ok {
|
||||
return fmt.Errorf("link %v not found", chanID)
|
||||
}
|
||||
|
||||
oldShortChanID := link.ShortChanID()
|
||||
|
||||
// Try to update the link's short channel ID, returning early if this
|
||||
// update failed.
|
||||
shortChanID, err := link.UpdateShortChanID()
|
||||
// Try to update the link's underlying channel state, returning early
|
||||
// if this update failed.
|
||||
_, err := link.UpdateShortChanID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reject any blank short channel ids.
|
||||
if shortChanID == hop.Source {
|
||||
return fmt.Errorf("refusing trivial short_chan_id for chan_id=%v"+
|
||||
"live link", chanID)
|
||||
// Since the zero-conf channel is confirmed, we should populate the
|
||||
// aliasToReal map and update the baseIndex.
|
||||
aliases := link.getAliases()
|
||||
|
||||
confirmedScid := link.confirmedScid()
|
||||
|
||||
for _, alias := range aliases {
|
||||
s.aliasToReal[alias] = confirmedScid
|
||||
}
|
||||
|
||||
log.Infof("Updated short_chan_id for ChannelLink(%v): old=%v, new=%v",
|
||||
chanID, oldShortChanID, shortChanID)
|
||||
|
||||
// Since the link was in the pending state before, we will remove it
|
||||
// from the pending link index and add it to the live link index so that
|
||||
// it can be available in forwarding.
|
||||
delete(s.pendingLinkIndex, chanID)
|
||||
s.addLiveLink(link)
|
||||
|
||||
// Finally, alert the mail orchestrator to the change of short channel
|
||||
// ID, and deliver any unclaimed packets to the link.
|
||||
mailbox := s.mailOrchestrator.GetOrCreateMailBox(chanID, shortChanID)
|
||||
s.mailOrchestrator.BindLiveShortChanID(
|
||||
mailbox, chanID, shortChanID,
|
||||
)
|
||||
s.baseIndex[confirmedScid] = link.ShortChanID()
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -2569,3 +2803,205 @@ func (s *Switch) evaluateDustThreshold(link ChannelLink,
|
||||
// If we reached this point, this HTLC is fine to forward.
|
||||
return false
|
||||
}
|
||||
|
||||
// failMailboxUpdate is passed to the mailbox orchestrator which in turn passes
|
||||
// it to individual mailboxes. It allows the mailboxes to construct a
|
||||
// FailureMessage when failing back HTLC's due to expiry and may include an
|
||||
// alias in the ShortChannelID field. The outgoingScid is the SCID originally
|
||||
// used in the onion. The mailboxScid is the SCID that the mailbox and link
|
||||
// use. The mailboxScid is only used in the non-alias case, so it is always
|
||||
// the confirmed SCID.
|
||||
func (s *Switch) failMailboxUpdate(outgoingScid,
|
||||
mailboxScid lnwire.ShortChannelID) lnwire.FailureMessage {
|
||||
|
||||
// Try to use the failAliasUpdate function in case this is a channel
|
||||
// that uses aliases. If it returns nil, we'll fallback to the original
|
||||
// pre-alias behavior.
|
||||
update := s.failAliasUpdate(outgoingScid, false)
|
||||
if update == nil {
|
||||
// Execute the fallback behavior.
|
||||
var err error
|
||||
update, err = s.cfg.FetchLastChannelUpdate(mailboxScid)
|
||||
if err != nil {
|
||||
return &lnwire.FailTemporaryNodeFailure{}
|
||||
}
|
||||
}
|
||||
|
||||
return lnwire.NewTemporaryChannelFailure(update)
|
||||
}
|
||||
|
||||
// failAliasUpdate prepares a ChannelUpdate for a failed incoming or outgoing
|
||||
// HTLC on a channel where the option-scid-alias feature bit was negotiated. If
|
||||
// the associated channel is not one of these, this function will return nil
|
||||
// and the caller is expected to handle this properly. In this case, a return
|
||||
// to the original non-alias behavior is expected.
|
||||
func (s *Switch) failAliasUpdate(scid lnwire.ShortChannelID,
|
||||
incoming bool) *lnwire.ChannelUpdate {
|
||||
|
||||
// This function does not defer the unlocking because of the database
|
||||
// lookups for ChannelUpdate.
|
||||
s.indexMtx.RLock()
|
||||
|
||||
if s.cfg.IsAlias(scid) {
|
||||
// The alias SCID was used. In the incoming case this means
|
||||
// the channel is zero-conf as the link sets the scid. In the
|
||||
// outgoing case, the sender set the scid to use and may be
|
||||
// either the alias or the confirmed one, if it exists.
|
||||
realScid, ok := s.aliasToReal[scid]
|
||||
if !ok {
|
||||
// The real, confirmed SCID does not exist yet. Find
|
||||
// the "base" SCID that the link uses via the
|
||||
// baseIndex. If we can't find it, return nil. This
|
||||
// means the channel is zero-conf.
|
||||
baseScid, ok := s.baseIndex[scid]
|
||||
s.indexMtx.RUnlock()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
update, err := s.cfg.FetchLastChannelUpdate(baseScid)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace the baseScid with the passed-in alias.
|
||||
update.ShortChannelID = scid
|
||||
sig, err := s.cfg.SignAliasUpdate(update)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
update.Signature, err = lnwire.NewSigFromSignature(sig)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return update
|
||||
}
|
||||
|
||||
s.indexMtx.RUnlock()
|
||||
|
||||
// Fetch the SCID via the confirmed SCID and replace it with
|
||||
// the alias.
|
||||
update, err := s.cfg.FetchLastChannelUpdate(realScid)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// In the incoming case, we want to ensure that we don't leak
|
||||
// the UTXO in case the channel is private. In the outgoing
|
||||
// case, since the alias was used, we do the same thing.
|
||||
update.ShortChannelID = scid
|
||||
sig, err := s.cfg.SignAliasUpdate(update)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
update.Signature, err = lnwire.NewSigFromSignature(sig)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return update
|
||||
}
|
||||
|
||||
// If the confirmed SCID is not in baseIndex, this is not an
|
||||
// option-scid-alias or zero-conf channel.
|
||||
baseScid, ok := s.baseIndex[scid]
|
||||
if !ok {
|
||||
s.indexMtx.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch the link so we can get an alias to use in the ShortChannelID
|
||||
// of the ChannelUpdate.
|
||||
link, ok := s.forwardingIndex[baseScid]
|
||||
s.indexMtx.RUnlock()
|
||||
if !ok {
|
||||
// This should never happen, but if it does for some reason,
|
||||
// fallback to the old behavior.
|
||||
return nil
|
||||
}
|
||||
|
||||
aliases := link.getAliases()
|
||||
if len(aliases) == 0 {
|
||||
// This should never happen, but if it does, fallback.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch the ChannelUpdate via the real, confirmed SCID.
|
||||
update, err := s.cfg.FetchLastChannelUpdate(scid)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The incoming case will replace the ShortChannelID in the retrieved
|
||||
// ChannelUpdate with the alias to ensure no privacy leak occurs. This
|
||||
// would happen if a private non-zero-conf option-scid-alias
|
||||
// feature-bit channel leaked its UTXO here rather than supplying an
|
||||
// alias. In the outgoing case, the confirmed SCID was actually used
|
||||
// for forwarding in the onion, so no replacement is necessary as the
|
||||
// sender knows the scid.
|
||||
if incoming {
|
||||
// We will replace and sign the update with the first alias.
|
||||
// Since this happens on the incoming side, it's not actually
|
||||
// possible to know what the sender used in the onion.
|
||||
update.ShortChannelID = aliases[0]
|
||||
sig, err := s.cfg.SignAliasUpdate(update)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
update.Signature, err = lnwire.NewSigFromSignature(sig)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return update
|
||||
}
|
||||
|
||||
// AddAliasForLink instructs the Switch to update its in-memory maps to reflect
|
||||
// that a link has a new alias.
|
||||
func (s *Switch) AddAliasForLink(chanID lnwire.ChannelID,
|
||||
alias lnwire.ShortChannelID) error {
|
||||
|
||||
// Fetch the link so that we can update the underlying channel's set of
|
||||
// aliases.
|
||||
s.indexMtx.RLock()
|
||||
link, err := s.getLink(chanID)
|
||||
s.indexMtx.RUnlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the link is a channel where the option-scid-alias feature bit was
|
||||
// not negotiated, we'll return an error.
|
||||
if !link.negotiatedAliasFeature() {
|
||||
return fmt.Errorf("attempted to update non-alias channel")
|
||||
}
|
||||
|
||||
linkScid := link.ShortChanID()
|
||||
|
||||
// We'll update the maps so the Switch includes this alias in its
|
||||
// forwarding decisions.
|
||||
if link.isZeroConf() {
|
||||
if link.zeroConfConfirmed() {
|
||||
// If the channel has confirmed on-chain, we'll
|
||||
// add this alias to the aliasToReal map.
|
||||
confirmedScid := link.confirmedScid()
|
||||
|
||||
s.aliasToReal[alias] = confirmedScid
|
||||
}
|
||||
|
||||
// Add this alias to the baseIndex mapping.
|
||||
s.baseIndex[alias] = linkScid
|
||||
} else if link.negotiatedAliasFeature() {
|
||||
// The channel is confirmed, so we'll populate the aliasToReal
|
||||
// and baseIndex maps.
|
||||
s.aliasToReal[alias] = linkScid
|
||||
s.baseIndex[alias] = linkScid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1128,6 +1128,12 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer,
|
||||
return nil
|
||||
}
|
||||
|
||||
getAliases := func(
|
||||
base lnwire.ShortChannelID) []lnwire.ShortChannelID {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
link := NewChannelLink(
|
||||
ChannelLinkConfig{
|
||||
Switch: server.htlcSwitch,
|
||||
@ -1168,6 +1174,7 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer,
|
||||
NotifyActiveChannel: func(wire.OutPoint) {},
|
||||
NotifyInactiveChannel: func(wire.OutPoint) {},
|
||||
HtlcNotifier: server.htlcSwitch.cfg.HtlcNotifier,
|
||||
GetAliases: getAliases,
|
||||
},
|
||||
channel,
|
||||
)
|
||||
|
@ -29,6 +29,15 @@ type ProtocolOptions struct {
|
||||
// opening or accepting channels having the script enforced commitment
|
||||
// type for leased channel.
|
||||
NoScriptEnforcedLease bool `long:"no-script-enforced-lease" description:"disable support for script enforced lease commitments"`
|
||||
|
||||
// OptionScidAlias should be set if we want to signal the
|
||||
// option-scid-alias feature bit. This allows scid aliases and the
|
||||
// option-scid-alias channel-type.
|
||||
OptionScidAlias bool `long:"option-scid-alias" description:"enable support for option_scid_alias channels"`
|
||||
|
||||
// OptionZeroConf should be set if we want to signal the zero-conf
|
||||
// feature bit.
|
||||
OptionZeroConf bool `long:"zero-conf" description:"enable support for zero-conf channels, must have option-scid-alias set also"`
|
||||
}
|
||||
|
||||
// Wumbo returns true if lnd should permit the creation and acceptance of wumbo
|
||||
@ -48,3 +57,13 @@ func (l *ProtocolOptions) NoAnchorCommitments() bool {
|
||||
func (l *ProtocolOptions) NoScriptEnforcementLease() bool {
|
||||
return l.NoScriptEnforcedLease
|
||||
}
|
||||
|
||||
// ScidAlias returns true if we have enabled the option-scid-alias feature bit.
|
||||
func (l *ProtocolOptions) ScidAlias() bool {
|
||||
return l.OptionScidAlias
|
||||
}
|
||||
|
||||
// ZeroConf returns true if we have enabled the zero-conf feature bit.
|
||||
func (l *ProtocolOptions) ZeroConf() bool {
|
||||
return l.OptionZeroConf
|
||||
}
|
||||
|
@ -30,6 +30,15 @@ type ProtocolOptions struct {
|
||||
//
|
||||
// TODO: Move to experimental?
|
||||
ScriptEnforcedLease bool `long:"script-enforced-lease" description:"enable support for script enforced lease commitments"`
|
||||
|
||||
// OptionScidAlias should be set if we want to signal the
|
||||
// option-scid-alias feature bit. This allows scid aliases and the
|
||||
// option-scid-alias channel-type.
|
||||
OptionScidAlias bool `long:"option-scid-alias" description:"enable support for option_scid_alias channels"`
|
||||
|
||||
// OptionZeroConf should be set if we want to signal the zero-conf
|
||||
// feature bit.
|
||||
OptionZeroConf bool `long:"zero-conf" description:"enable support for zero-conf channels, must have option-scid-alias set also"`
|
||||
}
|
||||
|
||||
// Wumbo returns true if lnd should permit the creation and acceptance of wumbo
|
||||
@ -49,3 +58,13 @@ func (l *ProtocolOptions) NoAnchorCommitments() bool {
|
||||
func (l *ProtocolOptions) NoScriptEnforcementLease() bool {
|
||||
return !l.ScriptEnforcedLease
|
||||
}
|
||||
|
||||
// ScidAlias returns true if we have enabled the option-scid-alias feature bit.
|
||||
func (l *ProtocolOptions) ScidAlias() bool {
|
||||
return l.OptionScidAlias
|
||||
}
|
||||
|
||||
// ZeroConf returns true if we have enabled the zero-conf feature bit.
|
||||
func (l *ProtocolOptions) ZeroConf() bool {
|
||||
return l.OptionZeroConf
|
||||
}
|
||||
|
@ -72,6 +72,10 @@ type AddInvoiceConfig struct {
|
||||
// GenAmpInvoiceFeatures returns a feature containing feature bits that
|
||||
// should be advertised on freshly generated AMP invoices.
|
||||
GenAmpInvoiceFeatures func() *lnwire.FeatureVector
|
||||
|
||||
// GetAlias allows the peer's alias SCID to be retrieved for private
|
||||
// option_scid_alias channels.
|
||||
GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
|
||||
}
|
||||
|
||||
// AddInvoiceData contains the required data to create a new invoice.
|
||||
@ -387,9 +391,27 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
|
||||
continue
|
||||
}
|
||||
|
||||
// If this is a zero-conf channel, check if the
|
||||
// confirmed SCID was used in forcedHints.
|
||||
realScid := c.ZeroConfRealScid().ToUint64()
|
||||
if c.IsZeroConf() {
|
||||
if _, ok := forcedHints[realScid]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
chanID := lnwire.NewChanIDFromOutPoint(
|
||||
&c.FundingOutpoint,
|
||||
)
|
||||
|
||||
// Check whether the the peer's alias was
|
||||
// provided in forcedHints.
|
||||
peerAlias, _ := cfg.GetAlias(chanID)
|
||||
peerScid := peerAlias.ToUint64()
|
||||
if _, ok := forcedHints[peerScid]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
isActive := cfg.IsChannelActive(chanID)
|
||||
|
||||
hopHintInfo := newHopHintInfo(c, isActive)
|
||||
@ -529,10 +551,17 @@ func chanCanBeHopHint(channel *HopHintInfo, cfg *SelectHopHintsCfg) (
|
||||
// Fetch the policies for each end of the channel.
|
||||
info, p1, p2, err := cfg.FetchChannelEdgesByID(channel.ShortChannelID)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to fetch the routing "+
|
||||
"policies for the edges of the channel "+
|
||||
"%v: %v", channel.ShortChannelID, err)
|
||||
return nil, false
|
||||
// In the case of zero-conf channels, it may be the case that
|
||||
// the alias SCID was deleted from the graph, and replaced by
|
||||
// the confirmed SCID. Check the Graph for the confirmed SCID.
|
||||
confirmedScid := channel.ConfirmedScidZC
|
||||
info, p1, p2, err = cfg.FetchChannelEdgesByID(confirmedScid)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to fetch the routing policies for "+
|
||||
"the edges of the channel %v: %v",
|
||||
channel.ShortChannelID, err)
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// Now, we'll need to determine which is the correct policy for HTLCs
|
||||
@ -550,7 +579,8 @@ func chanCanBeHopHint(channel *HopHintInfo, cfg *SelectHopHintsCfg) (
|
||||
// addHopHint creates a hop hint out of the passed channel and channel policy.
|
||||
// The new hop hint is appended to the passed slice.
|
||||
func addHopHint(hopHints *[][]zpay32.HopHint,
|
||||
channel *HopHintInfo, chanPolicy *channeldb.ChannelEdgePolicy) {
|
||||
channel *HopHintInfo, chanPolicy *channeldb.ChannelEdgePolicy,
|
||||
aliasScid lnwire.ShortChannelID) {
|
||||
|
||||
hopHint := zpay32.HopHint{
|
||||
NodeID: channel.RemotePubkey,
|
||||
@ -562,6 +592,11 @@ func addHopHint(hopHints *[][]zpay32.HopHint,
|
||||
CLTVExpiryDelta: chanPolicy.TimeLockDelta,
|
||||
}
|
||||
|
||||
var defaultScid lnwire.ShortChannelID
|
||||
if aliasScid != defaultScid {
|
||||
hopHint.ChannelID = aliasScid.ToUint64()
|
||||
}
|
||||
|
||||
*hopHints = append(*hopHints, []zpay32.HopHint{hopHint})
|
||||
}
|
||||
|
||||
@ -587,18 +622,28 @@ type HopHintInfo struct {
|
||||
|
||||
// ShortChannelID is the short channel ID of the channel.
|
||||
ShortChannelID uint64
|
||||
|
||||
// ConfirmedScidZC is the confirmed SCID of a zero-conf channel. This
|
||||
// may be used for looking up a channel in the graph.
|
||||
ConfirmedScidZC uint64
|
||||
|
||||
// ScidAliasFeature denotes whether the channel has negotiated the
|
||||
// option-scid-alias feature bit.
|
||||
ScidAliasFeature bool
|
||||
}
|
||||
|
||||
func newHopHintInfo(c *channeldb.OpenChannel, isActive bool) *HopHintInfo {
|
||||
isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0
|
||||
|
||||
return &HopHintInfo{
|
||||
IsPublic: isPublic,
|
||||
IsActive: isActive,
|
||||
FundingOutpoint: c.FundingOutpoint,
|
||||
RemotePubkey: c.IdentityPub,
|
||||
RemoteBalance: c.LocalCommitment.RemoteBalance,
|
||||
ShortChannelID: c.ShortChannelID.ToUint64(),
|
||||
IsPublic: isPublic,
|
||||
IsActive: isActive,
|
||||
FundingOutpoint: c.FundingOutpoint,
|
||||
RemotePubkey: c.IdentityPub,
|
||||
RemoteBalance: c.LocalCommitment.RemoteBalance,
|
||||
ShortChannelID: c.ShortChannelID.ToUint64(),
|
||||
ConfirmedScidZC: c.ZeroConfRealScid().ToUint64(),
|
||||
ScidAliasFeature: c.ChanType.HasScidAliasFeature(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -615,12 +660,17 @@ type SelectHopHintsCfg struct {
|
||||
FetchChannelEdgesByID func(chanID uint64) (*channeldb.ChannelEdgeInfo,
|
||||
*channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy,
|
||||
error)
|
||||
|
||||
// GetAlias allows the peer's alias SCID to be retrieved for private
|
||||
// option_scid_alias channels.
|
||||
GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
|
||||
}
|
||||
|
||||
func newSelectHopHintsCfg(invoicesCfg *AddInvoiceConfig) *SelectHopHintsCfg {
|
||||
return &SelectHopHintsCfg{
|
||||
IsPublicNode: invoicesCfg.Graph.IsPublicNode,
|
||||
FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID,
|
||||
GetAlias: invoicesCfg.GetAlias,
|
||||
}
|
||||
}
|
||||
|
||||
@ -693,9 +743,26 @@ func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *SelectHopHintsCfg,
|
||||
continue
|
||||
}
|
||||
|
||||
// Lookup and see if there is an alias SCID that exists.
|
||||
chanID := lnwire.NewChanIDFromOutPoint(
|
||||
&channel.FundingOutpoint,
|
||||
)
|
||||
alias, _ := cfg.GetAlias(chanID)
|
||||
|
||||
// If this is a channel where the option-scid-alias feature bit
|
||||
// was negotiated and the alias is not yet assigned, we cannot
|
||||
// issue an invoice. Doing so might expose the confirmed SCID
|
||||
// of a private channel.
|
||||
if channel.ScidAliasFeature {
|
||||
var defaultScid lnwire.ShortChannelID
|
||||
if alias == defaultScid {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we now this channel use usable, add it as a hop
|
||||
// hint and the indexes we'll use later.
|
||||
addHopHint(&hopHints, channel, edgePolicy)
|
||||
addHopHint(&hopHints, channel, edgePolicy, alias)
|
||||
|
||||
hopHintChans[channel.FundingOutpoint] = struct{}{}
|
||||
totalHintBandwidth += channel.RemoteBalance
|
||||
@ -733,9 +800,26 @@ func SelectHopHints(amtMSat lnwire.MilliSatoshi, cfg *SelectHopHintsCfg,
|
||||
continue
|
||||
}
|
||||
|
||||
// Lookup and see if there's an alias SCID that exists.
|
||||
chanID := lnwire.NewChanIDFromOutPoint(
|
||||
&channel.FundingOutpoint,
|
||||
)
|
||||
alias, _ := cfg.GetAlias(chanID)
|
||||
|
||||
// If this is a channel where the option-scid-alias feature bit
|
||||
// was negotiated and the alias is not yet assigned, we cannot
|
||||
// issue an invoice. Doing so might expose the confirmed SCID
|
||||
// of a private channel.
|
||||
if channel.ScidAliasFeature {
|
||||
var defaultScid lnwire.ShortChannelID
|
||||
if alias == defaultScid {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Include the route hint in our set of options that will be
|
||||
// used when creating the invoice.
|
||||
addHopHint(&hopHints, channel, remotePolicy)
|
||||
addHopHint(&hopHints, channel, remotePolicy, alias)
|
||||
|
||||
// As we've just added a new hop hint, we'll accumulate it's
|
||||
// available balance now to update our tally.
|
||||
|
@ -274,11 +274,11 @@ func TestSelectHopHints(t *testing.T) {
|
||||
// hop hint.
|
||||
h.Mock.On(
|
||||
"FetchChannelEdgesByID",
|
||||
private1ShortID,
|
||||
mock.Anything,
|
||||
).Return(
|
||||
nil, nil, nil,
|
||||
errors.New("no edge"),
|
||||
)
|
||||
).Times(4)
|
||||
},
|
||||
amount: 100,
|
||||
channels: []*HopHintInfo{
|
||||
@ -536,6 +536,10 @@ func TestSelectHopHints(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
getAlias := func(lnwire.ChannelID) (lnwire.ShortChannelID, error) {
|
||||
return lnwire.ShortChannelID{}, nil
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
@ -548,6 +552,7 @@ func TestSelectHopHints(t *testing.T) {
|
||||
cfg := &SelectHopHintsCfg{
|
||||
IsPublicNode: mock.IsPublicNode,
|
||||
FetchChannelEdgesByID: mock.FetchChannelEdgesByID,
|
||||
GetAlias: getAlias,
|
||||
}
|
||||
|
||||
hints := SelectHopHints(
|
||||
|
@ -60,4 +60,8 @@ type Config struct {
|
||||
// GenAmpInvoiceFeatures returns a feature containing feature bits that
|
||||
// should be advertised on freshly generated AMP invoices.
|
||||
GenAmpInvoiceFeatures func() *lnwire.FeatureVector
|
||||
|
||||
// GetAlias returns the peer's alias SCID if it exists given the
|
||||
// 32-byte ChannelID.
|
||||
GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
|
||||
}
|
||||
|
@ -331,6 +331,7 @@ func (s *Server) AddHoldInvoice(ctx context.Context,
|
||||
Graph: s.cfg.GraphDB,
|
||||
GenInvoiceFeatures: s.cfg.GenInvoiceFeatures,
|
||||
GenAmpInvoiceFeatures: s.cfg.GenAmpInvoiceFeatures,
|
||||
GetAlias: s.cfg.GetAlias,
|
||||
}
|
||||
|
||||
hash, err := lntypes.MakeHash(invoice.Hash)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -979,6 +979,13 @@ message ChannelAcceptResponse {
|
||||
The number of confirmations we require before we consider the channel open.
|
||||
*/
|
||||
uint32 min_accept_depth = 10;
|
||||
|
||||
/*
|
||||
Whether the responder wants this to be a zero-conf channel. This will fail
|
||||
if either side does not have the scid-alias feature bit set. The minimum
|
||||
depth field must be zero if this is true.
|
||||
*/
|
||||
bool zero_conf = 11;
|
||||
}
|
||||
|
||||
message ChannelPoint {
|
||||
@ -2114,6 +2121,17 @@ message OpenChannelRequest {
|
||||
the remote peer supports explicit channel negotiation.
|
||||
*/
|
||||
CommitmentType commitment_type = 18;
|
||||
|
||||
/*
|
||||
If this is true, then a zero-conf channel open will be attempted.
|
||||
*/
|
||||
bool zero_conf = 19;
|
||||
|
||||
/*
|
||||
If this is true, then an option-scid-alias channel-type open will be
|
||||
attempted.
|
||||
*/
|
||||
bool scid_alias = 20;
|
||||
}
|
||||
message OpenStatusUpdate {
|
||||
oneof update {
|
||||
|
@ -3409,6 +3409,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "The number of confirmations we require before we consider the channel open."
|
||||
},
|
||||
"zero_conf": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the responder wants this to be a zero-conf channel. This will fail\nif either side does not have the scid-alias feature bit set. The minimum\ndepth field must be zero if this is true."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -5414,6 +5418,14 @@
|
||||
"commitment_type": {
|
||||
"$ref": "#/definitions/lnrpcCommitmentType",
|
||||
"description": "The explicit commitment type to use. Note this field will only be used if\nthe remote peer supports explicit channel negotiation."
|
||||
},
|
||||
"zero_conf": {
|
||||
"type": "boolean",
|
||||
"description": "If this is true, then a zero-conf channel open will be attempted."
|
||||
},
|
||||
"scid_alias": {
|
||||
"type": "boolean",
|
||||
"description": "If this is true, then an option-scid-alias channel-type open will be\nattempted."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -989,6 +989,15 @@ type OpenChannelParams struct {
|
||||
// CommitmentType is the commitment type that should be used for the
|
||||
// channel to be opened.
|
||||
CommitmentType lnrpc.CommitmentType
|
||||
|
||||
// ZeroConf is used to determine if the channel will be a zero-conf
|
||||
// channel. This only works if the explicit negotiation is used with
|
||||
// anchors or script enforced leases.
|
||||
ZeroConf bool
|
||||
|
||||
// ScidAlias denotes whether the channel will be an option-scid-alias
|
||||
// channel type negotiation.
|
||||
ScidAlias bool
|
||||
}
|
||||
|
||||
// OpenChannel attempts to open a channel between srcNode and destNode with the
|
||||
@ -1027,6 +1036,8 @@ func (n *NetworkHarness) OpenChannel(srcNode, destNode *HarnessNode,
|
||||
FundingShim: p.FundingShim,
|
||||
SatPerByte: int64(p.SatPerVByte),
|
||||
CommitmentType: p.CommitmentType,
|
||||
ZeroConf: p.ZeroConf,
|
||||
ScidAlias: p.ScidAlias,
|
||||
}
|
||||
|
||||
// We need to use n.runCtx here to keep the response stream alive after
|
||||
|
@ -447,6 +447,97 @@ func testChannelBackupRestore(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface, for zero-conf anchor channels.
|
||||
{
|
||||
name: "restore from backup file for zero-conf " +
|
||||
"anchors channel",
|
||||
initiator: true,
|
||||
private: false,
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
zeroConf: true,
|
||||
restoreMethod: func(oldNode *lntest.HarnessNode,
|
||||
backupFilePath string,
|
||||
mnemonic []string) (nodeRestorer, error) {
|
||||
|
||||
// Read the entire Multi backup stored within
|
||||
// this node's channels.backup file.
|
||||
multi, err := ioutil.ReadFile(backupFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now that we have Dave's backup file, we'll
|
||||
// create a new nodeRestorer that we'll restore
|
||||
// using the on-disk channels.backup.
|
||||
return chanRestoreViaRPC(
|
||||
net, password, mnemonic, multi,
|
||||
oldNode,
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface for a zero-conf script-enforced leased channel.
|
||||
{
|
||||
name: "restore from backup file zero-conf " +
|
||||
"script-enforced leased channel",
|
||||
initiator: true,
|
||||
private: false,
|
||||
commitmentType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
|
||||
zeroConf: true,
|
||||
restoreMethod: func(oldNode *lntest.HarnessNode,
|
||||
backupFilePath string,
|
||||
mnemonic []string) (nodeRestorer, error) {
|
||||
|
||||
// Read the entire Multi backup stored within
|
||||
// this node's channel.backup file.
|
||||
multi, err := ioutil.ReadFile(backupFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now that we have Dave's backup file, we'll
|
||||
// create a new nodeRestorer that we'll restore
|
||||
// using the on-disk channel backup.
|
||||
return chanRestoreViaRPC(
|
||||
net, password, mnemonic, multi,
|
||||
oldNode,
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
// Restore a zero-conf anchors channel that was force closed by
|
||||
// dave just before going offline.
|
||||
{
|
||||
name: "restore force closed from backup file " +
|
||||
"anchors w/ zero-conf",
|
||||
initiator: true,
|
||||
private: false,
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
localForceClose: true,
|
||||
zeroConf: true,
|
||||
restoreMethod: func(oldNode *lntest.HarnessNode,
|
||||
backupFilePath string,
|
||||
mnemonic []string) (nodeRestorer, error) {
|
||||
|
||||
// Read the entire Multi backup stored within
|
||||
// this node's channel.backup file.
|
||||
multi, err := ioutil.ReadFile(backupFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now that we have Dave's backup file, we'll
|
||||
// create a new nodeRestorer that we'll restore
|
||||
// using the on-disk channel backup.
|
||||
return chanRestoreViaRPC(
|
||||
net, password, mnemonic, multi,
|
||||
oldNode,
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(roasbeef): online vs offline close?
|
||||
@ -861,6 +952,10 @@ type chanRestoreTestCase struct {
|
||||
restoreMethod func(oldNode *lntest.HarnessNode,
|
||||
backupFilePath string,
|
||||
mnemonic []string) (nodeRestorer, error)
|
||||
|
||||
// zeroConf denotes whether the opened channel is a zero-conf channel
|
||||
// or not.
|
||||
zeroConf bool
|
||||
}
|
||||
|
||||
// testChanRestoreScenario executes a chanRestoreTestCase from end to end,
|
||||
@ -886,6 +981,13 @@ func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness,
|
||||
nodeArgs = append(nodeArgs, args...)
|
||||
}
|
||||
|
||||
if testCase.zeroConf {
|
||||
nodeArgs = append(
|
||||
nodeArgs, "--protocol.option-scid-alias",
|
||||
"--protocol.zero-conf",
|
||||
)
|
||||
}
|
||||
|
||||
// First, we'll create a brand new node we'll use within the test. If
|
||||
// we have a custom backup file specified, then we'll also create that
|
||||
// for use.
|
||||
@ -971,14 +1073,16 @@ func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness,
|
||||
net, t, from, to, chanAmt, thawHeight, true,
|
||||
)
|
||||
}
|
||||
params := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
Private: testCase.private,
|
||||
FundingShim: fundingShim,
|
||||
CommitmentType: testCase.commitmentType,
|
||||
ZeroConf: testCase.zeroConf,
|
||||
}
|
||||
chanPoint = openChannelAndAssert(
|
||||
t, net, from, to, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
Private: testCase.private,
|
||||
FundingShim: fundingShim,
|
||||
CommitmentType: testCase.commitmentType,
|
||||
},
|
||||
t, net, from, to, params,
|
||||
)
|
||||
|
||||
// Wait for both sides to see the opened channel.
|
||||
|
@ -23,14 +23,15 @@ import (
|
||||
// case of anchor channels, the second-level spends can also be aggregated and
|
||||
// properly feebumped, so we'll check that as well.
|
||||
func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest,
|
||||
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
|
||||
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType,
|
||||
zeroConf bool) {
|
||||
|
||||
const finalCltvDelta = 40
|
||||
ctxb := context.Background()
|
||||
|
||||
// First, we'll create a three hop network: Alice -> Bob -> Carol.
|
||||
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork(
|
||||
t, net, alice, bob, false, c,
|
||||
t, net, alice, bob, false, c, zeroConf,
|
||||
)
|
||||
defer shutdownAndAssert(net, t, carol)
|
||||
|
||||
|
@ -20,7 +20,8 @@ import (
|
||||
// preimage via the witness beacon, we properly settle the HTLC on-chain using
|
||||
// the HTLC success transaction in order to ensure we don't lose any funds.
|
||||
func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
|
||||
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
|
||||
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType,
|
||||
zeroConf bool) {
|
||||
|
||||
ctxb := context.Background()
|
||||
|
||||
@ -28,7 +29,7 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
|
||||
// Carol refusing to actually settle or directly cancel any HTLC's
|
||||
// self.
|
||||
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork(
|
||||
t, net, alice, bob, false, c,
|
||||
t, net, alice, bob, false, c, zeroConf,
|
||||
)
|
||||
|
||||
// Clean up carol's node when the test finishes.
|
||||
|
@ -20,7 +20,8 @@ import (
|
||||
// canceled backwards. Once the timeout has been reached, then we should sweep
|
||||
// it on-chain, and cancel the HTLC backwards.
|
||||
func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest,
|
||||
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
|
||||
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType,
|
||||
zeroConf bool) {
|
||||
|
||||
ctxb := context.Background()
|
||||
|
||||
@ -28,7 +29,7 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest,
|
||||
// Carol refusing to actually settle or directly cancel any HTLC's
|
||||
// self.
|
||||
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork(
|
||||
t, net, alice, bob, true, c,
|
||||
t, net, alice, bob, true, c, zeroConf,
|
||||
)
|
||||
|
||||
// Clean up carol's node when the test finishes.
|
||||
|
@ -22,7 +22,8 @@ import (
|
||||
// extract the preimage from the sweep transaction, and finish settling the
|
||||
// HTLC backwards into the route.
|
||||
func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest,
|
||||
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
|
||||
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType,
|
||||
zeroConf bool) {
|
||||
|
||||
ctxb := context.Background()
|
||||
|
||||
@ -30,7 +31,7 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest,
|
||||
// Carol refusing to actually settle or directly cancel any HTLC's
|
||||
// self.
|
||||
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork(
|
||||
t, net, alice, bob, false, c,
|
||||
t, net, alice, bob, false, c, zeroConf,
|
||||
)
|
||||
|
||||
// Clean up carol's node when the test finishes.
|
||||
|
@ -20,7 +20,8 @@ import (
|
||||
// HTLC directly on-chain using the preimage in order to ensure that we don't
|
||||
// lose any funds.
|
||||
func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest,
|
||||
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
|
||||
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType,
|
||||
zeroConf bool) {
|
||||
|
||||
ctxb := context.Background()
|
||||
|
||||
@ -28,7 +29,7 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
|
||||
// Carol refusing to actually settle or directly cancel any HTLC's
|
||||
// self.
|
||||
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork(
|
||||
t, net, alice, bob, false, c,
|
||||
t, net, alice, bob, false, c, zeroConf,
|
||||
)
|
||||
|
||||
// Clean up carol's node when the test finishes.
|
||||
|
@ -19,7 +19,8 @@ import (
|
||||
// that's timed out. At this point, the node should timeout the HTLC using the
|
||||
// HTLC timeout transaction, then cancel it backwards as normal.
|
||||
func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
|
||||
t *harnessTest, alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
|
||||
t *harnessTest, alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType,
|
||||
zeroConf bool) {
|
||||
|
||||
ctxb := context.Background()
|
||||
|
||||
@ -27,7 +28,7 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
|
||||
// Carol refusing to actually settle or directly cancel any HTLC's
|
||||
// self.
|
||||
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork(
|
||||
t, net, alice, bob, true, c,
|
||||
t, net, alice, bob, true, c, zeroConf,
|
||||
)
|
||||
|
||||
// Clean up carol's node when the test finishes.
|
||||
|
@ -21,7 +21,8 @@ import (
|
||||
// transaction once the timeout has expired. Once we sweep the transaction, we
|
||||
// should also cancel back the initial HTLC.
|
||||
func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
|
||||
t *harnessTest, alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
|
||||
t *harnessTest, alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType,
|
||||
zeroConf bool) {
|
||||
|
||||
ctxb := context.Background()
|
||||
|
||||
@ -29,7 +30,7 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
|
||||
// Carol refusing to actually settle or directly cancel any HTLC's
|
||||
// self.
|
||||
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork(
|
||||
t, net, alice, bob, true, c,
|
||||
t, net, alice, bob, true, c, zeroConf,
|
||||
)
|
||||
|
||||
// Clean up carol's node when the test finishes.
|
||||
|
@ -19,7 +19,8 @@ func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
type testCase struct {
|
||||
name string
|
||||
test func(net *lntest.NetworkHarness, t *harnessTest, alice,
|
||||
bob *lntest.HarnessNode, c lnrpc.CommitmentType)
|
||||
bob *lntest.HarnessNode, c lnrpc.CommitmentType,
|
||||
zeroConf bool)
|
||||
}
|
||||
|
||||
subTests := []testCase{
|
||||
@ -68,20 +69,51 @@ func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
},
|
||||
}
|
||||
|
||||
commitTypes := []lnrpc.CommitmentType{
|
||||
lnrpc.CommitmentType_LEGACY,
|
||||
lnrpc.CommitmentType_ANCHORS,
|
||||
lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
|
||||
commitWithZeroConf := []struct {
|
||||
commitType lnrpc.CommitmentType
|
||||
zeroConf bool
|
||||
}{
|
||||
{
|
||||
commitType: lnrpc.CommitmentType_LEGACY,
|
||||
zeroConf: false,
|
||||
},
|
||||
{
|
||||
commitType: lnrpc.CommitmentType_ANCHORS,
|
||||
zeroConf: false,
|
||||
},
|
||||
{
|
||||
commitType: lnrpc.CommitmentType_ANCHORS,
|
||||
zeroConf: true,
|
||||
},
|
||||
{
|
||||
commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
|
||||
zeroConf: false,
|
||||
},
|
||||
{
|
||||
commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
|
||||
zeroConf: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, commitType := range commitTypes {
|
||||
commitType := commitType
|
||||
testName := fmt.Sprintf("committype=%v", commitType.String())
|
||||
for _, typeAndConf := range commitWithZeroConf {
|
||||
typeAndConf := typeAndConf
|
||||
testName := fmt.Sprintf(
|
||||
"committype=%v zeroconf=%v",
|
||||
typeAndConf.commitType.String(), typeAndConf.zeroConf,
|
||||
)
|
||||
|
||||
success := t.t.Run(testName, func(t *testing.T) {
|
||||
ht := newHarnessTest(t, net)
|
||||
|
||||
args := nodeArgsForCommitType(commitType)
|
||||
args := nodeArgsForCommitType(typeAndConf.commitType)
|
||||
|
||||
if typeAndConf.zeroConf {
|
||||
args = append(
|
||||
args, "--protocol.option-scid-alias",
|
||||
"--protocol.zero-conf",
|
||||
)
|
||||
}
|
||||
|
||||
alice := net.NewNode(t, "Alice", args)
|
||||
defer shutdownAndAssert(net, ht, alice)
|
||||
|
||||
@ -107,7 +139,11 @@ func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
// static fee estimate.
|
||||
net.SetFeeEstimate(12500)
|
||||
|
||||
subTest.test(net, ht, alice, bob, commitType)
|
||||
subTest.test(
|
||||
net, ht, alice, bob,
|
||||
typeAndConf.commitType,
|
||||
typeAndConf.zeroConf,
|
||||
)
|
||||
})
|
||||
if !success {
|
||||
return
|
||||
@ -204,7 +240,8 @@ func checkPaymentStatus(node *lntest.HarnessNode, preimage lntypes.Preimage,
|
||||
}
|
||||
|
||||
func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
|
||||
alice, bob *lntest.HarnessNode, carolHodl bool, c lnrpc.CommitmentType) (
|
||||
alice, bob *lntest.HarnessNode, carolHodl bool, c lnrpc.CommitmentType,
|
||||
zeroConf bool) (
|
||||
*lnrpc.ChannelPoint, *lnrpc.ChannelPoint, *lntest.HarnessNode) {
|
||||
|
||||
net.EnsureConnected(t.t, alice, bob)
|
||||
@ -234,6 +271,7 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
|
||||
Amt: chanAmt,
|
||||
CommitmentType: c,
|
||||
FundingShim: aliceFundingShim,
|
||||
ZeroConf: zeroConf,
|
||||
},
|
||||
)
|
||||
|
||||
@ -254,6 +292,14 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
|
||||
if carolHodl {
|
||||
carolFlags = append(carolFlags, "--hodl.exit-settle")
|
||||
}
|
||||
|
||||
if zeroConf {
|
||||
carolFlags = append(
|
||||
carolFlags, "--protocol.option-scid-alias",
|
||||
"--protocol.zero-conf",
|
||||
)
|
||||
}
|
||||
|
||||
carol := net.NewNode(t.t, "Carol", carolFlags)
|
||||
|
||||
net.ConnectNodes(t.t, bob, carol)
|
||||
@ -280,6 +326,7 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
|
||||
Amt: chanAmt,
|
||||
CommitmentType: c,
|
||||
FundingShim: bobFundingShim,
|
||||
ZeroConf: zeroConf,
|
||||
},
|
||||
)
|
||||
err = bob.WaitForNetworkChannelOpen(bobChanPoint)
|
||||
|
@ -407,4 +407,20 @@ var allTestCases = []*testCase{
|
||||
name: "resolution handoff",
|
||||
test: testResHandoff,
|
||||
},
|
||||
{
|
||||
name: "zero conf channel open",
|
||||
test: testZeroConfChannelOpen,
|
||||
},
|
||||
{
|
||||
name: "option scid alias",
|
||||
test: testOptionScidAlias,
|
||||
},
|
||||
{
|
||||
name: "scid alias channel update",
|
||||
test: testUpdateChannelPolicyScidAlias,
|
||||
},
|
||||
{
|
||||
name: "scid alias upgrade",
|
||||
test: testOptionScidUpgrade,
|
||||
},
|
||||
}
|
||||
|
1042
lntest/itest/lnd_zero_conf_test.go
Normal file
1042
lntest/itest/lnd_zero_conf_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,6 @@ import (
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
@ -208,11 +207,9 @@ type ChannelReservation struct {
|
||||
// creation of all channel reservations should be carried out via the
|
||||
// lnwallet.InitChannelReservation interface.
|
||||
func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
commitFeePerKw chainfee.SatPerKWeight, wallet *LightningWallet,
|
||||
id uint64, pushMSat lnwire.MilliSatoshi, chainHash *chainhash.Hash,
|
||||
flags lnwire.FundingFlag, commitType CommitmentType,
|
||||
fundingAssembler chanfunding.Assembler,
|
||||
pendingChanID [32]byte, thawHeight uint32) (*ChannelReservation, error) {
|
||||
wallet *LightningWallet, id uint64, chainHash *chainhash.Hash,
|
||||
thawHeight uint32, req *InitFundingReserveMsg) (*ChannelReservation,
|
||||
error) {
|
||||
|
||||
var (
|
||||
ourBalance lnwire.MilliSatoshi
|
||||
@ -223,10 +220,10 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
// Based on the channel type, we determine the initial commit weight
|
||||
// and fee.
|
||||
commitWeight := int64(input.CommitWeight)
|
||||
if commitType.HasAnchors() {
|
||||
if req.CommitType.HasAnchors() {
|
||||
commitWeight = int64(input.AnchorCommitWeight)
|
||||
}
|
||||
commitFee := commitFeePerKw.FeeForWeight(commitWeight)
|
||||
commitFee := req.CommitFeePerKw.FeeForWeight(commitWeight)
|
||||
|
||||
localFundingMSat := lnwire.NewMSatFromSatoshis(localFundingAmt)
|
||||
// TODO(halseth): make method take remote funding amount directly
|
||||
@ -236,7 +233,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
// The total fee paid by the initiator will be the commitment fee in
|
||||
// addition to the two anchor outputs.
|
||||
feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
|
||||
if commitType.HasAnchors() {
|
||||
if req.CommitType.HasAnchors() {
|
||||
feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize)
|
||||
}
|
||||
|
||||
@ -247,8 +244,8 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
// no initial balance in the channel unless the remote party is pushing
|
||||
// some funds to us within the first commitment state.
|
||||
if localFundingAmt == 0 {
|
||||
ourBalance = pushMSat
|
||||
theirBalance = capacityMSat - feeMSat - pushMSat
|
||||
ourBalance = req.PushMSat
|
||||
theirBalance = capacityMSat - feeMSat - req.PushMSat
|
||||
initiator = false
|
||||
|
||||
// If the responder doesn't have enough funds to actually pay
|
||||
@ -268,14 +265,14 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
// we pay all the initial fees within the commitment
|
||||
// transaction. We also deduct our balance by the
|
||||
// amount pushed as part of the initial state.
|
||||
ourBalance = capacityMSat - feeMSat - pushMSat
|
||||
theirBalance = pushMSat
|
||||
ourBalance = capacityMSat - feeMSat - req.PushMSat
|
||||
theirBalance = req.PushMSat
|
||||
} else {
|
||||
// Otherwise, this is a dual funder workflow where both
|
||||
// slides split the amount funded and the commitment
|
||||
// fee.
|
||||
ourBalance = localFundingMSat - (feeMSat / 2)
|
||||
theirBalance = capacityMSat - localFundingMSat - (feeMSat / 2) + pushMSat
|
||||
theirBalance = capacityMSat - localFundingMSat - (feeMSat / 2) + req.PushMSat
|
||||
}
|
||||
|
||||
initiator = true
|
||||
@ -320,16 +317,16 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
// If either of the balances are zero at this point, or we have a
|
||||
// non-zero push amt (there's no pushing for dual funder), then this is
|
||||
// a single-funder channel.
|
||||
if ourBalance == 0 || theirBalance == 0 || pushMSat != 0 {
|
||||
if ourBalance == 0 || theirBalance == 0 || req.PushMSat != 0 {
|
||||
// Both the tweakless type and the anchor type is tweakless,
|
||||
// hence set the bit.
|
||||
if commitType.HasStaticRemoteKey() {
|
||||
if req.CommitType.HasStaticRemoteKey() {
|
||||
chanType |= channeldb.SingleFunderTweaklessBit
|
||||
} else {
|
||||
chanType |= channeldb.SingleFunderBit
|
||||
}
|
||||
|
||||
switch a := fundingAssembler.(type) {
|
||||
switch a := req.ChanFunder.(type) {
|
||||
// The first channels of a batch shouldn't publish the batch TX
|
||||
// to avoid problems if some of the funding flows can't be
|
||||
// completed. Only the last channel of a batch should publish.
|
||||
@ -358,14 +355,14 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
|
||||
// We are adding anchor outputs to our commitment. We only support this
|
||||
// in combination with zero-fee second-levels HTLCs.
|
||||
if commitType.HasAnchors() {
|
||||
if req.CommitType.HasAnchors() {
|
||||
chanType |= channeldb.AnchorOutputsBit
|
||||
chanType |= channeldb.ZeroHtlcTxFeeBit
|
||||
}
|
||||
|
||||
// Set the appropriate LeaseExpiration/Frozen bit based on the
|
||||
// reservation parameters.
|
||||
if commitType == CommitmentTypeScriptEnforcedLease {
|
||||
if req.CommitType == CommitmentTypeScriptEnforcedLease {
|
||||
if thawHeight == 0 {
|
||||
return nil, errors.New("missing absolute expiration " +
|
||||
"for script enforced lease commitment type")
|
||||
@ -375,6 +372,18 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
chanType |= channeldb.FrozenBit
|
||||
}
|
||||
|
||||
if req.ZeroConf {
|
||||
chanType |= channeldb.ZeroConfBit
|
||||
}
|
||||
|
||||
if req.OptionScidAlias {
|
||||
chanType |= channeldb.ScidAliasChanBit
|
||||
}
|
||||
|
||||
if req.ScidAliasFeature {
|
||||
chanType |= channeldb.ScidAliasFeatureBit
|
||||
}
|
||||
|
||||
return &ChannelReservation{
|
||||
ourContribution: &ChannelContribution{
|
||||
FundingAmount: ourBalance.ToSatoshis(),
|
||||
@ -389,18 +398,18 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
ChainHash: *chainHash,
|
||||
IsPending: true,
|
||||
IsInitiator: initiator,
|
||||
ChannelFlags: flags,
|
||||
ChannelFlags: req.Flags,
|
||||
Capacity: capacity,
|
||||
LocalCommitment: channeldb.ChannelCommitment{
|
||||
LocalBalance: ourBalance,
|
||||
RemoteBalance: theirBalance,
|
||||
FeePerKw: btcutil.Amount(commitFeePerKw),
|
||||
FeePerKw: btcutil.Amount(req.CommitFeePerKw),
|
||||
CommitFee: commitFee,
|
||||
},
|
||||
RemoteCommitment: channeldb.ChannelCommitment{
|
||||
LocalBalance: ourBalance,
|
||||
RemoteBalance: theirBalance,
|
||||
FeePerKw: btcutil.Amount(commitFeePerKw),
|
||||
FeePerKw: btcutil.Amount(req.CommitFeePerKw),
|
||||
CommitFee: commitFee,
|
||||
},
|
||||
ThawHeight: thawHeight,
|
||||
@ -408,14 +417,22 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
InitialLocalBalance: ourBalance,
|
||||
InitialRemoteBalance: theirBalance,
|
||||
},
|
||||
pushMSat: pushMSat,
|
||||
pendingChanID: pendingChanID,
|
||||
pushMSat: req.PushMSat,
|
||||
pendingChanID: req.PendingChanID,
|
||||
reservationID: id,
|
||||
wallet: wallet,
|
||||
chanFunder: fundingAssembler,
|
||||
chanFunder: req.ChanFunder,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AddAlias stores the first alias for zero-conf channels.
|
||||
func (r *ChannelReservation) AddAlias(scid lnwire.ShortChannelID) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
r.partialState.ShortChannelID = scid
|
||||
}
|
||||
|
||||
// SetNumConfsRequired sets the number of confirmations that are required for
|
||||
// the ultimate funding transaction before the channel can be considered open.
|
||||
// This is distinct from the main reservation workflow as it allows
|
||||
@ -428,6 +445,15 @@ func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) {
|
||||
r.partialState.NumConfsRequired = numConfs
|
||||
}
|
||||
|
||||
// IsZeroConf returns if the reservation's underlying partial channel state is
|
||||
// a zero-conf channel.
|
||||
func (r *ChannelReservation) IsZeroConf() bool {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
|
||||
return r.partialState.IsZeroConf()
|
||||
}
|
||||
|
||||
// CommitConstraints takes the constraints that the remote party specifies for
|
||||
// the type of commitments that we can generate for them. These constraints
|
||||
// include several parameters that serve as flow control restricting the amount
|
||||
|
@ -728,11 +728,17 @@ func testCancelNonExistentReservation(miner *rpctest.Harness,
|
||||
feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
|
||||
require.NoError(t, err, "unable to query fee estimator")
|
||||
|
||||
req := &lnwallet.InitFundingReserveMsg{
|
||||
CommitFeePerKw: feePerKw,
|
||||
PushMSat: 10,
|
||||
Flags: lnwire.FFAnnounceChannel,
|
||||
CommitType: lnwallet.CommitmentTypeTweakless,
|
||||
PendingChanID: [32]byte{},
|
||||
}
|
||||
|
||||
// Create our own reservation, give it some ID.
|
||||
res, err := lnwallet.NewChannelReservation(
|
||||
10000, 10000, feePerKw, alice, 22, 10, &testHdSeed,
|
||||
lnwire.FFAnnounceChannel, lnwallet.CommitmentTypeTweakless,
|
||||
nil, [32]byte{}, 0,
|
||||
10000, 10000, alice, 22, &testHdSeed, 0, req,
|
||||
)
|
||||
require.NoError(t, err, "unable to create res")
|
||||
|
||||
|
@ -153,6 +153,18 @@ type InitFundingReserveMsg struct {
|
||||
// used.
|
||||
ChanFunder chanfunding.Assembler
|
||||
|
||||
// ZeroConf is a boolean that is true if a zero-conf channel was
|
||||
// negotiated.
|
||||
ZeroConf bool
|
||||
|
||||
// OptionScidAlias is a boolean that is true if an option-scid-alias
|
||||
// channel type was explicitly negotiated.
|
||||
OptionScidAlias bool
|
||||
|
||||
// ScidAliasFeature is true if the option-scid-alias feature bit was
|
||||
// negotiated.
|
||||
ScidAliasFeature bool
|
||||
|
||||
// err is a channel in which all errors will be sent across. Will be
|
||||
// nil if this initial set is successful.
|
||||
//
|
||||
@ -845,10 +857,8 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
||||
|
||||
id := atomic.AddUint64(&l.nextFundingID, 1)
|
||||
reservation, err := NewChannelReservation(
|
||||
capacity, localFundingAmt, req.CommitFeePerKw, l, id,
|
||||
req.PushMSat, l.Cfg.NetParams.GenesisHash, req.Flags,
|
||||
req.CommitType, req.ChanFunder, req.PendingChanID,
|
||||
thawHeight,
|
||||
capacity, localFundingAmt, l, id, l.Cfg.NetParams.GenesisHash,
|
||||
thawHeight, req,
|
||||
)
|
||||
if err != nil {
|
||||
fundingIntent.Cancel()
|
||||
|
@ -155,6 +155,16 @@ const (
|
||||
// TODO: Decide on actual feature bit value.
|
||||
ExplicitChannelTypeOptional = 45
|
||||
|
||||
// ScidAliasRequired is a required feature bit that signals that the
|
||||
// node requires understanding of ShortChannelID aliases in the TLV
|
||||
// segment of the funding_locked message.
|
||||
ScidAliasRequired FeatureBit = 46
|
||||
|
||||
// ScidAliasOptional is an optional feature bit that signals that the
|
||||
// node understands ShortChannelID aliases in the TLV segment of the
|
||||
// funding_locked message.
|
||||
ScidAliasOptional FeatureBit = 47
|
||||
|
||||
// PaymentMetadataRequired is a required bit that denotes that if an
|
||||
// invoice contains metadata, it must be passed along with the payment
|
||||
// htlc(s).
|
||||
@ -165,6 +175,14 @@ const (
|
||||
// htlc(s).
|
||||
PaymentMetadataOptional = 49
|
||||
|
||||
// ZeroConfRequired is a required feature bit that signals that the
|
||||
// node requires understanding of the zero-conf channel_type.
|
||||
ZeroConfRequired FeatureBit = 50
|
||||
|
||||
// ZeroConfOptional is an optional feature bit that signals that the
|
||||
// node understands the zero-conf channel type.
|
||||
ZeroConfOptional FeatureBit = 51
|
||||
|
||||
// KeysendRequired is a required bit that indicates that the node is
|
||||
// able and willing to accept keysend payments.
|
||||
KeysendRequired = 54
|
||||
@ -173,7 +191,7 @@ const (
|
||||
// able and willing to accept keysend payments.
|
||||
KeysendOptional = 55
|
||||
|
||||
// ScriptEnforcedLeaseRequired is a required feature bit that signals
|
||||
// ScriptEnforcedLeaseOptional is an optional feature bit that signals
|
||||
// that the node requires channels having zero-fee second-level HTLC
|
||||
// transactions, which also imply anchor commitments, along with an
|
||||
// additional CLTV constraint of a channel lease's expiration height
|
||||
@ -244,6 +262,10 @@ var Features = map[FeatureBit]string{
|
||||
KeysendRequired: "keysend",
|
||||
ScriptEnforcedLeaseRequired: "script-enforced-lease",
|
||||
ScriptEnforcedLeaseOptional: "script-enforced-lease",
|
||||
ScidAliasRequired: "scid-alias",
|
||||
ScidAliasOptional: "scid-alias",
|
||||
ZeroConfRequired: "zero-conf",
|
||||
ZeroConfOptional: "zero-conf",
|
||||
}
|
||||
|
||||
// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
// FundingLocked is the message that both parties to a new channel creation
|
||||
@ -21,6 +22,11 @@ type FundingLocked struct {
|
||||
// next commitment transaction for the channel.
|
||||
NextPerCommitmentPoint *btcec.PublicKey
|
||||
|
||||
// AliasScid is an alias ShortChannelID used to refer to the underlying
|
||||
// channel. It can be used instead of the confirmed on-chain
|
||||
// ShortChannelID for forwarding.
|
||||
AliasScid *ShortChannelID
|
||||
|
||||
// ExtraData is the set of data that was appended to this message to
|
||||
// fill out the full maximum transport message size. These fields can
|
||||
// be used to specify optional data such as custom TLV fields.
|
||||
@ -47,11 +53,31 @@ var _ Message = (*FundingLocked)(nil)
|
||||
//
|
||||
// This is part of the lnwire.Message interface.
|
||||
func (c *FundingLocked) Decode(r io.Reader, pver uint32) error {
|
||||
return ReadElements(r,
|
||||
// Read all the mandatory fields in the message.
|
||||
err := ReadElements(r,
|
||||
&c.ChanID,
|
||||
&c.NextPerCommitmentPoint,
|
||||
&c.ExtraData,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Next we'll parse out the set of known records. For now, this is just
|
||||
// the AliasScidRecordType.
|
||||
var aliasScid ShortChannelID
|
||||
typeMap, err := c.ExtraData.ExtractRecords(&aliasScid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We'll only set AliasScid if the corresponding TLV type was included
|
||||
// in the stream.
|
||||
if val, ok := typeMap[AliasScidRecordType]; ok && val == nil {
|
||||
c.AliasScid = &aliasScid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode serializes the target FundingLocked message into the passed io.Writer
|
||||
@ -68,6 +94,15 @@ func (c *FundingLocked) Encode(w *bytes.Buffer, pver uint32) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// We'll only encode the AliasScid in a TLV segment if it exists.
|
||||
if c.AliasScid != nil {
|
||||
recordProducers := []tlv.RecordProducer{c.AliasScid}
|
||||
err := EncodeMessageExtraData(&c.ExtraData, recordProducers...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return WriteBytes(w, c.ExtraData)
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,15 @@ package lnwire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
const (
|
||||
// AliasScidRecordType is the type of the experimental record to denote
|
||||
// the alias being used in an option_scid_alias channel.
|
||||
AliasScidRecordType tlv.Type = 1
|
||||
)
|
||||
|
||||
// ShortChannelID represents the set of data which is needed to retrieve all
|
||||
@ -46,3 +55,38 @@ func (c ShortChannelID) ToUint64() uint64 {
|
||||
func (c ShortChannelID) String() string {
|
||||
return fmt.Sprintf("%d:%d:%d", c.BlockHeight, c.TxIndex, c.TxPosition)
|
||||
}
|
||||
|
||||
// Record returns a TLV record that can be used to encode/decode a
|
||||
// ShortChannelID to/from a TLV stream.
|
||||
func (c *ShortChannelID) Record() tlv.Record {
|
||||
return tlv.MakeStaticRecord(
|
||||
AliasScidRecordType, c, 8, EShortChannelID, DShortChannelID,
|
||||
)
|
||||
}
|
||||
|
||||
// EShortChannelID is an encoder for ShortChannelID. It is exported so other
|
||||
// packages can use the encoding scheme.
|
||||
func EShortChannelID(w io.Writer, val interface{}, buf *[8]byte) error {
|
||||
if v, ok := val.(*ShortChannelID); ok {
|
||||
return tlv.EUint64T(w, v.ToUint64(), buf)
|
||||
}
|
||||
return tlv.NewTypeForEncodingErr(val, "lnwire.ShortChannelID")
|
||||
}
|
||||
|
||||
// DShortChannelID is a decoder for ShortChannelID. It is exported so other
|
||||
// packages can use the decoding scheme.
|
||||
func DShortChannelID(r io.Reader, val interface{}, buf *[8]byte,
|
||||
l uint64) error {
|
||||
|
||||
if v, ok := val.(*ShortChannelID); ok {
|
||||
var scid uint64
|
||||
err := tlv.DUint64(r, &scid, buf, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*v = NewShortChanIDFromInt(scid)
|
||||
return nil
|
||||
}
|
||||
return tlv.NewTypeForDecodingErr(val, "lnwire.ShortChannelID", l, 8)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestShortChannelIDEncoding(t *testing.T) {
|
||||
@ -39,3 +40,25 @@ func TestShortChannelIDEncoding(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestScidTypeEncodeDecode tests that we're able to properly encode and decode
|
||||
// ShortChannelID within TLV streams.
|
||||
func TestScidTypeEncodeDecode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliasScid := ShortChannelID{
|
||||
BlockHeight: (1 << 24) - 1,
|
||||
TxIndex: (1 << 24) - 1,
|
||||
TxPosition: (1 << 16) - 1,
|
||||
}
|
||||
|
||||
var extraData ExtraOpaqueData
|
||||
require.NoError(t, extraData.PackRecords(&aliasScid))
|
||||
|
||||
var aliasScid2 ShortChannelID
|
||||
tlvs, err := extraData.ExtractRecords(&aliasScid2)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, tlvs, AliasScidRecordType)
|
||||
require.Equal(t, aliasScid, aliasScid2)
|
||||
}
|
||||
|
@ -60,7 +60,8 @@ type ChanStatusConfig struct {
|
||||
// ApplyChannelUpdate processes new ChannelUpdates signed by our node by
|
||||
// updating our local routing table and broadcasting the update to our
|
||||
// peers.
|
||||
ApplyChannelUpdate func(*lnwire.ChannelUpdate) error
|
||||
ApplyChannelUpdate func(*lnwire.ChannelUpdate, *wire.OutPoint,
|
||||
bool) error
|
||||
|
||||
// DB stores the set of channels that are to be monitored.
|
||||
DB DB
|
||||
@ -621,7 +622,7 @@ func (m *ChanStatusManager) signAndSendNextUpdate(outpoint wire.OutPoint,
|
||||
|
||||
// Retrieve the latest update for this channel. We'll use this
|
||||
// as our starting point to send the new update.
|
||||
chanUpdate, err := m.fetchLastChanUpdateByOutPoint(outpoint)
|
||||
chanUpdate, private, err := m.fetchLastChanUpdateByOutPoint(outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -634,22 +635,26 @@ func (m *ChanStatusManager) signAndSendNextUpdate(outpoint wire.OutPoint,
|
||||
return err
|
||||
}
|
||||
|
||||
return m.cfg.ApplyChannelUpdate(chanUpdate)
|
||||
return m.cfg.ApplyChannelUpdate(chanUpdate, &outpoint, private)
|
||||
}
|
||||
|
||||
// fetchLastChanUpdateByOutPoint fetches the latest policy for our direction of
|
||||
// a channel, and crafts a new ChannelUpdate with this policy. Returns an error
|
||||
// in case our ChannelEdgePolicy is not found in the database.
|
||||
// in case our ChannelEdgePolicy is not found in the database. Also returns if
|
||||
// the channel is private by checking AuthProof for nil.
|
||||
func (m *ChanStatusManager) fetchLastChanUpdateByOutPoint(op wire.OutPoint) (
|
||||
*lnwire.ChannelUpdate, error) {
|
||||
*lnwire.ChannelUpdate, bool, error) {
|
||||
|
||||
// Get the edge info and policies for this channel from the graph.
|
||||
info, edge1, edge2, err := m.cfg.Graph.FetchChannelEdgesByOutpoint(&op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return ExtractChannelUpdate(m.ourPubKeyBytes, info, edge1, edge2)
|
||||
update, err := ExtractChannelUpdate(
|
||||
m.ourPubKeyBytes, info, edge1, edge2,
|
||||
)
|
||||
return update, info.AuthProof == nil, err
|
||||
}
|
||||
|
||||
// loadInitialChanState determines the initial ChannelState for a particular
|
||||
@ -660,7 +665,7 @@ func (m *ChanStatusManager) fetchLastChanUpdateByOutPoint(op wire.OutPoint) (
|
||||
func (m *ChanStatusManager) loadInitialChanState(
|
||||
outpoint *wire.OutPoint) (ChannelState, error) {
|
||||
|
||||
lastUpdate, err := m.fetchLastChanUpdateByOutPoint(*outpoint)
|
||||
lastUpdate, _, err := m.fetchLastChanUpdateByOutPoint(*outpoint)
|
||||
if err != nil {
|
||||
return ChannelState{}, err
|
||||
}
|
||||
|
@ -176,7 +176,9 @@ func (g *mockGraph) FetchChannelEdgesByOutpoint(
|
||||
return info, pol1, pol2, nil
|
||||
}
|
||||
|
||||
func (g *mockGraph) ApplyChannelUpdate(update *lnwire.ChannelUpdate) error {
|
||||
func (g *mockGraph) ApplyChannelUpdate(update *lnwire.ChannelUpdate,
|
||||
op *wire.OutPoint, private bool) error {
|
||||
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
|
@ -330,6 +330,18 @@ type Config struct {
|
||||
// from the peer.
|
||||
HandleCustomMessage func(peer [33]byte, msg *lnwire.Custom) error
|
||||
|
||||
// GetAliases is passed to created links so the Switch and link can be
|
||||
// aware of the channel's aliases.
|
||||
GetAliases func(base lnwire.ShortChannelID) []lnwire.ShortChannelID
|
||||
|
||||
// RequestAlias allows the Brontide struct to request an alias to send
|
||||
// to the peer.
|
||||
RequestAlias func() (lnwire.ShortChannelID, error)
|
||||
|
||||
// AddLocalAlias persists an alias to an underlying alias store.
|
||||
AddLocalAlias func(alias, base lnwire.ShortChannelID,
|
||||
gossip bool) error
|
||||
|
||||
// PongBuf is a slice we'll reuse instead of allocating memory on the
|
||||
// heap. Since only reads will occur and no writes, there is no need
|
||||
// for any synchronization primitives. As a result, it's safe to share
|
||||
@ -666,7 +678,65 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
|
||||
// cannot be loaded normally.
|
||||
var msgs []lnwire.Message
|
||||
|
||||
scidAliasNegotiated := p.hasNegotiatedScidAlias()
|
||||
|
||||
for _, dbChan := range chans {
|
||||
hasScidFeature := dbChan.ChanType.HasScidAliasFeature()
|
||||
if scidAliasNegotiated && !hasScidFeature {
|
||||
// We'll request and store an alias, making sure that a
|
||||
// gossiper mapping is not created for the alias to the
|
||||
// real SCID. This is done because the peer and funding
|
||||
// manager are not aware of each other's states and if
|
||||
// we did not do this, we would accept alias channel
|
||||
// updates after 6 confirmations, which would be buggy.
|
||||
// We'll queue a funding_locked message with the new
|
||||
// alias. This should technically be done *after* the
|
||||
// reestablish, but this behavior is pre-existing since
|
||||
// the funding manager may already queue a
|
||||
// funding_locked before the channel_reestablish.
|
||||
if !dbChan.IsPending {
|
||||
aliasScid, err := p.cfg.RequestAlias()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = p.cfg.AddLocalAlias(
|
||||
aliasScid, dbChan.ShortChanID(), false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chanID := lnwire.NewChanIDFromOutPoint(
|
||||
&dbChan.FundingOutpoint,
|
||||
)
|
||||
|
||||
// Fetch the second commitment point to send in
|
||||
// the funding_locked message.
|
||||
second, err := dbChan.SecondCommitmentPoint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fundingLockedMsg := lnwire.NewFundingLocked(
|
||||
chanID, second,
|
||||
)
|
||||
fundingLockedMsg.AliasScid = &aliasScid
|
||||
|
||||
msgs = append(msgs, fundingLockedMsg)
|
||||
}
|
||||
|
||||
// If we've negotiated the option-scid-alias feature
|
||||
// and this channel does not have ScidAliasFeature set
|
||||
// to true due to an upgrade where the feature bit was
|
||||
// turned on, we'll update the channel's database
|
||||
// state.
|
||||
err := dbChan.MarkScidAliasNegotiated()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
lnChan, err := lnwallet.NewLightningChannel(
|
||||
p.cfg.Signer, dbChan, p.cfg.SigPool,
|
||||
)
|
||||
@ -900,6 +970,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
|
||||
NotifyActiveChannel: p.cfg.ChannelNotifier.NotifyActiveChannelEvent,
|
||||
NotifyInactiveChannel: p.cfg.ChannelNotifier.NotifyInactiveChannelEvent,
|
||||
HtlcNotifier: p.cfg.HtlcNotifier,
|
||||
GetAliases: p.cfg.GetAliases,
|
||||
}
|
||||
|
||||
// Before adding our new link, purge the switch of any pending or live
|
||||
@ -3054,6 +3125,14 @@ func (p *Brontide) RemoteFeatures() *lnwire.FeatureVector {
|
||||
return p.remoteFeatures
|
||||
}
|
||||
|
||||
// hasNegotiatedScidAlias returns true if we've negotiated the
|
||||
// option-scid-alias feature bit with the peer.
|
||||
func (p *Brontide) hasNegotiatedScidAlias() bool {
|
||||
peerHas := p.remoteFeatures.HasFeature(lnwire.ScidAliasOptional)
|
||||
localHas := p.cfg.Features.HasFeature(lnwire.ScidAliasOptional)
|
||||
return peerHas && localHas
|
||||
}
|
||||
|
||||
// sendInitMsg sends the Init message to the remote peer. This message contains
|
||||
// our currently supported local and global features.
|
||||
func (p *Brontide) sendInitMsg(legacyChan bool) error {
|
||||
|
@ -340,7 +340,11 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
|
||||
OurPubKey: aliceKeyPub,
|
||||
OurKeyLoc: testKeyLoc,
|
||||
IsChannelActive: func(lnwire.ChannelID) bool { return true },
|
||||
ApplyChannelUpdate: func(*lnwire.ChannelUpdate) error { return nil },
|
||||
ApplyChannelUpdate: func(*lnwire.ChannelUpdate,
|
||||
*wire.OutPoint, bool) error {
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
|
@ -473,6 +473,9 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase,
|
||||
return next, nil
|
||||
},
|
||||
Clock: clock.NewTestClock(time.Unix(1, 0)),
|
||||
IsAlias: func(scid lnwire.ShortChannelID) bool {
|
||||
return false
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create router %v", err)
|
||||
|
@ -358,6 +358,10 @@ type Config struct {
|
||||
// Otherwise, we'll only prune the channel when both edges have a very
|
||||
// dated last update.
|
||||
StrictZombiePruning bool
|
||||
|
||||
// IsAlias returns whether a passed ShortChannelID is an alias. This is
|
||||
// only used for our local channels.
|
||||
IsAlias func(scid lnwire.ShortChannelID) bool
|
||||
}
|
||||
|
||||
// EdgeLocator is a struct used to identify a specific edge.
|
||||
@ -971,7 +975,9 @@ func (r *ChannelRouter) pruneZombieChans() error {
|
||||
toPrune = append(toPrune, chanID)
|
||||
log.Tracef("Pruning zombie channel with ChannelID(%v)", chanID)
|
||||
}
|
||||
err = r.cfg.Graph.DeleteChannelEdges(r.cfg.StrictZombiePruning, toPrune...)
|
||||
err = r.cfg.Graph.DeleteChannelEdges(
|
||||
r.cfg.StrictZombiePruning, true, toPrune...,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to delete zombie channels: %v", err)
|
||||
}
|
||||
@ -1455,8 +1461,12 @@ func (r *ChannelRouter) processUpdate(msg interface{},
|
||||
// If AssumeChannelValid is present, then we are unable to
|
||||
// perform any of the expensive checks below, so we'll
|
||||
// short-circuit our path straight to adding the edge to our
|
||||
// graph.
|
||||
if r.cfg.AssumeChannelValid {
|
||||
// graph. If the passed ShortChannelID is an alias, then we'll
|
||||
// skip validation as it will not map to a legitimate tx. This
|
||||
// is not a DoS vector as only we can add an alias
|
||||
// ChannelAnnouncement from the gossiper.
|
||||
scid := lnwire.NewShortChanIDFromInt(msg.ChannelID)
|
||||
if r.cfg.AssumeChannelValid || r.cfg.IsAlias(scid) {
|
||||
if err := r.cfg.Graph.AddChannelEdge(msg, op...); err != nil {
|
||||
return fmt.Errorf("unable to add edge: %v", err)
|
||||
}
|
||||
|
@ -82,6 +82,9 @@ func (c *testCtx) RestartRouter(t *testing.T) {
|
||||
Control: makeMockControlTower(),
|
||||
ChannelPruneExpiry: time.Hour * 24,
|
||||
GraphPruneInterval: time.Hour * 2,
|
||||
IsAlias: func(scid lnwire.ShortChannelID) bool {
|
||||
return false
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "unable to create router")
|
||||
require.NoError(t, router.Start(), "unable to start router")
|
||||
@ -165,6 +168,9 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T,
|
||||
Clock: clock.NewTestClock(time.Unix(1, 0)),
|
||||
AssumeChannelValid: assumeValid,
|
||||
StrictZombiePruning: strictPruning,
|
||||
IsAlias: func(scid lnwire.ShortChannelID) bool {
|
||||
return false
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "unable to create router")
|
||||
require.NoError(t, router.Start(), "unable to start router")
|
||||
@ -1738,6 +1744,10 @@ func TestWakeUpOnStaleBranch(t *testing.T) {
|
||||
|
||||
// We'll set the delay to zero to prune immediately.
|
||||
FirstTimePruneDelay: 0,
|
||||
|
||||
IsAlias: func(scid lnwire.ShortChannelID) bool {
|
||||
return false
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create router %v", err)
|
||||
@ -3485,6 +3495,10 @@ func TestSendMPPaymentSucceed(t *testing.T) {
|
||||
next := atomic.AddUint64(&uniquePaymentID, 1)
|
||||
return next, nil
|
||||
},
|
||||
|
||||
IsAlias: func(scid lnwire.ShortChannelID) bool {
|
||||
return false
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "failed to create router")
|
||||
|
||||
@ -3648,6 +3662,10 @@ func TestSendMPPaymentSucceedOnExtraShards(t *testing.T) {
|
||||
next := atomic.AddUint64(&uniquePaymentID, 1)
|
||||
return next, nil
|
||||
},
|
||||
|
||||
IsAlias: func(scid lnwire.ShortChannelID) bool {
|
||||
return false
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "failed to create router")
|
||||
|
||||
@ -3856,6 +3874,10 @@ func TestSendMPPaymentFailed(t *testing.T) {
|
||||
next := atomic.AddUint64(&uniquePaymentID, 1)
|
||||
return next, nil
|
||||
},
|
||||
|
||||
IsAlias: func(scid lnwire.ShortChannelID) bool {
|
||||
return false
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "failed to create router")
|
||||
|
||||
@ -4056,6 +4078,10 @@ func TestSendMPPaymentFailedWithShardsInFlight(t *testing.T) {
|
||||
next := atomic.AddUint64(&uniquePaymentID, 1)
|
||||
return next, nil
|
||||
},
|
||||
|
||||
IsAlias: func(scid lnwire.ShortChannelID) bool {
|
||||
return false
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "failed to create router")
|
||||
|
||||
|
38
rpcserver.go
38
rpcserver.go
@ -756,6 +756,7 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service,
|
||||
r.cfg.net.ResolveTCPAddr, genInvoiceFeatures,
|
||||
genAmpInvoiceFeatures, getNodeAnnouncement,
|
||||
s.updateAndBrodcastSelfNode, parseAddr, rpcsLog,
|
||||
s.aliasMgr.GetPeerAlias,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -2012,7 +2013,9 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
|
||||
var channelType *lnwire.ChannelType
|
||||
switch in.CommitmentType {
|
||||
case lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE:
|
||||
break
|
||||
if in.ZeroConf {
|
||||
return nil, fmt.Errorf("use anchors for zero-conf")
|
||||
}
|
||||
|
||||
case lnrpc.CommitmentType_LEGACY:
|
||||
channelType = new(lnwire.ChannelType)
|
||||
@ -2026,18 +2029,38 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
|
||||
|
||||
case lnrpc.CommitmentType_ANCHORS:
|
||||
channelType = new(lnwire.ChannelType)
|
||||
*channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector(
|
||||
fv := lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
))
|
||||
)
|
||||
|
||||
if in.ZeroConf {
|
||||
fv.Set(lnwire.ZeroConfRequired)
|
||||
}
|
||||
|
||||
if in.ScidAlias {
|
||||
fv.Set(lnwire.ScidAliasRequired)
|
||||
}
|
||||
|
||||
*channelType = lnwire.ChannelType(*fv)
|
||||
|
||||
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
||||
channelType = new(lnwire.ChannelType)
|
||||
*channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector(
|
||||
fv := lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.ScriptEnforcedLeaseRequired,
|
||||
))
|
||||
)
|
||||
|
||||
if in.ZeroConf {
|
||||
fv.Set(lnwire.ZeroConfRequired)
|
||||
}
|
||||
|
||||
if in.ScidAlias {
|
||||
fv.Set(lnwire.ScidAliasRequired)
|
||||
}
|
||||
|
||||
*channelType = lnwire.ChannelType(*fv)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled request channel type %v",
|
||||
@ -2576,7 +2599,7 @@ func abandonChanFromGraph(chanGraph *channeldb.ChannelGraph,
|
||||
|
||||
// If the channel ID is still in the graph, then that means the channel
|
||||
// is still open, so we'll now move to purge it from the graph.
|
||||
return chanGraph.DeleteChannelEdges(false, chanID)
|
||||
return chanGraph.DeleteChannelEdges(false, true, chanID)
|
||||
}
|
||||
|
||||
// abandonChan removes a channel from the database, graph and contract court.
|
||||
@ -3039,7 +3062,7 @@ func (r *rpcServer) SubscribePeerEvents(req *lnrpc.PeerEventSubscription,
|
||||
// confirmed unspent outputs and all unconfirmed unspent outputs under control
|
||||
// by the wallet. This method can be modified by having the request specify
|
||||
// only witness outputs should be factored into the final output sum.
|
||||
// TODO(roasbeef): add async hooks into wallet balance changes
|
||||
// TODO(roasbeef): add async hooks into wallet balance changes.
|
||||
func (r *rpcServer) WalletBalance(ctx context.Context,
|
||||
in *lnrpc.WalletBalanceRequest) (*lnrpc.WalletBalanceResponse, error) {
|
||||
|
||||
@ -5260,6 +5283,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
|
||||
GenAmpInvoiceFeatures: func() *lnwire.FeatureVector {
|
||||
return r.server.featureMgr.Get(feature.SetInvoiceAmp)
|
||||
},
|
||||
GetAlias: r.server.aliasMgr.GetPeerAlias,
|
||||
}
|
||||
|
||||
value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMsat)
|
||||
|
@ -1182,6 +1182,14 @@ litecoin.node=ltcd
|
||||
; channel type if it is enabled.
|
||||
; protocol.no-script-enforced-lease=true
|
||||
|
||||
; Set to enable support for option_scid_alias channels, which can be referred
|
||||
; to by an alias instead of the confirmed ShortChannelID. Additionally, is
|
||||
; needed to open zero-conf channels.
|
||||
; protocol.option-scid-alias=true
|
||||
|
||||
; Set to enable support for zero-conf channels. This requires the
|
||||
; option-scid-alias flag to also be set.
|
||||
; protocol.zero-conf=true
|
||||
|
||||
[db]
|
||||
|
||||
|
132
server.go
132
server.go
@ -16,6 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/connmgr"
|
||||
@ -23,6 +24,7 @@ import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/go-errors/errors"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/aliasmgr"
|
||||
"github.com/lightningnetwork/lnd/autopilot"
|
||||
"github.com/lightningnetwork/lnd/brontide"
|
||||
"github.com/lightningnetwork/lnd/cert"
|
||||
@ -243,6 +245,8 @@ type server struct {
|
||||
// channel DB that haven't been separated out yet.
|
||||
miscDB *channeldb.DB
|
||||
|
||||
aliasMgr *aliasmgr.Manager
|
||||
|
||||
htlcSwitch *htlcswitch.Switch
|
||||
|
||||
interceptableSwitch *htlcswitch.InterceptableSwitch
|
||||
@ -530,6 +534,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
NoWumbo: !cfg.ProtocolOptions.Wumbo(),
|
||||
NoScriptEnforcementLease: cfg.ProtocolOptions.NoScriptEnforcementLease(),
|
||||
NoKeysend: !cfg.AcceptKeySend,
|
||||
NoOptionScidAlias: !cfg.ProtocolOptions.ScidAlias(),
|
||||
NoZeroConf: !cfg.ProtocolOptions.ZeroConf(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -614,6 +620,11 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
thresholdSats := btcutil.Amount(cfg.DustThreshold)
|
||||
thresholdMSats := lnwire.NewMSatFromSatoshis(thresholdSats)
|
||||
|
||||
s.aliasMgr, err = aliasmgr.NewManager(dbs.ChanStateDB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.htlcSwitch, err = htlcswitch.New(htlcswitch.Config{
|
||||
DB: dbs.ChanStateDB,
|
||||
FetchAllOpenChannels: s.chanStateDB.FetchAllOpenChannels,
|
||||
@ -646,6 +657,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
Clock: clock.NewDefaultClock(),
|
||||
HTLCExpiry: htlcswitch.DefaultHTLCExpiry,
|
||||
DustThreshold: thresholdMSats,
|
||||
SignAliasUpdate: s.signAliasUpdate,
|
||||
IsAlias: aliasmgr.IsAlias,
|
||||
}, uint32(currentHeight))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -909,6 +922,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
PathFindingConfig: pathFindingConfig,
|
||||
Clock: clock.NewDefaultClock(),
|
||||
StrictZombiePruning: strictPruning,
|
||||
IsAlias: aliasmgr.IsAlias,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create router: %v", err)
|
||||
@ -951,6 +965,10 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
PinnedSyncers: cfg.Gossip.PinnedSyncers,
|
||||
MaxChannelUpdateBurst: cfg.Gossip.MaxChannelUpdateBurst,
|
||||
ChannelUpdateInterval: cfg.Gossip.ChannelUpdateInterval,
|
||||
IsAlias: aliasmgr.IsAlias,
|
||||
SignAliasUpdate: s.signAliasUpdate,
|
||||
FindBaseByAlias: s.aliasMgr.FindBaseSCID,
|
||||
GetAlias: s.aliasMgr.GetPeerAlias,
|
||||
}, nodeKeyDesc)
|
||||
|
||||
s.localChanMgr = &localchans.Manager{
|
||||
@ -1155,6 +1173,46 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wrap the DeleteChannelEdges method so that the funding manager can
|
||||
// use it without depending on several layers of indirection.
|
||||
deleteAliasEdge := func(scid lnwire.ShortChannelID) (
|
||||
*channeldb.ChannelEdgePolicy, error) {
|
||||
|
||||
info, e1, e2, err := s.graphDB.FetchChannelEdgesByID(
|
||||
scid.ToUint64(),
|
||||
)
|
||||
if err == channeldb.ErrEdgeNotFound {
|
||||
// This is unlikely but there is a slim chance of this
|
||||
// being hit if lnd was killed via SIGKILL and the
|
||||
// funding manager was stepping through the delete
|
||||
// alias edge logic.
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Grab our key to find our policy.
|
||||
var ourKey [33]byte
|
||||
copy(ourKey[:], nodeKeyDesc.PubKey.SerializeCompressed())
|
||||
|
||||
var ourPolicy *channeldb.ChannelEdgePolicy
|
||||
if info != nil && info.NodeKey1Bytes == ourKey {
|
||||
ourPolicy = e1
|
||||
} else {
|
||||
ourPolicy = e2
|
||||
}
|
||||
|
||||
if ourPolicy == nil {
|
||||
// Something is wrong, so return an error.
|
||||
return nil, fmt.Errorf("we don't have an edge")
|
||||
}
|
||||
|
||||
err = s.graphDB.DeleteChannelEdges(
|
||||
false, false, scid.ToUint64(),
|
||||
)
|
||||
return ourPolicy, err
|
||||
}
|
||||
|
||||
s.fundingMgr, err = funding.NewFundingManager(funding.Config{
|
||||
NoWumboChans: !cfg.ProtocolOptions.Wumbo(),
|
||||
IDKey: nodeKeyDesc.PubKey,
|
||||
@ -1173,15 +1231,16 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
SendAnnouncement: s.authGossiper.ProcessLocalAnnouncement,
|
||||
NotifyWhenOnline: s.NotifyWhenOnline,
|
||||
TempChanIDSeed: chanIDSeed,
|
||||
FindChannel: func(chanID lnwire.ChannelID) (
|
||||
*channeldb.OpenChannel, error) {
|
||||
FindChannel: func(node *btcec.PublicKey,
|
||||
chanID lnwire.ChannelID) (*channeldb.OpenChannel,
|
||||
error) {
|
||||
|
||||
dbChannels, err := s.chanStateDB.FetchAllChannels()
|
||||
nodeChans, err := s.chanStateDB.FetchOpenChannels(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, channel := range dbChannels {
|
||||
for _, channel := range nodeChans {
|
||||
if chanID.IsChanPoint(&channel.FundingOutpoint) {
|
||||
return channel, nil
|
||||
}
|
||||
@ -1339,6 +1398,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
RegisteredChains: cfg.registeredChains,
|
||||
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(
|
||||
s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(),
|
||||
DeleteAliasEdge: deleteAliasEdge,
|
||||
AliasManager: s.aliasMgr,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1495,6 +1556,20 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// signAliasUpdate takes a ChannelUpdate and returns the signature. This is
|
||||
// used for option_scid_alias channels where the ChannelUpdate to be sent back
|
||||
// may differ from what is on disk.
|
||||
func (s *server) signAliasUpdate(u *lnwire.ChannelUpdate) (*ecdsa.Signature,
|
||||
error) {
|
||||
|
||||
data, err := u.DataToSign()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.cc.MsgSigner.SignMessage(s.identityKeyLoc, data, true)
|
||||
}
|
||||
|
||||
// createLivenessMonitor creates a set of health checks using our configured
|
||||
// values and uses these checks to create a liveliness monitor. Available
|
||||
// health checks,
|
||||
@ -2063,7 +2138,9 @@ func (s *server) Stop() error {
|
||||
s.connMgr.Stop()
|
||||
|
||||
// Shutdown the wallet, funding manager, and the rpc server.
|
||||
s.chanStatusMgr.Stop()
|
||||
if err := s.chanStatusMgr.Stop(); err != nil {
|
||||
srvrLog.Warnf("failed to stop chanStatusMgr: %v", err)
|
||||
}
|
||||
if err := s.htlcSwitch.Stop(); err != nil {
|
||||
srvrLog.Warnf("failed to stop htlcSwitch: %v", err)
|
||||
}
|
||||
@ -3100,7 +3177,6 @@ func (s *server) NotifyWhenOnline(peerKey [33]byte,
|
||||
peerChan chan<- lnpeer.Peer) {
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// Compute the target peer's identifier.
|
||||
pubStr := string(peerKey[:])
|
||||
@ -3108,6 +3184,19 @@ func (s *server) NotifyWhenOnline(peerKey [33]byte,
|
||||
// Check if peer is connected.
|
||||
peer, ok := s.peersByPub[pubStr]
|
||||
if ok {
|
||||
// Unlock here so that the mutex isn't held while we are
|
||||
// waiting for the peer to become active.
|
||||
s.mu.Unlock()
|
||||
|
||||
// Wait until the peer signals that it is actually active
|
||||
// rather than only in the server's maps.
|
||||
select {
|
||||
case <-peer.ActiveSignal():
|
||||
case <-peer.QuitSignal():
|
||||
// The peer quit so we'll just return.
|
||||
return
|
||||
}
|
||||
|
||||
// Connected, can return early.
|
||||
srvrLog.Debugf("Notifying that peer %x is online", peerKey)
|
||||
|
||||
@ -3124,6 +3213,7 @@ func (s *server) NotifyWhenOnline(peerKey [33]byte,
|
||||
s.peerConnectedListeners[pubStr] = append(
|
||||
s.peerConnectedListeners[pubStr], peerChan,
|
||||
)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// NotifyWhenOffline delivers a notification to the caller of when the peer with
|
||||
@ -3621,6 +3711,9 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
||||
PendingCommitInterval: s.cfg.PendingCommitInterval,
|
||||
ChannelCommitBatchSize: s.cfg.ChannelCommitBatchSize,
|
||||
HandleCustomMessage: s.handleCustomMessage,
|
||||
GetAliases: s.aliasMgr.GetAliases,
|
||||
RequestAlias: s.aliasMgr.RequestAlias,
|
||||
AddLocalAlias: s.aliasMgr.AddLocalAlias,
|
||||
Quit: s.quit,
|
||||
}
|
||||
|
||||
@ -4369,9 +4462,30 @@ func (s *server) fetchLastChanUpdate() func(lnwire.ShortChannelID) (
|
||||
}
|
||||
|
||||
// applyChannelUpdate applies the channel update to the different sub-systems of
|
||||
// the server.
|
||||
func (s *server) applyChannelUpdate(update *lnwire.ChannelUpdate) error {
|
||||
errChan := s.authGossiper.ProcessLocalAnnouncement(update)
|
||||
// the server. The useAlias boolean denotes whether or not to send an alias in
|
||||
// place of the real SCID.
|
||||
func (s *server) applyChannelUpdate(update *lnwire.ChannelUpdate,
|
||||
op *wire.OutPoint, useAlias bool) error {
|
||||
|
||||
var (
|
||||
peerAlias *lnwire.ShortChannelID
|
||||
defaultAlias lnwire.ShortChannelID
|
||||
)
|
||||
|
||||
chanID := lnwire.NewChanIDFromOutPoint(op)
|
||||
|
||||
// Fetch the peer's alias from the lnwire.ChannelID so it can be used
|
||||
// in the ChannelUpdate if it hasn't been announced yet.
|
||||
if useAlias {
|
||||
foundAlias, _ := s.aliasMgr.GetPeerAlias(chanID)
|
||||
if foundAlias != defaultAlias {
|
||||
peerAlias = &foundAlias
|
||||
}
|
||||
}
|
||||
|
||||
errChan := s.authGossiper.ProcessLocalAnnouncement(
|
||||
update, discovery.RemoteAlias(peerAlias),
|
||||
)
|
||||
select {
|
||||
case err := <-errChan:
|
||||
return err
|
||||
|
@ -121,7 +121,8 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config,
|
||||
getNodeAnnouncement func() (lnwire.NodeAnnouncement, error),
|
||||
updateNodeAnnouncement func(modifiers ...netann.NodeAnnModifier) error,
|
||||
parseAddr func(addr string) (net.Addr, error),
|
||||
rpcLogger btclog.Logger) error {
|
||||
rpcLogger btclog.Logger,
|
||||
getAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)) error {
|
||||
|
||||
// First, we'll use reflect to obtain a version of the config struct
|
||||
// that allows us to programmatically inspect its fields.
|
||||
@ -257,6 +258,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config,
|
||||
subCfgValue.FieldByName("GenAmpInvoiceFeatures").Set(
|
||||
reflect.ValueOf(genAmpInvoiceFeatures),
|
||||
)
|
||||
subCfgValue.FieldByName("GetAlias").Set(
|
||||
reflect.ValueOf(getAlias),
|
||||
)
|
||||
|
||||
case *neutrinorpc.Config:
|
||||
subCfgValue := extractReflectValue(subCfg)
|
||||
|
Loading…
Reference in New Issue
Block a user