293971cd03
This commit moves the query routes backend logic from the main rpc server into the sub server. It is another step towards splitting up the main rpc server code. In addition to this, a unit test is added to verify rpc parameter parsing.
230 lines
6.7 KiB
Go
230 lines
6.7 KiB
Go
package routerrpc
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/routing"
|
|
context "golang.org/x/net/context"
|
|
)
|
|
|
|
// RouterBackend contains the backend implementation of the router rpc sub
|
|
// server calls.
|
|
type RouterBackend struct {
|
|
MaxPaymentMSat lnwire.MilliSatoshi
|
|
SelfNode routing.Vertex
|
|
|
|
FetchChannelCapacity func(chanID uint64) (btcutil.Amount, error)
|
|
|
|
FindRoutes func(source, target routing.Vertex,
|
|
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
|
|
numPaths uint32, finalExpiry ...uint16) (
|
|
[]*routing.Route, error)
|
|
}
|
|
|
|
// QueryRoutes attempts to query the daemons' Channel Router for a possible
|
|
// route to a target destination capable of carrying a specific amount of
|
|
// satoshis within the route's flow. The retuned route contains the full
|
|
// details required to craft and send an HTLC, also including the necessary
|
|
// information that should be present within the Sphinx packet encapsulated
|
|
// within the HTLC.
|
|
//
|
|
// TODO(roasbeef): should return a slice of routes in reality
|
|
// * create separate PR to send based on well formatted route
|
|
func (r *RouterBackend) QueryRoutes(ctx context.Context,
|
|
in *lnrpc.QueryRoutesRequest) (*lnrpc.QueryRoutesResponse, error) {
|
|
|
|
parsePubKey := func(key string) (routing.Vertex, error) {
|
|
pubKeyBytes, err := hex.DecodeString(key)
|
|
if err != nil {
|
|
return routing.Vertex{}, err
|
|
}
|
|
|
|
if len(pubKeyBytes) != 33 {
|
|
return routing.Vertex{},
|
|
errors.New("invalid key length")
|
|
}
|
|
|
|
var v routing.Vertex
|
|
copy(v[:], pubKeyBytes)
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// Parse the hex-encoded source and target public keys into full public
|
|
// key objects we can properly manipulate.
|
|
targetPubKey, err := parsePubKey(in.PubKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var sourcePubKey routing.Vertex
|
|
if in.SourcePubKey != "" {
|
|
var err error
|
|
sourcePubKey, err = parsePubKey(in.SourcePubKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
// If no source is specified, use self.
|
|
sourcePubKey = r.SelfNode
|
|
}
|
|
|
|
// Currently, within the bootstrap phase of the network, we limit the
|
|
// largest payment size allotted to (2^32) - 1 mSAT or 4.29 million
|
|
// satoshis.
|
|
amt := btcutil.Amount(in.Amt)
|
|
amtMSat := lnwire.NewMSatFromSatoshis(amt)
|
|
if amtMSat > r.MaxPaymentMSat {
|
|
return nil, fmt.Errorf("payment of %v is too large, max payment "+
|
|
"allowed is %v", amt, r.MaxPaymentMSat.ToSatoshis())
|
|
}
|
|
|
|
// Unmarshall restrictions from request.
|
|
feeLimit := calculateFeeLimit(in.FeeLimit, amtMSat)
|
|
|
|
ignoredNodes := make(map[routing.Vertex]struct{})
|
|
for _, ignorePubKey := range in.IgnoredNodes {
|
|
if len(ignorePubKey) != 33 {
|
|
return nil, fmt.Errorf("invalid ignore node pubkey")
|
|
}
|
|
var ignoreVertex routing.Vertex
|
|
copy(ignoreVertex[:], ignorePubKey)
|
|
ignoredNodes[ignoreVertex] = struct{}{}
|
|
}
|
|
|
|
ignoredEdges := make(map[routing.EdgeLocator]struct{})
|
|
for _, ignoredEdge := range in.IgnoredEdges {
|
|
locator := routing.EdgeLocator{
|
|
ChannelID: ignoredEdge.ChannelId,
|
|
}
|
|
if ignoredEdge.DirectionReverse {
|
|
locator.Direction = 1
|
|
}
|
|
ignoredEdges[locator] = struct{}{}
|
|
}
|
|
|
|
restrictions := &routing.RestrictParams{
|
|
FeeLimit: feeLimit,
|
|
IgnoredNodes: ignoredNodes,
|
|
IgnoredEdges: ignoredEdges,
|
|
}
|
|
|
|
// numRoutes will default to 10 if not specified explicitly.
|
|
numRoutesIn := uint32(in.NumRoutes)
|
|
if numRoutesIn == 0 {
|
|
numRoutesIn = 10
|
|
}
|
|
|
|
// Query the channel router for a possible path to the destination that
|
|
// can carry `in.Amt` satoshis _including_ the total fee required on
|
|
// the route.
|
|
var (
|
|
routes []*routing.Route
|
|
findErr error
|
|
)
|
|
|
|
if in.FinalCltvDelta == 0 {
|
|
routes, findErr = r.FindRoutes(
|
|
sourcePubKey, targetPubKey, amtMSat, restrictions, numRoutesIn,
|
|
)
|
|
} else {
|
|
routes, findErr = r.FindRoutes(
|
|
sourcePubKey, targetPubKey, amtMSat, restrictions, numRoutesIn,
|
|
uint16(in.FinalCltvDelta),
|
|
)
|
|
}
|
|
if findErr != nil {
|
|
return nil, findErr
|
|
}
|
|
|
|
// As the number of returned routes can be less than the number of
|
|
// requested routes, we'll clamp down the length of the response to the
|
|
// minimum of the two.
|
|
numRoutes := uint32(len(routes))
|
|
if numRoutesIn < numRoutes {
|
|
numRoutes = numRoutesIn
|
|
}
|
|
|
|
// For each valid route, we'll convert the result into the format
|
|
// required by the RPC system.
|
|
routeResp := &lnrpc.QueryRoutesResponse{
|
|
Routes: make([]*lnrpc.Route, 0, in.NumRoutes),
|
|
}
|
|
for i := uint32(0); i < numRoutes; i++ {
|
|
routeResp.Routes = append(
|
|
routeResp.Routes,
|
|
r.MarshallRoute(routes[i]),
|
|
)
|
|
}
|
|
|
|
return routeResp, nil
|
|
}
|
|
|
|
// calculateFeeLimit returns the fee limit in millisatoshis. If a percentage
|
|
// based fee limit has been requested, we'll factor in the ratio provided with
|
|
// the amount of the payment.
|
|
func calculateFeeLimit(feeLimit *lnrpc.FeeLimit,
|
|
amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {
|
|
|
|
switch feeLimit.GetLimit().(type) {
|
|
case *lnrpc.FeeLimit_Fixed:
|
|
return lnwire.NewMSatFromSatoshis(
|
|
btcutil.Amount(feeLimit.GetFixed()),
|
|
)
|
|
case *lnrpc.FeeLimit_Percent:
|
|
return amount * lnwire.MilliSatoshi(feeLimit.GetPercent()) / 100
|
|
default:
|
|
// If a fee limit was not specified, we'll use the payment's
|
|
// amount as an upper bound in order to avoid payment attempts
|
|
// from incurring fees higher than the payment amount itself.
|
|
return amount
|
|
}
|
|
}
|
|
|
|
// MarshallRoute marshalls an internal route to an rpc route struct.
|
|
func (r *RouterBackend) MarshallRoute(route *routing.Route) *lnrpc.Route {
|
|
resp := &lnrpc.Route{
|
|
TotalTimeLock: route.TotalTimeLock,
|
|
TotalFees: int64(route.TotalFees.ToSatoshis()),
|
|
TotalFeesMsat: int64(route.TotalFees),
|
|
TotalAmt: int64(route.TotalAmount.ToSatoshis()),
|
|
TotalAmtMsat: int64(route.TotalAmount),
|
|
Hops: make([]*lnrpc.Hop, len(route.Hops)),
|
|
}
|
|
incomingAmt := route.TotalAmount
|
|
for i, hop := range route.Hops {
|
|
fee := route.HopFee(i)
|
|
|
|
// Channel capacity is not a defining property of a route. For
|
|
// backwards RPC compatibility, we retrieve it here from the
|
|
// graph.
|
|
chanCapacity, err := r.FetchChannelCapacity(hop.ChannelID)
|
|
if err != nil {
|
|
// If capacity cannot be retrieved, this may be a
|
|
// not-yet-received or private channel. Then report
|
|
// amount that is sent through the channel as capacity.
|
|
chanCapacity = incomingAmt.ToSatoshis()
|
|
}
|
|
|
|
resp.Hops[i] = &lnrpc.Hop{
|
|
ChanId: hop.ChannelID,
|
|
ChanCapacity: int64(chanCapacity),
|
|
AmtToForward: int64(hop.AmtToForward.ToSatoshis()),
|
|
AmtToForwardMsat: int64(hop.AmtToForward),
|
|
Fee: int64(fee.ToSatoshis()),
|
|
FeeMsat: int64(fee),
|
|
Expiry: uint32(hop.OutgoingTimeLock),
|
|
PubKey: hex.EncodeToString(
|
|
hop.PubKeyBytes[:]),
|
|
}
|
|
incomingAmt = hop.AmtToForward
|
|
}
|
|
|
|
return resp
|
|
}
|