Skip to content

Commit 1983cc2

Browse files
committed
sweepbatcher: make func constructUnsignedTx pure
Also added a unit test for it.
1 parent f5b34ca commit 1983cc2

File tree

2 files changed

+326
-7
lines changed

2 files changed

+326
-7
lines changed

sweepbatcher/sweep_batch.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,9 +1094,9 @@ func (b *batch) createPsbt(unsignedTx *wire.MsgTx, sweeps []sweep) ([]byte,
10941094

10951095
// constructUnsignedTx creates unsigned tx from the sweeps, paying to the addr.
10961096
// It also returns absolute fee (from weight and clamped).
1097-
func (b *batch) constructUnsignedTx(sweeps []sweep,
1098-
address btcutil.Address) (*wire.MsgTx, lntypes.WeightUnit,
1099-
btcutil.Amount, btcutil.Amount, error) {
1097+
func constructUnsignedTx(sweeps []sweep, address btcutil.Address,
1098+
currentHeight int32, feeRate chainfee.SatPerKWeight) (*wire.MsgTx,
1099+
lntypes.WeightUnit, btcutil.Amount, btcutil.Amount, error) {
11001100

11011101
// Sanity check, there should be at least 1 sweep in this batch.
11021102
if len(sweeps) == 0 {
@@ -1106,7 +1106,7 @@ func (b *batch) constructUnsignedTx(sweeps []sweep,
11061106
// Create the batch transaction.
11071107
batchTx := &wire.MsgTx{
11081108
Version: 2,
1109-
LockTime: uint32(b.currentHeight),
1109+
LockTime: uint32(currentHeight),
11101110
}
11111111

11121112
// Add transaction inputs and estimate its weight.
@@ -1158,7 +1158,7 @@ func (b *batch) constructUnsignedTx(sweeps []sweep,
11581158

11591159
// Find weight and fee.
11601160
weight := weightEstimate.Weight()
1161-
feeForWeight := b.rbfCache.FeeRate.FeeForWeight(weight)
1161+
feeForWeight := feeRate.FeeForWeight(weight)
11621162

11631163
// Clamp the calculated fee to the max allowed fee amount for the batch.
11641164
fee := clampBatchFee(feeForWeight, batchAmt)
@@ -1243,8 +1243,8 @@ func (b *batch) publishMixedBatch(ctx context.Context) (btcutil.Amount, error,
12431243

12441244
// Construct unsigned batch transaction.
12451245
var err error
1246-
tx, weight, feeForWeight, fee, err = b.constructUnsignedTx(
1247-
sweeps, address,
1246+
tx, weight, feeForWeight, fee, err = constructUnsignedTx(
1247+
sweeps, address, b.currentHeight, b.rbfCache.FeeRate,
12481248
)
12491249
if err != nil {
12501250
return 0, fmt.Errorf("failed to construct tx: %w", err),

sweepbatcher/sweep_batch_test.go

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
package sweepbatcher
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/btcsuite/btcd/btcutil"
8+
"github.com/btcsuite/btcd/chaincfg"
9+
"github.com/btcsuite/btcd/chaincfg/chainhash"
10+
"github.com/btcsuite/btcd/txscript"
11+
"github.com/btcsuite/btcd/wire"
12+
"github.com/lightninglabs/loop/loopdb"
13+
"github.com/lightninglabs/loop/utils"
14+
"github.com/lightningnetwork/lnd/input"
15+
"github.com/lightningnetwork/lnd/lntypes"
16+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
17+
"github.com/stretchr/testify/require"
18+
)
19+
20+
// TestConstructUnsignedTx verifies that the function constructUnsignedTx
21+
// correctly creates unsigned transactions.
22+
func TestConstructUnsignedTx(t *testing.T) {
23+
// Prepare the necessary data for test cases.
24+
op1 := wire.OutPoint{
25+
Hash: chainhash.Hash{1, 1, 1},
26+
Index: 1,
27+
}
28+
op2 := wire.OutPoint{
29+
Hash: chainhash.Hash{2, 2, 2},
30+
Index: 2,
31+
}
32+
33+
batchPkScript, err := txscript.PayToAddrScript(destAddr)
34+
require.NoError(t, err)
35+
36+
p2trAddr := "bcrt1pa38tp2hgjevqv3jcsxeu7v72n0s5a3ck8q2u8r" +
37+
"k6mm67dv7uk26qq8je7e"
38+
p2trAddress, err := btcutil.DecodeAddress(p2trAddr, nil)
39+
require.NoError(t, err)
40+
p2trPkScript, err := txscript.PayToAddrScript(p2trAddress)
41+
require.NoError(t, err)
42+
43+
serializedPubKey := []byte{
44+
0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95,
45+
0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03,
46+
0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca,
47+
0x52, 0xc6, 0xb4,
48+
}
49+
p2pkAddress, err := btcutil.NewAddressPubKey(
50+
serializedPubKey, &chaincfg.RegressionNetParams,
51+
)
52+
require.NoError(t, err)
53+
54+
swapHash := lntypes.Hash{1, 1, 1}
55+
56+
swapContract := &loopdb.SwapContract{
57+
CltvExpiry: 222,
58+
AmountRequested: 2_000_000,
59+
ProtocolVersion: loopdb.ProtocolVersionMuSig2,
60+
HtlcKeys: htlcKeys,
61+
}
62+
63+
htlc, err := utils.GetHtlc(
64+
swapHash, swapContract, &chaincfg.RegressionNetParams,
65+
)
66+
require.NoError(t, err)
67+
estimator := htlc.AddSuccessToEstimator
68+
69+
brokenEstimator := func(*input.TxWeightEstimator) error {
70+
return fmt.Errorf("weight estimator test failure")
71+
}
72+
73+
cases := []struct {
74+
name string
75+
sweeps []sweep
76+
address btcutil.Address
77+
currentHeight int32
78+
feeRate chainfee.SatPerKWeight
79+
wantErr string
80+
wantTx *wire.MsgTx
81+
wantWeight lntypes.WeightUnit
82+
wantFeeForWeight btcutil.Amount
83+
wantFee btcutil.Amount
84+
}{
85+
{
86+
name: "no sweeps error",
87+
wantErr: "no sweeps in batch",
88+
},
89+
90+
{
91+
name: "two coop sweeps",
92+
sweeps: []sweep{
93+
{
94+
outpoint: op1,
95+
value: 1_000_000,
96+
},
97+
{
98+
outpoint: op2,
99+
value: 2_000_000,
100+
},
101+
},
102+
address: destAddr,
103+
currentHeight: 800_000,
104+
feeRate: 1000,
105+
wantTx: &wire.MsgTx{
106+
Version: 2,
107+
LockTime: 800_000,
108+
TxIn: []*wire.TxIn{
109+
{
110+
PreviousOutPoint: op1,
111+
},
112+
{
113+
PreviousOutPoint: op2,
114+
},
115+
},
116+
TxOut: []*wire.TxOut{
117+
{
118+
Value: 2999374,
119+
PkScript: batchPkScript,
120+
},
121+
},
122+
},
123+
wantWeight: 626,
124+
wantFeeForWeight: 626,
125+
wantFee: 626,
126+
},
127+
128+
{
129+
name: "p2tr destination address",
130+
sweeps: []sweep{
131+
{
132+
outpoint: op1,
133+
value: 1_000_000,
134+
},
135+
{
136+
outpoint: op2,
137+
value: 2_000_000,
138+
},
139+
},
140+
address: p2trAddress,
141+
currentHeight: 800_000,
142+
feeRate: 1000,
143+
wantTx: &wire.MsgTx{
144+
Version: 2,
145+
LockTime: 800_000,
146+
TxIn: []*wire.TxIn{
147+
{
148+
PreviousOutPoint: op1,
149+
},
150+
{
151+
PreviousOutPoint: op2,
152+
},
153+
},
154+
TxOut: []*wire.TxOut{
155+
{
156+
Value: 2999326,
157+
PkScript: p2trPkScript,
158+
},
159+
},
160+
},
161+
wantWeight: 674,
162+
wantFeeForWeight: 674,
163+
wantFee: 674,
164+
},
165+
166+
{
167+
name: "unknown kind of address",
168+
sweeps: []sweep{
169+
{
170+
outpoint: op1,
171+
value: 1_000_000,
172+
},
173+
{
174+
outpoint: op2,
175+
value: 2_000_000,
176+
},
177+
},
178+
address: nil,
179+
wantErr: "unsupported address type",
180+
},
181+
182+
{
183+
name: "pay-to-pubkey address",
184+
sweeps: []sweep{
185+
{
186+
outpoint: op1,
187+
value: 1_000_000,
188+
},
189+
{
190+
outpoint: op2,
191+
value: 2_000_000,
192+
},
193+
},
194+
address: p2pkAddress,
195+
wantErr: "unknown address type",
196+
},
197+
198+
{
199+
name: "fee more than 20% clamped",
200+
sweeps: []sweep{
201+
{
202+
outpoint: op1,
203+
value: 1_000_000,
204+
},
205+
{
206+
outpoint: op2,
207+
value: 2_000_000,
208+
},
209+
},
210+
address: destAddr,
211+
currentHeight: 800_000,
212+
feeRate: 1_000_000,
213+
wantTx: &wire.MsgTx{
214+
Version: 2,
215+
LockTime: 800_000,
216+
TxIn: []*wire.TxIn{
217+
{
218+
PreviousOutPoint: op1,
219+
},
220+
{
221+
PreviousOutPoint: op2,
222+
},
223+
},
224+
TxOut: []*wire.TxOut{
225+
{
226+
Value: 2400000,
227+
PkScript: batchPkScript,
228+
},
229+
},
230+
},
231+
wantWeight: 626,
232+
wantFeeForWeight: 626_000,
233+
wantFee: 600_000,
234+
},
235+
236+
{
237+
name: "coop and noncoop",
238+
sweeps: []sweep{
239+
{
240+
outpoint: op1,
241+
value: 1_000_000,
242+
},
243+
{
244+
outpoint: op2,
245+
value: 2_000_000,
246+
nonCoopHint: true,
247+
htlc: *htlc,
248+
htlcSuccessEstimator: estimator,
249+
},
250+
},
251+
address: destAddr,
252+
currentHeight: 800_000,
253+
feeRate: 1000,
254+
wantTx: &wire.MsgTx{
255+
Version: 2,
256+
LockTime: 800_000,
257+
TxIn: []*wire.TxIn{
258+
{
259+
PreviousOutPoint: op1,
260+
},
261+
{
262+
PreviousOutPoint: op2,
263+
Sequence: 1,
264+
},
265+
},
266+
TxOut: []*wire.TxOut{
267+
{
268+
Value: 2999211,
269+
PkScript: batchPkScript,
270+
},
271+
},
272+
},
273+
wantWeight: 789,
274+
wantFeeForWeight: 789,
275+
wantFee: 789,
276+
},
277+
278+
{
279+
name: "weight estimator fails",
280+
sweeps: []sweep{
281+
{
282+
outpoint: op1,
283+
value: 1_000_000,
284+
},
285+
{
286+
outpoint: op2,
287+
value: 2_000_000,
288+
nonCoopHint: true,
289+
htlc: *htlc,
290+
htlcSuccessEstimator: brokenEstimator,
291+
},
292+
},
293+
address: destAddr,
294+
currentHeight: 800_000,
295+
feeRate: 1000,
296+
wantErr: "sweep.htlcSuccessEstimator failed: " +
297+
"weight estimator test failure",
298+
},
299+
}
300+
301+
for _, tc := range cases {
302+
t.Run(tc.name, func(t *testing.T) {
303+
tx, weight, feeForW, fee, err := constructUnsignedTx(
304+
tc.sweeps, tc.address, tc.currentHeight,
305+
tc.feeRate,
306+
)
307+
if tc.wantErr != "" {
308+
require.Error(t, err)
309+
require.ErrorContains(t, err, tc.wantErr)
310+
} else {
311+
require.NoError(t, err)
312+
require.Equal(t, tc.wantTx, tx)
313+
require.Equal(t, tc.wantWeight, weight)
314+
require.Equal(t, tc.wantFeeForWeight, feeForW)
315+
require.Equal(t, tc.wantFee, fee)
316+
}
317+
})
318+
}
319+
}

0 commit comments

Comments
 (0)