lnd_estimate_route_fee_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. package itest
  2. import (
  3. "testing"
  4. "github.com/btcsuite/btcd/btcutil"
  5. "github.com/lightningnetwork/lnd/chainreg"
  6. "github.com/lightningnetwork/lnd/lnrpc"
  7. "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
  8. "github.com/lightningnetwork/lnd/lntest"
  9. "github.com/lightningnetwork/lnd/lntest/node"
  10. "github.com/lightningnetwork/lnd/routing"
  11. "github.com/stretchr/testify/require"
  12. )
  13. var (
  14. probeInitiator *node.HarnessNode
  15. probeAmount = btcutil.Amount(100_000)
  16. probeAmt = int64(probeAmount) * 1_000
  17. failureReasonNone = lnrpc.PaymentFailureReason_FAILURE_REASON_NONE
  18. failureReasonNoRoute = lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE //nolint:lll
  19. )
  20. const (
  21. ErrNoRouteInGraph = "unable to find a path to destination"
  22. )
  23. type estimateRouteFeeTestCase struct {
  24. // name is the name of the target test case.
  25. name string
  26. // probing is a flag that indicates whether the test case estimates fees
  27. // using the graph or by probing.
  28. probing bool
  29. // destination is the node that will receive the probe.
  30. destination *node.HarnessNode
  31. // routeHints are the route hints that will be used for the probe.
  32. routeHints []*lnrpc.RouteHint
  33. // expectedRoutingFeesMsat are the expected routing fees that will be
  34. // returned by the probe.
  35. expectedRoutingFeesMsat int64
  36. // expectedCltvDelta is the expected cltv delta that will be returned by
  37. // the fee estimation.
  38. expectedCltvDelta int64
  39. // expectedFailureReason is the expected payment failure reason that
  40. // will be returned by the probe.
  41. expectedFailureReason lnrpc.PaymentFailureReason
  42. // expectedError are the expected error that will be returned if the
  43. // probing fails.
  44. expectedError string
  45. }
  46. // testEstimateRouteFee tests the estimation of routing fees using either graph
  47. // data or sending out a probe payment.
  48. func testEstimateRouteFee(ht *lntest.HarnessTest) {
  49. mts := newMppTestScenario(ht)
  50. // We extend the regular mpp test scenario with a new node Paula. Paula
  51. // is connected to Bob and Eve through private channels.
  52. // /-------------\
  53. // _ Eve _ (private) \
  54. // / \ \
  55. // Alice -- Carol ---- Bob --------- Paula
  56. // \ / (private)
  57. // \__ Dave ____/
  58. //
  59. req := &mppOpenChannelRequest{
  60. amtAliceCarol: 200_000,
  61. amtAliceDave: 200_000,
  62. amtCarolBob: 200_000,
  63. amtCarolEve: 200_000,
  64. amtDaveBob: 200_000,
  65. amtEveBob: 200_000,
  66. }
  67. mts.openChannels(req)
  68. chanPointDaveBob := mts.channelPoints[4]
  69. chanPointEveBob := mts.channelPoints[5]
  70. // Alice will initiate all probe payments.
  71. probeInitiator = mts.alice
  72. paula := ht.NewNode("Paula", nil)
  73. // The channel from Bob to Paula actually doesn't have enough liquidity
  74. // to carry out the probe. We assume in normal operation that hop hints
  75. // added to the invoice always have enough liquidity, but here we check
  76. // that the prober uses the more expensive route.
  77. ht.EnsureConnected(mts.bob, paula)
  78. channelPointBobPaula := ht.OpenChannel(
  79. mts.bob, paula, lntest.OpenChannelParams{
  80. Private: true,
  81. Amt: 90_000,
  82. PushAmt: 69_000,
  83. },
  84. )
  85. ht.EnsureConnected(mts.eve, paula)
  86. channelPointEvePaula := ht.OpenChannel(
  87. mts.eve, paula, lntest.OpenChannelParams{
  88. Private: true,
  89. Amt: 1_000_000,
  90. },
  91. )
  92. bobsPrivChannels := ht.Bob.RPC.ListChannels(&lnrpc.ListChannelsRequest{
  93. PrivateOnly: true,
  94. })
  95. require.Len(ht, bobsPrivChannels.Channels, 1)
  96. bobPaulaChanID := bobsPrivChannels.Channels[0].ChanId
  97. evesPrivChannels := mts.eve.RPC.ListChannels(&lnrpc.ListChannelsRequest{
  98. PrivateOnly: true,
  99. })
  100. require.Len(ht, evesPrivChannels.Channels, 1)
  101. evePaulaChanID := evesPrivChannels.Channels[0].ChanId
  102. // Let's disable the paths from Alice to Bob through Dave and Eve with
  103. // high fees. This ensures that the path estimates are based on Carol's
  104. // channel to Bob for the first set of tests.
  105. expectedPolicy := &lnrpc.RoutingPolicy{
  106. FeeBaseMsat: 200_000,
  107. FeeRateMilliMsat: int64(0.001 * 1_000_000),
  108. TimeLockDelta: 40,
  109. MinHtlc: 1000, // default value
  110. MaxHtlcMsat: 133_650_000,
  111. }
  112. mts.dave.UpdateGlobalPolicy(expectedPolicy)
  113. ht.AssertChannelPolicyUpdate(
  114. mts.alice, mts.dave, expectedPolicy, chanPointDaveBob, false,
  115. )
  116. expectedPolicy.FeeBaseMsat = 500_000
  117. mts.eve.UpdateGlobalPolicy(expectedPolicy)
  118. ht.AssertChannelPolicyUpdate(
  119. mts.alice, mts.eve, expectedPolicy, chanPointEveBob, false,
  120. )
  121. var (
  122. bobHopHint = &lnrpc.HopHint{
  123. NodeId: mts.bob.PubKeyStr,
  124. FeeBaseMsat: 1_000,
  125. FeeProportionalMillionths: 1,
  126. CltvExpiryDelta: 100,
  127. ChanId: bobPaulaChanID,
  128. }
  129. bobExpHint = &lnrpc.HopHint{
  130. NodeId: mts.bob.PubKeyStr,
  131. FeeBaseMsat: 2000,
  132. FeeProportionalMillionths: 2000,
  133. CltvExpiryDelta: 80,
  134. ChanId: bobPaulaChanID,
  135. }
  136. eveHopHint = &lnrpc.HopHint{
  137. NodeId: mts.eve.PubKeyStr,
  138. FeeBaseMsat: 1_000_000,
  139. FeeProportionalMillionths: 1,
  140. CltvExpiryDelta: 200,
  141. ChanId: evePaulaChanID,
  142. }
  143. singleRouteHint = []*lnrpc.RouteHint{
  144. {
  145. HopHints: []*lnrpc.HopHint{
  146. bobHopHint,
  147. },
  148. },
  149. }
  150. lspDifferentFeesHints = []*lnrpc.RouteHint{
  151. {
  152. HopHints: []*lnrpc.HopHint{
  153. bobHopHint,
  154. },
  155. },
  156. {
  157. HopHints: []*lnrpc.HopHint{
  158. bobExpHint,
  159. },
  160. },
  161. }
  162. nonLspProbingRouteHints = []*lnrpc.RouteHint{
  163. {
  164. HopHints: []*lnrpc.HopHint{
  165. eveHopHint,
  166. },
  167. },
  168. {
  169. HopHints: []*lnrpc.HopHint{
  170. bobHopHint,
  171. },
  172. },
  173. }
  174. )
  175. defaultTimelock := int64(chainreg.DefaultBitcoinTimeLockDelta)
  176. // Going A -> Carol -> Bob
  177. feeStandardSingleHop := int64(1_000) + probeAmt/1_000_000
  178. // Going A -> Carol -> Bob -> Paula/no node
  179. feeBP := int64(bobHopHint.FeeBaseMsat) +
  180. int64(bobHopHint.FeeProportionalMillionths)*(probeAmt)/1_000_000
  181. deltaBP := int64(bobHopHint.CltvExpiryDelta)
  182. feeCB := int64(1_000) + (probeAmt+feeBP)/1_000_000
  183. deltaCB := defaultTimelock
  184. deltaACBP := deltaCB + deltaBP
  185. feeACBP := feeCB + feeBP
  186. // The expensive alternative to Bob.
  187. expFeeBP := int64(bobExpHint.FeeBaseMsat) +
  188. int64(bobExpHint.FeeProportionalMillionths)*(probeAmt)/1_000_000
  189. expFeeCB := int64(1_000) + (probeAmt+expFeeBP)/1_000_000
  190. expensiveFeeACBP := expFeeCB + expFeeBP
  191. // Going A -> Carol -> Eve -> Paula
  192. feeEP := int64(eveHopHint.FeeBaseMsat) +
  193. int64(eveHopHint.FeeProportionalMillionths)*(probeAmt)/1_000_000
  194. deltaEP := int64(eveHopHint.CltvExpiryDelta)
  195. feeCE := int64(1_000) + (probeAmt+feeEP)/1_000_000
  196. deltaCE := defaultTimelock
  197. feeACEP := feeEP + feeCE
  198. deltaACEP := deltaCE + deltaEP
  199. initialBlockHeight := int64(mts.alice.RPC.GetInfo().BlockHeight)
  200. // Locktime is always composed of the initial block height and the
  201. // time lock delta for the first hop. Additionally, there's a block
  202. // accounting for block height variance.
  203. locktime := initialBlockHeight + defaultTimelock +
  204. int64(routing.BlockPadding)
  205. var testCases = []*estimateRouteFeeTestCase{
  206. // Single hop payment is free.
  207. {
  208. name: "graph based estimate, 0 fee",
  209. probing: false,
  210. destination: mts.dave,
  211. expectedRoutingFeesMsat: 0,
  212. expectedCltvDelta: locktime,
  213. expectedFailureReason: failureReasonNone,
  214. },
  215. // 1000 msat base fee + 100 msat(1ppm*100_000sat)
  216. {
  217. name: "graph based estimate",
  218. probing: false,
  219. destination: mts.bob,
  220. expectedRoutingFeesMsat: feeStandardSingleHop,
  221. expectedCltvDelta: locktime + deltaCB,
  222. expectedFailureReason: failureReasonNone,
  223. },
  224. // We expect the same result as the graph based estimate to Bob.
  225. {
  226. name: "probe based estimate, empty " +
  227. "route hint",
  228. probing: true,
  229. destination: mts.bob,
  230. routeHints: []*lnrpc.RouteHint{},
  231. expectedRoutingFeesMsat: feeStandardSingleHop,
  232. expectedCltvDelta: locktime + deltaCB,
  233. expectedFailureReason: failureReasonNone,
  234. },
  235. // We expect the previous probing results adjusted by Paula's
  236. // hop data.
  237. {
  238. name: "probe based estimate, single" +
  239. " route hint",
  240. probing: true,
  241. destination: paula,
  242. routeHints: singleRouteHint,
  243. expectedRoutingFeesMsat: feeACBP,
  244. expectedCltvDelta: locktime + deltaACBP,
  245. expectedFailureReason: failureReasonNone,
  246. },
  247. // With multiple route hints and lsp detected, we expect the
  248. // highest of fee settings to be used for estimation.
  249. {
  250. name: "probe based estimate, " +
  251. "multiple route hints, diff lsp fees",
  252. probing: true,
  253. destination: paula,
  254. routeHints: lspDifferentFeesHints,
  255. expectedRoutingFeesMsat: expensiveFeeACBP,
  256. expectedCltvDelta: locktime + deltaACBP,
  257. expectedFailureReason: failureReasonNone,
  258. },
  259. // A destination without channels and an existing hop hint hop
  260. // should result in the same estimate as if the hidden node was
  261. // connected through a channel. This ensures that the probe is
  262. // actually just send to the LSP and not the destination.
  263. {
  264. name: "single hop hint, destination " +
  265. "without channels",
  266. probing: true,
  267. destination: ht.NewNode(
  268. "ImWithoutChannels", nil,
  269. ),
  270. routeHints: singleRouteHint,
  271. expectedRoutingFeesMsat: feeACBP,
  272. expectedCltvDelta: locktime + deltaACBP,
  273. expectedFailureReason: failureReasonNone,
  274. },
  275. // Test lnd native hop processing with non lsp probing. Paula is
  276. // lacking liqudity in the channel to Bob, so Eve is used, but
  277. // it has higher fees.
  278. {
  279. name: "probe based estimate, non " +
  280. "lsp",
  281. probing: true,
  282. destination: paula,
  283. routeHints: nonLspProbingRouteHints,
  284. expectedRoutingFeesMsat: feeACEP,
  285. expectedCltvDelta: locktime + deltaACEP,
  286. expectedFailureReason: failureReasonNone,
  287. },
  288. // We expect a NO_ROUTE error if route hints to paula aren't
  289. // provided while probing the graph.
  290. {
  291. name: "no route via graph",
  292. probing: false,
  293. destination: paula,
  294. expectedError: ErrNoRouteInGraph,
  295. },
  296. // We expect a NO_ROUTE error if route hints to paula aren't
  297. // provided while sending a probe payment.
  298. {
  299. name: "no route via probe",
  300. probing: true,
  301. destination: paula,
  302. expectedRoutingFeesMsat: 0,
  303. expectedCltvDelta: 0,
  304. expectedFailureReason: failureReasonNoRoute,
  305. },
  306. }
  307. for _, testCase := range testCases {
  308. success := ht.Run(
  309. testCase.name, func(tt *testing.T) {
  310. runFeeEstimationTestCase(ht, testCase)
  311. },
  312. )
  313. if !success {
  314. break
  315. }
  316. }
  317. mts.ht.CloseChannelAssertPending(mts.bob, channelPointBobPaula, false)
  318. mts.ht.CloseChannelAssertPending(mts.eve, channelPointEvePaula, false)
  319. mts.closeChannels()
  320. }
  321. // runTestCase runs a single test case asserting that test conditions are met.
  322. func runFeeEstimationTestCase(ht *lntest.HarnessTest,
  323. tc *estimateRouteFeeTestCase) {
  324. // Legacy graph based fee estimation.
  325. var feeReq *routerrpc.RouteFeeRequest
  326. if tc.probing {
  327. payReqs, _, _ := ht.CreatePayReqs(
  328. tc.destination, probeAmount, 1, tc.routeHints...,
  329. )
  330. feeReq = &routerrpc.RouteFeeRequest{
  331. PaymentRequest: payReqs[0],
  332. Timeout: 10,
  333. }
  334. } else {
  335. feeReq = &routerrpc.RouteFeeRequest{
  336. Dest: tc.destination.PubKey[:],
  337. AmtSat: int64(probeAmount),
  338. }
  339. }
  340. ctx := ht.Context()
  341. // Kick off the parametrized fee estimation.
  342. resp, err := probeInitiator.RPC.Router.EstimateRouteFee(ctx, feeReq)
  343. if err != nil {
  344. require.ErrorContains(ht, err, tc.expectedError)
  345. return
  346. }
  347. require.Equal(ht, tc.expectedFailureReason, resp.FailureReason)
  348. require.Equal(
  349. ht, tc.expectedRoutingFeesMsat, resp.RoutingFeeMsat,
  350. "routing fees",
  351. )
  352. require.Equal(
  353. ht, tc.expectedCltvDelta, resp.TimeLockDelay,
  354. "cltv delta",
  355. )
  356. }