|
9 | 9 | "time" |
10 | 10 |
|
11 | 11 | "github.com/btcsuite/btcd/blockchain" |
| 12 | + "github.com/btcsuite/btcd/btcec/v2" |
12 | 13 | "github.com/btcsuite/btcd/btcutil" |
13 | 14 | "github.com/btcsuite/btcd/wire" |
14 | 15 | "github.com/lightninglabs/lndclient" |
@@ -87,12 +88,13 @@ func testLoopOutPaymentParameters(t *testing.T) { |
87 | 88 |
|
88 | 89 | go func() { |
89 | 90 | err := swap.execute(swapCtx, &executeConfig{ |
90 | | - statusChan: statusChan, |
91 | | - sweeper: sweeper, |
92 | | - blockEpochChan: blockEpochChan, |
93 | | - timerFactory: timerFactory, |
94 | | - loopOutMaxParts: maxParts, |
95 | | - cancelSwap: server.CancelLoopOutSwap, |
| 91 | + statusChan: statusChan, |
| 92 | + sweeper: sweeper, |
| 93 | + blockEpochChan: blockEpochChan, |
| 94 | + timerFactory: timerFactory, |
| 95 | + loopOutMaxParts: maxParts, |
| 96 | + cancelSwap: server.CancelLoopOutSwap, |
| 97 | + verifySchnorrSig: mockVerifySchnorrSigFail, |
96 | 98 | }, height) |
97 | 99 | if err != nil { |
98 | 100 | log.Error(err) |
@@ -209,11 +211,12 @@ func testLateHtlcPublish(t *testing.T) { |
209 | 211 | errChan := make(chan error) |
210 | 212 | go func() { |
211 | 213 | err := swap.execute(context.Background(), &executeConfig{ |
212 | | - statusChan: statusChan, |
213 | | - sweeper: sweeper, |
214 | | - blockEpochChan: blockEpochChan, |
215 | | - timerFactory: timerFactory, |
216 | | - cancelSwap: server.CancelLoopOutSwap, |
| 214 | + statusChan: statusChan, |
| 215 | + sweeper: sweeper, |
| 216 | + blockEpochChan: blockEpochChan, |
| 217 | + timerFactory: timerFactory, |
| 218 | + cancelSwap: server.CancelLoopOutSwap, |
| 219 | + verifySchnorrSig: mockVerifySchnorrSigFail, |
217 | 220 | }, height) |
218 | 221 | if err != nil { |
219 | 222 | log.Error(err) |
@@ -320,11 +323,12 @@ func testCustomSweepConfTarget(t *testing.T) { |
320 | 323 | errChan := make(chan error) |
321 | 324 | go func() { |
322 | 325 | err := swap.execute(context.Background(), &executeConfig{ |
323 | | - statusChan: statusChan, |
324 | | - blockEpochChan: blockEpochChan, |
325 | | - timerFactory: timerFactory, |
326 | | - sweeper: sweeper, |
327 | | - cancelSwap: server.CancelLoopOutSwap, |
| 326 | + statusChan: statusChan, |
| 327 | + blockEpochChan: blockEpochChan, |
| 328 | + timerFactory: timerFactory, |
| 329 | + sweeper: sweeper, |
| 330 | + cancelSwap: server.CancelLoopOutSwap, |
| 331 | + verifySchnorrSig: mockVerifySchnorrSigFail, |
328 | 332 | }, ctx.Lnd.Height) |
329 | 333 | if err != nil { |
330 | 334 | log.Error(err) |
@@ -550,11 +554,12 @@ func testPreimagePush(t *testing.T) { |
550 | 554 | errChan := make(chan error) |
551 | 555 | go func() { |
552 | 556 | err := swap.execute(context.Background(), &executeConfig{ |
553 | | - statusChan: statusChan, |
554 | | - blockEpochChan: blockEpochChan, |
555 | | - timerFactory: timerFactory, |
556 | | - sweeper: sweeper, |
557 | | - cancelSwap: server.CancelLoopOutSwap, |
| 557 | + statusChan: statusChan, |
| 558 | + blockEpochChan: blockEpochChan, |
| 559 | + timerFactory: timerFactory, |
| 560 | + sweeper: sweeper, |
| 561 | + cancelSwap: server.CancelLoopOutSwap, |
| 562 | + verifySchnorrSig: mockVerifySchnorrSigFail, |
558 | 563 | }, ctx.Lnd.Height) |
559 | 564 | if err != nil { |
560 | 565 | log.Error(err) |
@@ -783,10 +788,11 @@ func testExpiryBeforeReveal(t *testing.T) { |
783 | 788 | errChan := make(chan error) |
784 | 789 | go func() { |
785 | 790 | err := swap.execute(context.Background(), &executeConfig{ |
786 | | - statusChan: statusChan, |
787 | | - blockEpochChan: blockEpochChan, |
788 | | - timerFactory: timerFactory, |
789 | | - sweeper: sweeper, |
| 791 | + statusChan: statusChan, |
| 792 | + blockEpochChan: blockEpochChan, |
| 793 | + timerFactory: timerFactory, |
| 794 | + sweeper: sweeper, |
| 795 | + verifySchnorrSig: mockVerifySchnorrSigFail, |
790 | 796 | }, ctx.Lnd.Height) |
791 | 797 | if err != nil { |
792 | 798 | log.Error(err) |
@@ -909,11 +915,12 @@ func testFailedOffChainCancelation(t *testing.T) { |
909 | 915 | errChan := make(chan error) |
910 | 916 | go func() { |
911 | 917 | cfg := &executeConfig{ |
912 | | - statusChan: statusChan, |
913 | | - sweeper: sweeper, |
914 | | - blockEpochChan: blockEpochChan, |
915 | | - timerFactory: timerFactory, |
916 | | - cancelSwap: server.CancelLoopOutSwap, |
| 918 | + statusChan: statusChan, |
| 919 | + sweeper: sweeper, |
| 920 | + blockEpochChan: blockEpochChan, |
| 921 | + timerFactory: timerFactory, |
| 922 | + cancelSwap: server.CancelLoopOutSwap, |
| 923 | + verifySchnorrSig: mockVerifySchnorrSigFail, |
917 | 924 | } |
918 | 925 |
|
919 | 926 | err := swap.execute(context.Background(), cfg, ctx.Lnd.Height) |
@@ -1011,3 +1018,174 @@ func testFailedOffChainCancelation(t *testing.T) { |
1011 | 1018 | require.Equal(t, state.State, loopdb.StateFailOffchainPayments) |
1012 | 1019 | require.NoError(t, <-errChan) |
1013 | 1020 | } |
| 1021 | + |
| 1022 | +// TestLoopOutMuSig2Sweep tests the loop out sweep flow when the MuSig2 signing |
| 1023 | +// process is successful. |
| 1024 | +func TestLoopOutMuSig2Sweep(t *testing.T) { |
| 1025 | + defer test.Guard(t)() |
| 1026 | + |
| 1027 | + // TODO(bhandras): remove when MuSig2 is default. |
| 1028 | + loopdb.EnableExperimentalProtocol() |
| 1029 | + defer loopdb.ResetCurrentProtocolVersion() |
| 1030 | + |
| 1031 | + lnd := test.NewMockLnd() |
| 1032 | + ctx := test.NewContext(t, lnd) |
| 1033 | + server := newServerMock(lnd) |
| 1034 | + |
| 1035 | + testReq := *testRequest |
| 1036 | + testReq.SweepConfTarget = 10 |
| 1037 | + testReq.Expiry = ctx.Lnd.Height + testLoopOutMinOnChainCltvDelta |
| 1038 | + |
| 1039 | + // We set our mock fee estimate for our target sweep confs to be our |
| 1040 | + // max miner fee * 2. With MuSig2 we still expect that the client will |
| 1041 | + // publish the sweep but with the fee clamped to the maximum allowed |
| 1042 | + // miner fee as the preimage is revealed before the sweep txn is |
| 1043 | + // published. |
| 1044 | + ctx.Lnd.SetFeeEstimate( |
| 1045 | + testReq.SweepConfTarget, chainfee.SatPerKWeight( |
| 1046 | + testReq.MaxMinerFee*2, |
| 1047 | + ), |
| 1048 | + ) |
| 1049 | + |
| 1050 | + cfg := newSwapConfig( |
| 1051 | + &lnd.LndServices, newStoreMock(t), server, |
| 1052 | + ) |
| 1053 | + |
| 1054 | + initResult, err := newLoopOutSwap( |
| 1055 | + context.Background(), cfg, ctx.Lnd.Height, &testReq, |
| 1056 | + ) |
| 1057 | + require.NoError(t, err) |
| 1058 | + swap := initResult.swap |
| 1059 | + |
| 1060 | + // Set up the required dependencies to execute the swap. |
| 1061 | + sweeper := &sweep.Sweeper{Lnd: &lnd.LndServices} |
| 1062 | + blockEpochChan := make(chan interface{}) |
| 1063 | + statusChan := make(chan SwapInfo) |
| 1064 | + expiryChan := make(chan time.Time) |
| 1065 | + timerFactory := func(_ time.Duration) <-chan time.Time { |
| 1066 | + return expiryChan |
| 1067 | + } |
| 1068 | + |
| 1069 | + errChan := make(chan error) |
| 1070 | + |
| 1071 | + // Mock a successful signature verify to make sure we don't fail |
| 1072 | + // creating the MuSig2 sweep. |
| 1073 | + mockVerifySchnorrSigSuccess := func(pubKey *btcec.PublicKey, hash, |
| 1074 | + sig []byte) error { |
| 1075 | + |
| 1076 | + return nil |
| 1077 | + } |
| 1078 | + |
| 1079 | + go func() { |
| 1080 | + err := swap.execute(context.Background(), &executeConfig{ |
| 1081 | + statusChan: statusChan, |
| 1082 | + blockEpochChan: blockEpochChan, |
| 1083 | + timerFactory: timerFactory, |
| 1084 | + sweeper: sweeper, |
| 1085 | + cancelSwap: server.CancelLoopOutSwap, |
| 1086 | + verifySchnorrSig: mockVerifySchnorrSigSuccess, |
| 1087 | + }, ctx.Lnd.Height) |
| 1088 | + if err != nil { |
| 1089 | + log.Error(err) |
| 1090 | + } |
| 1091 | + errChan <- err |
| 1092 | + }() |
| 1093 | + |
| 1094 | + // The swap should be found in its initial state. |
| 1095 | + cfg.store.(*storeMock).assertLoopOutStored() |
| 1096 | + state := <-statusChan |
| 1097 | + require.Equal(t, loopdb.StateInitiated, state.State) |
| 1098 | + |
| 1099 | + // We'll then pay both the swap and prepay invoice, which should trigger |
| 1100 | + // the server to publish the on-chain HTLC. |
| 1101 | + signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc) |
| 1102 | + signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc) |
| 1103 | + |
| 1104 | + signalSwapPaymentResult(nil) |
| 1105 | + signalPrepaymentResult(nil) |
| 1106 | + |
| 1107 | + // Notify the confirmation notification for the HTLC. |
| 1108 | + ctx.AssertRegisterConf(false, defaultConfirmations) |
| 1109 | + |
| 1110 | + blockEpochChan <- ctx.Lnd.Height + 1 |
| 1111 | + |
| 1112 | + htlcTx := wire.NewMsgTx(2) |
| 1113 | + htlcTx.AddTxOut(&wire.TxOut{ |
| 1114 | + Value: int64(swap.AmountRequested), |
| 1115 | + PkScript: swap.htlc.PkScript, |
| 1116 | + }) |
| 1117 | + |
| 1118 | + ctx.NotifyConf(htlcTx) |
| 1119 | + |
| 1120 | + // The client should then register for a spend of the HTLC and attempt |
| 1121 | + // to sweep it using the custom confirmation target. |
| 1122 | + ctx.AssertRegisterSpendNtfn(swap.htlc.PkScript) |
| 1123 | + |
| 1124 | + // Assert that we made a query to track our payment, as required for |
| 1125 | + // preimage push tracking. |
| 1126 | + trackPayment := ctx.AssertTrackPayment() |
| 1127 | + |
| 1128 | + // Tick the expiry channel, we are still using our client confirmation |
| 1129 | + // target at this stage which has fees higher than our max acceptable |
| 1130 | + // fee. We do not expect a sweep attempt at this point. Since our |
| 1131 | + // preimage is not revealed, we also do not expect a preimage push. |
| 1132 | + expiryChan <- testTime |
| 1133 | + |
| 1134 | + // When using taproot htlcs the flow is different as we do reveal the |
| 1135 | + // preimage before sweeping in order for the server to trust us with |
| 1136 | + // our MuSig2 signing attempts. |
| 1137 | + cfg.store.(*storeMock).assertLoopOutState( |
| 1138 | + loopdb.StatePreimageRevealed, |
| 1139 | + ) |
| 1140 | + status := <-statusChan |
| 1141 | + require.Equal( |
| 1142 | + t, status.State, loopdb.StatePreimageRevealed, |
| 1143 | + ) |
| 1144 | + |
| 1145 | + preimage := <-server.preimagePush |
| 1146 | + require.Equal(t, swap.Preimage, preimage) |
| 1147 | + |
| 1148 | + // We expect the sweep tx to have been published. |
| 1149 | + ctx.ReceiveTx() |
| 1150 | + |
| 1151 | + // Since we don't have a reliable mechanism to non-intrusively avoid |
| 1152 | + // races by setting the fee estimate too soon, let's sleep here a bit |
| 1153 | + // to ensure the first sweep fails. |
| 1154 | + time.Sleep(500 * time.Millisecond) |
| 1155 | + |
| 1156 | + // Now we decrease our fees for the swap's confirmation target to less |
| 1157 | + // than the maximum miner fee. |
| 1158 | + ctx.Lnd.SetFeeEstimate(testReq.SweepConfTarget, chainfee.SatPerKWeight( |
| 1159 | + testReq.MaxMinerFee/2, |
| 1160 | + )) |
| 1161 | + |
| 1162 | + // Now when we report a new block and tick our expiry fee timer, and |
| 1163 | + // fees are acceptably low so we expect our sweep to be published. |
| 1164 | + blockEpochChan <- ctx.Lnd.Height + 2 |
| 1165 | + expiryChan <- testTime |
| 1166 | + |
| 1167 | + preimage = <-server.preimagePush |
| 1168 | + require.Equal(t, swap.Preimage, preimage) |
| 1169 | + |
| 1170 | + // We expect the sweep tx to have been published. |
| 1171 | + sweepTx := ctx.ReceiveTx() |
| 1172 | + |
| 1173 | + // This time, we send a payment succeeded update into our payment stream |
| 1174 | + // to reflect that the server received our preimage push and settled off |
| 1175 | + // chain. |
| 1176 | + trackPayment.Updates <- lndclient.PaymentStatus{ |
| 1177 | + State: lnrpc.Payment_SUCCEEDED, |
| 1178 | + } |
| 1179 | + |
| 1180 | + // Make sure our sweep tx has a single witness indicating keyspend. |
| 1181 | + require.Len(t, sweepTx.TxIn[0].Witness, 1) |
| 1182 | + |
| 1183 | + // Finally, we put this swap out of its misery and notify a successful |
| 1184 | + // spend our our sweepTx and assert that the swap succeeds. |
| 1185 | + ctx.NotifySpend(sweepTx, 0) |
| 1186 | + |
| 1187 | + cfg.store.(*storeMock).assertLoopOutState(loopdb.StateSuccess) |
| 1188 | + status = <-statusChan |
| 1189 | + require.Equal(t, status.State, loopdb.StateSuccess) |
| 1190 | + require.NoError(t, <-errChan) |
| 1191 | +} |
0 commit comments