@@ -107,6 +107,13 @@ var (
107107 OutgoingChanSet : loopdb.ChannelSet {999 },
108108 }
109109
110+ autoInContract = & loopdb.LoopInContract {
111+ SwapContract : loopdb.SwapContract {
112+ Label : labels .AutoloopLabel (swap .TypeIn ),
113+ InitiationTime : testBudgetStart ,
114+ },
115+ }
116+
110117 testRestrictions = NewRestrictions (1 , 10000 )
111118
112119 // noneDisqualified can be used in tests where we don't have any
@@ -1123,9 +1130,10 @@ func TestFeeBudget(t *testing.T) {
11231130// that are allowed.
11241131func TestInFlightLimit (t * testing.T ) {
11251132 tests := []struct {
1126- name string
1127- maxInFlight int
1128- existingSwaps []* loopdb.LoopOut
1133+ name string
1134+ maxInFlight int
1135+ existingSwaps []* loopdb.LoopOut
1136+ existingInSwaps []* loopdb.LoopIn
11291137 // peerRules will only be set (instead of test default values)
11301138 // is it is non-nil.
11311139 peerRules map [route.Vertex ]* SwapRule
@@ -1194,8 +1202,10 @@ func TestInFlightLimit(t *testing.T) {
11941202 {
11951203 Contract : autoOutContract ,
11961204 },
1205+ },
1206+ existingInSwaps : []* loopdb.LoopIn {
11971207 {
1198- Contract : autoOutContract ,
1208+ Contract : autoInContract ,
11991209 },
12001210 },
12011211 suggestions : & Suggestions {
@@ -1247,6 +1257,9 @@ func TestInFlightLimit(t *testing.T) {
12471257 cfg .ListLoopOut = func () ([]* loopdb.LoopOut , error ) {
12481258 return testCase .existingSwaps , nil
12491259 }
1260+ cfg .ListLoopIn = func () ([]* loopdb.LoopIn , error ) {
1261+ return testCase .existingInSwaps , nil
1262+ }
12501263
12511264 lnd .Channels = []lndclient.ChannelInfo {
12521265 channel1 , channel2 ,
@@ -1568,6 +1581,181 @@ func TestFeePercentage(t *testing.T) {
15681581 }
15691582}
15701583
1584+ // TestBudgetWithLoopin tests that our autoloop budget accounts for loop in
1585+ // swaps that have been automatically dispatched. It tests out swaps that have
1586+ // already completed and those that are pending, inside and outside of our
1587+ // budget period to ensure that we account for all relevant swaps.
1588+ func TestBudgetWithLoopin (t * testing.T ) {
1589+ var (
1590+ budget btcutil.Amount = 10000
1591+
1592+ outsideBudget = testBudgetStart .Add (- 5 )
1593+ insideBudget = testBudgetStart .Add (5 )
1594+
1595+ contractOutsideBudget = & loopdb.LoopInContract {
1596+ SwapContract : loopdb.SwapContract {
1597+ InitiationTime : outsideBudget ,
1598+ MaxSwapFee : budget ,
1599+ },
1600+ Label : labels .AutoloopLabel (swap .TypeIn ),
1601+ }
1602+
1603+ // Set our spend equal to our budget so we don't need to
1604+ // calculate exact costs.
1605+ eventOutsideBudget = & loopdb.LoopEvent {
1606+ SwapStateData : loopdb.SwapStateData {
1607+ Cost : loopdb.SwapCost {
1608+ Server : budget ,
1609+ },
1610+ State : loopdb .StateSuccess ,
1611+ },
1612+ Time : outsideBudget ,
1613+ }
1614+
1615+ successWithinBudget = & loopdb.LoopEvent {
1616+ SwapStateData : loopdb.SwapStateData {
1617+ Cost : loopdb.SwapCost {
1618+ Server : budget ,
1619+ },
1620+ State : loopdb .StateSuccess ,
1621+ },
1622+ Time : insideBudget ,
1623+ }
1624+
1625+ okQuote = & loop.LoopOutQuote {
1626+ SwapFee : 15 ,
1627+ PrepayAmount : 30 ,
1628+ MinerFee : 1 ,
1629+ }
1630+
1631+ rec = loop.OutRequest {
1632+ Amount : 7500 ,
1633+ OutgoingChanSet : loopdb.ChannelSet {chanID1 .ToUint64 ()},
1634+ MaxMinerFee : scaleMinerFee (okQuote .MinerFee ),
1635+ MaxSwapFee : okQuote .SwapFee ,
1636+ MaxPrepayAmount : okQuote .PrepayAmount ,
1637+ SweepConfTarget : defaultConfTarget ,
1638+ Initiator : autoloopSwapInitiator ,
1639+ }
1640+
1641+ testPPM uint64 = 100000
1642+ )
1643+
1644+ rec .MaxPrepayRoutingFee , rec .MaxSwapRoutingFee = testPPMFees (
1645+ testPPM , okQuote , 7500 ,
1646+ )
1647+
1648+ tests := []struct {
1649+ name string
1650+
1651+ // loopIns is the set of loop in swaps that the client has
1652+ // performed.
1653+ loopIns []* loopdb.LoopIn
1654+
1655+ // suggestions is the set of swaps that we expect to be
1656+ // suggested given our current traffic.
1657+ suggestions * Suggestions
1658+ }{
1659+ {
1660+ name : "completed swap outside of budget" ,
1661+ loopIns : []* loopdb.LoopIn {
1662+ {
1663+ Loop : loopdb.Loop {
1664+ Events : []* loopdb.LoopEvent {
1665+ eventOutsideBudget ,
1666+ },
1667+ },
1668+ Contract : contractOutsideBudget ,
1669+ },
1670+ },
1671+ suggestions : & Suggestions {
1672+ OutSwaps : []loop.OutRequest {
1673+ rec ,
1674+ },
1675+ DisqualifiedChans : noneDisqualified ,
1676+ DisqualifiedPeers : noPeersDisqualified ,
1677+ },
1678+ },
1679+ {
1680+ name : "completed within budget" ,
1681+ loopIns : []* loopdb.LoopIn {
1682+ {
1683+ Loop : loopdb.Loop {
1684+ Events : []* loopdb.LoopEvent {
1685+ successWithinBudget ,
1686+ },
1687+ },
1688+ Contract : contractOutsideBudget ,
1689+ },
1690+ },
1691+ suggestions : & Suggestions {
1692+ DisqualifiedChans : map [lnwire.ShortChannelID ]Reason {
1693+ chanID1 : ReasonBudgetElapsed ,
1694+ },
1695+ DisqualifiedPeers : noPeersDisqualified ,
1696+ },
1697+ },
1698+ {
1699+ name : "pending created before budget" ,
1700+ loopIns : []* loopdb.LoopIn {
1701+ {
1702+ Contract : contractOutsideBudget ,
1703+ },
1704+ },
1705+ suggestions : & Suggestions {
1706+ DisqualifiedChans : map [lnwire.ShortChannelID ]Reason {
1707+ chanID1 : ReasonBudgetElapsed ,
1708+ },
1709+ DisqualifiedPeers : noPeersDisqualified ,
1710+ },
1711+ },
1712+ }
1713+
1714+ for _ , testCase := range tests {
1715+ testCase := testCase
1716+
1717+ t .Run (testCase .name , func (t * testing.T ) {
1718+ cfg , lnd := newTestConfig ()
1719+
1720+ // Set our channel and rules so that we will need to
1721+ // swap 7500 sats and our fee limit is 10% of that
1722+ // amount (750 sats).
1723+ lnd .Channels = []lndclient.ChannelInfo {
1724+ channel1 ,
1725+ }
1726+
1727+ cfg .ListLoopIn = func () ([]* loopdb.LoopIn , error ) {
1728+ return testCase .loopIns , nil
1729+ }
1730+
1731+ cfg .LoopOutQuote = func (_ context.Context ,
1732+ _ * loop.LoopOutQuoteRequest ) (* loop.LoopOutQuote ,
1733+ error ) {
1734+
1735+ return okQuote , nil
1736+ }
1737+
1738+ params := defaultParameters
1739+ params .AutoFeeBudget = budget
1740+ params .AutoFeeStartDate = testBudgetStart
1741+
1742+ params .FeeLimit = NewFeePortion (testPPM )
1743+ params .ChannelRules = map [lnwire.ShortChannelID ]* SwapRule {
1744+ chanID1 : chanRule ,
1745+ }
1746+
1747+ // Allow more than one in flight swap, to ensure that
1748+ // we restrict based on budget, not in-flight.
1749+ params .MaxAutoInFlight = 2
1750+
1751+ testSuggestSwaps (
1752+ t , newSuggestSwapsSetup (cfg , lnd , params ),
1753+ testCase .suggestions , nil ,
1754+ )
1755+ })
1756+ }
1757+ }
1758+
15711759// testSuggestSwapsSetup contains the elements that are used to create a
15721760// suggest swaps test.
15731761type testSuggestSwapsSetup struct {
0 commit comments