Skip to content

Commit f8b9f36

Browse files
committed
[wip]itest: add itest
1 parent ad9dd8f commit f8b9f36

File tree

3 files changed

+284
-0
lines changed

3 files changed

+284
-0
lines changed

itest/lnd_wallet.go

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
package itest
22

33
import (
4+
"bytes"
5+
46
"github.com/btcsuite/btcd/btcutil"
7+
"github.com/btcsuite/btcd/chaincfg"
8+
"github.com/btcsuite/btcd/txscript"
59
"github.com/btcsuite/btcd/wire"
10+
"github.com/davecgh/go-spew/spew"
11+
"github.com/lightningnetwork/lnd/input"
12+
"github.com/lightningnetwork/lnd/lnrpc"
13+
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
14+
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
615
"github.com/lightningnetwork/lnd/lntest"
716
"github.com/lightningnetwork/lnd/lntest/node"
817
"github.com/lightningnetwork/lnd/lnwallet"
18+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
919
"github.com/stretchr/testify/require"
1020
)
1121

@@ -48,6 +58,12 @@ var walletTestCases = []*lntest.TestCase{
4858
runTestListUnspentRestart(ht, ht.FundCoinsP2TR)
4959
},
5060
},
61+
{
62+
Name: "submit package basic",
63+
TestFunc: func(ht *lntest.HarnessTest) {
64+
runTestSubmitPackageBasicCPFP(ht)
65+
},
66+
},
5167
}
5268

5369
type fundMethod func(amt btcutil.Amount, hn *node.HarnessNode) *wire.MsgTx
@@ -118,3 +134,243 @@ func runTestListUnspentRestart(ht *lntest.HarnessTest, fundCoins fundMethod) {
118134
// Alice should have no UTXO.
119135
ht.AssertNumUTXOs(alice, 0)
120136
}
137+
138+
// Helper to convert walletrpc.KeyDescriptor to a P2WPKH address.
139+
func keyDescToAddrP2WPKH(ht *lntest.HarnessTest,
140+
keyDesc *signrpc.KeyDescriptor) btcutil.Address {
141+
142+
harnessNetParams := &chaincfg.RegressionNetParams
143+
144+
pubKeyBytes := keyDesc.RawKeyBytes
145+
require.NotEmpty(ht, pubKeyBytes, "KeyDescriptor has empty RawKeyBytes")
146+
147+
addr, err := btcutil.NewAddressWitnessPubKeyHash(
148+
btcutil.Hash160(pubKeyBytes),
149+
harnessNetParams,
150+
)
151+
require.NoError(ht, err)
152+
153+
return addr
154+
}
155+
156+
// runTestSubmitPackageBasicCPFP tests the SubmitPackage RPC for a simple
157+
// Child-Pays-For-Parent (CPFP) scenario.
158+
func runTestSubmitPackageBasicCPFP(ht *lntest.HarnessTest) {
159+
const (
160+
fundingAmount = btcutil.Amount(10_000_000)
161+
childFeeRate = 100
162+
testKeyFamily = 111
163+
)
164+
165+
// Create a new node (Alice).
166+
alice := ht.NewNode("Alice", nil)
167+
defer ht.Shutdown(alice)
168+
169+
// Fund Alice with a UTXO.
170+
ht.FundCoins(fundingAmount*2, alice)
171+
172+
// Derive a key and address for the initial funding.
173+
fundingKey := alice.RPC.DeriveNextKey(
174+
&walletrpc.KeyReq{
175+
KeyFamily: testKeyFamily,
176+
},
177+
)
178+
179+
fundingAddr := keyDescToAddrP2WPKH(ht, fundingKey)
180+
ht.Logf("Funding address: %s", fundingAddr)
181+
182+
// Send funds to this specific address.
183+
ht.SendCoinsToAddr(alice, fundingAddr, fundingAmount)
184+
block := ht.MineBlocksAndAssertNumTxes(1, 1)
185+
186+
// Now find the funding tx in the mined block so we can use the output
187+
// as an input to the parent tx.
188+
fundingPkScript := ht.PayToAddrScript(fundingAddr)
189+
var (
190+
fundingOutIndex uint32
191+
fundingOut *wire.TxOut
192+
fundingTx *wire.MsgTx
193+
)
194+
195+
for _, tx := range block[0].Transactions {
196+
for i, txOut := range tx.TxOut {
197+
if bytes.Equal(txOut.PkScript, fundingPkScript) {
198+
fundingTx = tx
199+
fundingOut = txOut
200+
fundingOutIndex = uint32(i)
201+
202+
break
203+
}
204+
}
205+
}
206+
require.NotNil(ht, fundingTx, "Funding output not found")
207+
208+
fundOutPoint := wire.OutPoint{
209+
Hash: fundingTx.TxHash(),
210+
Index: fundingOutIndex,
211+
}
212+
213+
parentOutValue := int64(fundingAmount)
214+
215+
// Create a V3 "parent" transaction spending the funding output.
216+
parentTx := wire.NewMsgTx(3)
217+
parentTx.AddTxIn(wire.NewTxIn(&fundOutPoint, nil, nil))
218+
219+
// Derive a key and address for the parent's output.
220+
parentOutKey := alice.RPC.DeriveNextKey(
221+
&walletrpc.KeyReq{
222+
KeyFamily: testKeyFamily,
223+
},
224+
)
225+
226+
parentOutAddr := keyDescToAddrP2WPKH(ht, parentOutKey)
227+
parentOutPkScript := ht.PayToAddrScript(parentOutAddr)
228+
parentTx.AddTxOut(wire.NewTxOut(parentOutValue, parentOutPkScript))
229+
230+
// Serialize the parent tx.
231+
var parentBuf bytes.Buffer
232+
require.NoError(ht, parentTx.Serialize(&parentBuf))
233+
234+
// Create a sign descriptor for the parent tx.
235+
parentSignDesc := &signrpc.SignDescriptor{
236+
KeyDesc: fundingKey,
237+
Output: &signrpc.TxOut{
238+
Value: fundingOut.Value,
239+
PkScript: fundingOut.PkScript,
240+
},
241+
Sighash: uint32(txscript.SigHashAll),
242+
WitnessScript: fundingOut.PkScript,
243+
SignMethod: signrpc.SignMethod_SIGN_METHOD_WITNESS_V0,
244+
}
245+
246+
// Sign the parent tx.
247+
parentSignResp, err := alice.RPC.Signer.SignOutputRaw(
248+
ht.Context(),
249+
&signrpc.SignReq{
250+
RawTxBytes: parentBuf.Bytes(),
251+
SignDescs: []*signrpc.SignDescriptor{
252+
parentSignDesc,
253+
},
254+
},
255+
)
256+
require.NoError(ht, err)
257+
258+
// Set the parent tx's witness to the signature.
259+
parentTx.TxIn[0].Witness = makeP2WPKHWitness(
260+
parentSignResp.RawSigs[0], fundingKey,
261+
)
262+
263+
// Reserialize the parent after the witness has been set.
264+
parentBuf.Reset()
265+
require.NoError(ht, parentTx.Serialize(&parentBuf))
266+
267+
parentOut := &wire.OutPoint{
268+
Hash: parentTx.TxHash(),
269+
Index: 0,
270+
}
271+
272+
// Create a new P2WPKH address for the child's output.
273+
childOutAddrResp := alice.RPC.NewAddress(
274+
&lnrpc.NewAddressRequest{
275+
Account: lnwallet.DefaultAccountName,
276+
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
277+
},
278+
)
279+
require.NoError(ht, err)
280+
281+
// Estimate child's weight and fee.
282+
childEstimator := input.TxWeightEstimator{}
283+
childEstimator.AddP2WKHInput()
284+
childEstimator.AddP2WKHOutput()
285+
childWeight := childEstimator.Weight()
286+
childFee := chainfee.SatPerVByte(
287+
childFeeRate,
288+
).FeePerKWeight().FeeForWeight(
289+
childWeight,
290+
)
291+
292+
childOutValue := btcutil.Amount(parentOutValue) - childFee
293+
294+
// Create the unsigned child transaction spending the parent's output.
295+
childTx := wire.NewMsgTx(3)
296+
childTx.AddTxIn(wire.NewTxIn(parentOut, nil, nil))
297+
298+
childOutAddr := ht.DecodeAddress(childOutAddrResp.Address)
299+
childOutPkScript := ht.PayToAddrScript(childOutAddr)
300+
301+
childTx.AddTxOut(
302+
wire.NewTxOut(int64(childOutValue), childOutPkScript),
303+
)
304+
305+
// Serialize the child tx.
306+
var childBuf bytes.Buffer
307+
require.NoError(ht, childTx.Serialize(&childBuf))
308+
309+
// Create a sign descriptor for the child tx.
310+
childSignDesc := &signrpc.SignDescriptor{
311+
KeyDesc: parentOutKey,
312+
Output: &signrpc.TxOut{
313+
Value: parentOutValue,
314+
PkScript: parentOutPkScript,
315+
},
316+
Sighash: uint32(txscript.SigHashAll),
317+
WitnessScript: parentOutPkScript,
318+
SignMethod: signrpc.SignMethod_SIGN_METHOD_WITNESS_V0,
319+
}
320+
321+
// Sign the child tx.
322+
childSignResp, err := alice.RPC.Signer.SignOutputRaw(
323+
ht.Context(),
324+
&signrpc.SignReq{
325+
RawTxBytes: childBuf.Bytes(),
326+
SignDescs: []*signrpc.SignDescriptor{
327+
childSignDesc,
328+
},
329+
},
330+
)
331+
require.NoError(ht, err)
332+
333+
// Set the child tx's witness to the signature.
334+
childTx.TxIn[0].Witness = makeP2WPKHWitness(
335+
childSignResp.RawSigs[0], parentOutKey,
336+
)
337+
338+
// Reserialize the child after the witness has been set.
339+
childBuf.Reset()
340+
require.NoError(ht, childTx.Serialize(&childBuf))
341+
342+
submitPackageResp, err := alice.RPC.WalletKit.SubmitPackage(
343+
ht.Context(), &walletrpc.SubmitPackageRequest{
344+
ParentTxs: [][]byte{parentBuf.Bytes()},
345+
ChildTx: childBuf.Bytes(),
346+
},
347+
)
348+
require.NoError(ht, err)
349+
ht.Logf("SubmitPackage response: %v", spew.Sdump(submitPackageResp))
350+
351+
// TODO(bhandras): once we have package support for btcd or we switch
352+
// to bitcoind miner in the tests we can uncomment the following lines
353+
// and test the package transactions in the mempool and mined blocks.
354+
/*
355+
ht.AssertTxInMempool(parentTx.TxHash())
356+
ht.AssertTxInMempool(childTx.TxHash())
357+
358+
ht.Log("Mining block with package transactions...")
359+
block = ht.MineBlocksAndAssertNumTxes(1, 2)
360+
ht.AssertTxInBlock(block[0], parentTx.TxHash())
361+
ht.AssertTxInBlock(block[0], childTx.TxHash())
362+
*/
363+
}
364+
365+
// makeP2WPKHWitness creates a P2WPKH witness stack for the given signature and
366+
// key descriptor.
367+
func makeP2WPKHWitness(sig []byte,
368+
keyDesc *signrpc.KeyDescriptor) wire.TxWitness {
369+
370+
sigWithHash := append(sig, byte(txscript.SigHashAll))
371+
372+
return wire.TxWitness{
373+
sigWithHash, // element 0: 70-73-byte signature.
374+
keyDesc.RawKeyBytes, // element 1: 33-byte compressed pubkey.
375+
}
376+
}

lntest/harness.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,13 @@ func (h *HarnessTest) IsNeutrinoBackend() bool {
14191419
return h.manager.chainBackend.Name() == NeutrinoBackendName
14201420
}
14211421

1422+
// IsBitcoindBackend returns a bool indicating whether the node is using a
1423+
// bitcoind as its backend. This is useful when we want to only run certain
1424+
// tests with a bitcoind backend.
1425+
func (h *HarnessTest) IsBitcoindBackend() bool {
1426+
return h.manager.chainBackend.Name() == BitcoindBackendName
1427+
}
1428+
14221429
// fundCoins attempts to send amt satoshis from the internal mining node to the
14231430
// targeted lightning node. The confirmed boolean indicates whether the
14241431
// transaction that pays to the target should confirm. For neutrino backend,
@@ -2282,6 +2289,24 @@ func (h *HarnessTest) SendCoins(a, b *node.HarnessNode,
22822289
return tx
22832290
}
22842291

2292+
// SendCoinsToAddr sends the given amount from the passed node to the given
2293+
// address amount, returns the sending tx.
2294+
func (h *HarnessTest) SendCoinsToAddr(node *node.HarnessNode,
2295+
addr btcutil.Address, amt btcutil.Amount) *wire.MsgTx {
2296+
2297+
// Send the coins to the given address. We should expect a tx to be
2298+
// broadcast and seen in the mempool.
2299+
sendReq := &lnrpc.SendCoinsRequest{
2300+
Addr: addr.String(),
2301+
Amount: int64(amt),
2302+
TargetConf: 6,
2303+
}
2304+
node.RPC.SendCoins(sendReq)
2305+
tx := h.GetNumTxsFromMempool(1)[0]
2306+
2307+
return tx
2308+
}
2309+
22852310
// SendCoins sends all coins from node A to node B, returns the sending tx.
22862311
func (h *HarnessTest) SendAllCoins(a, b *node.HarnessNode) *wire.MsgTx {
22872312
// Create an address for Bob receive the coins.

lntest/utils.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ const (
2323
// NeutrinoBackendName is the name of the neutrino backend.
2424
NeutrinoBackendName = "neutrino"
2525

26+
// BitcoindBackendName is the name of the bitcoind backend.
27+
BitcoindBackendName = "bitcoind"
28+
2629
DefaultTimeout = wait.DefaultTimeout
2730

2831
// noFeeLimitMsat is used to specify we will put no requirements on fee

0 commit comments

Comments
 (0)