Add constant and percentage-based fee limits to payments
- Extend SendRequest and QueryRoutesRequest protos - newRoute function takes fee limit and cuts off routes that exceed it - queryRoutes, payInvoice and sendPayment commands take the feeLimit inputs and pass them down to newRoute - When no feeLimit is included, don't enforce any feeLimits at all (by setting feeLimit to maxValue)
This commit is contained in:
1 parent
80b531db62
commit
6746609ec6
10 files changed
+537
-402
No files matched your search
@@ -42,6 +42,10 @@ const (
|
||||
// ErrPaymentAttemptTimeout is an error that indicates that a payment
|
||||
// attempt timed out before we were able to successfully route an HTLC.
|
||||
ErrPaymentAttemptTimeout
|
||||
|
||||
// ErrFeeCutoffExceeded is returned when the total fees of a route
|
||||
// exceed the user-specified maximum payment for the fee.
|
||||
ErrFeeCutoffExceeded
|
||||
)
|
||||
|
||||
// routerError is a structure that represent the error inside the routing package,
|
||||
|
||||
+9
-4
@@ -250,7 +250,7 @@ func (r *Route) ToHopPayloads() []sphinx.HopData {
|
||||
// the source to the target node of the path finding attempt.
|
||||
func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||
pathEdges []*ChannelHop, currentHeight uint32,
|
||||
finalCLTVDelta uint16) (*Route, error) {
|
||||
finalCLTVDelta uint16, feeLimit btcutil.Amount) (*Route, error) {
|
||||
|
||||
// First, we'll create a new empty route with enough hops to match the
|
||||
// amount of path edges. We set the TotalTimeLock to the current block
|
||||
@@ -265,9 +265,6 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||
prevHopMap: make(map[Vertex]*ChannelHop),
|
||||
}
|
||||
|
||||
// TODO(roasbeef): need to do sanity check to ensure we don't make a
|
||||
// "dust" payment: over x% of money sending to fees
|
||||
|
||||
// We'll populate the next hop map for the _source_ node with the
|
||||
// information for the first hop so the mapping is sound.
|
||||
route.nextHopMap[sourceVertex] = pathEdges[0]
|
||||
@@ -342,6 +339,14 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||
|
||||
route.TotalFees += nextHop.Fee
|
||||
|
||||
// If the total fees exceed the fee limit established for this
|
||||
// payment, stop hopping the route and return
|
||||
if route.TotalFees.ToSatoshis() > feeLimit {
|
||||
err := fmt.Sprintf("Route fee exceeded fee limit of %v",
|
||||
feeLimit)
|
||||
return nil, newErrf(ErrFeeCutoffExceeded, err)
|
||||
}
|
||||
|
||||
// As a sanity check, we ensure that the selected channel has
|
||||
// enough capacity to forward the required amount which
|
||||
// includes the fee dictated at each hop.
|
||||
|
||||
@@ -310,6 +310,7 @@ func TestBasicGraphPathFinding(t *testing.T) {
|
||||
)
|
||||
|
||||
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
||||
feeLimit := paymentAmt.ToSatoshis()
|
||||
target := aliases["sophon"]
|
||||
path, err := findPath(
|
||||
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
||||
@@ -318,8 +319,9 @@ func TestBasicGraphPathFinding(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find path: %v", err)
|
||||
}
|
||||
|
||||
route, err := newRoute(paymentAmt, sourceVertex, path, startingHeight,
|
||||
finalHopCLTV)
|
||||
finalHopCLTV, feeLimit)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create path: %v", err)
|
||||
}
|
||||
@@ -460,8 +462,9 @@ func TestBasicGraphPathFinding(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find route: %v", err)
|
||||
}
|
||||
|
||||
route, err = newRoute(paymentAmt, sourceVertex, path, startingHeight,
|
||||
finalHopCLTV)
|
||||
finalHopCLTV, feeLimit)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create path: %v", err)
|
||||
}
|
||||
@@ -852,7 +855,8 @@ func TestPathFindSpecExample(t *testing.T) {
|
||||
// Query for a route of 4,999,999 mSAT to carol.
|
||||
carol := ctx.aliases["C"]
|
||||
const amt lnwire.MilliSatoshi = 4999999
|
||||
routes, err := ctx.router.FindRoutes(carol, amt, 100)
|
||||
feeLimit := amt.ToSatoshis()
|
||||
routes, err := ctx.router.FindRoutes(carol, amt, feeLimit, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find route: %v", err)
|
||||
}
|
||||
@@ -912,7 +916,7 @@ func TestPathFindSpecExample(t *testing.T) {
|
||||
|
||||
// We'll now request a route from A -> B -> C.
|
||||
ctx.router.routeCache = make(map[routeTuple][]*Route)
|
||||
routes, err = ctx.router.FindRoutes(carol, amt, 100)
|
||||
routes, err = ctx.router.FindRoutes(carol, amt, feeLimit, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find routes: %v", err)
|
||||
}
|
||||
|
||||
+9
-3
@@ -1259,7 +1259,8 @@ func pruneChannelFromRoutes(routes []*Route, skipChan uint64) []*Route {
|
||||
// initial set of paths as it's possible we drop a route if it can't handle the
|
||||
// total payment flow after fees are calculated.
|
||||
func pathsToFeeSortedRoutes(source Vertex, paths [][]*ChannelHop, finalCLTVDelta uint16,
|
||||
amt lnwire.MilliSatoshi, currentHeight uint32) ([]*Route, error) {
|
||||
amt lnwire.MilliSatoshi, feeLimit btcutil.Amount,
|
||||
currentHeight uint32) ([]*Route, error) {
|
||||
|
||||
validRoutes := make([]*Route, 0, len(paths))
|
||||
for _, path := range paths {
|
||||
@@ -1268,6 +1269,7 @@ func pathsToFeeSortedRoutes(source Vertex, paths [][]*ChannelHop, finalCLTVDelta
|
||||
// by our KSP algorithm.
|
||||
route, err := newRoute(
|
||||
amt, source, path[1:], currentHeight, finalCLTVDelta,
|
||||
feeLimit,
|
||||
)
|
||||
if err != nil {
|
||||
// TODO(roasbeef): report straw breaking edge?
|
||||
@@ -1316,7 +1318,8 @@ func pathsToFeeSortedRoutes(source Vertex, paths [][]*ChannelHop, finalCLTVDelta
|
||||
// route that will be ranked the highest is the one with the lowest cumulative
|
||||
// fee along the route.
|
||||
func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
|
||||
amt lnwire.MilliSatoshi, numPaths uint32, finalExpiry ...uint16) ([]*Route, error) {
|
||||
amt lnwire.MilliSatoshi, feeLimit btcutil.Amount, numPaths uint32,
|
||||
finalExpiry ...uint16) ([]*Route, error) {
|
||||
|
||||
var finalCLTVDelta uint16
|
||||
if len(finalExpiry) == 0 {
|
||||
@@ -1402,7 +1405,7 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
|
||||
// factored in.
|
||||
sourceVertex := Vertex(r.selfNode.PubKeyBytes)
|
||||
validRoutes, err := pathsToFeeSortedRoutes(
|
||||
sourceVertex, shortestPaths, finalCLTVDelta, amt,
|
||||
sourceVertex, shortestPaths, finalCLTVDelta, amt, feeLimit,
|
||||
uint32(currentHeight),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -1502,6 +1505,9 @@ type LightningPayment struct {
|
||||
// milli-satoshis.
|
||||
Amount lnwire.MilliSatoshi
|
||||
|
||||
// FeeLimit is a user-specified maximum fee for this payment in satoshis.
|
||||
FeeLimit btcutil.Amount
|
||||
|
||||
// PaymentHash is the r-hash value to use within the HTLC extended to
|
||||
// the first hop.
|
||||
PaymentHash [32]byte
|
||||
|
||||
+24
-10
@@ -174,9 +174,12 @@ func TestFindRoutesFeeSorting(t *testing.T) {
|
||||
|
||||
// Execute a query for all possible routes between roasbeef and luo ji.
|
||||
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
||||
feeLimit := paymentAmt.ToSatoshis()
|
||||
target := ctx.aliases["luoji"]
|
||||
routes, err := ctx.router.FindRoutes(target, paymentAmt,
|
||||
defaultNumRoutes, DefaultFinalCLTVDelta)
|
||||
routes, err := ctx.router.FindRoutes(
|
||||
target, paymentAmt, feeLimit, defaultNumRoutes,
|
||||
DefaultFinalCLTVDelta,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find any routes: %v", err)
|
||||
}
|
||||
@@ -221,12 +224,15 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {
|
||||
}
|
||||
|
||||
// Craft a LightningPayment struct that'll send a payment from roasbeef
|
||||
// to luo ji for 100 satoshis.
|
||||
// to luo ji for 1000 satoshis, with a maximum of 1000 satoshis in fees.
|
||||
var payHash [32]byte
|
||||
paymentAmt := lnwire.NewMSatFromSatoshis(1000)
|
||||
feeLimit := paymentAmt.ToSatoshis()
|
||||
payment := LightningPayment{
|
||||
Target: ctx.aliases["luoji"],
|
||||
Amount: lnwire.NewMSatFromSatoshis(1000),
|
||||
Amount: paymentAmt,
|
||||
PaymentHash: payHash,
|
||||
FeeLimit: feeLimit,
|
||||
}
|
||||
|
||||
var preImage [32]byte
|
||||
@@ -524,12 +530,15 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
||||
}
|
||||
|
||||
// Craft a LightningPayment struct that'll send a payment from roasbeef
|
||||
// to luo ji for 100 satoshis.
|
||||
// to luo ji for 1000 satoshis, with a maximum of 1000 satoshis in fees.
|
||||
var payHash [32]byte
|
||||
paymentAmt := lnwire.NewMSatFromSatoshis(1000)
|
||||
feeLimit := paymentAmt.ToSatoshis()
|
||||
payment := LightningPayment{
|
||||
Target: ctx.aliases["luoji"],
|
||||
Amount: lnwire.NewMSatFromSatoshis(1000),
|
||||
Amount: paymentAmt,
|
||||
PaymentHash: payHash,
|
||||
FeeLimit: feeLimit,
|
||||
}
|
||||
|
||||
var preImage [32]byte
|
||||
@@ -965,9 +974,12 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
|
||||
|
||||
// We should now be able to find two routes to node 2.
|
||||
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
||||
feeLimit := paymentAmt.ToSatoshis()
|
||||
targetNode := priv2.PubKey()
|
||||
routes, err := ctx.router.FindRoutes(targetNode, paymentAmt,
|
||||
defaultNumRoutes, DefaultFinalCLTVDelta)
|
||||
routes, err := ctx.router.FindRoutes(
|
||||
targetNode, paymentAmt, feeLimit, defaultNumRoutes,
|
||||
DefaultFinalCLTVDelta,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find any routes: %v", err)
|
||||
}
|
||||
@@ -1009,8 +1021,10 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
|
||||
|
||||
// Should still be able to find the routes, and the info should be
|
||||
// updated.
|
||||
routes, err = ctx.router.FindRoutes(targetNode, paymentAmt,
|
||||
defaultNumRoutes, DefaultFinalCLTVDelta)
|
||||
routes, err = ctx.router.FindRoutes(
|
||||
targetNode, paymentAmt, feeLimit, defaultNumRoutes,
|
||||
DefaultFinalCLTVDelta,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find any routes: %v", err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user