@@ -273,6 +273,102 @@ func testSendOnionTwice(ht *lntest.HarnessTest) {
273273 require .Equal (ht , resp .ErrorMessage , htlcswitch .ErrDuplicateAdd .Error ())
274274}
275275
276+ func testTrackThenSend (ht * lntest.HarnessTest ) {
277+ alice := ht .NewNodeWithCoins ("Alice" , nil )
278+ bob := ht .NewNodeWithCoins ("Bob" , nil )
279+ carol := ht .NewNode ("Carol" , nil )
280+ dave := ht .NewNode ("Dave" , nil )
281+
282+ ht .EnsureConnected (alice , bob )
283+ ht .EnsureConnected (bob , carol )
284+ ht .EnsureConnected (carol , dave )
285+
286+ const chanAmt = btcutil .Amount (100000 )
287+
288+ ht .FundCoins (btcutil .SatoshiPerBitcoin , dave )
289+ ht .FundCoins (btcutil .SatoshiPerBitcoin , carol )
290+
291+ chanPointAliceBob := ht .OpenChannel (alice , bob , lntest.OpenChannelParams {Amt : chanAmt })
292+ defer ht .CloseChannel (alice , chanPointAliceBob )
293+
294+ chanPointBobCarol := ht .OpenChannel (bob , carol , lntest.OpenChannelParams {Amt : chanAmt })
295+ defer ht .CloseChannel (bob , chanPointBobCarol )
296+
297+ chanPointCarolDave := ht .OpenChannel (carol , dave , lntest.OpenChannelParams {Amt : chanAmt })
298+ defer ht .CloseChannel (carol , chanPointCarolDave )
299+
300+ ht .AssertChannelInGraph (alice , chanPointBobCarol )
301+ ht .AssertChannelInGraph (alice , chanPointCarolDave )
302+
303+ const paymentAmt = 10000
304+
305+ // Request an invoice from Dave.
306+ _ , rHashes , invoices := ht .CreatePayReqs (dave , paymentAmt , 1 )
307+ paymentHash := rHashes [0 ]
308+
309+ // Query for routes to Dave.
310+ routesReq := & lnrpc.QueryRoutesRequest {
311+ PubKey : dave .PubKeyStr ,
312+ Amt : paymentAmt ,
313+ }
314+ routes := alice .RPC .QueryRoutes (routesReq )
315+ route := routes .Routes [0 ]
316+ finalHop := route .Hops [len (route .Hops )- 1 ]
317+ finalHop .MppRecord = & lnrpc.MPPRecord {
318+ PaymentAddr : invoices [0 ].PaymentAddr ,
319+ TotalAmtMsat : int64 (lnwire .NewMSatFromSatoshis (paymentAmt )),
320+ }
321+
322+ // Build the onion.
323+ onionReq := & switchrpc.BuildOnionRequest {
324+ Route : route ,
325+ PaymentHash : paymentHash ,
326+ }
327+ onionResp := alice .RPC .BuildOnion (onionReq )
328+
329+ // Simulate a scenario in which the request to track an onion for a
330+ // given attempt ID is processed by the remote server *before* the
331+ // request to send the onion.
332+
333+ // Track the payment first. We expect to receive notice that no HTLC
334+ // by this attempt ID is in-flight or known about.
335+ trackReq := & switchrpc.TrackOnionRequest {
336+ AttemptId : 1 ,
337+ PaymentHash : paymentHash ,
338+ SessionKey : onionResp .SessionKey ,
339+ HopPubkeys : onionResp .HopPubkeys ,
340+ }
341+ trackResp := alice .RPC .TrackOnion (trackReq )
342+ require .False (ht , trackResp .Success , "expected failure on onion track" )
343+ require .Equal (ht , trackResp .ErrorCode ,
344+ switchrpc .ErrorCode_ERROR_CODE_PAYMENT_ID_NOT_FOUND ,
345+ "unexpected error code" )
346+ require .Equal (ht , trackResp .ErrorMessage ,
347+ htlcswitch .ErrPaymentIDNotFound .Error ())
348+
349+ // Now send the onion for the first time. This should fail as our server
350+ // enforces "send then track" ordering in order to protect rpc client
351+ // from duplicate onion attempts.
352+ //
353+ // NOTE(calvin): Undesired attempt duplication can also be prevented by
354+ // careful programming on the rpc client side, but handling this on the
355+ // rpc server allows us to avoid changing lnd ChannelRouter code.
356+ sendReq := & switchrpc.SendOnionRequest {
357+ FirstHopPubkey : bob .PubKey [:],
358+ Amount : route .TotalAmtMsat ,
359+ Timelock : route .TotalTimeLock ,
360+ PaymentHash : paymentHash ,
361+ OnionBlob : onionResp .OnionBlob ,
362+ AttemptId : 1 ,
363+ }
364+ resp := alice .RPC .SendOnion (sendReq )
365+ require .False (ht , resp .Success , "expected failure on onion send" )
366+ require .Equal (ht , resp .ErrorCode ,
367+ switchrpc .ErrorCode_ERROR_CODE_DUPLICATE_HTLC ,
368+ "unexpected error code" )
369+ require .Equal (ht , resp .ErrorMessage , htlcswitch .ErrDuplicateAdd .Error ())
370+ }
371+
276372func testTrackOnion (ht * lntest.HarnessTest ) {
277373 // Create a four-node context consisting of Alice, Bob and two new
278374 // nodes: Carol and Dave. This will provide a 4 node, 3 channel topology.
0 commit comments