Skip to content

Commit f274c68

Browse files
stariushieblmi
authored andcommitted
staticaddr: add unit test for HTLC re-registration
1 parent 1dd145b commit f274c68

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

staticaddr/loopin/actions_test.go

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package loopin
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
"time"
8+
9+
"github.com/btcsuite/btcd/btcec/v2"
10+
"github.com/lightninglabs/lndclient"
11+
"github.com/lightninglabs/loop/fsm"
12+
"github.com/lightninglabs/loop/staticaddr/address"
13+
"github.com/lightninglabs/loop/staticaddr/deposit"
14+
"github.com/lightninglabs/loop/staticaddr/script"
15+
"github.com/lightninglabs/loop/staticaddr/version"
16+
"github.com/lightninglabs/loop/test"
17+
"github.com/lightningnetwork/lnd/invoices"
18+
"github.com/lightningnetwork/lnd/lntypes"
19+
"github.com/stretchr/testify/require"
20+
)
21+
22+
// TestMonitorInvoiceAndHtlcTxReRegistersOnConfErr ensures that an error from
23+
// the HTLC confirmation subscription triggers a re-registration. Without the
24+
// regression fix, only the initial registration would be performed and the
25+
// test would time out waiting for the second one.
26+
func TestMonitorInvoiceAndHtlcTxReRegistersOnConfErr(t *testing.T) {
27+
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
28+
defer cancel()
29+
30+
mockLnd := test.NewMockLnd()
31+
32+
clientKey, err := btcec.NewPrivateKey()
33+
require.NoError(t, err)
34+
serverKey, err := btcec.NewPrivateKey()
35+
require.NoError(t, err)
36+
37+
swapHash := lntypes.Hash{1, 2, 3}
38+
39+
loopIn := &StaticAddressLoopIn{
40+
SwapHash: swapHash,
41+
HtlcCltvExpiry: 2_000,
42+
InitiationHeight: uint32(mockLnd.Height),
43+
InitiationTime: time.Now(),
44+
ProtocolVersion: version.ProtocolVersion_V0,
45+
ClientPubkey: clientKey.PubKey(),
46+
ServerPubkey: serverKey.PubKey(),
47+
PaymentTimeoutSeconds: 3_600,
48+
}
49+
loopIn.SetState(MonitorInvoiceAndHtlcTx)
50+
51+
// Seed the mock invoice store so LookupInvoice succeeds.
52+
mockLnd.Invoices[swapHash] = &lndclient.Invoice{
53+
Hash: swapHash,
54+
State: invoices.ContractOpen,
55+
}
56+
57+
cfg := &Config{
58+
AddressManager: &mockAddressManager{
59+
params: &address.Parameters{
60+
ClientPubkey: clientKey.PubKey(),
61+
ServerPubkey: serverKey.PubKey(),
62+
ProtocolVersion: version.ProtocolVersion_V0,
63+
},
64+
},
65+
ChainNotifier: mockLnd.ChainNotifier,
66+
DepositManager: &noopDepositManager{},
67+
InvoicesClient: mockLnd.LndServices.Invoices,
68+
LndClient: mockLnd.Client,
69+
ChainParams: mockLnd.ChainParams,
70+
}
71+
72+
f, err := NewFSM(ctx, loopIn, cfg, false)
73+
require.NoError(t, err)
74+
75+
resultChan := make(chan fsm.EventType, 1)
76+
go func() {
77+
resultChan <- f.MonitorInvoiceAndHtlcTxAction(ctx, nil)
78+
}()
79+
80+
// Capture the invoice subscription the action registers so we can feed
81+
// an update later and let the action exit.
82+
var invSub *test.SingleInvoiceSubscription
83+
select {
84+
case invSub = <-mockLnd.SingleInvoiceSubcribeChannel:
85+
case <-ctx.Done():
86+
t.Fatalf("invoice subscription not registered: %v", ctx.Err())
87+
}
88+
89+
// The first confirmation registration should happen immediately.
90+
var firstReg *test.ConfRegistration
91+
select {
92+
case firstReg = <-mockLnd.RegisterConfChannel:
93+
case <-ctx.Done():
94+
t.Fatalf("htlc conf registration not received: %v", ctx.Err())
95+
}
96+
97+
// Force the confirmation stream to error so the FSM re-registers.
98+
firstReg.ErrChan <- errors.New("test htlc conf error")
99+
100+
// FSM registers again, otherwise it would time out.
101+
var secondReg *test.ConfRegistration
102+
select {
103+
case secondReg = <-mockLnd.RegisterConfChannel:
104+
case <-ctx.Done():
105+
t.Fatalf("htlc conf was not re-registered: %v", ctx.Err())
106+
}
107+
108+
require.NotEqual(t, firstReg, secondReg)
109+
110+
// Settle the invoice to let the action exit.
111+
invSub.Update <- lndclient.InvoiceUpdate{
112+
Invoice: lndclient.Invoice{
113+
Hash: swapHash,
114+
State: invoices.ContractSettled,
115+
},
116+
}
117+
118+
select {
119+
case event := <-resultChan:
120+
require.Equal(t, OnPaymentReceived, event)
121+
case <-ctx.Done():
122+
t.Fatalf("fsm did not return: %v", ctx.Err())
123+
}
124+
}
125+
126+
// mockAddressManager is a minimal AddressManager implementation used by the
127+
// test FSM setup.
128+
type mockAddressManager struct {
129+
params *address.Parameters
130+
}
131+
132+
// GetStaticAddressParameters returns the configured address parameters.
133+
func (m *mockAddressManager) GetStaticAddressParameters(_ context.Context) (
134+
*address.Parameters, error) {
135+
136+
return m.params, nil
137+
}
138+
139+
// GetStaticAddress is unused for this test and returns nil.
140+
func (m *mockAddressManager) GetStaticAddress(_ context.Context) (
141+
*script.StaticAddress, error) {
142+
143+
return nil, nil
144+
}
145+
146+
// noopDepositManager is a stub DepositManager used to satisfy FSM config.
147+
type noopDepositManager struct{}
148+
149+
// GetAllDeposits implements DepositManager with a no-op.
150+
func (n *noopDepositManager) GetAllDeposits(_ context.Context) (
151+
[]*deposit.Deposit, error) {
152+
153+
return nil, nil
154+
}
155+
156+
// AllStringOutpointsActiveDeposits implements DepositManager with a no-op.
157+
func (n *noopDepositManager) AllStringOutpointsActiveDeposits(
158+
_ []string, _ fsm.StateType) ([]*deposit.Deposit, bool) {
159+
160+
return nil, false
161+
}
162+
163+
// TransitionDeposits implements DepositManager with a no-op.
164+
func (n *noopDepositManager) TransitionDeposits(context.Context,
165+
[]*deposit.Deposit, fsm.EventType, fsm.StateType) error {
166+
167+
return nil
168+
}
169+
170+
// DepositsForOutpoints implements DepositManager with a no-op.
171+
func (n *noopDepositManager) DepositsForOutpoints(context.Context, []string,
172+
bool) ([]*deposit.Deposit, error) {
173+
174+
return nil, nil
175+
}
176+
177+
// GetActiveDepositsInState implements DepositManager with a no-op.
178+
func (n *noopDepositManager) GetActiveDepositsInState(fsm.StateType) (
179+
[]*deposit.Deposit, error) {
180+
181+
return nil, nil
182+
}

0 commit comments

Comments
 (0)