Skip to content

Commit f6c7107

Browse files
committed
sweepbatcher: test spending notification and error
1 parent 330c8d4 commit f6c7107

File tree

1 file changed

+220
-24
lines changed

1 file changed

+220
-24
lines changed

sweepbatcher/sweep_batcher_test.go

Lines changed: 220 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -762,31 +762,42 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore,
762762
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
763763
testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams,
764764
batcherStore, sweepStore)
765+
runErrChan := make(chan error)
765766
go func() {
766-
err := batcher.Run(ctx)
767-
checkBatcherError(t, err)
767+
runErrChan <- batcher.Run(ctx)
768768
}()
769769

770770
// Create a sweep request.
771771
op1 := wire.OutPoint{
772772
Hash: chainhash.Hash{1, 1},
773773
Index: 1,
774774
}
775+
const (
776+
inputValue = 111
777+
outputValue = 50
778+
fee = inputValue - outputValue
779+
)
780+
spendErrChan := make(chan error, 1)
781+
notifier := &SpendNotifier{
782+
SpendChan: make(chan *SpendDetail, 1),
783+
SpendErrChan: spendErrChan,
784+
QuitChan: make(chan bool, 1),
785+
}
775786
sweepReq1 := SweepRequest{
776787
SwapHash: lntypes.Hash{1, 1, 1},
777788
Inputs: []Input{{
778-
Value: 111,
789+
Value: inputValue,
779790
Outpoint: op1,
780791
}},
781-
Notifier: &dummyNotifier,
792+
Notifier: notifier,
782793
}
783794

784795
const initiationHeight = 550
785796

786797
swap1 := &loopdb.LoopOutContract{
787798
SwapContract: loopdb.SwapContract{
788799
CltvExpiry: 111,
789-
AmountRequested: 111,
800+
AmountRequested: inputValue,
790801
ProtocolVersion: loopdb.ProtocolVersionMuSig2,
791802
HtlcKeys: htlcKeys,
792803
InitiationHeight: initiationHeight,
@@ -806,48 +817,98 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore,
806817

807818
// When batch is successfully created it will execute it's first step,
808819
// which leads to a spend monitor of the primary sweep.
809-
<-lnd.RegisterSpendChannel
820+
spendReg := <-lnd.RegisterSpendChannel
821+
822+
// Wait for tx to be published.
823+
<-lnd.TxPublishChannel
810824

811825
// Eventually request will be consumed and a new batch will spin up.
826+
var primarySweepID wire.OutPoint
812827
require.Eventually(t, func() bool {
813-
return batcher.numBatches(ctx) == 1
814-
}, test.Timeout, eventuallyCheckFrequency)
828+
batch := tryGetOnlyBatch(ctx, batcher)
829+
if batch == nil {
830+
return false
831+
}
815832

816-
// Find the batch and assign it to a local variable for easier access.
817-
batch := &batch{}
818-
for _, btch := range getBatches(ctx, batcher) {
819-
btch.testRunInEventLoop(ctx, func() {
820-
if btch.primarySweepID == op1 {
821-
batch = btch
822-
}
823-
})
824-
}
833+
primarySweepID = batch.snapshot(ctx).primarySweepID
825834

826-
require.Eventually(t, func() bool {
827835
// Batch should have the sweep stored.
828836
return batch.numSweeps(ctx) == 1
829837
}, test.Timeout, eventuallyCheckFrequency)
830838

831839
// The primary sweep id should be that of the first inserted sweep.
832-
require.Equal(t, batch.primarySweepID, op1)
833-
834-
// Wait for tx to be published.
835-
<-lnd.TxPublishChannel
840+
require.Equal(t, primarySweepID, op1)
836841

837842
err = lnd.NotifyHeight(601)
838843
require.NoError(t, err)
839844

840845
// After receiving a height notification the batch will step again,
841846
// leading to a new spend monitoring.
842847
require.Eventually(t, func() bool {
843-
batch := batch.snapshot(ctx)
848+
batch := tryGetOnlyBatch(ctx, batcher)
849+
if batch == nil {
850+
return false
851+
}
852+
batch = batch.snapshot(ctx)
844853

845854
return batch.currentHeight == 601
846855
}, test.Timeout, eventuallyCheckFrequency)
847856

848857
// Wait for tx to be published.
849858
<-lnd.TxPublishChannel
850859

860+
// Emulate spend error.
861+
testError := errors.New("test error")
862+
spendReg.ErrChan <- testError
863+
864+
// Make sure the caller of AddSweep got the spending error.
865+
notifierErr := <-spendErrChan
866+
require.Error(t, notifierErr)
867+
require.ErrorIs(t, notifierErr, testError)
868+
869+
// Wait for the batcher to crash because of the spending error.
870+
runErr := <-runErrChan
871+
require.ErrorIs(t, runErr, testError)
872+
873+
// Now launch the batcher again.
874+
batcher = NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
875+
testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams,
876+
batcherStore, sweepStore)
877+
go func() {
878+
runErrChan <- batcher.Run(ctx)
879+
}()
880+
881+
// When batch is successfully created it will execute it's first step,
882+
// which leads to a spend monitor of the primary sweep.
883+
spendReg = <-lnd.RegisterSpendChannel
884+
885+
// Wait for tx to be published.
886+
<-lnd.TxPublishChannel
887+
888+
// Deliver sweep request to batcher.
889+
spendChan := make(chan *SpendDetail, 1)
890+
notifier = &SpendNotifier{
891+
SpendChan: spendChan,
892+
SpendErrChan: make(chan error, 1),
893+
QuitChan: make(chan bool, 1),
894+
}
895+
sweepReq1.Notifier = notifier
896+
require.NoError(t, batcher.AddSweep(&sweepReq1))
897+
898+
// Wait for the notifier to be installed.
899+
require.Eventually(t, func() bool {
900+
batch := tryGetOnlyBatch(ctx, batcher)
901+
if batch == nil {
902+
return false
903+
}
904+
batch = batch.snapshot(ctx)
905+
906+
sweep := batch.sweeps[batch.primarySweepID]
907+
908+
return sweep.notifier != nil &&
909+
sweep.notifier.SpendChan == spendChan
910+
}, test.Timeout, eventuallyCheckFrequency)
911+
851912
// Create the spending tx that will trigger the spend monitor of the
852913
// batch.
853914
spendingTx := &wire.MsgTx{
@@ -861,6 +922,7 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore,
861922
},
862923
TxOut: []*wire.TxOut{
863924
{
925+
Value: outputValue,
864926
PkScript: []byte{3, 2, 1},
865927
},
866928
},
@@ -879,6 +941,11 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore,
879941
// We notify the spend.
880942
lnd.SpendChannel <- spendDetail
881943

944+
// Make sure the notifier got a proper spending notification.
945+
spending := <-spendChan
946+
require.Equal(t, spendingTxHash, spending.Tx.TxHash())
947+
require.Equal(t, btcutil.Amount(fee), spending.OnChainFeePortion)
948+
882949
// After receiving the spend, the batch is now monitoring for confs.
883950
confReg := <-lnd.RegisterConfChannel
884951

@@ -889,7 +956,84 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore,
889956
// The batch should eventually read the spend notification and progress
890957
// its state to closed.
891958
require.Eventually(t, func() bool {
892-
batch := batch.snapshot(ctx)
959+
batch := tryGetOnlyBatch(ctx, batcher)
960+
if batch == nil {
961+
return false
962+
}
963+
batch = batch.snapshot(ctx)
964+
965+
return batch.state == Closed
966+
}, test.Timeout, eventuallyCheckFrequency)
967+
968+
// Emulate a confirmation error.
969+
confReg.ErrChan <- testError
970+
971+
// Wait for the batcher to crash because of the confirmation error.
972+
runErr = <-runErrChan
973+
require.ErrorIs(t, runErr, testError)
974+
975+
// Now launch the batcher again.
976+
batcher = NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
977+
testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams,
978+
batcherStore, sweepStore)
979+
go func() {
980+
runErrChan <- batcher.Run(ctx)
981+
}()
982+
983+
// When batch is successfully created it will execute it's first step,
984+
// which leads to a spend monitor of the primary sweep.
985+
spendReg = <-lnd.RegisterSpendChannel
986+
987+
// Deliver sweep request to batcher.
988+
spendChan = make(chan *SpendDetail, 1)
989+
notifier = &SpendNotifier{
990+
SpendChan: spendChan,
991+
SpendErrChan: make(chan error, 1),
992+
QuitChan: make(chan bool, 1),
993+
}
994+
sweepReq1.Notifier = notifier
995+
require.NoError(t, batcher.AddSweep(&sweepReq1))
996+
997+
// Wait for tx to be published. A closed batch is stored in DB as Open.
998+
<-lnd.TxPublishChannel
999+
1000+
// Wait for the notifier to be installed.
1001+
require.Eventually(t, func() bool {
1002+
batch := tryGetOnlyBatch(ctx, batcher)
1003+
if batch == nil {
1004+
return false
1005+
}
1006+
batch = batch.snapshot(ctx)
1007+
1008+
sweep := batch.sweeps[batch.primarySweepID]
1009+
1010+
return sweep.notifier != nil &&
1011+
sweep.notifier.SpendChan == spendChan
1012+
}, test.Timeout, eventuallyCheckFrequency)
1013+
1014+
// We notify the spend.
1015+
lnd.SpendChannel <- spendDetail
1016+
1017+
// Make sure the notifier got a proper spending notification.
1018+
spending = <-spendChan
1019+
require.Equal(t, spendingTxHash, spending.Tx.TxHash())
1020+
require.Equal(t, btcutil.Amount(fee), spending.OnChainFeePortion)
1021+
1022+
// After receiving the spend, the batch is now monitoring for confs.
1023+
confReg = <-lnd.RegisterConfChannel
1024+
1025+
// Make sure the confirmation has proper height hint. It should pass
1026+
// the swap initiation height, not the current height.
1027+
require.Equal(t, int32(initiationHeight), confReg.HeightHint)
1028+
1029+
// The batch should eventually read the spend notification and progress
1030+
// its state to closed.
1031+
require.Eventually(t, func() bool {
1032+
batch := tryGetOnlyBatch(ctx, batcher)
1033+
if batch == nil {
1034+
return false
1035+
}
1036+
batch = batch.snapshot(ctx)
8931037

8941038
return batch.state == Closed
8951039
}, test.Timeout, eventuallyCheckFrequency)
@@ -905,8 +1049,60 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore,
9051049
// Eventually the batch receives the confirmation notification and
9061050
// confirms itself.
9071051
require.Eventually(t, func() bool {
1052+
batch := tryGetOnlyBatch(ctx, batcher)
1053+
if batch == nil {
1054+
return false
1055+
}
1056+
9081057
return batch.isComplete()
9091058
}, test.Timeout, eventuallyCheckFrequency)
1059+
1060+
// Now emulate adding the sweep again after it was fully confirmed.
1061+
// This triggers another code path (monitorSpendAndNotify).
1062+
spendChan = make(chan *SpendDetail, 1)
1063+
notifier = &SpendNotifier{
1064+
SpendChan: spendChan,
1065+
SpendErrChan: make(chan error, 1),
1066+
QuitChan: make(chan bool, 1),
1067+
}
1068+
sweepReq1.Notifier = notifier
1069+
require.NoError(t, batcher.AddSweep(&sweepReq1))
1070+
1071+
// Expect a spending registration.
1072+
<-lnd.RegisterSpendChannel
1073+
1074+
// We notify the spend.
1075+
lnd.SpendChannel <- spendDetail
1076+
1077+
// Now expect the notifier to produce the spending details.
1078+
spending = <-spendChan
1079+
require.Equal(t, spendingTxHash, spending.Tx.TxHash())
1080+
require.Equal(t, btcutil.Amount(fee), spending.OnChainFeePortion)
1081+
1082+
// Now check what happens in case of a spending error.
1083+
spendErrChan = make(chan error, 1)
1084+
notifier = &SpendNotifier{
1085+
SpendChan: make(chan *SpendDetail, 1),
1086+
SpendErrChan: spendErrChan,
1087+
QuitChan: make(chan bool, 1),
1088+
}
1089+
sweepReq1.Notifier = notifier
1090+
require.NoError(t, batcher.AddSweep(&sweepReq1))
1091+
1092+
// Expect a spending registration.
1093+
spendReg = <-lnd.RegisterSpendChannel
1094+
1095+
// Emulate spend error.
1096+
spendReg.ErrChan <- testError
1097+
1098+
// Make sure the caller of AddSweep got the spending error.
1099+
notifierErr = <-spendErrChan
1100+
require.Error(t, notifierErr)
1101+
require.ErrorIs(t, notifierErr, testError)
1102+
1103+
// Wait for the batcher to crash because of the spending error.
1104+
runErr = <-runErrChan
1105+
require.ErrorIs(t, runErr, testError)
9101106
}
9111107

9121108
// wrappedLogger implements btclog.Logger, recording last debug message format.

0 commit comments

Comments
 (0)