func testFundingTransactionLockedOutputs(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running funding txn locked outputs test") // Create a single channel asking for 16 BTC total. fundingAmount := btcutil.Amount(8 * 1e8) _, err := wallet.InitChannelReservation(fundingAmount, fundingAmount, testPub, bobAddr, numReqConfs, 4) if err != nil { t.Fatalf("unable to initialize funding reservation 1: %v", err) } // Now attempt to reserve funds for another channel, this time // requesting 900 BTC. We only have around 64BTC worth of outpoints // that aren't locked, so this should fail. amt := btcutil.Amount(900 * 1e8) failedReservation, err := wallet.InitChannelReservation(amt, amt, testPub, bobAddr, numReqConfs, 4) if err == nil { t.Fatalf("not error returned, should fail on coin selection") } if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok { t.Fatalf("error not coinselect error: %v", err) } if failedReservation != nil { t.Fatalf("reservation should be nil") } }
// newServer creates a new instance of the server which is to listen using the // passed listener address. func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, bio lnwallet.BlockChainIO, wallet *lnwallet.LightningWallet, chanDB *channeldb.DB) (*server, error) { privKey, err := wallet.GetIdentitykey() if err != nil { return nil, err } listeners := make([]net.Listener, len(listenAddrs)) for i, addr := range listenAddrs { listeners[i], err = brontide.NewListener(privKey, addr) if err != nil { return nil, err } } serializedPubKey := privKey.PubKey().SerializeCompressed() s := &server{ bio: bio, chainNotifier: notifier, chanDB: chanDB, fundingMgr: newFundingManager(wallet), invoices: newInvoiceRegistry(chanDB), lnwallet: wallet, identityPriv: privKey, // TODO(roasbeef): derive proper onion key based on rotation // schedule sphinx: sphinx.NewRouter(privKey, activeNetParams.Params), lightningID: fastsha256.Sum256(serializedPubKey), listeners: listeners, peers: make(map[int32]*peer), newPeers: make(chan *peer, 100), donePeers: make(chan *peer, 100), queries: make(chan interface{}), quit: make(chan struct{}), } // If the debug HTLC flag is on, then we invoice a "master debug" // invoice which all outgoing payments will be sent and all incoming // HTLC's with the debug R-Hash immediately settled. if cfg.DebugHTLC { kiloCoin := btcutil.Amount(btcutil.SatoshiPerBitcoin * 1000) s.invoices.AddDebugInvoice(kiloCoin, *debugPre) srvrLog.Debugf("Debug HTLC invoice inserted, preimage=%x, hash=%x", debugPre[:], debugHash[:]) } s.utxoNursery = newUtxoNursery(notifier, wallet) // Create a new routing manager with ourself as the sole node within // the graph. selfVertex := hex.EncodeToString(serializedPubKey) s.routingMgr = routing.NewRoutingManager(graph.NewID(selfVertex), nil) s.htlcSwitch = newHtlcSwitch(serializedPubKey, s.routingMgr) s.rpcServer = newRpcServer(s) return s, nil }
// assertProperBalance asserts than the total value of the unspent outputs // within the wallet are *exactly* amount. If unable to retrieve the current // balance, or the assertion fails, the test will halt with a fatal error. func assertProperBalance(t *testing.T, lw *lnwallet.LightningWallet, numConfirms int32, amount int64) { balance, err := lw.ConfirmedBalance(numConfirms, false) if err != nil { t.Fatalf("unable to query for balance: %v", err) } if balance != btcutil.Amount(amount*1e8) { t.Fatalf("wallet credits not properly loaded, should have 40BTC, "+ "instead have %v", balance) } }
func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet, numOutputs, btcPerOutput int) error { // Using the mining node, spend from a coinbase output numOutputs to // give us btcPerOutput with each output. satoshiPerOutput := int64(btcPerOutput * 1e8) addrs := make([]btcutil.Address, 0, numOutputs) for i := 0; i < numOutputs; i++ { // Grab a fresh address from the wallet to house this output. walletAddr, err := w.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { return err } script, err := txscript.PayToAddrScript(walletAddr) if err != nil { return err } addrs = append(addrs, walletAddr) output := &wire.TxOut{satoshiPerOutput, script} if _, err := miner.CoinbaseSpend([]*wire.TxOut{output}); err != nil { return err } } // TODO(roasbeef): shouldn't hardcode 10, use config param that dictates // how many confs we wait before opening a channel. // Generate 10 blocks with the mining node, this should mine all // numOutputs transactions created above. We generate 10 blocks here // in order to give all the outputs a "sufficient" number of confirmations. if _, err := miner.Node.Generate(10); err != nil { return err } // Wait until the wallet has finished syncing up to the main chain. ticker := time.NewTicker(100 * time.Millisecond) expectedBalance := btcutil.Amount(satoshiPerOutput * int64(numOutputs)) out: for { select { case <-ticker.C: balance, err := w.ConfirmedBalance(1, false) if err != nil { return err } if balance == expectedBalance { break out } } } ticker.Stop() return nil }
func testListTransactionDetails(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running list transaction details test") // Create 5 new outputs spendable by the wallet. const numTxns = 5 const outputAmt = btcutil.SatoshiPerBitcoin txids := make(map[wire.ShaHash]struct{}) for i := 0; i < numTxns; i++ { addr, err := wallet.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { t.Fatalf("unable to create new address: %v", err) } script, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create output script: %v", err) } output := &wire.TxOut{outputAmt, script} txid, err := miner.CoinbaseSpend([]*wire.TxOut{output}) if err != nil { t.Fatalf("unable to send coinbase: %v", err) } txids[*txid] = struct{}{} } // Generate 10 blocks to mine all the transactions created above. const numBlocksMined = 10 blocks, err := miner.Node.Generate(numBlocksMined) if err != nil { t.Fatalf("unable to mine blocks: %v", err) } // Next, fetch all the current transaction details. // TODO(roasbeef): use ntfn client here instead? time.Sleep(time.Second * 2) txDetails, err := wallet.ListTransactionDetails() if err != nil { t.Fatalf("unable to fetch tx details: %v", err) } // Each of the transactions created above should be found with the // proper details populated. for _, txDetail := range txDetails { if _, ok := txids[txDetail.Hash]; !ok { continue } if txDetail.NumConfirmations != numBlocksMined { t.Fatalf("num confs incorrect, got %v expected %v", txDetail.NumConfirmations, numBlocksMined) } if txDetail.Value != outputAmt { t.Fatalf("tx value incorrect, got %v expected %v", txDetail.Value, outputAmt) } if !bytes.Equal(txDetail.BlockHash[:], blocks[0][:]) { t.Fatalf("block hash mismatch, got %v expected %v", txDetail.BlockHash, blocks[0]) } delete(txids, txDetail.Hash) } if len(txids) != 0 { t.Fatalf("all transactions not found in details!") } // Next create a transaction paying to an output which isn't under the // wallet's control. b := txscript.NewScriptBuilder() b.AddOp(txscript.OP_0) outputScript, err := b.Script() if err != nil { t.Fatalf("unable to make output script: %v", err) } burnOutput := wire.NewTxOut(outputAmt, outputScript) burnTXID, err := wallet.SendOutputs([]*wire.TxOut{burnOutput}) if err != nil { t.Fatalf("unable to create burn tx: %v", err) } burnBlock, err := miner.Node.Generate(1) if err != nil { t.Fatalf("unable to mine block: %v", err) } // Fetch the transaction details again, the new transaction should be // shown as debiting from the wallet's balance. time.Sleep(time.Second * 2) txDetails, err = wallet.ListTransactionDetails() if err != nil { t.Fatalf("unable to fetch tx details: %v", err) } var burnTxFound bool for _, txDetail := range txDetails { if !bytes.Equal(txDetail.Hash[:], burnTXID[:]) { continue } burnTxFound = true if txDetail.NumConfirmations != 1 { t.Fatalf("num confs incorrect, got %v expected %v", txDetail.NumConfirmations, 1) } if txDetail.Value >= -outputAmt { t.Fatalf("tx value incorrect, got %v expected %v", txDetail.Value, -outputAmt) } if !bytes.Equal(txDetail.BlockHash[:], burnBlock[0][:]) { t.Fatalf("block hash mismatch, got %v expected %v", txDetail.BlockHash, burnBlock[0]) } } if !burnTxFound { t.Fatalf("tx burning btc not found") } }
func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running single funder workflow responder test") // For this scenario, bob will initiate the channel, while we simply act as // the responder. capacity := btcutil.Amount(4 * 1e8) // Create the bob-test wallet which will be initiator of a single // funder channel shortly. bobNode, err := newBobNode(miner, capacity) if err != nil { t.Fatalf("unable to create bob node: %v", err) } // Bob sends over a single funding request, so we allocate our // contribution and the necessary resources. fundingAmt := btcutil.Amount(0) chanReservation, err := wallet.InitChannelReservation(capacity, fundingAmt, bobNode.id, bobAddr, numReqConfs, 4) if err != nil { t.Fatalf("unable to init channel reservation: %v", err) } // Verify all contribution fields have been set properly. Since we are // the recipient of a single-funder channel, we shouldn't have selected // any coins or generated any change outputs. ourContribution := chanReservation.OurContribution() if len(ourContribution.Inputs) != 0 { t.Fatalf("outputs for funding tx not properly selected, have %v "+ "outputs should have 0", len(ourContribution.Inputs)) } if len(ourContribution.ChangeOutputs) != 0 { t.Fatalf("coin selection failed, should have no change outputs, "+ "instead have: %v", ourContribution.ChangeOutputs[0].Value) } if ourContribution.MultiSigKey == nil { t.Fatalf("alice's key for multi-sig not found") } if ourContribution.CommitKey == nil { t.Fatalf("alice's key for commit not found") } if ourContribution.DeliveryAddress == nil { t.Fatalf("alice's final delivery address not found") } if ourContribution.CsvDelay == 0 { t.Fatalf("csv delay not set") } // Next we process Bob's single funder contribution which doesn't // include any inputs or change addresses, as only Bob will construct // the funding transaction. bobContribution := bobNode.Contribution(ourContribution.CommitKey) if err := chanReservation.ProcessSingleContribution(bobContribution); err != nil { t.Fatalf("unable to process bob's contribution: %v", err) } if chanReservation.FinalFundingTx() != nil { t.Fatalf("funding transaction populated!") } if len(bobContribution.Inputs) != 1 { t.Fatalf("bob shouldn't have one inputs, instead has %v", len(bobContribution.Inputs)) } if ourContribution.RevocationKey == nil { t.Fatalf("alice's revocation key not found") } if len(bobContribution.ChangeOutputs) != 1 { t.Fatalf("bob shouldn't have one change output, instead "+ "has %v", len(bobContribution.ChangeOutputs)) } if bobContribution.MultiSigKey == nil { t.Fatalf("bob's key for multi-sig not found") } if bobContribution.CommitKey == nil { t.Fatalf("bob's key for commit tx not found") } if bobContribution.DeliveryAddress == nil { t.Fatalf("bob's final delivery address not found") } if bobContribution.RevocationKey == nil { t.Fatalf("bob's revocaiton key not found") } fundingRedeemScript, multiOut, err := lnwallet.GenFundingPkScript( ourContribution.MultiSigKey.SerializeCompressed(), bobContribution.MultiSigKey.SerializeCompressed(), // TODO(roasbeef): account for hard-coded fee, remove bob node int64(capacity)+5000) if err != nil { t.Fatalf("unable to generate multi-sig output: %v", err) } // At this point, we send Bob our contribution, allowing him to // construct the funding transaction, and sign our version of the // commitment transaction. fundingTx := wire.NewMsgTx() fundingTx.AddTxIn(bobNode.availableOutputs[0]) fundingTx.AddTxOut(bobNode.changeOutputs[0]) fundingTx.AddTxOut(multiOut) txsort.InPlaceSort(fundingTx) if _, err := bobNode.signFundingTx(fundingTx); err != nil { t.Fatalf("unable to generate bob's funding sigs: %v", err) } // Locate the output index of the 2-of-2 in order to send back to the // wallet so it can finalize the transaction by signing bob's commitment // transaction. fundingTxID := fundingTx.TxSha() _, multiSigIndex := lnwallet.FindScriptOutputIndex(fundingTx, multiOut.PkScript) fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex) fundingTxIn := wire.NewTxIn(fundingOutpoint, nil, nil) aliceCommitTx, err := lnwallet.CreateCommitTx(fundingTxIn, ourContribution.CommitKey, bobContribution.CommitKey, ourContribution.RevocationKey, ourContribution.CsvDelay, 0, capacity) if err != nil { t.Fatalf("unable to create alice's commit tx: %v", err) } txsort.InPlaceSort(aliceCommitTx) bobCommitSig, err := bobNode.signCommitTx(aliceCommitTx, // TODO(roasbeef): account for hard-coded fee, remove bob node fundingRedeemScript, int64(capacity)+5000) if err != nil { t.Fatalf("unable to sign alice's commit tx: %v", err) } // With this stage complete, Alice can now complete the reservation. bobRevokeKey := bobContribution.RevocationKey if err := chanReservation.CompleteReservationSingle(bobRevokeKey, fundingOutpoint, bobCommitSig); err != nil { t.Fatalf("unable to complete reservation: %v", err) } // Alice should have saved the funding output. if chanReservation.FundingOutpoint() != fundingOutpoint { t.Fatalf("funding outputs don't match: %#v vs %#v", chanReservation.FundingOutpoint(), fundingOutpoint) } // Some period of time later, Bob presents us with an SPV proof // attesting to an open channel. At this point Alice recognizes the // channel, saves the state to disk, and creates the channel itself. if _, err := chanReservation.FinalizeReservation(); err != nil { t.Fatalf("unable to finalize reservation: %v", err) } // TODO(roasbeef): bob verify alice's sig }
func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, lnwallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running single funder workflow initiator test") // For this scenario, we (lnwallet) will be the channel initiator while bob // will be the recipient. // Create the bob-test wallet which will be the other side of our funding // channel. bobNode, err := newBobNode(miner, 0) if err != nil { t.Fatalf("unable to create bob node: %v", err) } // Initialize a reservation for a channel with 4 BTC funded solely by us. fundingAmt := btcutil.Amount(4 * 1e8) chanReservation, err := lnwallet.InitChannelReservation(fundingAmt, fundingAmt, bobNode.id, bobAddr, numReqConfs, 4) if err != nil { t.Fatalf("unable to init channel reservation: %v", err) } // Verify all contribution fields have been set properly. ourContribution := chanReservation.OurContribution() if len(ourContribution.Inputs) < 1 { t.Fatalf("outputs for funding tx not properly selected, have %v "+ "outputs should at least 1", len(ourContribution.Inputs)) } if len(ourContribution.ChangeOutputs) != 1 { t.Fatalf("coin selection failed, should have one change outputs, "+ "instead have: %v", len(ourContribution.ChangeOutputs)) } if ourContribution.MultiSigKey == nil { t.Fatalf("alice's key for multi-sig not found") } if ourContribution.CommitKey == nil { t.Fatalf("alice's key for commit not found") } if ourContribution.DeliveryAddress == nil { t.Fatalf("alice's final delivery address not found") } if ourContribution.CsvDelay == 0 { t.Fatalf("csv delay not set") } // At this point bob now responds to our request with a response // containing his channel contribution. The contribution will have no // inputs, only a multi-sig key, csv delay, etc. bobContribution := bobNode.SingleContribution(ourContribution.CommitKey) if err := chanReservation.ProcessContribution(bobContribution); err != nil { t.Fatalf("unable to add bob's contribution: %v", err) } // At this point, the reservation should have our signatures, and a // partial funding transaction (missing bob's sigs). theirContribution := chanReservation.TheirContribution() ourFundingSigs, ourCommitSig := chanReservation.OurSignatures() if ourFundingSigs == nil { t.Fatalf("funding sigs not found") } if ourCommitSig == nil { t.Fatalf("commitment sig not found") } // Additionally, the funding tx should have been populated. if chanReservation.FinalFundingTx() == nil { t.Fatalf("funding transaction never created!") } // Their funds should also be filled in. if len(theirContribution.Inputs) != 0 { t.Fatalf("bob shouldn't have any inputs, instead has %v", len(theirContribution.Inputs)) } if len(theirContribution.ChangeOutputs) != 0 { t.Fatalf("bob shouldn't have any change outputs, instead "+ "has %v", theirContribution.ChangeOutputs[0].Value) } if ourContribution.RevocationKey == nil { t.Fatalf("alice's revocation hash not found") } if theirContribution.MultiSigKey == nil { t.Fatalf("bob's key for multi-sig not found") } if theirContribution.CommitKey == nil { t.Fatalf("bob's key for commit tx not found") } if theirContribution.DeliveryAddress == nil { t.Fatalf("bob's final delivery address not found") } if theirContribution.RevocationKey == nil { t.Fatalf("bob's revocaiton hash not found") } // With this contribution processed, we're able to create the // funding+commitment transactions, as well as generate a signature // for bob's version of the commitment transaction. // // Now Bob can generate a signature for our version of the commitment // transaction, allowing us to complete the reservation. bobCommitSig, err := bobNode.signCommitTx( chanReservation.LocalCommitTx(), chanReservation.FundingRedeemScript(), // TODO(roasbeef): account for current hard-coded fee, need to // remove bobNode entirely int64(fundingAmt)+5000) if err != nil { t.Fatalf("bob is unable to sign alice's commit tx: %v", err) } if err := chanReservation.CompleteReservation(nil, bobCommitSig); err != nil { t.Fatalf("unable to complete funding tx: %v", err) } // TODO(roasbeef): verify our sig for bob's once sighash change is // merged. // The resulting active channel state should have been persisted to the DB. // TODO(roasbeef): de-duplicate fundingTx := chanReservation.FinalFundingTx() fundingSha := fundingTx.TxSha() channels, err := lnwallet.ChannelDB.FetchOpenChannels(bobNode.id) if err != nil { t.Fatalf("unable to retrieve channel from DB: %v", err) } if !bytes.Equal(channels[0].FundingOutpoint.Hash[:], fundingSha[:]) { t.Fatalf("channel state not properly saved: %v vs %v", hex.EncodeToString(channels[0].FundingOutpoint.Hash[:]), hex.EncodeToString(fundingSha[:])) } assertChannelOpen(t, miner, uint32(numReqConfs), chanReservation.DispatchChan()) }
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running funding insufficient funds tests") // Create a reservation for 44 BTC. fundingAmount := btcutil.Amount(44 * 1e8) chanReservation, err := wallet.InitChannelReservation(fundingAmount, fundingAmount, testPub, bobAddr, numReqConfs, 4) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) } // Attempt to create another channel with 44 BTC, this should fail. _, err = wallet.InitChannelReservation(fundingAmount, fundingAmount, testPub, bobAddr, numReqConfs, 4) if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok { t.Fatalf("coin selection succeded should have insufficient funds: %v", err) } // Now cancel that old reservation. if err := chanReservation.Cancel(); err != nil { t.Fatalf("unable to cancel reservation: %v", err) } // Those outpoints should no longer be locked. lockedOutPoints := wallet.LockedOutpoints() if len(lockedOutPoints) != 0 { t.Fatalf("outpoints still locked") } // Reservation ID should no longer be tracked. numReservations := wallet.ActiveReservations() if len(wallet.ActiveReservations()) != 0 { t.Fatalf("should have 0 reservations, instead have %v", numReservations) } // TODO(roasbeef): create method like Balance that ignores locked // outpoints, will let us fail early/fast instead of querying and // attempting coin selection. // Request to fund a new channel should now succeeed. _, err = wallet.InitChannelReservation(fundingAmount, fundingAmount, testPub, bobAddr, numReqConfs, 4) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) } }
func testDualFundingReservationWorkflow(miner *rpctest.Harness, wallet *lnwallet.LightningWallet, t *testing.T) { t.Log("Running dual reservation workflow test") // Create the bob-test wallet which will be the other side of our funding // channel. fundingAmount := btcutil.Amount(5 * 1e8) bobNode, err := newBobNode(miner, fundingAmount) if err != nil { t.Fatalf("unable to create bob node: %v", err) } // Bob initiates a channel funded with 5 BTC for each side, so 10 // BTC total. He also generates 2 BTC in change. chanReservation, err := wallet.InitChannelReservation(fundingAmount*2, fundingAmount, bobNode.id, bobAddr, numReqConfs, 4) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) } // The channel reservation should now be populated with a multi-sig key // from our HD chain, a change output with 3 BTC, and 2 outputs selected // of 4 BTC each. Additionally, the rest of the items needed to fufill a // funding contribution should also have been filled in. ourContribution := chanReservation.OurContribution() if len(ourContribution.Inputs) != 2 { t.Fatalf("outputs for funding tx not properly selected, have %v "+ "outputs should have 2", len(ourContribution.Inputs)) } if ourContribution.MultiSigKey == nil { t.Fatalf("alice's key for multi-sig not found") } if ourContribution.CommitKey == nil { t.Fatalf("alice's key for commit not found") } if ourContribution.DeliveryAddress == nil { t.Fatalf("alice's final delivery address not found") } if ourContribution.CsvDelay == 0 { t.Fatalf("csv delay not set") } // Bob sends over his output, change addr, pub keys, initial revocation, // final delivery address, and his accepted csv delay for the // commitment transactions. bobContribution := bobNode.Contribution(ourContribution.CommitKey) if err := chanReservation.ProcessContribution(bobContribution); err != nil { t.Fatalf("unable to add bob's funds to the funding tx: %v", err) } // At this point, the reservation should have our signatures, and a // partial funding transaction (missing bob's sigs). theirContribution := chanReservation.TheirContribution() ourFundingSigs, ourCommitSig := chanReservation.OurSignatures() if len(ourFundingSigs) != 2 { t.Fatalf("only %v of our sigs present, should have 2", len(ourFundingSigs)) } if ourCommitSig == nil { t.Fatalf("commitment sig not found") } if ourContribution.RevocationKey == nil { t.Fatalf("alice's revocation key not found") } // Additionally, the funding tx should have been populated. fundingTx := chanReservation.FinalFundingTx() if fundingTx == nil { t.Fatalf("funding transaction never created!") } // Their funds should also be filled in. if len(theirContribution.Inputs) != 1 { t.Fatalf("bob's outputs for funding tx not properly selected, have %v "+ "outputs should have 2", len(theirContribution.Inputs)) } if theirContribution.ChangeOutputs[0].Value != 2e8 { t.Fatalf("bob should have one change output with value 2e8"+ "satoshis, is instead %v", theirContribution.ChangeOutputs[0].Value) } if theirContribution.MultiSigKey == nil { t.Fatalf("bob's key for multi-sig not found") } if theirContribution.CommitKey == nil { t.Fatalf("bob's key for commit tx not found") } if theirContribution.DeliveryAddress == nil { t.Fatalf("bob's final delivery address not found") } if theirContribution.RevocationKey == nil { t.Fatalf("bob's revocaiton key not found") } // TODO(roasbeef): account for current hard-coded commit fee, // need to remove bob all together chanCapacity := int64(10e8 + 5000) // Alice responds with her output, change addr, multi-sig key and signatures. // Bob then responds with his signatures. bobsSigs, err := bobNode.signFundingTx(fundingTx) if err != nil { t.Fatalf("unable to sign inputs for bob: %v", err) } commitSig, err := bobNode.signCommitTx( chanReservation.LocalCommitTx(), chanReservation.FundingRedeemScript(), chanCapacity) if err != nil { t.Fatalf("bob is unable to sign alice's commit tx: %v", err) } if err := chanReservation.CompleteReservation(bobsSigs, commitSig); err != nil { t.Fatalf("unable to complete funding tx: %v", err) } // At this point, the channel can be considered "open" when the funding // txn hits a "comfortable" depth. // The resulting active channel state should have been persisted to the DB. fundingSha := fundingTx.TxSha() channels, err := wallet.ChannelDB.FetchOpenChannels(bobNode.id) if err != nil { t.Fatalf("unable to retrieve channel from DB: %v", err) } if !bytes.Equal(channels[0].FundingOutpoint.Hash[:], fundingSha[:]) { t.Fatalf("channel state not properly saved") } // Assert that tha channel opens after a single block. lnc := assertChannelOpen(t, miner, uint32(numReqConfs), chanReservation.DispatchChan()) // Now that the channel is open, execute a cooperative closure of the // now open channel. aliceCloseSig, _, err := lnc.InitCooperativeClose() if err != nil { t.Fatalf("unable to init cooperative closure: %v", err) } aliceCloseSig = append(aliceCloseSig, byte(txscript.SigHashAll)) chanInfo := lnc.StateSnapshot() // Obtain bob's signature for the closure transaction. witnessScript := lnc.FundingWitnessScript fundingOut := lnc.ChannelPoint() fundingTxIn := wire.NewTxIn(fundingOut, nil, nil) bobCloseTx := lnwallet.CreateCooperativeCloseTx(fundingTxIn, chanInfo.RemoteBalance, chanInfo.LocalBalance, lnc.RemoteDeliveryScript, lnc.LocalDeliveryScript, false) bobSig, err := bobNode.signCommitTx(bobCloseTx, witnessScript, int64(lnc.Capacity)) if err != nil { t.Fatalf("unable to generate bob's signature for closing tx: %v", err) } // Broadcast the transaction to the network. This transaction should // be accepted, and found in the next mined block. ourKey := chanReservation.OurContribution().MultiSigKey.SerializeCompressed() theirKey := chanReservation.TheirContribution().MultiSigKey.SerializeCompressed() witness := lnwallet.SpendMultiSig(witnessScript, ourKey, aliceCloseSig, theirKey, bobSig) bobCloseTx.TxIn[0].Witness = witness if err := wallet.PublishTransaction(bobCloseTx); err != nil { t.Fatalf("broadcast of close tx rejected: %v", err) } }
func clearWalletState(w *lnwallet.LightningWallet) error { w.ResetReservations() return w.ChannelDB.Wipe() }
func testTransactionSubscriptions(miner *rpctest.Harness, w *lnwallet.LightningWallet, t *testing.T) { t.Log("Running transaction subscriptions test") // First, check to see if this wallet meets the TransactionNotifier // interface, if not then we'll skip this test for this particular // implementation of the WalletController. txClient, err := w.SubscribeTransactions() if err != nil { t.Fatalf("unable to generate tx subscription: %v") } defer txClient.Cancel() const ( outputAmt = btcutil.SatoshiPerBitcoin numTxns = 3 ) unconfirmedNtfns := make(chan struct{}) go func() { for i := 0; i < numTxns; i++ { txDetail := <-txClient.UnconfirmedTransactions() if txDetail.NumConfirmations != 0 { t.Fatalf("incorrect number of confs, expected %v got %v", 0, txDetail.NumConfirmations) } if txDetail.Value != outputAmt { t.Fatalf("incorrect output amt, expected %v got %v", outputAmt, txDetail.Value) } if txDetail.BlockHash != nil { t.Fatalf("block hash should be nil, is instead %v", txDetail.BlockHash) } } close(unconfirmedNtfns) }() // Next, fetch a fresh address from the wallet, create 3 new outputs // with the pkScript. for i := 0; i < numTxns; i++ { addr, err := w.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { t.Fatalf("unable to create new address: %v", err) } script, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create output script: %v", err) } output := &wire.TxOut{outputAmt, script} if _, err := miner.CoinbaseSpend([]*wire.TxOut{output}); err != nil { t.Fatalf("unable to send coinbase: %v", err) } } // We should receive a notification for all three transactions // generated above. select { case <-time.After(time.Second * 5): t.Fatalf("transactions not received after 3 seconds") case <-unconfirmedNtfns: // Fall through on successs } confirmedNtfns := make(chan struct{}) go func() { for i := 0; i < numTxns; i++ { txDetail := <-txClient.ConfirmedTransactions() if txDetail.NumConfirmations != 1 { t.Fatalf("incorrect number of confs, expected %v got %v", 0, txDetail.NumConfirmations) } if txDetail.Value != outputAmt { t.Fatalf("incorrect output amt, expected %v got %v", outputAmt, txDetail.Value) } } close(confirmedNtfns) }() // Next mine a single block, all the transactions generated above // should be included. if _, err := miner.Node.Generate(1); err != nil { t.Fatalf("unable to generate block: %v", err) } // We should receive a notification for all three transactions // since they should be mined in the next block. select { case <-time.After(time.Second * 5): t.Fatalf("transactions not received after 3 seconds") case <-confirmedNtfns: // Fall through on successs } }