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