@@ -95,12 +95,12 @@ func TestAutoLoopEnabled(t *testing.T) {
95
95
// autoloop budget is set to allow exactly 2 swaps at the prices
96
96
// that we set in our test quotes.
97
97
params = Parameters {
98
- Autoloop : true ,
99
- AutoFeeBudget : 40066 ,
100
- AutoFeeStartDate : testTime ,
101
- MaxAutoInFlight : 2 ,
102
- FailureBackOff : time .Hour ,
103
- SweepConfTarget : 10 ,
98
+ Autoloop : true ,
99
+ AutoFeeBudget : 40066 ,
100
+ AutoFeeRefreshPeriod : testBudgetRefresh ,
101
+ MaxAutoInFlight : 2 ,
102
+ FailureBackOff : time .Hour ,
103
+ SweepConfTarget : 10 ,
104
104
FeeLimit : NewFeeCategoryLimit (
105
105
swapFeePPM , routeFeePPM , prepayFeePPM , maxMiner ,
106
106
prepayAmount , 20000 ,
@@ -112,6 +112,7 @@ func TestAutoLoopEnabled(t *testing.T) {
112
112
HtlcConfTarget : defaultHtlcConfTarget ,
113
113
}
114
114
)
115
+
115
116
c := newAutoloopTestCtx (t , params , channels , testRestrictions )
116
117
c .start ()
117
118
@@ -335,13 +336,13 @@ func TestAutoloopAddress(t *testing.T) {
335
336
// Create some dummy parameters for autoloop and also specify an
336
337
// destination address.
337
338
params = Parameters {
338
- Autoloop : true ,
339
- AutoFeeBudget : 40066 ,
340
- DestAddr : addr ,
341
- AutoFeeStartDate : testTime ,
342
- MaxAutoInFlight : 2 ,
343
- FailureBackOff : time .Hour ,
344
- SweepConfTarget : 10 ,
339
+ Autoloop : true ,
340
+ AutoFeeBudget : 40066 ,
341
+ DestAddr : addr ,
342
+ AutoFeeRefreshPeriod : testBudgetRefresh ,
343
+ MaxAutoInFlight : 2 ,
344
+ FailureBackOff : time .Hour ,
345
+ SweepConfTarget : 10 ,
345
346
FeeLimit : NewFeeCategoryLimit (
346
347
swapFeePPM , routeFeePPM , prepayFeePPM , maxMiner ,
347
348
prepayAmount , 20000 ,
@@ -491,12 +492,12 @@ func TestCompositeRules(t *testing.T) {
491
492
swapFeePPM , routeFeePPM , prepayFeePPM , maxMiner ,
492
493
prepayAmount , 20000 ,
493
494
),
494
- Autoloop : true ,
495
- AutoFeeBudget : 100000 ,
496
- AutoFeeStartDate : testTime ,
497
- MaxAutoInFlight : 2 ,
498
- FailureBackOff : time .Hour ,
499
- SweepConfTarget : 10 ,
495
+ Autoloop : true ,
496
+ AutoFeeBudget : 100000 ,
497
+ AutoFeeRefreshPeriod : testBudgetRefresh ,
498
+ MaxAutoInFlight : 2 ,
499
+ FailureBackOff : time .Hour ,
500
+ SweepConfTarget : 10 ,
500
501
ChannelRules : map [lnwire.ShortChannelID ]* SwapRule {
501
502
chanID1 : chanRule ,
502
503
},
@@ -670,13 +671,13 @@ func TestAutoLoopInEnabled(t *testing.T) {
670
671
peer2MaxFee = ppmToSat (peer2ExpectedAmt , swapFeePPM )
671
672
672
673
params = Parameters {
673
- Autoloop : true ,
674
- AutoFeeBudget : peer1MaxFee + peer2MaxFee + 1 ,
675
- AutoFeeStartDate : testTime ,
676
- MaxAutoInFlight : 2 ,
677
- FailureBackOff : time .Hour ,
678
- FeeLimit : NewFeePortion (swapFeePPM ),
679
- ChannelRules : make (map [lnwire.ShortChannelID ]* SwapRule ),
674
+ Autoloop : true ,
675
+ AutoFeeBudget : peer1MaxFee + peer2MaxFee + 1 ,
676
+ AutoFeeRefreshPeriod : testBudgetRefresh ,
677
+ MaxAutoInFlight : 2 ,
678
+ FailureBackOff : time .Hour ,
679
+ FeeLimit : NewFeePortion (swapFeePPM ),
680
+ ChannelRules : make (map [lnwire.ShortChannelID ]* SwapRule ),
680
681
PeerRules : map [route.Vertex ]* SwapRule {
681
682
peer1 : rule ,
682
683
peer2 : rule ,
@@ -853,12 +854,12 @@ func TestAutoloopBothTypes(t *testing.T) {
853
854
loopInMaxFee = ppmToSat (loopInAmount , swapFeePPM )
854
855
855
856
params = Parameters {
856
- Autoloop : true ,
857
- AutoFeeBudget : loopOutMaxFee + loopInMaxFee + 1 ,
858
- AutoFeeStartDate : testTime ,
859
- MaxAutoInFlight : 2 ,
860
- FailureBackOff : time .Hour ,
861
- FeeLimit : NewFeePortion (swapFeePPM ),
857
+ Autoloop : true ,
858
+ AutoFeeBudget : loopOutMaxFee + loopInMaxFee + 1 ,
859
+ AutoFeeRefreshPeriod : testBudgetRefresh ,
860
+ MaxAutoInFlight : 2 ,
861
+ FailureBackOff : time .Hour ,
862
+ FeeLimit : NewFeePortion (swapFeePPM ),
862
863
ChannelRules : map [lnwire.ShortChannelID ]* SwapRule {
863
864
chanID1 : outRule ,
864
865
},
@@ -965,6 +966,211 @@ func TestAutoloopBothTypes(t *testing.T) {
965
966
c .stop ()
966
967
}
967
968
969
+ // TestAutoLoopRecurringBudget tests that the autolooper will perform swaps that
970
+ // respect the fee budget, and that it will refresh the budget based on the
971
+ // defined refresh period.
972
+ func TestAutoLoopRecurringBudget (t * testing.T ) {
973
+ defer test .Guard (t )()
974
+
975
+ var (
976
+ channels = []lndclient.ChannelInfo {
977
+ channel1 , channel2 ,
978
+ }
979
+
980
+ swapFeePPM uint64 = 1000
981
+ routeFeePPM uint64 = 1000
982
+ prepayFeePPM uint64 = 1000
983
+ prepayAmount = btcutil .Amount (20000 )
984
+ maxMiner = btcutil .Amount (20000 )
985
+
986
+ params = Parameters {
987
+ Autoloop : true ,
988
+ AutoFeeBudget : 36000 ,
989
+ AutoFeeRefreshPeriod : time .Hour * 3 ,
990
+ MaxAutoInFlight : 2 ,
991
+ FailureBackOff : time .Hour ,
992
+ SweepConfTarget : 10 ,
993
+ FeeLimit : NewFeeCategoryLimit (
994
+ swapFeePPM , routeFeePPM , prepayFeePPM , maxMiner ,
995
+ prepayAmount , 20000 ,
996
+ ),
997
+ ChannelRules : map [lnwire.ShortChannelID ]* SwapRule {
998
+ chanID1 : chanRule ,
999
+ chanID2 : chanRule ,
1000
+ },
1001
+ HtlcConfTarget : defaultHtlcConfTarget ,
1002
+ }
1003
+ )
1004
+
1005
+ c := newAutoloopTestCtx (t , params , channels , testRestrictions )
1006
+ c .start ()
1007
+
1008
+ // Calculate our maximum allowed fees and create quotes that fall within
1009
+ // our budget.
1010
+ var (
1011
+ amt = chan1Rec .Amount
1012
+
1013
+ maxSwapFee = ppmToSat (amt , swapFeePPM )
1014
+
1015
+ // Create a quote that is within our limits. We do not set miner
1016
+ // fee because this value is not actually set by the server.
1017
+ quote1 = & loop.LoopOutQuote {
1018
+ SwapFee : maxSwapFee ,
1019
+ PrepayAmount : prepayAmount - 10 ,
1020
+ MinerFee : maxMiner - 10 ,
1021
+ }
1022
+
1023
+ quote2 = & loop.LoopOutQuote {
1024
+ SwapFee : maxSwapFee ,
1025
+ PrepayAmount : prepayAmount - 20 ,
1026
+ MinerFee : maxMiner - 10 ,
1027
+ }
1028
+
1029
+ quoteRequest = & loop.LoopOutQuoteRequest {
1030
+ Amount : amt ,
1031
+ SweepConfTarget : params .SweepConfTarget ,
1032
+ }
1033
+
1034
+ quotes1 = []quoteRequestResp {
1035
+ {
1036
+ request : quoteRequest ,
1037
+ quote : quote1 ,
1038
+ },
1039
+ {
1040
+ request : quoteRequest ,
1041
+ quote : quote2 ,
1042
+ },
1043
+ }
1044
+
1045
+ quotes2 = []quoteRequestResp {
1046
+ {
1047
+ request : quoteRequest ,
1048
+ quote : quote2 ,
1049
+ },
1050
+ }
1051
+
1052
+ maxRouteFee = ppmToSat (amt , routeFeePPM )
1053
+
1054
+ chan1Swap = & loop.OutRequest {
1055
+ Amount : amt ,
1056
+ MaxSwapRoutingFee : maxRouteFee ,
1057
+ MaxPrepayRoutingFee : ppmToSat (
1058
+ quote1 .PrepayAmount , prepayFeePPM ,
1059
+ ),
1060
+ MaxSwapFee : quote1 .SwapFee ,
1061
+ MaxPrepayAmount : quote1 .PrepayAmount ,
1062
+ MaxMinerFee : maxMiner ,
1063
+ SweepConfTarget : params .SweepConfTarget ,
1064
+ OutgoingChanSet : loopdb.ChannelSet {chanID1 .ToUint64 ()},
1065
+ Label : labels .AutoloopLabel (swap .TypeOut ),
1066
+ Initiator : autoloopSwapInitiator ,
1067
+ }
1068
+
1069
+ chan2Swap = & loop.OutRequest {
1070
+ Amount : amt ,
1071
+ MaxSwapRoutingFee : maxRouteFee ,
1072
+ MaxPrepayRoutingFee : ppmToSat (
1073
+ quote2 .PrepayAmount , routeFeePPM ,
1074
+ ),
1075
+ MaxSwapFee : quote2 .SwapFee ,
1076
+ MaxPrepayAmount : quote2 .PrepayAmount ,
1077
+ MaxMinerFee : maxMiner ,
1078
+ SweepConfTarget : params .SweepConfTarget ,
1079
+ OutgoingChanSet : loopdb.ChannelSet {chanID2 .ToUint64 ()},
1080
+ Label : labels .AutoloopLabel (swap .TypeOut ),
1081
+ Initiator : autoloopSwapInitiator ,
1082
+ }
1083
+
1084
+ loopOuts1 = []loopOutRequestResp {
1085
+ {
1086
+ request : chan1Swap ,
1087
+ response : & loop.LoopOutSwapInfo {
1088
+ SwapHash : lntypes.Hash {1 },
1089
+ },
1090
+ },
1091
+ }
1092
+
1093
+ loopOuts2 = []loopOutRequestResp {
1094
+ {
1095
+ request : chan2Swap ,
1096
+ response : & loop.LoopOutSwapInfo {
1097
+ SwapHash : lntypes.Hash {1 },
1098
+ },
1099
+ },
1100
+ }
1101
+ )
1102
+
1103
+ // Tick our autolooper with no existing swaps, we expect a loop out
1104
+ // swap to be dispatched on first channel.
1105
+ step := & autoloopStep {
1106
+ minAmt : 1 ,
1107
+ maxAmt : amt + 1 ,
1108
+ quotesOut : quotes1 ,
1109
+ expectedOut : loopOuts1 ,
1110
+ }
1111
+ c .autoloop (step )
1112
+
1113
+ existing := []* loopdb.LoopOut {
1114
+ existingSwapFromRequest (
1115
+ chan1Swap , testTime , []* loopdb.LoopEvent {
1116
+ {
1117
+ SwapStateData : loopdb.SwapStateData {
1118
+ State : loopdb .StateInitiated ,
1119
+ },
1120
+ Time : testTime ,
1121
+ },
1122
+ },
1123
+ ),
1124
+ }
1125
+
1126
+ step = & autoloopStep {
1127
+ minAmt : 1 ,
1128
+ maxAmt : amt + 1 ,
1129
+ quotesOut : quotes2 ,
1130
+ existingOut : existing ,
1131
+ expectedOut : nil ,
1132
+ }
1133
+ // Tick again, we should expect no loop outs because our budget would be
1134
+ // exceeded.
1135
+ c .autoloop (step )
1136
+
1137
+ // Create the existing entry for the first swap, marking its last update
1138
+ // with success and a specific timestamp.
1139
+ existing2 := []* loopdb.LoopOut {
1140
+ existingSwapFromRequest (
1141
+ chan1Swap , testTime , []* loopdb.LoopEvent {
1142
+ {
1143
+ SwapStateData : loopdb.SwapStateData {
1144
+ State : loopdb .StateSuccess ,
1145
+ },
1146
+ Time : testTime ,
1147
+ },
1148
+ },
1149
+ ),
1150
+ }
1151
+
1152
+ // Apply the balance shifts on the channels in order to get the correct
1153
+ // recommendations on next tick.
1154
+ c .lnd .Channels [0 ].LocalBalance = 2500
1155
+ c .lnd .Channels [0 ].RemoteBalance = 7500
1156
+
1157
+ // Advance time to the future, causing a budget refresh.
1158
+ c .testClock .SetTime (testTime .Add (time .Hour * 25 ))
1159
+
1160
+ step = & autoloopStep {
1161
+ minAmt : 1 ,
1162
+ maxAmt : amt + 1 ,
1163
+ quotesOut : quotes2 ,
1164
+ existingOut : existing2 ,
1165
+ expectedOut : loopOuts2 ,
1166
+ }
1167
+
1168
+ // Tick again, we should expect a loop out to occur on the 2nd channel.
1169
+ c .autoloop (step )
1170
+
1171
+ c .stop ()
1172
+ }
1173
+
968
1174
// existingSwapFromRequest is a helper function which returns the db
969
1175
// representation of a loop out request with the event set provided.
970
1176
func existingSwapFromRequest (request * loop.OutRequest , initTime time.Time ,
0 commit comments