Merge pull request #1382 from joostjager/newroute-bug
routing: fix route fee calculation and channel capacity check
This commit is contained in:
commit
64a07341f6
121
lnd_test.go
121
lnd_test.go
@ -41,6 +41,10 @@ var (
|
|||||||
harnessNetParams = &chaincfg.SimNetParams
|
harnessNetParams = &chaincfg.SimNetParams
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testFeeBase = 1e+6
|
||||||
|
)
|
||||||
|
|
||||||
// harnessTest wraps a regular testing.T providing enhanced error detection
|
// harnessTest wraps a regular testing.T providing enhanced error detection
|
||||||
// and propagation. All error will be augmented with a full stack-trace in
|
// and propagation. All error will be augmented with a full stack-trace in
|
||||||
// order to aid in debugging. Additionally, any panics caused by active
|
// order to aid in debugging. Additionally, any panics caused by active
|
||||||
@ -876,14 +880,13 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// With our little cluster set up, we'll update the fees for the
|
// With our little cluster set up, we'll update the fees for the
|
||||||
// channel Bob side of the Alice->Bob channel, and make sure all nodes
|
// channel Bob side of the Alice->Bob channel, and make sure all nodes
|
||||||
// learn about it.
|
// learn about it.
|
||||||
const feeBase = 1000000
|
|
||||||
baseFee := int64(1500)
|
baseFee := int64(1500)
|
||||||
feeRate := int64(12)
|
feeRate := int64(12)
|
||||||
timeLockDelta := uint32(66)
|
timeLockDelta := uint32(66)
|
||||||
|
|
||||||
expectedPolicy := &lnrpc.RoutingPolicy{
|
expectedPolicy := &lnrpc.RoutingPolicy{
|
||||||
FeeBaseMsat: baseFee,
|
FeeBaseMsat: baseFee,
|
||||||
FeeRateMilliMsat: feeBase * feeRate,
|
FeeRateMilliMsat: testFeeBase * feeRate,
|
||||||
TimeLockDelta: timeLockDelta,
|
TimeLockDelta: timeLockDelta,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -971,7 +974,7 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
timeLockDelta = uint32(22)
|
timeLockDelta = uint32(22)
|
||||||
|
|
||||||
expectedPolicy.FeeBaseMsat = baseFee
|
expectedPolicy.FeeBaseMsat = baseFee
|
||||||
expectedPolicy.FeeRateMilliMsat = feeBase * feeRate
|
expectedPolicy.FeeRateMilliMsat = testFeeBase * feeRate
|
||||||
expectedPolicy.TimeLockDelta = timeLockDelta
|
expectedPolicy.TimeLockDelta = timeLockDelta
|
||||||
|
|
||||||
req = &lnrpc.PolicyUpdateRequest{
|
req = &lnrpc.PolicyUpdateRequest{
|
||||||
@ -2773,6 +2776,48 @@ func assertAmountPaid(t *harnessTest, ctxb context.Context, channelName string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateChannelPolicy updates the channel policy of node to the
|
||||||
|
// given fees and timelock delta. This function blocks until
|
||||||
|
// listenerNode has received the policy update.
|
||||||
|
func updateChannelPolicy(t *harnessTest, node *lntest.HarnessNode,
|
||||||
|
chanPoint *lnrpc.ChannelPoint, baseFee int64, feeRate int64,
|
||||||
|
timeLockDelta uint32, listenerNode *lntest.HarnessNode) {
|
||||||
|
|
||||||
|
ctxb := context.Background()
|
||||||
|
timeout := time.Duration(time.Second * 15)
|
||||||
|
|
||||||
|
expectedPolicy := &lnrpc.RoutingPolicy{
|
||||||
|
FeeBaseMsat: baseFee,
|
||||||
|
FeeRateMilliMsat: feeRate,
|
||||||
|
TimeLockDelta: timeLockDelta,
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFeeReq := &lnrpc.PolicyUpdateRequest{
|
||||||
|
BaseFeeMsat: baseFee,
|
||||||
|
FeeRate: float64(feeRate) / testFeeBase,
|
||||||
|
TimeLockDelta: timeLockDelta,
|
||||||
|
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
|
||||||
|
ChanPoint: chanPoint,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||||
|
if _, err := node.UpdateChannelPolicy(ctxt, updateFeeReq); err != nil {
|
||||||
|
t.Fatalf("unable to update chan policy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for listener node to receive the channel update from node.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
|
listenerUpdates, aQuit := subscribeGraphNotifications(t, ctxt,
|
||||||
|
listenerNode)
|
||||||
|
defer close(aQuit)
|
||||||
|
|
||||||
|
waitForChannelUpdate(
|
||||||
|
t, listenerUpdates, node.PubKeyStr, expectedPolicy,
|
||||||
|
chanPoint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
|
func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
const chanAmt = btcutil.Amount(100000)
|
const chanAmt = btcutil.Amount(100000)
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
@ -2934,6 +2979,15 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
|
|
||||||
time.Sleep(time.Millisecond * 50)
|
time.Sleep(time.Millisecond * 50)
|
||||||
|
|
||||||
|
// Set the fee policies of the Alice -> Bob and the Dave -> Alice
|
||||||
|
// channel edges to relatively large non default values. This makes it
|
||||||
|
// possible to pick up more subtle fee calculation errors.
|
||||||
|
updateChannelPolicy(t, net.Alice, chanPointAlice, 1000, 100000,
|
||||||
|
144, carol)
|
||||||
|
|
||||||
|
updateChannelPolicy(t, dave, chanPointDave, 5000, 150000,
|
||||||
|
144, carol)
|
||||||
|
|
||||||
// Using Carol as the source, pay to the 5 invoices from Bob created
|
// Using Carol as the source, pay to the 5 invoices from Bob created
|
||||||
// above.
|
// above.
|
||||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||||
@ -2953,42 +3007,61 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// increasing of time is needed to embed the HTLC in commitment
|
// increasing of time is needed to embed the HTLC in commitment
|
||||||
// transaction, in channel Carol->David->Alice->Bob, order is Bob,
|
// transaction, in channel Carol->David->Alice->Bob, order is Bob,
|
||||||
// Alice, David, Carol.
|
// Alice, David, Carol.
|
||||||
const amountPaid = int64(5000)
|
|
||||||
|
// The final node bob expects to get paid five times 1000 sat.
|
||||||
|
expectedAmountPaidAtoB := int64(5 * 1000)
|
||||||
|
|
||||||
assertAmountPaid(t, ctxb, "Alice(local) => Bob(remote)", net.Bob,
|
assertAmountPaid(t, ctxb, "Alice(local) => Bob(remote)", net.Bob,
|
||||||
aliceFundPoint, int64(0), amountPaid)
|
aliceFundPoint, int64(0), expectedAmountPaidAtoB)
|
||||||
assertAmountPaid(t, ctxb, "Alice(local) => Bob(remote)", net.Alice,
|
assertAmountPaid(t, ctxb, "Alice(local) => Bob(remote)", net.Alice,
|
||||||
aliceFundPoint, amountPaid, int64(0))
|
aliceFundPoint, expectedAmountPaidAtoB, int64(0))
|
||||||
|
|
||||||
|
// To forward a payment of 1000 sat, Alice is charging a fee of
|
||||||
|
// 1 sat + 10% = 101 sat.
|
||||||
|
const expectedFeeAlice = 5 * 101
|
||||||
|
|
||||||
|
// Dave needs to pay what Alice pays plus Alice's fee.
|
||||||
|
expectedAmountPaidDtoA := expectedAmountPaidAtoB + expectedFeeAlice
|
||||||
|
|
||||||
assertAmountPaid(t, ctxb, "Dave(local) => Alice(remote)", net.Alice,
|
assertAmountPaid(t, ctxb, "Dave(local) => Alice(remote)", net.Alice,
|
||||||
daveFundPoint, int64(0), amountPaid+(baseFee*numPayments))
|
daveFundPoint, int64(0), expectedAmountPaidDtoA)
|
||||||
assertAmountPaid(t, ctxb, "Dave(local) => Alice(remote)", dave,
|
assertAmountPaid(t, ctxb, "Dave(local) => Alice(remote)", dave,
|
||||||
daveFundPoint, amountPaid+(baseFee*numPayments), int64(0))
|
daveFundPoint, expectedAmountPaidDtoA, int64(0))
|
||||||
|
|
||||||
|
// To forward a payment of 1101 sat, Dave is charging a fee of
|
||||||
|
// 5 sat + 15% = 170.15 sat. This is rounded down in rpcserver to 170.
|
||||||
|
const expectedFeeDave = 5 * 170
|
||||||
|
|
||||||
|
// Carol needs to pay what Dave pays plus Dave's fee.
|
||||||
|
expectedAmountPaidCtoD := expectedAmountPaidDtoA + expectedFeeDave
|
||||||
|
|
||||||
assertAmountPaid(t, ctxb, "Carol(local) => Dave(remote)", dave,
|
assertAmountPaid(t, ctxb, "Carol(local) => Dave(remote)", dave,
|
||||||
carolFundPoint, int64(0), amountPaid+((baseFee*numPayments)*2))
|
carolFundPoint, int64(0), expectedAmountPaidCtoD)
|
||||||
assertAmountPaid(t, ctxb, "Carol(local) => Dave(remote)", carol,
|
assertAmountPaid(t, ctxb, "Carol(local) => Dave(remote)", carol,
|
||||||
carolFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0))
|
carolFundPoint, expectedAmountPaidCtoD, int64(0))
|
||||||
|
|
||||||
// Now that we know all the balances have been settled out properly,
|
// Now that we know all the balances have been settled out properly,
|
||||||
// we'll ensure that our internal record keeping for completed circuits
|
// we'll ensure that our internal record keeping for completed circuits
|
||||||
// was properly updated.
|
// was properly updated.
|
||||||
|
|
||||||
// First, check that the FeeReport response shows the proper fees
|
// First, check that the FeeReport response shows the proper fees
|
||||||
// accrued over each time range. Dave should've earned 1 satoshi for
|
// accrued over each time range. Dave should've earned 170 satoshi for
|
||||||
// each of the forwarded payments.
|
// each of the forwarded payments.
|
||||||
feeReport, err := dave.FeeReport(ctxb, &lnrpc.FeeReportRequest{})
|
feeReport, err := dave.FeeReport(ctxb, &lnrpc.FeeReportRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to query for fee report: %v", err)
|
t.Fatalf("unable to query for fee report: %v", err)
|
||||||
}
|
}
|
||||||
const exectedFees = 5
|
|
||||||
if feeReport.DayFeeSum != exectedFees {
|
if feeReport.DayFeeSum != uint64(expectedFeeDave) {
|
||||||
t.Fatalf("fee mismatch: expected %v, got %v", 5,
|
t.Fatalf("fee mismatch: expected %v, got %v", expectedFeeDave,
|
||||||
feeReport.DayFeeSum)
|
feeReport.DayFeeSum)
|
||||||
}
|
}
|
||||||
if feeReport.WeekFeeSum != exectedFees {
|
if feeReport.WeekFeeSum != uint64(expectedFeeDave) {
|
||||||
t.Fatalf("fee mismatch: expected %v, got %v", 5,
|
t.Fatalf("fee mismatch: expected %v, got %v", expectedFeeDave,
|
||||||
feeReport.WeekFeeSum)
|
feeReport.WeekFeeSum)
|
||||||
}
|
}
|
||||||
if feeReport.MonthFeeSum != exectedFees {
|
if feeReport.MonthFeeSum != uint64(expectedFeeDave) {
|
||||||
t.Fatalf("fee mismatch: expected %v, got %v", 5,
|
t.Fatalf("fee mismatch: expected %v, got %v", expectedFeeDave,
|
||||||
feeReport.MonthFeeSum)
|
feeReport.MonthFeeSum)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3004,11 +3077,12 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
t.Fatalf("wrong number of forwarding event: expected %v, "+
|
t.Fatalf("wrong number of forwarding event: expected %v, "+
|
||||||
"got %v", 5, len(fwdingHistory.ForwardingEvents))
|
"got %v", 5, len(fwdingHistory.ForwardingEvents))
|
||||||
}
|
}
|
||||||
|
expectedForwardingFee := uint64(expectedFeeDave / numPayments)
|
||||||
for _, event := range fwdingHistory.ForwardingEvents {
|
for _, event := range fwdingHistory.ForwardingEvents {
|
||||||
// Each event should show a fee of 1 satoshi.
|
// Each event should show a fee of 170 satoshi.
|
||||||
if event.Fee != 1 {
|
if event.Fee != expectedForwardingFee {
|
||||||
t.Fatalf("fee mismatch: expected %v, got %v", 1,
|
t.Fatalf("fee mismatch: expected %v, got %v",
|
||||||
event.Fee)
|
expectedForwardingFee, event.Fee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9775,14 +9849,13 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// Therefore, we'll update the fee policy on Carol's side for the
|
// Therefore, we'll update the fee policy on Carol's side for the
|
||||||
// channel between her and Dave to invalidate the route:
|
// channel between her and Dave to invalidate the route:
|
||||||
// Alice -> Carol -> Dave
|
// Alice -> Carol -> Dave
|
||||||
const feeBase = 1e+6
|
|
||||||
baseFee := int64(10000)
|
baseFee := int64(10000)
|
||||||
feeRate := int64(5)
|
feeRate := int64(5)
|
||||||
timeLockDelta := uint32(144)
|
timeLockDelta := uint32(144)
|
||||||
|
|
||||||
expectedPolicy := &lnrpc.RoutingPolicy{
|
expectedPolicy := &lnrpc.RoutingPolicy{
|
||||||
FeeBaseMsat: baseFee,
|
FeeBaseMsat: baseFee,
|
||||||
FeeRateMilliMsat: feeBase * feeRate,
|
FeeRateMilliMsat: testFeeBase * feeRate,
|
||||||
TimeLockDelta: timeLockDelta,
|
TimeLockDelta: timeLockDelta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,10 +50,11 @@ type HopHint struct {
|
|||||||
CLTVExpiryDelta uint16
|
CLTVExpiryDelta uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelHop is an intermediate hop within the network with a greater
|
// ChannelHop describes the channel through which an intermediate or final
|
||||||
// multi-hop payment route. This struct contains the relevant routing policy of
|
// hop can be reached. This struct contains the relevant routing policy of
|
||||||
// the particular edge, as well as the total capacity, and origin chain of the
|
// the particular edge (which is a property of the source node of the channel
|
||||||
// channel itself.
|
// edge), as well as the total capacity. It also includes the origin chain of
|
||||||
|
// the channel itself.
|
||||||
type ChannelHop struct {
|
type ChannelHop struct {
|
||||||
// Capacity is the total capacity of the channel being traversed. This
|
// Capacity is the total capacity of the channel being traversed. This
|
||||||
// value is expressed for stability in satoshis.
|
// value is expressed for stability in satoshis.
|
||||||
@ -69,13 +70,15 @@ type ChannelHop struct {
|
|||||||
*channeldb.ChannelEdgePolicy
|
*channeldb.ChannelEdgePolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hop represents the forwarding details at a particular position within the
|
// Hop represents an intermediate or final node of the route. This naming
|
||||||
// final route. This struct houses the values necessary to create the HTLC
|
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
|
||||||
// which will travel along this hop, and also encode the per-hop payload
|
// The struct houses the channel along which this hop can be reached and
|
||||||
// included within the Sphinx packet.
|
// the values necessary to create the HTLC that needs to be sent to the
|
||||||
|
// next hop. It is also used to encode the per-hop payload included within
|
||||||
|
// the Sphinx packet.
|
||||||
type Hop struct {
|
type Hop struct {
|
||||||
// Channel is the active payment channel edge that this hop will travel
|
// Channel is the active payment channel edge along which the packet
|
||||||
// along.
|
// travels to reach this hop. This is the _incoming_ channel to this hop.
|
||||||
Channel *ChannelHop
|
Channel *ChannelHop
|
||||||
|
|
||||||
// OutgoingTimeLock is the timelock value that should be used when
|
// OutgoingTimeLock is the timelock value that should be used when
|
||||||
@ -269,11 +272,6 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
|||||||
// information for the first hop so the mapping is sound.
|
// information for the first hop so the mapping is sound.
|
||||||
route.nextHopMap[sourceVertex] = pathEdges[0]
|
route.nextHopMap[sourceVertex] = pathEdges[0]
|
||||||
|
|
||||||
// The running amount is the total amount of satoshis required at this
|
|
||||||
// point in the route. We start this value at the amount we want to
|
|
||||||
// send to the destination. This value will then get successively
|
|
||||||
// larger as we compute the fees going backwards.
|
|
||||||
runningAmt := amtToSend
|
|
||||||
pathLength := len(pathEdges)
|
pathLength := len(pathEdges)
|
||||||
for i := pathLength - 1; i >= 0; i-- {
|
for i := pathLength - 1; i >= 0; i-- {
|
||||||
edge := pathEdges[i]
|
edge := pathEdges[i]
|
||||||
@ -285,59 +283,56 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
|||||||
route.nodeIndex[v] = struct{}{}
|
route.nodeIndex[v] = struct{}{}
|
||||||
route.chanIndex[edge.ChannelID] = struct{}{}
|
route.chanIndex[edge.ChannelID] = struct{}{}
|
||||||
|
|
||||||
// If this isn't a direct payment, and this isn't the last hop
|
// If this isn't a direct payment, and this isn't the edge to
|
||||||
// in the route, then we'll also populate the nextHop map to
|
// the last hop in the route, then we'll also populate the
|
||||||
// allow easy route traversal by callers.
|
// nextHop map to allow easy route traversal by callers.
|
||||||
if len(pathEdges) > 1 && i != len(pathEdges)-1 {
|
if len(pathEdges) > 1 && i != len(pathEdges)-1 {
|
||||||
route.nextHopMap[v] = route.Hops[i+1].Channel
|
route.nextHopMap[v] = route.Hops[i+1].Channel
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we'll start to calculate the items within the per-hop
|
// Now we'll start to calculate the items within the per-hop
|
||||||
// payload for this current hop.
|
// payload for the hop this edge is leading to. This hop will
|
||||||
//
|
// be called the 'current hop'.
|
||||||
// If this is the last hop, then we send the exact amount and
|
|
||||||
// pay no fee, as we're paying directly to the receiver, and
|
// If it is the last hop, then the hop payload will contain
|
||||||
// there're no additional hops.
|
// the exact amount. In BOLT #4: Onion Routing
|
||||||
amtToForward := runningAmt
|
// Protocol / "Payload for the Last Node", this is detailed.
|
||||||
|
amtToForward := amtToSend
|
||||||
|
|
||||||
|
// Fee is not part of the hop payload, but only used for
|
||||||
|
// reporting through RPC. Set to zero for the final hop.
|
||||||
fee := lnwire.MilliSatoshi(0)
|
fee := lnwire.MilliSatoshi(0)
|
||||||
|
|
||||||
// If this isn't the last hop, to add enough funds to pay for
|
// If the current hop isn't the last hop, then add enough funds
|
||||||
// transit over the next link.
|
// to pay for transit over the next link.
|
||||||
if i != len(pathEdges)-1 {
|
if i != len(pathEdges)-1 {
|
||||||
// We'll grab the edge policy and per-hop payload of
|
// We'll grab the per-hop payload of the next hop (the
|
||||||
// the prior hop so we can calculate fees properly.
|
// hop _after_ the hop this edge leads to) in the
|
||||||
prevEdge := pathEdges[i+1]
|
// route so we can calculate fees properly.
|
||||||
prevHop := route.Hops[i+1]
|
nextHop := route.Hops[i+1]
|
||||||
|
|
||||||
// The fee for this hop, will be based on how much the
|
// The amount that the current hop needs to forward is
|
||||||
// prior hop carried, as we'll need to increase the
|
// based on how much the next hop forwards plus the fee
|
||||||
// amount of satoshis incoming into this hop to
|
// that needs to be paid to the next hop.
|
||||||
// properly pay the required fees.
|
amtToForward = nextHop.AmtToForward + nextHop.Fee
|
||||||
prevAmount := prevHop.AmtToForward
|
|
||||||
fee = computeFee(prevAmount, prevEdge.ChannelEdgePolicy)
|
|
||||||
|
|
||||||
// With the fee computed, we increment the total amount
|
// The fee that needs to be paid to the current hop is
|
||||||
// as we need to pay this fee. This value represents
|
// based on the amount that this hop needs to forward
|
||||||
// the amount of funds that will come _into_ this edge.
|
// and its policy for the outgoing channel. This policy
|
||||||
runningAmt += fee
|
// is stored as part of the incoming channel of
|
||||||
|
// the next hop.
|
||||||
// Otherwise, for a node to forward an HTLC, then
|
fee = computeFee(amtToForward, nextHop.Channel.ChannelEdgePolicy)
|
||||||
// following inequality most hold true:
|
|
||||||
//
|
|
||||||
// * amt_in - fee >= amt_to_forward
|
|
||||||
amtToForward = runningAmt - fee
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we create the hop struct for this point in the route.
|
// Now we create the hop struct for the current hop.
|
||||||
// The amount to forward is the running amount, and we compute
|
currentHop := &Hop{
|
||||||
// the required fee based on this amount.
|
|
||||||
nextHop := &Hop{
|
|
||||||
Channel: edge,
|
Channel: edge,
|
||||||
AmtToForward: amtToForward,
|
AmtToForward: amtToForward,
|
||||||
Fee: fee,
|
Fee: fee,
|
||||||
}
|
}
|
||||||
|
|
||||||
route.TotalFees += nextHop.Fee
|
// Accumulate all fees.
|
||||||
|
route.TotalFees += currentHop.Fee
|
||||||
|
|
||||||
// Invalidate this route if its total fees exceed our fee limit.
|
// Invalidate this route if its total fees exceed our fee limit.
|
||||||
if route.TotalFees > feeLimit {
|
if route.TotalFees > feeLimit {
|
||||||
@ -346,14 +341,17 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
|||||||
return nil, newErrf(ErrFeeLimitExceeded, err)
|
return nil, newErrf(ErrFeeLimitExceeded, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// As a sanity check, we ensure that the selected channel has
|
// As a sanity check, we ensure that the incoming channel has
|
||||||
// enough capacity to forward the required amount which
|
// enough capacity to carry the required amount which
|
||||||
// includes the fee dictated at each hop.
|
// includes the fee dictated at each hop. Make the comparison
|
||||||
if nextHop.AmtToForward.ToSatoshis() > nextHop.Channel.Capacity {
|
// in msat to prevent rounding errors.
|
||||||
|
if currentHop.AmtToForward + fee > lnwire.NewMSatFromSatoshis(
|
||||||
|
currentHop.Channel.Capacity) {
|
||||||
|
|
||||||
err := fmt.Sprintf("channel graph has insufficient "+
|
err := fmt.Sprintf("channel graph has insufficient "+
|
||||||
"capacity for the payment: need %v, have %v",
|
"capacity for the payment: need %v, have %v",
|
||||||
nextHop.AmtToForward.ToSatoshis(),
|
currentHop.AmtToForward.ToSatoshis(),
|
||||||
nextHop.Channel.Capacity)
|
currentHop.Channel.Capacity)
|
||||||
|
|
||||||
return nil, newErrf(ErrInsufficientCapacity, err)
|
return nil, newErrf(ErrInsufficientCapacity, err)
|
||||||
}
|
}
|
||||||
@ -367,7 +365,7 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
|||||||
// last link in the route.
|
// last link in the route.
|
||||||
route.TotalTimeLock += uint32(finalCLTVDelta)
|
route.TotalTimeLock += uint32(finalCLTVDelta)
|
||||||
|
|
||||||
nextHop.OutgoingTimeLock = currentHeight + uint32(finalCLTVDelta)
|
currentHop.OutgoingTimeLock = currentHeight + uint32(finalCLTVDelta)
|
||||||
} else {
|
} else {
|
||||||
// Next, increment the total timelock of the entire
|
// Next, increment the total timelock of the entire
|
||||||
// route such that each hops time lock increases as we
|
// route such that each hops time lock increases as we
|
||||||
@ -380,10 +378,10 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
|||||||
// be the value of the time-lock for the _outgoing_
|
// be the value of the time-lock for the _outgoing_
|
||||||
// HTLC, so we factor in their specified grace period
|
// HTLC, so we factor in their specified grace period
|
||||||
// (time lock delta).
|
// (time lock delta).
|
||||||
nextHop.OutgoingTimeLock = route.TotalTimeLock - delta
|
currentHop.OutgoingTimeLock = route.TotalTimeLock - delta
|
||||||
}
|
}
|
||||||
|
|
||||||
route.Hops[i] = nextHop
|
route.Hops[i] = currentHop
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll then make a second run through our route in order to set up
|
// We'll then make a second run through our route in order to set up
|
||||||
@ -393,9 +391,11 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
|||||||
route.prevHopMap[vertex] = hop.Channel
|
route.prevHopMap[vertex] = hop.Channel
|
||||||
}
|
}
|
||||||
|
|
||||||
// The total amount required for this route will be the value the
|
// The total amount required for this route will be the value
|
||||||
// source extends to the first hop in the route.
|
// that the first hop needs to forward plus the fee that
|
||||||
route.TotalAmount = runningAmt
|
// the first hop charges for this. Note that the sender of the
|
||||||
|
// payment is not a hop in the route.
|
||||||
|
route.TotalAmount = route.Hops[0].AmtToForward + route.Hops[0].Fee
|
||||||
|
|
||||||
return route, nil
|
return route, nil
|
||||||
}
|
}
|
||||||
|
@ -616,6 +616,236 @@ func TestKShortestPathFinding(t *testing.T) {
|
|||||||
assertExpectedPath(t, paths[1], "roasbeef", "satoshi", "luoji")
|
assertExpectedPath(t, paths[1], "roasbeef", "satoshi", "luoji")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNewRoute tests whether the construction of hop payloads by newRoute
|
||||||
|
// is executed correctly.
|
||||||
|
func TestNewRoute(t *testing.T) {
|
||||||
|
|
||||||
|
var sourceKey [33]byte
|
||||||
|
sourceVertex := Vertex(sourceKey)
|
||||||
|
|
||||||
|
const (
|
||||||
|
startingHeight = 100
|
||||||
|
finalHopCLTV = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
createHop := func(baseFee lnwire.MilliSatoshi,
|
||||||
|
feeRate lnwire.MilliSatoshi,
|
||||||
|
capacity btcutil.Amount,
|
||||||
|
timeLockDelta uint16) (*ChannelHop) {
|
||||||
|
|
||||||
|
return &ChannelHop {
|
||||||
|
ChannelEdgePolicy: &channeldb.ChannelEdgePolicy {
|
||||||
|
Node: &channeldb.LightningNode{},
|
||||||
|
FeeProportionalMillionths: feeRate,
|
||||||
|
FeeBaseMSat: baseFee,
|
||||||
|
TimeLockDelta: timeLockDelta,
|
||||||
|
},
|
||||||
|
Capacity: capacity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
// name identifies the test case in the test output.
|
||||||
|
name string
|
||||||
|
|
||||||
|
// hops is the list of hops (the route) that gets passed into
|
||||||
|
// the call to newRoute.
|
||||||
|
hops []*ChannelHop
|
||||||
|
|
||||||
|
// paymentAmount is the amount that is send into the route
|
||||||
|
// indicated by hops.
|
||||||
|
paymentAmount lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// expectedFees is a list of fees that every hop is expected
|
||||||
|
// to charge for forwarding.
|
||||||
|
expectedFees []lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// expectedTimeLocks is a list of time lock values that every
|
||||||
|
// hop is expected to specify in its outgoing HTLC. The time
|
||||||
|
// lock values in this list are relative to the current block
|
||||||
|
// height.
|
||||||
|
expectedTimeLocks []uint32
|
||||||
|
|
||||||
|
// expectedTotalAmount is the total amount that is expected to
|
||||||
|
// be returned from newRoute. This amount should include all
|
||||||
|
// the fees to be paid to intermediate hops.
|
||||||
|
expectedTotalAmount lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// expectedTotalTimeLock is the time lock that is expected to
|
||||||
|
// be returned from newRoute. This is the time lock that should
|
||||||
|
// be specified in the HTLC that is sent by the source node.
|
||||||
|
// expectedTotalTimeLock is relative to the current block height.
|
||||||
|
expectedTotalTimeLock uint32
|
||||||
|
|
||||||
|
// expectError indicates whether the newRoute call is expected
|
||||||
|
// to fail or succeed.
|
||||||
|
expectError bool
|
||||||
|
|
||||||
|
// expectedErrorCode indicates the expected error code when
|
||||||
|
// expectError is true.
|
||||||
|
expectedErrorCode errorCode
|
||||||
|
} {
|
||||||
|
{
|
||||||
|
// For a single hop payment, no fees are expected to be paid.
|
||||||
|
name: "single hop",
|
||||||
|
paymentAmount: 100000,
|
||||||
|
hops: []*ChannelHop {
|
||||||
|
createHop(100, 1000, 1000, 10),
|
||||||
|
},
|
||||||
|
expectedFees: []lnwire.MilliSatoshi {0},
|
||||||
|
expectedTimeLocks: []uint32 {1},
|
||||||
|
expectedTotalAmount: 100000,
|
||||||
|
expectedTotalTimeLock: 1,
|
||||||
|
}, {
|
||||||
|
// For a two hop payment, only the fee for the first hop
|
||||||
|
// needs to be paid. The destination hop does not require
|
||||||
|
// a fee to receive the payment.
|
||||||
|
name: "two hop",
|
||||||
|
paymentAmount: 100000,
|
||||||
|
hops: []*ChannelHop {
|
||||||
|
createHop(0, 1000, 1000, 10),
|
||||||
|
createHop(30, 1000, 1000, 5),
|
||||||
|
},
|
||||||
|
expectedFees: []lnwire.MilliSatoshi {130, 0},
|
||||||
|
expectedTimeLocks: []uint32 {1, 1},
|
||||||
|
expectedTotalAmount: 100130,
|
||||||
|
expectedTotalTimeLock: 6,
|
||||||
|
}, {
|
||||||
|
// Insufficient capacity in first channel when fees are added.
|
||||||
|
name: "two hop insufficient",
|
||||||
|
paymentAmount: 100000,
|
||||||
|
hops: []*ChannelHop {
|
||||||
|
createHop(0, 1000, 100, 10),
|
||||||
|
createHop(0, 1000, 1000, 5),
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectedErrorCode: ErrInsufficientCapacity,
|
||||||
|
}, {
|
||||||
|
// A three hop payment where the first and second hop
|
||||||
|
// will both charge 1 msat. The fee for the first hop
|
||||||
|
// is actually slightly higher than 1, because the amount
|
||||||
|
// to forward also includes the fee for the second hop. This
|
||||||
|
// gets rounded down to 1.
|
||||||
|
name: "three hop",
|
||||||
|
paymentAmount: 100000,
|
||||||
|
hops: []*ChannelHop {
|
||||||
|
createHop(0, 10, 1000, 10),
|
||||||
|
createHop(0, 10, 1000, 5),
|
||||||
|
createHop(0, 10, 1000, 3),
|
||||||
|
},
|
||||||
|
expectedFees: []lnwire.MilliSatoshi {1, 1, 0},
|
||||||
|
expectedTotalAmount: 100002,
|
||||||
|
expectedTimeLocks: []uint32 {4, 1, 1},
|
||||||
|
expectedTotalTimeLock: 9,
|
||||||
|
}, {
|
||||||
|
// A three hop payment where the fee of the first hop
|
||||||
|
// is slightly higher (11) than the fee at the second hop,
|
||||||
|
// because of the increase amount to forward.
|
||||||
|
name: "three hop with fee carry over",
|
||||||
|
paymentAmount: 100000,
|
||||||
|
hops: []*ChannelHop {
|
||||||
|
createHop(0, 10000, 1000, 10),
|
||||||
|
createHop(0, 10000, 1000, 5),
|
||||||
|
createHop(0, 10000, 1000, 3),
|
||||||
|
},
|
||||||
|
expectedFees: []lnwire.MilliSatoshi {1010, 1000, 0},
|
||||||
|
expectedTotalAmount: 102010,
|
||||||
|
expectedTimeLocks: []uint32 {4, 1, 1},
|
||||||
|
expectedTotalTimeLock: 9,
|
||||||
|
}, {
|
||||||
|
// A three hop payment where the fee policies of the first and
|
||||||
|
// second hop are just high enough to show the fee carry over
|
||||||
|
// effect.
|
||||||
|
name: "three hop with minimal fees for carry over",
|
||||||
|
paymentAmount: 100000,
|
||||||
|
hops: []*ChannelHop {
|
||||||
|
createHop(0, 10000, 1000, 10),
|
||||||
|
|
||||||
|
// First hop charges 0.1% so the second hop fee
|
||||||
|
// should show up in the first hop fee as 1 msat
|
||||||
|
// extra.
|
||||||
|
createHop(0, 1000, 1000, 5),
|
||||||
|
|
||||||
|
// Second hop charges a fixed 1000 msat.
|
||||||
|
createHop(1000, 0, 1000, 3),
|
||||||
|
},
|
||||||
|
expectedFees: []lnwire.MilliSatoshi {101, 1000, 0},
|
||||||
|
expectedTotalAmount: 101101,
|
||||||
|
expectedTimeLocks: []uint32 {4, 1, 1},
|
||||||
|
expectedTotalTimeLock: 9,
|
||||||
|
} }
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
assertRoute := func(t *testing.T, route *Route) {
|
||||||
|
if route.TotalAmount != testCase.expectedTotalAmount {
|
||||||
|
t.Errorf("Expected total amount is be %v" +
|
||||||
|
", but got %v instead",
|
||||||
|
testCase.expectedTotalAmount,
|
||||||
|
route.TotalAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(testCase.expectedFees); i++ {
|
||||||
|
if testCase.expectedFees[i] !=
|
||||||
|
route.Hops[i].Fee {
|
||||||
|
|
||||||
|
t.Errorf("Expected fee for hop %v to " +
|
||||||
|
"be %v, but got %v instead",
|
||||||
|
i, testCase.expectedFees[i],
|
||||||
|
route.Hops[i].Fee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedTimeLockHeight := startingHeight +
|
||||||
|
testCase.expectedTotalTimeLock
|
||||||
|
|
||||||
|
if route.TotalTimeLock != expectedTimeLockHeight {
|
||||||
|
|
||||||
|
t.Errorf("Expected total time lock to be %v" +
|
||||||
|
", but got %v instead",
|
||||||
|
expectedTimeLockHeight,
|
||||||
|
route.TotalTimeLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(testCase.expectedTimeLocks); i++ {
|
||||||
|
expectedTimeLockHeight := startingHeight +
|
||||||
|
testCase.expectedTimeLocks[i]
|
||||||
|
|
||||||
|
if expectedTimeLockHeight !=
|
||||||
|
route.Hops[i].OutgoingTimeLock {
|
||||||
|
|
||||||
|
t.Errorf("Expected time lock for hop " +
|
||||||
|
"%v to be %v, but got %v instead",
|
||||||
|
i, expectedTimeLockHeight,
|
||||||
|
route.Hops[i].OutgoingTimeLock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
route, err := newRoute(testCase.paymentAmount,
|
||||||
|
noFeeLimit,
|
||||||
|
sourceVertex, testCase.hops, startingHeight,
|
||||||
|
finalHopCLTV)
|
||||||
|
|
||||||
|
if testCase.expectError {
|
||||||
|
expectedCode := testCase.expectedErrorCode
|
||||||
|
if err == nil || !IsError(err, expectedCode) {
|
||||||
|
t.Errorf("expected newRoute to fail " +
|
||||||
|
"with error code %v, but got" +
|
||||||
|
"%v instead",
|
||||||
|
expectedCode, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to create path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertRoute(t, route)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewRoutePathTooLong(t *testing.T) {
|
func TestNewRoutePathTooLong(t *testing.T) {
|
||||||
t.Skip()
|
t.Skip()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user