2
0
mirror of https://github.com/lightninglabs/loop synced 2024-11-09 19:10:47 +00:00
loop/utils.go
2022-03-25 11:00:16 +01:00

170 lines
4.5 KiB
Go

package loop
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/zpay32"
)
var (
// DefaultMaxHopHints is set to 20 as that is the default set in LND.
DefaultMaxHopHints = 20
)
// 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,
)
if err != nil {
return false, err
}
return (nodeInfo.ChannelCount > 0), nil
}
// 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) {
chanInfo, err := lnd.Client.GetChanInfo(ctx, chanID)
if err != nil {
return nil, nil, nil, err
}
edgeInfo := &channeldb.ChannelEdgeInfo{
ChannelID: chanID,
NodeKey1Bytes: chanInfo.Node1,
NodeKey2Bytes: chanInfo.Node2,
}
policy1 := &channeldb.ChannelEdgePolicy{
FeeBaseMSat: lnwire.MilliSatoshi(
chanInfo.Node1Policy.FeeBaseMsat,
),
FeeProportionalMillionths: lnwire.MilliSatoshi(
chanInfo.Node1Policy.FeeRateMilliMsat,
),
TimeLockDelta: uint16(chanInfo.Node1Policy.TimeLockDelta),
}
policy2 := &channeldb.ChannelEdgePolicy{
FeeBaseMSat: lnwire.MilliSatoshi(
chanInfo.Node2Policy.FeeBaseMsat,
),
FeeProportionalMillionths: lnwire.MilliSatoshi(
chanInfo.Node2Policy.FeeRateMilliMsat,
),
TimeLockDelta: uint16(chanInfo.Node2Policy.TimeLockDelta),
}
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 {
if len(includeNodes) > 0 {
if _, ok := includeNodes[channel.PubKeyBytes]; !ok {
continue
}
}
outPoint, err := parseOutPoint(channel.ChannelPoint)
if err != nil {
return nil, err
}
remotePubkey, err := btcec.ParsePubKey(channel.PubKeyBytes[:])
if err != nil {
return nil, err
}
openChannels = append(
openChannels, &invoicesrpc.HopHintInfo{
IsPublic: !channel.Private,
IsActive: channel.Active,
FundingOutpoint: *outPoint,
RemotePubkey: remotePubkey,
RemoteBalance: lnwire.MilliSatoshi(
channel.RemoteBalance * 1000,
),
ShortChannelID: channel.ChannelID,
},
)
}
routeHints := invoicesrpc.SelectHopHints(
lnwire.MilliSatoshi(amt*1000), cfg, openChannels, numMaxHophints,
)
return routeHints, nil
}