github.com/decred/dcrlnd@v0.7.6/contractcourt/htlc_incoming_resolver_test.go (about) 1 package contractcourt 2 3 import ( 4 "bytes" 5 "io" 6 "io/ioutil" 7 "testing" 8 9 "github.com/decred/dcrlnd/chainntnfs" 10 "github.com/decred/dcrlnd/channeldb" 11 "github.com/decred/dcrlnd/htlcswitch/hop" 12 "github.com/decred/dcrlnd/input" 13 "github.com/decred/dcrlnd/invoices" 14 "github.com/decred/dcrlnd/kvdb" 15 "github.com/decred/dcrlnd/lntest/mock" 16 "github.com/decred/dcrlnd/lntypes" 17 "github.com/decred/dcrlnd/lnwallet" 18 "github.com/decred/dcrlnd/lnwire" 19 sphinx "github.com/decred/lightning-onion/v4" 20 ) 21 22 const ( 23 testInitialBlockHeight = 100 24 testHtlcExpiry = 150 25 ) 26 27 var ( 28 testResPreimage = lntypes.Preimage{1, 2, 3} 29 testResHash = testResPreimage.Hash() 30 testResCircuitKey = channeldb.CircuitKey{} 31 testOnionBlob = []byte{4, 5, 6} 32 testAcceptHeight int32 = 1234 33 testHtlcAmount = 2300 34 ) 35 36 // TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc 37 // for which the preimage is already known initially. 38 func TestHtlcIncomingResolverFwdPreimageKnown(t *testing.T) { 39 t.Parallel() 40 defer timeout(t)() 41 42 ctx := newIncomingResolverTestContext(t, false) 43 ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage 44 ctx.resolve() 45 ctx.waitForResult(true) 46 } 47 48 // TestHtlcIncomingResolverFwdContestedSuccess tests resolution of a forwarded 49 // htlc for which the preimage becomes known after the resolver has been 50 // started. 51 func TestHtlcIncomingResolverFwdContestedSuccess(t *testing.T) { 52 t.Parallel() 53 defer timeout(t)() 54 55 ctx := newIncomingResolverTestContext(t, false) 56 ctx.resolve() 57 58 // Simulate a new block coming in. HTLC is not yet expired. 59 ctx.notifyEpoch(testInitialBlockHeight + 1) 60 61 ctx.witnessBeacon.preImageUpdates <- testResPreimage 62 ctx.waitForResult(true) 63 } 64 65 // TestHtlcIncomingResolverFwdContestedTimeout tests resolution of a forwarded 66 // htlc that times out after the resolver has been started. 67 func TestHtlcIncomingResolverFwdContestedTimeout(t *testing.T) { 68 t.Parallel() 69 defer timeout(t)() 70 71 ctx := newIncomingResolverTestContext(t, false) 72 73 // Replace our checkpoint with one which will push reports into a 74 // channel for us to consume. We replace this function on the resolver 75 // itself because it is created by the test context. 76 reportChan := make(chan *channeldb.ResolverReport) 77 ctx.resolver.Checkpoint = func(_ ContractResolver, 78 reports ...*channeldb.ResolverReport) error { 79 80 // Send all of our reports into the channel. 81 for _, report := range reports { 82 reportChan <- report 83 } 84 85 return nil 86 } 87 88 ctx.resolve() 89 90 // Simulate a new block coming in. HTLC expires. 91 ctx.notifyEpoch(testHtlcExpiry) 92 93 // Assert that we have a failure resolution because our invoice was 94 // cancelled. 95 assertResolverReport(t, reportChan, &channeldb.ResolverReport{ 96 Amount: lnwire.MilliAtom(testHtlcAmount).ToAtoms(), 97 ResolverType: channeldb.ResolverTypeIncomingHtlc, 98 ResolverOutcome: channeldb.ResolverOutcomeTimeout, 99 }) 100 101 ctx.waitForResult(false) 102 } 103 104 // TestHtlcIncomingResolverFwdTimeout tests resolution of a forwarded htlc that 105 // has already expired when the resolver starts. 106 func TestHtlcIncomingResolverFwdTimeout(t *testing.T) { 107 t.Parallel() 108 defer timeout(t)() 109 110 ctx := newIncomingResolverTestContext(t, true) 111 ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage 112 ctx.resolver.htlcExpiry = 90 113 ctx.resolve() 114 ctx.waitForResult(false) 115 } 116 117 // TestHtlcIncomingResolverExitSettle tests resolution of an exit hop htlc for 118 // which the invoice has already been settled when the resolver starts. 119 func TestHtlcIncomingResolverExitSettle(t *testing.T) { 120 t.Parallel() 121 defer timeout(t)() 122 123 ctx := newIncomingResolverTestContext(t, true) 124 ctx.registry.notifyResolution = invoices.NewSettleResolution( 125 testResPreimage, testResCircuitKey, testAcceptHeight, 126 invoices.ResultReplayToSettled, 127 ) 128 129 ctx.resolve() 130 131 data := <-ctx.registry.notifyChan 132 if data.expiry != testHtlcExpiry { 133 t.Fatal("incorrect expiry") 134 } 135 if data.currentHeight != testInitialBlockHeight { 136 t.Fatal("incorrect block height") 137 } 138 139 ctx.waitForResult(true) 140 141 if !bytes.Equal( 142 ctx.onionProcessor.offeredOnionBlob, testOnionBlob, 143 ) { 144 t.Fatal("unexpected onion blob") 145 } 146 } 147 148 // TestHtlcIncomingResolverExitCancel tests resolution of an exit hop htlc for 149 // an invoice that is already canceled when the resolver starts. 150 func TestHtlcIncomingResolverExitCancel(t *testing.T) { 151 t.Parallel() 152 defer timeout(t)() 153 154 ctx := newIncomingResolverTestContext(t, true) 155 ctx.registry.notifyResolution = invoices.NewFailResolution( 156 testResCircuitKey, testAcceptHeight, 157 invoices.ResultInvoiceAlreadyCanceled, 158 ) 159 160 ctx.resolve() 161 ctx.waitForResult(false) 162 } 163 164 // TestHtlcIncomingResolverExitSettleHodl tests resolution of an exit hop htlc 165 // for a hodl invoice that is settled after the resolver has started. 166 func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) { 167 t.Parallel() 168 defer timeout(t)() 169 170 ctx := newIncomingResolverTestContext(t, true) 171 ctx.resolve() 172 173 notifyData := <-ctx.registry.notifyChan 174 notifyData.hodlChan <- invoices.NewSettleResolution( 175 testResPreimage, testResCircuitKey, testAcceptHeight, 176 invoices.ResultSettled, 177 ) 178 179 ctx.waitForResult(true) 180 } 181 182 // TestHtlcIncomingResolverExitTimeoutHodl tests resolution of an exit hop htlc 183 // for a hodl invoice that times out. 184 func TestHtlcIncomingResolverExitTimeoutHodl(t *testing.T) { 185 t.Parallel() 186 defer timeout(t)() 187 188 ctx := newIncomingResolverTestContext(t, true) 189 190 // Replace our checkpoint with one which will push reports into a 191 // channel for us to consume. We replace this function on the resolver 192 // itself because it is created by the test context. 193 reportChan := make(chan *channeldb.ResolverReport) 194 ctx.resolver.Checkpoint = func(_ ContractResolver, 195 reports ...*channeldb.ResolverReport) error { 196 197 // Send all of our reports into the channel. 198 for _, report := range reports { 199 reportChan <- report 200 } 201 202 return nil 203 } 204 205 ctx.resolve() 206 ctx.notifyEpoch(testHtlcExpiry) 207 208 // Assert that we have a failure resolution because our invoice was 209 // cancelled. 210 assertResolverReport(t, reportChan, &channeldb.ResolverReport{ 211 Amount: lnwire.MilliAtom(testHtlcAmount).ToAtoms(), 212 ResolverType: channeldb.ResolverTypeIncomingHtlc, 213 ResolverOutcome: channeldb.ResolverOutcomeTimeout, 214 }) 215 216 ctx.waitForResult(false) 217 } 218 219 // TestHtlcIncomingResolverExitCancelHodl tests resolution of an exit hop htlc 220 // for a hodl invoice that is canceled after the resolver has started. 221 func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) { 222 t.Parallel() 223 defer timeout(t)() 224 225 ctx := newIncomingResolverTestContext(t, true) 226 227 // Replace our checkpoint with one which will push reports into a 228 // channel for us to consume. We replace this function on the resolver 229 // itself because it is created by the test context. 230 reportChan := make(chan *channeldb.ResolverReport) 231 ctx.resolver.Checkpoint = func(_ ContractResolver, 232 reports ...*channeldb.ResolverReport) error { 233 234 // Send all of our reports into the channel. 235 for _, report := range reports { 236 reportChan <- report 237 } 238 239 return nil 240 } 241 242 ctx.resolve() 243 notifyData := <-ctx.registry.notifyChan 244 notifyData.hodlChan <- invoices.NewFailResolution( 245 testResCircuitKey, testAcceptHeight, invoices.ResultCanceled, 246 ) 247 248 // Assert that we have a failure resolution because our invoice was 249 // cancelled. 250 assertResolverReport(t, reportChan, &channeldb.ResolverReport{ 251 Amount: lnwire.MilliAtom(testHtlcAmount).ToAtoms(), 252 ResolverType: channeldb.ResolverTypeIncomingHtlc, 253 ResolverOutcome: channeldb.ResolverOutcomeAbandoned, 254 }) 255 256 ctx.waitForResult(false) 257 } 258 259 type mockHopIterator struct { 260 isExit bool 261 hop.Iterator 262 } 263 264 func (h *mockHopIterator) HopPayload() (*hop.Payload, error) { 265 var nextAddress [8]byte 266 if !h.isExit { 267 nextAddress = [8]byte{0x01} 268 } 269 270 return hop.NewLegacyPayload(&sphinx.HopData{ 271 Realm: [1]byte{}, 272 NextAddress: nextAddress, 273 ForwardAmount: 100, 274 OutgoingCltv: 40, 275 ExtraBytes: [12]byte{}, 276 }), nil 277 } 278 279 type mockOnionProcessor struct { 280 isExit bool 281 offeredOnionBlob []byte 282 } 283 284 func (o *mockOnionProcessor) ReconstructHopIterator(r io.Reader, rHash []byte) ( 285 hop.Iterator, error) { 286 287 data, err := ioutil.ReadAll(r) 288 if err != nil { 289 return nil, err 290 } 291 o.offeredOnionBlob = data 292 293 return &mockHopIterator{isExit: o.isExit}, nil 294 } 295 296 type incomingResolverTestContext struct { 297 registry *mockRegistry 298 witnessBeacon *mockWitnessBeacon 299 resolver *htlcIncomingContestResolver 300 notifier *mock.ChainNotifier 301 onionProcessor *mockOnionProcessor 302 resolveErr chan error 303 nextResolver ContractResolver 304 t *testing.T 305 } 306 307 func newIncomingResolverTestContext(t *testing.T, isExit bool) *incomingResolverTestContext { 308 notifier := &mock.ChainNotifier{ 309 EpochChan: make(chan *chainntnfs.BlockEpoch), 310 SpendChan: make(chan *chainntnfs.SpendDetail), 311 ConfChan: make(chan *chainntnfs.TxConfirmation), 312 } 313 witnessBeacon := newMockWitnessBeacon() 314 registry := &mockRegistry{ 315 notifyChan: make(chan notifyExitHopData, 1), 316 } 317 318 onionProcessor := &mockOnionProcessor{isExit: isExit} 319 320 checkPointChan := make(chan struct{}, 1) 321 322 chainCfg := ChannelArbitratorConfig{ 323 ChainArbitratorConfig: ChainArbitratorConfig{ 324 Notifier: notifier, 325 PreimageDB: witnessBeacon, 326 Registry: registry, 327 OnionProcessor: onionProcessor, 328 }, 329 PutResolverReport: func(_ kvdb.RwTx, 330 _ *channeldb.ResolverReport) error { 331 332 return nil 333 }, 334 } 335 336 cfg := ResolverConfig{ 337 ChannelArbitratorConfig: chainCfg, 338 Checkpoint: func(_ ContractResolver, 339 _ ...*channeldb.ResolverReport) error { 340 341 checkPointChan <- struct{}{} 342 return nil 343 }, 344 } 345 resolver := &htlcIncomingContestResolver{ 346 htlcSuccessResolver: &htlcSuccessResolver{ 347 contractResolverKit: *newContractResolverKit(cfg), 348 htlcResolution: lnwallet.IncomingHtlcResolution{}, 349 htlc: channeldb.HTLC{ 350 Amt: lnwire.MilliAtom(testHtlcAmount), 351 RHash: testResHash, 352 OnionBlob: testOnionBlob, 353 }, 354 }, 355 htlcExpiry: testHtlcExpiry, 356 } 357 358 return &incomingResolverTestContext{ 359 registry: registry, 360 witnessBeacon: witnessBeacon, 361 resolver: resolver, 362 notifier: notifier, 363 onionProcessor: onionProcessor, 364 t: t, 365 } 366 } 367 368 func (i *incomingResolverTestContext) resolve() { 369 // Start resolver. 370 i.resolveErr = make(chan error, 1) 371 go func() { 372 var err error 373 i.nextResolver, err = i.resolver.Resolve() 374 i.resolveErr <- err 375 }() 376 377 // Notify initial block height. 378 i.notifyEpoch(testInitialBlockHeight) 379 } 380 381 func (i *incomingResolverTestContext) notifyEpoch(height int32) { 382 i.notifier.EpochChan <- &chainntnfs.BlockEpoch{ 383 Height: height, 384 } 385 } 386 387 func (i *incomingResolverTestContext) waitForResult(expectSuccessRes bool) { 388 i.t.Helper() 389 390 err := <-i.resolveErr 391 if err != nil { 392 i.t.Fatal(err) 393 } 394 395 if !expectSuccessRes { 396 if i.nextResolver != nil { 397 i.t.Fatal("expected no next resolver") 398 } 399 return 400 } 401 402 successResolver, ok := i.nextResolver.(*htlcSuccessResolver) 403 if !ok { 404 i.t.Fatal("expected htlcSuccessResolver") 405 } 406 407 if successResolver.htlcResolution.Preimage != testResPreimage { 408 i.t.Fatal("invalid preimage") 409 } 410 411 successTx := successResolver.htlcResolution.SignedSuccessTx 412 if successTx == nil { 413 return 414 } 415 416 witnessStack, err := input.SigScriptToWitnessStack( 417 successTx.TxIn[0].SignatureScript, 418 ) 419 if err != nil { 420 i.t.Fatalf("unable to parse sigScript into stack: %v", err) 421 } 422 if !bytes.Equal(witnessStack[2], testResPreimage[:]) { 423 424 i.t.Fatal("invalid preimage") 425 } 426 }