Skip to content

Commit 7140501

Browse files
committed
sweepbatcher: make func constructUnsignedTx pure
Also added a unit test for it.
1 parent 84820f2 commit 7140501

File tree

2 files changed

+325
-7
lines changed

2 files changed

+325
-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: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
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+
p2pkAddress, err := btcutil.NewAddressPubKey(
49+
serializedPubKey, &chaincfg.RegressionNetParams,
50+
)
51+
require.NoError(t, err)
52+
53+
swapHash := lntypes.Hash{1, 1, 1}
54+
55+
swapContract := &loopdb.SwapContract{
56+
CltvExpiry: 222,
57+
AmountRequested: 2_000_000,
58+
ProtocolVersion: loopdb.ProtocolVersionMuSig2,
59+
HtlcKeys: htlcKeys,
60+
}
61+
62+
htlc, err := utils.GetHtlc(
63+
swapHash, swapContract, &chaincfg.RegressionNetParams,
64+
)
65+
require.NoError(t, err)
66+
estimator := htlc.AddSuccessToEstimator
67+
68+
brokenEstimator := func(*input.TxWeightEstimator) error {
69+
return fmt.Errorf("weight estimator test failure")
70+
}
71+
72+
cases := []struct {
73+
name string
74+
sweeps []sweep
75+
address btcutil.Address
76+
currentHeight int32
77+
feeRate chainfee.SatPerKWeight
78+
wantErr string
79+
wantTx *wire.MsgTx
80+
wantWeight lntypes.WeightUnit
81+
wantFeeForWeight btcutil.Amount
82+
wantFee btcutil.Amount
83+
}{
84+
{
85+
name: "no sweeps error",
86+
wantErr: "no sweeps in batch",
87+
},
88+
89+
{
90+
name: "two coop sweeps",
91+
sweeps: []sweep{
92+
{
93+
outpoint: op1,
94+
value: 1_000_000,
95+
},
96+
{
97+
outpoint: op2,
98+
value: 2_000_000,
99+
},
100+
},
101+
address: destAddr,
102+
currentHeight: 800_000,
103+
feeRate: 1000,
104+
wantTx: &wire.MsgTx{
105+
Version: 2,
106+
LockTime: 800_000,
107+
TxIn: []*wire.TxIn{
108+
{
109+
PreviousOutPoint: op1,
110+
},
111+
{
112+
PreviousOutPoint: op2,
113+
},
114+
},
115+
TxOut: []*wire.TxOut{
116+
{
117+
Value: 2999374,
118+
PkScript: batchPkScript,
119+
},
120+
},
121+
},
122+
wantWeight: 626,
123+
wantFeeForWeight: 626,
124+
wantFee: 626,
125+
},
126+
127+
{
128+
name: "p2tr destination address",
129+
sweeps: []sweep{
130+
{
131+
outpoint: op1,
132+
value: 1_000_000,
133+
},
134+
{
135+
outpoint: op2,
136+
value: 2_000_000,
137+
},
138+
},
139+
address: p2trAddress,
140+
currentHeight: 800_000,
141+
feeRate: 1000,
142+
wantTx: &wire.MsgTx{
143+
Version: 2,
144+
LockTime: 800_000,
145+
TxIn: []*wire.TxIn{
146+
{
147+
PreviousOutPoint: op1,
148+
},
149+
{
150+
PreviousOutPoint: op2,
151+
},
152+
},
153+
TxOut: []*wire.TxOut{
154+
{
155+
Value: 2999326,
156+
PkScript: p2trPkScript,
157+
},
158+
},
159+
},
160+
wantWeight: 674,
161+
wantFeeForWeight: 674,
162+
wantFee: 674,
163+
},
164+
165+
{
166+
name: "unknown kind of address",
167+
sweeps: []sweep{
168+
{
169+
outpoint: op1,
170+
value: 1_000_000,
171+
},
172+
{
173+
outpoint: op2,
174+
value: 2_000_000,
175+
},
176+
},
177+
address: nil,
178+
wantErr: "unsupported address type",
179+
},
180+
181+
{
182+
name: "pay-to-pubkey address",
183+
sweeps: []sweep{
184+
{
185+
outpoint: op1,
186+
value: 1_000_000,
187+
},
188+
{
189+
outpoint: op2,
190+
value: 2_000_000,
191+
},
192+
},
193+
address: p2pkAddress,
194+
wantErr: "unknown address type",
195+
},
196+
197+
{
198+
name: "fee more than 20% clamped",
199+
sweeps: []sweep{
200+
{
201+
outpoint: op1,
202+
value: 1_000_000,
203+
},
204+
{
205+
outpoint: op2,
206+
value: 2_000_000,
207+
},
208+
},
209+
address: destAddr,
210+
currentHeight: 800_000,
211+
feeRate: 1_000_000,
212+
wantTx: &wire.MsgTx{
213+
Version: 2,
214+
LockTime: 800_000,
215+
TxIn: []*wire.TxIn{
216+
{
217+
PreviousOutPoint: op1,
218+
},
219+
{
220+
PreviousOutPoint: op2,
221+
},
222+
},
223+
TxOut: []*wire.TxOut{
224+
{
225+
Value: 2400000,
226+
PkScript: batchPkScript,
227+
},
228+
},
229+
},
230+
wantWeight: 626,
231+
wantFeeForWeight: 626_000,
232+
wantFee: 600_000,
233+
},
234+
235+
{
236+
name: "coop and noncoop",
237+
sweeps: []sweep{
238+
{
239+
outpoint: op1,
240+
value: 1_000_000,
241+
},
242+
{
243+
outpoint: op2,
244+
value: 2_000_000,
245+
nonCoopHint: true,
246+
htlc: *htlc,
247+
htlcSuccessEstimator: estimator,
248+
},
249+
},
250+
address: destAddr,
251+
currentHeight: 800_000,
252+
feeRate: 1000,
253+
wantTx: &wire.MsgTx{
254+
Version: 2,
255+
LockTime: 800_000,
256+
TxIn: []*wire.TxIn{
257+
{
258+
PreviousOutPoint: op1,
259+
},
260+
{
261+
PreviousOutPoint: op2,
262+
Sequence: 1,
263+
},
264+
},
265+
TxOut: []*wire.TxOut{
266+
{
267+
Value: 2999211,
268+
PkScript: batchPkScript,
269+
},
270+
},
271+
},
272+
wantWeight: 789,
273+
wantFeeForWeight: 789,
274+
wantFee: 789,
275+
},
276+
277+
{
278+
name: "weight estimator fails",
279+
sweeps: []sweep{
280+
{
281+
outpoint: op1,
282+
value: 1_000_000,
283+
},
284+
{
285+
outpoint: op2,
286+
value: 2_000_000,
287+
nonCoopHint: true,
288+
htlc: *htlc,
289+
htlcSuccessEstimator: brokenEstimator,
290+
},
291+
},
292+
address: destAddr,
293+
currentHeight: 800_000,
294+
feeRate: 1000,
295+
wantErr: "sweep.htlcSuccessEstimator failed: " +
296+
"weight estimator test failure",
297+
},
298+
}
299+
300+
for _, tc := range cases {
301+
t.Run(tc.name, func(t *testing.T) {
302+
tx, weight, feeForW, fee, err := constructUnsignedTx(
303+
tc.sweeps, tc.address, tc.currentHeight,
304+
tc.feeRate,
305+
)
306+
if tc.wantErr != "" {
307+
require.Error(t, err)
308+
require.ErrorContains(t, err, tc.wantErr)
309+
} else {
310+
require.NoError(t, err)
311+
require.Equal(t, tc.wantTx, tx)
312+
require.Equal(t, tc.wantWeight, weight)
313+
require.Equal(t, tc.wantFeeForWeight, feeForW)
314+
require.Equal(t, tc.wantFee, fee)
315+
}
316+
})
317+
}
318+
}

0 commit comments

Comments
 (0)