2021-10-19 21:45:15 +00:00
|
|
|
package loop
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-01-27 17:52:20 +00:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2021-10-19 21:45:15 +00:00
|
|
|
|
2022-03-14 12:36:02 +00:00
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2022-01-27 17:52:20 +00:00
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
2021-10-19 21:45:15 +00:00
|
|
|
"github.com/lightninglabs/lndclient"
|
2022-01-27 17:52:20 +00:00
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
2021-10-19 21:45:15 +00:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
|
|
"github.com/lightningnetwork/lnd/zpay32"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2022-01-27 17:52:20 +00:00
|
|
|
// DefaultMaxHopHints is set to 20 as that is the default set in LND.
|
2021-10-19 21:45:15 +00:00
|
|
|
DefaultMaxHopHints = 20
|
|
|
|
)
|
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
// isPublicNode checks if a node is public, by simply checking if there's any
|
|
|
|
// channels reported to the node.
|
|
|
|
func isPublicNode(ctx context.Context, lnd *lndclient.LndServices,
|
|
|
|
pubKey [33]byte) (bool, error) {
|
|
|
|
|
|
|
|
// GetNodeInfo doesn't report our private channels with the queried node
|
|
|
|
// so we can use it to determine if the node is considered public.
|
|
|
|
nodeInfo, err := lnd.Client.GetNodeInfo(
|
|
|
|
ctx, pubKey, true,
|
|
|
|
)
|
2021-10-19 21:45:15 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2022-01-27 17:52:20 +00:00
|
|
|
return false, err
|
2021-10-19 21:45:15 +00:00
|
|
|
}
|
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
return (nodeInfo.ChannelCount > 0), nil
|
|
|
|
}
|
2021-10-19 21:45:15 +00:00
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
// fetchChannelEdgesByID fetches the edge info for the passed channel and
|
|
|
|
// returns the channeldb structs filled with the data that is needed for
|
|
|
|
// LND's SelectHopHints implementation.
|
|
|
|
func fetchChannelEdgesByID(ctx context.Context, lnd *lndclient.LndServices,
|
|
|
|
chanID uint64) (*channeldb.ChannelEdgeInfo, *channeldb.ChannelEdgePolicy,
|
|
|
|
*channeldb.ChannelEdgePolicy, error) {
|
2021-10-19 21:45:15 +00:00
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
chanInfo, err := lnd.Client.GetChanInfo(ctx, chanID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
2021-10-19 21:45:15 +00:00
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
edgeInfo := &channeldb.ChannelEdgeInfo{
|
|
|
|
ChannelID: chanID,
|
|
|
|
NodeKey1Bytes: chanInfo.Node1,
|
|
|
|
NodeKey2Bytes: chanInfo.Node2,
|
|
|
|
}
|
2021-10-19 21:45:15 +00:00
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
policy1 := &channeldb.ChannelEdgePolicy{
|
|
|
|
FeeBaseMSat: lnwire.MilliSatoshi(
|
|
|
|
chanInfo.Node1Policy.FeeBaseMsat,
|
|
|
|
),
|
|
|
|
FeeProportionalMillionths: lnwire.MilliSatoshi(
|
|
|
|
chanInfo.Node1Policy.FeeRateMilliMsat,
|
|
|
|
),
|
|
|
|
TimeLockDelta: uint16(chanInfo.Node1Policy.TimeLockDelta),
|
|
|
|
}
|
2021-10-19 21:45:15 +00:00
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
policy2 := &channeldb.ChannelEdgePolicy{
|
|
|
|
FeeBaseMSat: lnwire.MilliSatoshi(
|
|
|
|
chanInfo.Node2Policy.FeeBaseMsat,
|
|
|
|
),
|
|
|
|
FeeProportionalMillionths: lnwire.MilliSatoshi(
|
|
|
|
chanInfo.Node2Policy.FeeRateMilliMsat,
|
|
|
|
),
|
|
|
|
TimeLockDelta: uint16(chanInfo.Node2Policy.TimeLockDelta),
|
|
|
|
}
|
2021-10-19 21:45:15 +00:00
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
return edgeInfo, policy1, policy2, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseOutPoint attempts to parse an outpoint from the passed in string.
|
|
|
|
func parseOutPoint(s string) (*wire.OutPoint, error) {
|
|
|
|
split := strings.Split(s, ":")
|
|
|
|
if len(split) != 2 {
|
|
|
|
return nil, fmt.Errorf("expecting outpoint to be in format "+
|
|
|
|
"of txid:index: %s", s)
|
|
|
|
}
|
|
|
|
|
|
|
|
index, err := strconv.ParseInt(split[1], 10, 32)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to decode output index: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
txid, err := chainhash.NewHashFromStr(split[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to parse hex string: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &wire.OutPoint{
|
|
|
|
Hash: *txid,
|
|
|
|
Index: uint32(index),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SelectHopHints calls into LND's exposed SelectHopHints prefiltered to the
|
|
|
|
// includeNodes map (unless it's empty).
|
|
|
|
func SelectHopHints(ctx context.Context, lnd *lndclient.LndServices,
|
|
|
|
amt btcutil.Amount, numMaxHophints int,
|
|
|
|
includeNodes map[route.Vertex]struct{}) ([][]zpay32.HopHint, error) {
|
|
|
|
|
|
|
|
cfg := &invoicesrpc.SelectHopHintsCfg{
|
|
|
|
IsPublicNode: func(pubKey [33]byte) (bool, error) {
|
|
|
|
return isPublicNode(ctx, lnd, pubKey)
|
|
|
|
},
|
|
|
|
FetchChannelEdgesByID: func(chanID uint64) (
|
|
|
|
*channeldb.ChannelEdgeInfo, *channeldb.ChannelEdgePolicy,
|
|
|
|
*channeldb.ChannelEdgePolicy, error) {
|
|
|
|
|
|
|
|
return fetchChannelEdgesByID(ctx, lnd, chanID)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
// Fetch all active and public channels.
|
|
|
|
channels, err := lnd.Client.ListChannels(ctx, false, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
openChannels := []*invoicesrpc.HopHintInfo{}
|
|
|
|
for _, channel := range channels {
|
2021-10-19 21:45:15 +00:00
|
|
|
if len(includeNodes) > 0 {
|
|
|
|
if _, ok := includeNodes[channel.PubKeyBytes]; !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
outPoint, err := parseOutPoint(channel.ChannelPoint)
|
|
|
|
if err != nil {
|
2021-10-19 21:45:15 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-03-14 12:36:02 +00:00
|
|
|
remotePubkey, err := btcec.ParsePubKey(channel.PubKeyBytes[:])
|
2021-10-19 21:45:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
openChannels = append(
|
|
|
|
openChannels, &invoicesrpc.HopHintInfo{
|
|
|
|
IsPublic: !channel.Private,
|
|
|
|
IsActive: channel.Active,
|
|
|
|
FundingOutpoint: *outPoint,
|
|
|
|
RemotePubkey: remotePubkey,
|
|
|
|
RemoteBalance: lnwire.MilliSatoshi(
|
|
|
|
channel.RemoteBalance * 1000,
|
|
|
|
),
|
|
|
|
ShortChannelID: channel.ChannelID,
|
|
|
|
},
|
|
|
|
)
|
2021-10-19 21:45:15 +00:00
|
|
|
}
|
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
routeHints := invoicesrpc.SelectHopHints(
|
|
|
|
lnwire.MilliSatoshi(amt*1000), cfg, openChannels, numMaxHophints,
|
|
|
|
)
|
2021-10-19 21:45:15 +00:00
|
|
|
|
2022-01-27 17:52:20 +00:00
|
|
|
return routeHints, nil
|
2021-10-19 21:45:15 +00:00
|
|
|
}
|