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  }