@@ -7,16 +7,22 @@ import (
77 "time"
88
99 "github.com/btcsuite/btcd/btcec/v2"
10+ "github.com/btcsuite/btcd/chaincfg/chainhash"
11+ "github.com/btcsuite/btcd/wire"
1012 "github.com/lightninglabs/lndclient"
1113 "github.com/lightninglabs/loop/fsm"
1214 "github.com/lightninglabs/loop/staticaddr/address"
1315 "github.com/lightninglabs/loop/staticaddr/deposit"
1416 "github.com/lightninglabs/loop/staticaddr/script"
1517 "github.com/lightninglabs/loop/staticaddr/version"
18+ "github.com/lightninglabs/loop/swap"
19+ "github.com/lightninglabs/loop/swapserverrpc"
1620 "github.com/lightninglabs/loop/test"
1721 "github.com/lightningnetwork/lnd/invoices"
1822 "github.com/lightningnetwork/lnd/lntypes"
23+ "github.com/lightningnetwork/lnd/zpay32"
1924 "github.com/stretchr/testify/require"
25+ "google.golang.org/grpc"
2026)
2127
2228// TestMonitorInvoiceAndHtlcTxReRegistersOnConfErr ensures that an error from
@@ -123,6 +129,148 @@ func TestMonitorInvoiceAndHtlcTxReRegistersOnConfErr(t *testing.T) {
123129 }
124130}
125131
132+ // TestInitHtlcActionPreservesRouteHints asserts that static-address loop-in
133+ // propagates explicit route hints into the encoded swap invoice sent to the
134+ // server. This currently fails because lndclient.AddInvoice drops route hints.
135+ func TestInitHtlcActionPreservesRouteHints (t * testing.T ) {
136+ t .Parallel ()
137+
138+ mockLnd := test .NewMockLnd ()
139+ _ , serverKey := test .CreateKey (21 )
140+
141+ server := & mockStaticAddressServer {
142+ response : testStaticAddressLoopInResponse (
143+ serverKey .SerializeCompressed (),
144+ ),
145+ }
146+
147+ dep := & deposit.Deposit {
148+ OutPoint : wire.OutPoint {
149+ Hash : chainhash.Hash {1 },
150+ Index : 0 ,
151+ },
152+ Value : 500_000 ,
153+ }
154+
155+ loopIn := & StaticAddressLoopIn {
156+ Deposits : []* deposit.Deposit {dep },
157+ DepositOutpoints : []string {dep .OutPoint .String ()},
158+ SelectedAmount : dep .Value ,
159+ QuotedSwapFee : 1_000 ,
160+ RouteHints : testStaticAddressRouteHints (),
161+ InitiationHeight : uint32 (mockLnd .Height ),
162+ InitiationTime : time .Now (),
163+ PaymentTimeoutSeconds : 3_600 ,
164+ }
165+
166+ f := & FSM {
167+ StateMachine : & fsm.StateMachine {},
168+ cfg : & Config {
169+ Server : server ,
170+ DepositManager : & noopDepositManager {},
171+ LndClient : mockLnd .Client ,
172+ WalletKit : mockLnd .WalletKit ,
173+ ChainParams : mockLnd .ChainParams ,
174+ Store : & mockStore {},
175+ ValidateLoopInContract : testValidateLoopInContract ,
176+ MaxStaticAddrHtlcFeePercentage : 1 ,
177+ MaxStaticAddrHtlcBackupFeePercentage : 1 ,
178+ },
179+ loopIn : loopIn ,
180+ }
181+
182+ event := f .InitHtlcAction (t .Context (), nil )
183+ require .Equal (t , OnHtlcInitiated , event )
184+ require .Nil (t , f .LastActionError )
185+ require .NotNil (t , server .request )
186+
187+ _ , routeHints , _ , _ , err := swap .DecodeInvoice (
188+ mockLnd .ChainParams , server .request .SwapInvoice ,
189+ )
190+ require .NoError (t , err )
191+
192+ test .RequireRouteHintsEqual (t , loopIn .RouteHints , routeHints )
193+ }
194+
195+ // mockStaticAddressServer captures static-address loop-in requests in tests.
196+ type mockStaticAddressServer struct {
197+ swapserverrpc.StaticAddressServerClient
198+
199+ request * swapserverrpc.ServerStaticAddressLoopInRequest
200+ response * swapserverrpc.ServerStaticAddressLoopInResponse
201+ }
202+
203+ // ServerStaticAddressLoopIn records the request and returns the prepared
204+ // response.
205+ func (m * mockStaticAddressServer ) ServerStaticAddressLoopIn (
206+ _ context.Context , in * swapserverrpc.ServerStaticAddressLoopInRequest ,
207+ _ ... grpc.CallOption ) (* swapserverrpc.ServerStaticAddressLoopInResponse ,
208+ error ) {
209+
210+ m .request = in
211+
212+ return m .response , nil
213+ }
214+
215+ // testStaticAddressLoopInResponse returns a minimal successful server response
216+ // for InitHtlcAction tests.
217+ func testStaticAddressLoopInResponse (
218+ serverPubKey []byte ) * swapserverrpc.ServerStaticAddressLoopInResponse {
219+
220+ signingInfo := & swapserverrpc.ServerHtlcSigningInfo {
221+ FeeRate : 1 ,
222+ }
223+
224+ return & swapserverrpc.ServerStaticAddressLoopInResponse {
225+ HtlcServerPubKey : serverPubKey ,
226+ HtlcExpiry : 1_000 ,
227+ StandardHtlcInfo : signingInfo ,
228+ HighFeeHtlcInfo : signingInfo ,
229+ ExtremeFeeHtlcInfo : signingInfo ,
230+ }
231+ }
232+
233+ // testStaticAddressRouteHints returns deterministic route hints for static
234+ // loop-in invoice regression tests.
235+ func testStaticAddressRouteHints () [][]zpay32.HopHint {
236+ _ , pubKey1 := test .CreateKey (31 )
237+ _ , pubKey2 := test .CreateKey (32 )
238+ _ , pubKey3 := test .CreateKey (33 )
239+
240+ return [][]zpay32.HopHint {
241+ {
242+ {
243+ NodeID : pubKey1 ,
244+ ChannelID : 11 ,
245+ FeeBaseMSat : 101 ,
246+ FeeProportionalMillionths : 201 ,
247+ CLTVExpiryDelta : 31 ,
248+ },
249+ {
250+ NodeID : pubKey2 ,
251+ ChannelID : 12 ,
252+ FeeBaseMSat : 102 ,
253+ FeeProportionalMillionths : 202 ,
254+ CLTVExpiryDelta : 32 ,
255+ },
256+ },
257+ {
258+ {
259+ NodeID : pubKey3 ,
260+ ChannelID : 13 ,
261+ FeeBaseMSat : 103 ,
262+ FeeProportionalMillionths : 203 ,
263+ CLTVExpiryDelta : 33 ,
264+ },
265+ },
266+ }
267+ }
268+
269+ // testValidateLoopInContract accepts all server contract parameters in tests.
270+ func testValidateLoopInContract (_ int32 , _ int32 ) error {
271+ return nil
272+ }
273+
126274// mockAddressManager is a minimal AddressManager implementation used by the
127275// test FSM setup.
128276type mockAddressManager struct {
0 commit comments