cnct/test: add incoming contest resolver test

This commit is contained in:
Joost Jager 2019-04-16 11:33:30 +02:00
parent 16ff4e3ffa
commit 3d17c2bcfe
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
3 changed files with 345 additions and 0 deletions

View File

@ -0,0 +1,274 @@
package contractcourt
import (
"bytes"
"testing"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lntypes"
)
const (
testInitialBlockHeight = 100
testHtlcExpiry = 150
)
var (
testResPreimage = lntypes.Preimage{1, 2, 3}
testResHash = testResPreimage.Hash()
)
// TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc
// for which the preimage is already known initially.
func TestHtlcIncomingResolverFwdPreimageKnown(t *testing.T) {
t.Parallel()
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
ctx.resolve()
ctx.waitForResult(true)
}
// TestHtlcIncomingResolverFwdContestedSuccess tests resolution of a forwarded
// htlc for which the preimage becomes known after the resolver has been
// started.
func TestHtlcIncomingResolverFwdContestedSuccess(t *testing.T) {
t.Parallel()
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
ctx.resolve()
// Simulate a new block coming in. HTLC is not yet expired.
ctx.notifyEpoch(testInitialBlockHeight + 1)
ctx.witnessBeacon.preImageUpdates <- testResPreimage
ctx.waitForResult(true)
}
// TestHtlcIncomingResolverFwdContestedTimeout tests resolution of a forwarded
// htlc that times out after the resolver has been started.
func TestHtlcIncomingResolverFwdContestedTimeout(t *testing.T) {
t.Parallel()
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
ctx.resolve()
// Simulate a new block coming in. HTLC expires.
ctx.notifyEpoch(testHtlcExpiry)
ctx.waitForResult(false)
}
// TestHtlcIncomingResolverFwdTimeout tests resolution of a forwarded htlc that
// has already expired when the resolver starts.
func TestHtlcIncomingResolverFwdTimeout(t *testing.T) {
t.Parallel()
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
ctx.resolver.htlcExpiry = 90
ctx.resolve()
ctx.waitForResult(false)
}
// TestHtlcIncomingResolverExitSettle tests resolution of an exit hop htlc for
// which the invoice has already been settled when the resolver starts.
func TestHtlcIncomingResolverExitSettle(t *testing.T) {
t.Parallel()
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyEvent = &invoices.HodlEvent{
Hash: testResHash,
Preimage: &testResPreimage,
}
ctx.resolve()
data := <-ctx.registry.notifyChan
if data.expiry != testHtlcExpiry {
t.Fatal("incorrect expiry")
}
if data.currentHeight != testInitialBlockHeight {
t.Fatal("incorrect block height")
}
ctx.waitForResult(true)
}
// TestHtlcIncomingResolverExitCancel tests resolution of an exit hop htlc for
// an invoice that is already canceled when the resolver starts.
func TestHtlcIncomingResolverExitCancel(t *testing.T) {
t.Parallel()
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyEvent = &invoices.HodlEvent{
Hash: testResHash,
}
ctx.resolve()
ctx.waitForResult(false)
}
// TestHtlcIncomingResolverExitSettleHodl tests resolution of an exit hop htlc
// for a hodl invoice that is settled after the resolver has started.
func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) {
t.Parallel()
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.resolve()
notifyData := <-ctx.registry.notifyChan
notifyData.hodlChan <- invoices.HodlEvent{
Hash: testResHash,
Preimage: &testResPreimage,
}
ctx.waitForResult(true)
}
// TestHtlcIncomingResolverExitTimeoutHodl tests resolution of an exit hop htlc
// for a hodl invoice that times out.
func TestHtlcIncomingResolverExitTimeoutHodl(t *testing.T) {
t.Parallel()
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.resolve()
ctx.notifyEpoch(testHtlcExpiry)
ctx.waitForResult(false)
}
// TestHtlcIncomingResolverExitCancelHodl tests resolution of an exit hop htlc
// for a hodl invoice that is canceled after the resolver has started.
func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) {
t.Parallel()
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.resolve()
notifyData := <-ctx.registry.notifyChan
notifyData.hodlChan <- invoices.HodlEvent{
Hash: testResHash,
}
ctx.waitForResult(false)
}
type incomingResolverTestContext struct {
registry *mockRegistry
witnessBeacon *mockWitnessBeacon
resolver *htlcIncomingContestResolver
notifier *mockNotifier
resolveErr chan error
nextResolver ContractResolver
t *testing.T
}
func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
notifier := &mockNotifier{
epochChan: make(chan *chainntnfs.BlockEpoch),
spendChan: make(chan *chainntnfs.SpendDetail),
confChan: make(chan *chainntnfs.TxConfirmation),
}
witnessBeacon := newMockWitnessBeacon()
registry := &mockRegistry{
notifyChan: make(chan notifyExitHopData, 1),
}
checkPointChan := make(chan struct{}, 1)
chainCfg := ChannelArbitratorConfig{
ChainArbitratorConfig: ChainArbitratorConfig{
Notifier: notifier,
PreimageDB: witnessBeacon,
Registry: registry,
},
}
resolver := &htlcIncomingContestResolver{
htlcSuccessResolver: htlcSuccessResolver{
ResolverKit: ResolverKit{
ChannelArbitratorConfig: chainCfg,
Checkpoint: func(_ ContractResolver) error {
checkPointChan <- struct{}{}
return nil
},
},
htlcResolution: lnwallet.IncomingHtlcResolution{},
payHash: testResHash,
},
htlcExpiry: testHtlcExpiry,
}
return &incomingResolverTestContext{
registry: registry,
witnessBeacon: witnessBeacon,
resolver: resolver,
notifier: notifier,
t: t,
}
}
func (i *incomingResolverTestContext) resolve() {
// Start resolver.
i.resolveErr = make(chan error, 1)
go func() {
var err error
i.nextResolver, err = i.resolver.Resolve()
i.resolveErr <- err
}()
// Notify initial block height.
i.notifyEpoch(testInitialBlockHeight)
}
func (i *incomingResolverTestContext) notifyEpoch(height int32) {
i.notifier.epochChan <- &chainntnfs.BlockEpoch{
Height: height,
}
}
func (i *incomingResolverTestContext) waitForResult(expectSuccessRes bool) {
i.t.Helper()
err := <-i.resolveErr
if err != nil {
i.t.Fatal(err)
}
if !expectSuccessRes {
if err != nil {
i.t.Fatal("expected no next resolver")
}
return
}
successResolver, ok := i.nextResolver.(*htlcSuccessResolver)
if !ok {
i.t.Fatal("expected htlcSuccessResolver")
}
if successResolver.htlcResolution.Preimage != testResPreimage {
i.t.Fatal("invalid preimage")
}
successTx := successResolver.htlcResolution.SignedSuccessTx
if successTx != nil &&
!bytes.Equal(successTx.TxIn[0].Witness[3], testResPreimage[:]) {
i.t.Fatal("invalid preimage")
}
}

View File

@ -0,0 +1,45 @@
package contractcourt
import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
)
type notifyExitHopData struct {
payHash lntypes.Hash
paidAmount lnwire.MilliSatoshi
hodlChan chan<- interface{}
expiry uint32
currentHeight int32
}
type mockRegistry struct {
notifyChan chan notifyExitHopData
notifyErr error
notifyEvent *invoices.HodlEvent
}
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
hodlChan chan<- interface{}) (*invoices.HodlEvent, error) {
r.notifyChan <- notifyExitHopData{
hodlChan: hodlChan,
payHash: payHash,
paidAmount: paidAmount,
expiry: expiry,
currentHeight: currentHeight,
}
return r.notifyEvent, r.notifyErr
}
func (r *mockRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {}
func (r *mockRegistry) LookupInvoice(lntypes.Hash) (channeldb.Invoice, uint32,
error) {
return channeldb.Invoice{}, 0, channeldb.ErrInvoiceNotFound
}

View File

@ -0,0 +1,26 @@
package contractcourt
import (
"os"
"runtime/pprof"
"testing"
"time"
)
// timeout implements a test level timeout.
func timeout(t *testing.T) func() {
done := make(chan struct{})
go func() {
select {
case <-time.After(5 * time.Second):
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
panic("test timeout")
case <-done:
}
}()
return func() {
close(done)
}
}