@@ -966,6 +966,211 @@ func TestAutoloopBothTypes(t *testing.T) {
966
966
c .stop ()
967
967
}
968
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
+
969
1174
// existingSwapFromRequest is a helper function which returns the db
970
1175
// representation of a loop out request with the event set provided.
971
1176
func existingSwapFromRequest (request * loop.OutRequest , initTime time.Time ,
0 commit comments