|
1 | 1 | package itest
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "bytes" |
| 5 | + |
4 | 6 | "github.com/btcsuite/btcd/btcutil"
|
| 7 | + "github.com/btcsuite/btcd/chaincfg" |
| 8 | + "github.com/btcsuite/btcd/txscript" |
5 | 9 | "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" |
6 | 15 | "github.com/lightningnetwork/lnd/lntest"
|
7 | 16 | "github.com/lightningnetwork/lnd/lntest/node"
|
8 | 17 | "github.com/lightningnetwork/lnd/lnwallet"
|
| 18 | + "github.com/lightningnetwork/lnd/lnwallet/chainfee" |
9 | 19 | "github.com/stretchr/testify/require"
|
10 | 20 | )
|
11 | 21 |
|
@@ -48,6 +58,12 @@ var walletTestCases = []*lntest.TestCase{
|
48 | 58 | runTestListUnspentRestart(ht, ht.FundCoinsP2TR)
|
49 | 59 | },
|
50 | 60 | },
|
| 61 | + { |
| 62 | + Name: "submit package basic", |
| 63 | + TestFunc: func(ht *lntest.HarnessTest) { |
| 64 | + runTestSubmitPackageBasicCPFP(ht) |
| 65 | + }, |
| 66 | + }, |
51 | 67 | }
|
52 | 68 |
|
53 | 69 | type fundMethod func(amt btcutil.Amount, hn *node.HarnessNode) *wire.MsgTx
|
@@ -118,3 +134,243 @@ func runTestListUnspentRestart(ht *lntest.HarnessTest, fundCoins fundMethod) {
|
118 | 134 | // Alice should have no UTXO.
|
119 | 135 | ht.AssertNumUTXOs(alice, 0)
|
120 | 136 | }
|
| 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 | +} |
0 commit comments