Merge pull request #225 from halseth/channel-close-negotiation2

Add fee negotiation on channel cooperative shutdown.
This commit is contained in:
Olaoluwa Osuntokun 2017-08-14 17:48:05 -07:00 committed by GitHub
commit 06782ebdb3
7 changed files with 1314 additions and 364 deletions

@ -22,14 +22,10 @@ import (
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
// The block height returned by the mock BlockChainIO's GetBestBlock.
const fundingBroadcastHeight = 123
var (
privPass = []byte("dummy-pass")
@ -70,143 +66,6 @@ var (
}
)
// mockWalletController is used by the LightningWallet, and let us mock the
// interaction with the bitcoin network.
type mockWalletController struct {
rootKey *btcec.PrivateKey
prevAddres btcutil.Address
publishedTransactions chan *wire.MsgTx
}
// FetchInputInfo will be called to get info about the inputs to the funding
// transaction.
func (*mockWalletController) FetchInputInfo(
prevOut *wire.OutPoint) (*wire.TxOut, error) {
txOut := &wire.TxOut{
Value: int64(10 * btcutil.SatoshiPerBitcoin),
PkScript: []byte("dummy"),
}
return txOut, nil
}
func (*mockWalletController) ConfirmedBalance(confs int32,
witness bool) (btcutil.Amount, error) {
return 0, nil
}
// NewAddress is called to get new addresses for delivery, change etc.
func (m *mockWalletController) NewAddress(addrType lnwallet.AddressType,
change bool) (btcutil.Address, error) {
addr, _ := btcutil.NewAddressPubKey(
m.rootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams)
return addr, nil
}
func (*mockWalletController) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) {
return nil, nil
}
// NewRawKey will be called to get keys to be used for the funding tx and the
// commitment tx.
func (m *mockWalletController) NewRawKey() (*btcec.PublicKey, error) {
return m.rootKey.PubKey(), nil
}
// FetchRootKey will be called to provide the wallet with a root key.
func (m *mockWalletController) FetchRootKey() (*btcec.PrivateKey, error) {
return m.rootKey, nil
}
func (*mockWalletController) SendOutputs(outputs []*wire.TxOut) (*chainhash.Hash, error) {
return nil, nil
}
// ListUnspentWitness is called by the wallet when doing coin selection. We just
// need one unspent for the funding transaction.
func (*mockWalletController) ListUnspentWitness(confirms int32) ([]*lnwallet.Utxo, error) {
utxo := &lnwallet.Utxo{
Value: btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
OutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: 0,
},
}
var ret []*lnwallet.Utxo
ret = append(ret, utxo)
return ret, nil
}
func (*mockWalletController) ListTransactionDetails() ([]*lnwallet.TransactionDetail, error) {
return nil, nil
}
func (*mockWalletController) LockOutpoint(o wire.OutPoint) {}
func (*mockWalletController) UnlockOutpoint(o wire.OutPoint) {}
func (m *mockWalletController) PublishTransaction(tx *wire.MsgTx) error {
m.publishedTransactions <- tx
return nil
}
func (*mockWalletController) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
return nil, nil
}
func (*mockWalletController) IsSynced() (bool, error) {
return true, nil
}
func (*mockWalletController) Start() error {
return nil
}
func (*mockWalletController) Stop() error {
return nil
}
type mockSigner struct {
key *btcec.PrivateKey
}
func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx,
signDesc *lnwallet.SignDescriptor) ([]byte, error) {
amt := signDesc.Output.Value
witnessScript := signDesc.WitnessScript
privKey := m.key
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, amt, witnessScript, txscript.SigHashAll,
privKey)
if err != nil {
return nil, err
}
return sig[:len(sig)-1], nil
}
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx,
signDesc *lnwallet.SignDescriptor) (*lnwallet.InputScript, error) {
witnessScript, err := txscript.WitnessScript(tx, signDesc.SigHashes,
signDesc.InputIndex, signDesc.Output.Value,
signDesc.Output.PkScript, txscript.SigHashAll, m.key, true)
if err != nil {
return nil, err
}
return &lnwallet.InputScript{
Witness: witnessScript,
}, nil
}
type mockChainIO struct{}
func (*mockChainIO) GetBestBlock() (*chainhash.Hash, int32, error) {
return activeNetParams.GenesisHash, fundingBroadcastHeight, nil
}
func (*mockChainIO) GetUtxo(op *wire.OutPoint,
heightHint uint32) (*wire.TxOut, error) {
return nil, nil
}
func (*mockChainIO) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
return nil, nil
}
func (*mockChainIO) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
return nil, nil
}
type mockNotifier struct {
confChannel chan *chainntnfs.TxConfirmation
epochChan chan *chainntnfs.BlockEpoch
@ -250,7 +109,7 @@ type testNode struct {
testDir string
}
func disableLogger(t *testing.T) {
func disableFndgLogger(t *testing.T) {
channeldb.UseLogger(btclog.Disabled)
lnwallet.UseLogger(btclog.Disabled)
fndgLog = btclog.Disabled
@ -652,7 +511,7 @@ func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
}
func TestFundingManagerNormalWorkflow(t *testing.T) {
disableLogger(t)
disableFndgLogger(t)
shutdownChannel := make(chan struct{})
@ -860,7 +719,7 @@ func TestFundingManagerNormalWorkflow(t *testing.T) {
}
func TestFundingManagerRestartBehavior(t *testing.T) {
disableLogger(t)
disableFndgLogger(t)
shutdownChannel := make(chan struct{})
@ -1095,7 +954,7 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
}
func TestFundingManagerFundingTimeout(t *testing.T) {
disableLogger(t)
disableFndgLogger(t)
shutdownChannel := make(chan struct{})

@ -3671,14 +3671,14 @@ func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
//
// TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs,
// settle any in flight.
func (lc *LightningChannel) CreateCloseProposal(feeRate uint64,
func (lc *LightningChannel) CreateCloseProposal(proposedFee uint64,
localDeliveryScript, remoteDeliveryScript []byte) ([]byte, uint64, error) {
lc.Lock()
defer lc.Unlock()
// If we're already closing the channel, then ignore this request.
if lc.status == channelClosing || lc.status == channelClosed {
// If we've already closed the channel, then ignore this request.
if lc.status == channelClosed {
// TODO(roasbeef): check to ensure no pending payments
return nil, 0, ErrChanClosing
}
@ -3686,7 +3686,6 @@ func (lc *LightningChannel) CreateCloseProposal(feeRate uint64,
// Subtract the proposed fee from the appropriate balance, taking care
// not to persist the adjusted balance, as the feeRate may change
// during the channel closing process.
proposedFee := (feeRate * uint64(commitWeight)) / 1000
ourBalance := lc.channelState.LocalBalance
theirBalance := lc.channelState.RemoteBalance
@ -3734,7 +3733,7 @@ func (lc *LightningChannel) CreateCloseProposal(feeRate uint64,
// signatures including the proper sighash byte.
func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig,
localDeliveryScript, remoteDeliveryScript []byte,
feeRate uint64) (*wire.MsgTx, error) {
proposedFee uint64) (*wire.MsgTx, error) {
lc.Lock()
defer lc.Unlock()
@ -3748,7 +3747,6 @@ func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig,
// Subtract the proposed fee from the appropriate balance, taking care
// not to persist the adjusted balance, as the feeRate may change
// during the channel closing process.
proposedFee := (feeRate * uint64(commitWeight)) / 1000
ourBalance := lc.channelState.LocalBalance
theirBalance := lc.channelState.RemoteBalance
@ -3947,3 +3945,9 @@ func CreateCooperativeCloseTx(fundingTxIn *wire.TxIn,
return closeTx
}
// CalcFee returns the commitment fee to use for the given
// fee rate (fee-per-kw).
func (lc *LightningChannel) CalcFee(feeRate uint64) uint64 {
return (feeRate * uint64(commitWeight)) / 1000
}

@ -737,17 +737,19 @@ func TestCooperativeChannelClosure(t *testing.T) {
bobFeeRate := uint64(bobChannel.channelState.FeePerKw)
// We'll store with both Alice and Bob creating a new close proposal
// with the same fee rate.
// with the same fee.
aliceFee := aliceChannel.CalcFee(aliceFeeRate)
aliceSig, _, err := aliceChannel.CreateCloseProposal(
aliceFeeRate, aliceDeliveryScript, bobDeliveryScript,
aliceFee, aliceDeliveryScript, bobDeliveryScript,
)
if err != nil {
t.Fatalf("unable to create alice coop close proposal: %v", err)
}
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
bobFee := bobChannel.CalcFee(bobFeeRate)
bobSig, _, err := bobChannel.CreateCloseProposal(
bobFeeRate, bobDeliveryScript, aliceDeliveryScript,
bobFee, bobDeliveryScript, aliceDeliveryScript,
)
if err != nil {
t.Fatalf("unable to create bob coop close proposal: %v", err)
@ -759,7 +761,7 @@ func TestCooperativeChannelClosure(t *testing.T) {
// transaction is well formed, and the signatures verify.
aliceCloseTx, err := bobChannel.CompleteCooperativeClose(
bobCloseSig, aliceCloseSig, bobDeliveryScript,
aliceDeliveryScript, bobFeeRate)
aliceDeliveryScript, bobFee)
if err != nil {
t.Fatalf("unable to complete alice cooperative close: %v", err)
}
@ -767,7 +769,7 @@ func TestCooperativeChannelClosure(t *testing.T) {
bobCloseTx, err := aliceChannel.CompleteCooperativeClose(
aliceCloseSig, bobCloseSig, aliceDeliveryScript,
bobDeliveryScript, aliceFeeRate)
bobDeliveryScript, aliceFee)
if err != nil {
t.Fatalf("unable to complete bob cooperative close: %v", err)
}
@ -1590,14 +1592,16 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
// Both sides currently have over 1 BTC settled as part of their
// balances. As a result, performing a cooperative closure now result
// in both sides having an output within the closure transaction.
aliceSig, _, err := aliceChannel.CreateCloseProposal(aliceFeeRate,
aliceFee := aliceChannel.CalcFee(aliceFeeRate)
aliceSig, _, err := aliceChannel.CreateCloseProposal(aliceFee,
aliceDeliveryScript, bobDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
bobSig, _, err := bobChannel.CreateCloseProposal(bobFeeRate,
bobFee := bobChannel.CalcFee(bobFeeRate)
bobSig, _, err := bobChannel.CreateCloseProposal(bobFee,
bobDeliveryScript, aliceDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
@ -1606,7 +1610,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
closeTx, err := bobChannel.CompleteCooperativeClose(
bobCloseSig, aliceCloseSig,
bobDeliveryScript, aliceDeliveryScript, bobFeeRate)
bobDeliveryScript, aliceDeliveryScript, bobFee)
if err != nil {
t.Fatalf("unable to accept channel close: %v", err)
}
@ -1628,14 +1632,14 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
// Attempt another cooperative channel closure. It should succeed
// without any issues.
aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFeeRate,
aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFee,
aliceDeliveryScript, bobDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
bobSig, _, err = bobChannel.CreateCloseProposal(bobFeeRate,
bobSig, _, err = bobChannel.CreateCloseProposal(bobFee,
bobDeliveryScript, aliceDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
@ -1644,7 +1648,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
closeTx, err = bobChannel.CompleteCooperativeClose(
bobCloseSig, aliceCloseSig,
bobDeliveryScript, aliceDeliveryScript, bobFeeRate)
bobDeliveryScript, aliceDeliveryScript, bobFee)
if err != nil {
t.Fatalf("unable to accept channel close: %v", err)
}
@ -1667,14 +1671,14 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
// Our final attempt at another cooperative channel closure. It should
// succeed without any issues.
aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFeeRate,
aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFee,
aliceDeliveryScript, bobDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
bobSig, _, err = bobChannel.CreateCloseProposal(bobFeeRate,
bobSig, _, err = bobChannel.CreateCloseProposal(bobFee,
bobDeliveryScript, aliceDeliveryScript)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
@ -1683,7 +1687,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
closeTx, err = bobChannel.CompleteCooperativeClose(
bobCloseSig, aliceCloseSig,
bobDeliveryScript, aliceDeliveryScript, bobFeeRate)
bobDeliveryScript, aliceDeliveryScript, bobFee)
if err != nil {
t.Fatalf("unable to accept channel close: %v", err)
}

182
mock.go Normal file

@ -0,0 +1,182 @@
package main
import (
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
// The block height returned by the mock BlockChainIO's GetBestBlock.
const fundingBroadcastHeight = 123
type mockSigner struct {
key *btcec.PrivateKey
}
func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx,
signDesc *lnwallet.SignDescriptor) ([]byte, error) {
amt := signDesc.Output.Value
witnessScript := signDesc.WitnessScript
privKey := m.key
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, amt, witnessScript, txscript.SigHashAll,
privKey)
if err != nil {
return nil, err
}
return sig[:len(sig)-1], nil
}
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx,
signDesc *lnwallet.SignDescriptor) (*lnwallet.InputScript, error) {
witnessScript, err := txscript.WitnessScript(tx, signDesc.SigHashes,
signDesc.InputIndex, signDesc.Output.Value,
signDesc.Output.PkScript, txscript.SigHashAll, m.key, true)
if err != nil {
return nil, err
}
return &lnwallet.InputScript{
Witness: witnessScript,
}, nil
}
type mockNotfier struct {
confChannel chan *chainntnfs.TxConfirmation
}
func (m *mockNotfier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs,
heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
return &chainntnfs.ConfirmationEvent{
Confirmed: m.confChannel,
}, nil
}
func (m *mockNotfier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent,
error) {
return nil, nil
}
func (m *mockNotfier) Start() error {
return nil
}
func (m *mockNotfier) Stop() error {
return nil
}
func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint,
heightHint uint32) (*chainntnfs.SpendEvent, error) {
return &chainntnfs.SpendEvent{
Spend: make(chan *chainntnfs.SpendDetail),
Cancel: func() {},
}, nil
}
type mockChainIO struct{}
func (*mockChainIO) GetBestBlock() (*chainhash.Hash, int32, error) {
return activeNetParams.GenesisHash, fundingBroadcastHeight, nil
}
func (*mockChainIO) GetUtxo(op *wire.OutPoint,
heightHint uint32) (*wire.TxOut, error) {
return nil, nil
}
func (*mockChainIO) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
return nil, nil
}
func (*mockChainIO) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
return nil, nil
}
// mockWalletController is used by the LightningWallet, and let us mock the
// interaction with the bitcoin network.
type mockWalletController struct {
rootKey *btcec.PrivateKey
prevAddres btcutil.Address
publishedTransactions chan *wire.MsgTx
}
// FetchInputInfo will be called to get info about the inputs to the funding
// transaction.
func (*mockWalletController) FetchInputInfo(
prevOut *wire.OutPoint) (*wire.TxOut, error) {
txOut := &wire.TxOut{
Value: int64(10 * btcutil.SatoshiPerBitcoin),
PkScript: []byte("dummy"),
}
return txOut, nil
}
func (*mockWalletController) ConfirmedBalance(confs int32,
witness bool) (btcutil.Amount, error) {
return 0, nil
}
// NewAddress is called to get new addresses for delivery, change etc.
func (m *mockWalletController) NewAddress(addrType lnwallet.AddressType,
change bool) (btcutil.Address, error) {
addr, _ := btcutil.NewAddressPubKey(
m.rootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams)
return addr, nil
}
func (*mockWalletController) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) {
return nil, nil
}
// NewRawKey will be called to get keys to be used for the funding tx and the
// commitment tx.
func (m *mockWalletController) NewRawKey() (*btcec.PublicKey, error) {
return m.rootKey.PubKey(), nil
}
// FetchRootKey will be called to provide the wallet with a root key.
func (m *mockWalletController) FetchRootKey() (*btcec.PrivateKey, error) {
return m.rootKey, nil
}
func (*mockWalletController) SendOutputs(outputs []*wire.TxOut) (*chainhash.Hash, error) {
return nil, nil
}
// ListUnspentWitness is called by the wallet when doing coin selection. We just
// need one unspent for the funding transaction.
func (*mockWalletController) ListUnspentWitness(confirms int32) ([]*lnwallet.Utxo, error) {
utxo := &lnwallet.Utxo{
Value: btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
OutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: 0,
},
}
var ret []*lnwallet.Utxo
ret = append(ret, utxo)
return ret, nil
}
func (*mockWalletController) ListTransactionDetails() ([]*lnwallet.TransactionDetail, error) {
return nil, nil
}
func (*mockWalletController) LockOutpoint(o wire.OutPoint) {}
func (*mockWalletController) UnlockOutpoint(o wire.OutPoint) {}
func (m *mockWalletController) PublishTransaction(tx *wire.MsgTx) error {
m.publishedTransactions <- tx
return nil
}
func (*mockWalletController) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
return nil, nil
}
func (*mockWalletController) IsSynced() (bool, error) {
return true, nil
}
func (*mockWalletController) Start() error {
return nil
}
func (*mockWalletController) Stop() error {
return nil
}

511
peer.go

@ -4,6 +4,7 @@ import (
"container/list"
"fmt"
"net"
"strings"
"sync"
"sync/atomic"
"time"
@ -900,11 +901,19 @@ func (p *peer) channelManager() {
deliveryAddrs := make(map[lnwire.ChannelID]*closingScripts)
// shutdownSigs is a map of signatures maintained by the responder in a
// cooperative channel close. This map enables us to respond to
// subsequent steps in the workflow without having to recalculate our
// signature for the channel close transaction.
shutdownSigs := make(map[lnwire.ChannelID][]byte)
// initiator[ShutdownSigs|FeeProposals] holds the
// [signature|feeProposal] for the last ClosingSigned sent to the peer
// by the initiator. This enables us to respond to subsequent steps in
// the workflow without having to recalculate our signature for the
// channel close transaction, and track the sent fee proposals for fee
// negotiation purposes.
initiatorShutdownSigs := make(map[lnwire.ChannelID][]byte)
initiatorFeeProposals := make(map[lnwire.ChannelID]uint64)
// responder[ShutdownSigs|FeeProposals] is similar to the the maps
// above, just for the responder.
responderShutdownSigs := make(map[lnwire.ChannelID][]byte)
responderFeeProposals := make(map[lnwire.ChannelID]uint64)
// TODO(roasbeef): move to cfg closure func
genDeliveryScript := func() ([]byte, error) {
@ -1060,6 +1069,16 @@ out:
// entry for this channel, then this means that we're
// the responder to the workflow.
if _, ok := chanShutdowns[req.ChannelID]; !ok {
// Check responderShutdownSigs for an already
// existing shutdown signature for this channel.
// If such a signature exists, it means we
// already have sent a response to a shutdown
// message for this channel, so ignore this one.
_, exists := responderShutdownSigs[req.ChannelID]
if exists {
continue
}
// As we're the responder, we'll need to
// generate a delivery script of our own.
deliveryScript, err := genDeliveryScript()
@ -1072,11 +1091,12 @@ out:
// In this case, we'll send a shutdown message,
// and also prep our closing signature for the
// case they fees are immediately agreed upon.
closeSig := p.handleShutdownResponse(req,
deliveryScript)
// case the fees are immediately agreed upon.
closeSig, proposedFee := p.handleShutdownResponse(
req, deliveryScript)
if closeSig != nil {
shutdownSigs[chanID] = closeSig
responderShutdownSigs[req.ChannelID] = closeSig
responderFeeProposals[req.ChannelID] = proposedFee
}
}
@ -1092,30 +1112,55 @@ out:
// If it does, then this means we were the initiator of
// the channel shutdown procedure.
if ok {
// To finalize this shutdown, we'll now send a
shutdownSig := initiatorShutdownSigs[req.ChannelID]
initiatorSig := append(shutdownSig,
byte(txscript.SigHashAll))
// To finalize this shtudown, we'll now send a
// matching close signed message to the other
// party, and broadcast the closing transaction
// to the network.
p.handleInitClosingSigned(localCloseReq, req,
deliveryAddrs[chanID])
delete(chanShutdowns, req.ChannelID)
delete(deliveryAddrs, req.ChannelID)
// to the network. If the fees are still being
// negotiated, handleClosingSigned returns the
// signature and proposed fee we sent to the
// peer. In the case fee negotiation was
// complete, and the closing tx was broadcasted,
// closeSig will be nil, and we can delete the
// state associated with this channel shutdown.
closeSig, proposedFee := p.handleClosingSigned(
localCloseReq, req,
deliveryAddrs[chanID], initiatorSig,
initiatorFeeProposals[req.ChannelID])
if closeSig != nil {
initiatorShutdownSigs[req.ChannelID] = closeSig
initiatorFeeProposals[req.ChannelID] = proposedFee
} else {
delete(initiatorShutdownSigs, req.ChannelID)
delete(initiatorFeeProposals, req.ChannelID)
delete(chanShutdowns, req.ChannelID)
delete(deliveryAddrs, req.ChannelID)
}
continue
}
// Otherwise, we're the responder to the channel
// shutdown procedure. In this case, we'll mark the
// channel as pending close, and watch the network for
// the ultimate confirmation of the closing
// transaction.
responderSig := append(shutdownSigs[chanID],
shutdownSig := responderShutdownSigs[req.ChannelID]
responderSig := append(shutdownSig,
byte(txscript.SigHashAll))
p.handleResponseClosingSigned(req, responderSig,
deliveryAddrs[chanID])
delete(shutdownSigs, chanID)
delete(deliveryAddrs, chanID)
// Otherwise, we're the responder to the channel
// shutdown procedure. The procedure will be the same,
// but we don't have a local request to to notify about
// updates, so just pass in nil instead.
closeSig, proposedFee := p.handleClosingSigned(nil, req,
deliveryAddrs[chanID], responderSig,
responderFeeProposals[req.ChannelID])
if closeSig != nil {
responderShutdownSigs[req.ChannelID] = closeSig
responderFeeProposals[req.ChannelID] = proposedFee
} else {
delete(responderShutdownSigs, req.ChannelID)
delete(responderFeeProposals, req.ChannelID)
delete(deliveryAddrs, chanID)
}
case <-p.quit:
break out
@ -1174,16 +1219,17 @@ func (p *peer) handleLocalClose(req *htlcswitch.ChanClose, deliveryScript []byte
// close workflow receives a Shutdown message. This is the second step in the
// cooperative close workflow. This function generates a close transaction with
// a proposed fee amount and sends the signed transaction to the initiator.
// Returns the signature used to signed the close proposal, and the proposed
// fee.
func (p *peer) handleShutdownResponse(msg *lnwire.Shutdown,
localDeliveryScript []byte) []byte {
localDeliveryScript []byte) ([]byte, uint64) {
p.activeChanMtx.RLock()
channel, ok := p.activeChannels[msg.ChannelID]
p.activeChanMtx.RUnlock()
if !ok {
peerLog.Errorf("unable to close channel, ChannelPoint(%v) is "+
"unknown", msg.ChannelID)
return nil
return nil, 0
}
// As we just received a shutdown message, we'll also send a shutdown
@ -1191,46 +1237,94 @@ func (p *peer) handleShutdownResponse(msg *lnwire.Shutdown,
err := p.sendShutdown(channel, localDeliveryScript)
if err != nil {
peerLog.Errorf("error while sending shutdown message: %v", err)
return nil
return nil, 0
}
// Calculate an initial proposed fee rate for the close transaction.
feeRate := p.server.cc.feeEstimator.EstimateFeePerWeight(1) * 1000
// TODO(roasbeef): actually perform fee negotiation here, only send sig
// if we agree to fee
// Once both sides agree on a fee, we'll create a signature that closes
// the channel using the agree upon fee rate.
// We propose a fee and send a close proposal to the peer. This will
// start the fee negotiations. Once both sides agree on a fee, we'll
// create a signature that closes the channel using the agreed upon fee.
fee := channel.CalcFee(feeRate)
closeSig, proposedFee, err := channel.CreateCloseProposal(
feeRate, localDeliveryScript, msg.Address,
fee, localDeliveryScript, msg.Address,
)
if err != nil {
peerLog.Errorf("unable to create close proposal: %v", err)
return nil
return nil, 0
}
parsedSig, err := btcec.ParseSignature(closeSig, btcec.S256())
if err != nil {
peerLog.Errorf("unable to parse signature: %v", err)
return nil
return nil, 0
}
// With the closing signature assembled, we'll send the matching close
// signed message to the other party so they can broadcast the closing
// transaction.
// transaction if they agree with the fee, or create a new close
// proposal if they don't.
closingSigned := lnwire.NewClosingSigned(msg.ChannelID, proposedFee,
parsedSig)
p.queueMsg(closingSigned, nil)
return closeSig
return closeSig, proposedFee
}
// handleInitClosingSigned is called when the initiator in a cooperative
// channel close workflow receives a ClosingSigned message from the responder.
// This method completes the channel close transaction, sends back a
// corresponding ClosingSigned message, then broadcasts the channel close
// transaction. It also performs channel cleanup and reports status back to the
// caller. This is the initiator's final step in the channel close workflow.
// calculateCompromiseFee performs the current fee negotiation algorithm,
// taking into consideration our ideal fee based on current fee environment,
// the fee we last proposed (if any), and the fee proposed by the peer.
func calculateCompromiseFee(ourIdealFee, lastSentFee, peerFee uint64) uint64 {
// We will accept a proposed fee in the interval
// [0.5*ourIdealFee, 2*ourIdealFee]. If the peer's fee doesn't fall in
// this range, we'll propose the average of the peer's fee and our last
// sent fee, as long as it is in this range.
// TODO(halseth): Dynamic fee to determine what we consider min/max for
// timely confirmation.
maxFee := 2 * ourIdealFee
minFee := ourIdealFee / 2
// If we didn't propose a fee before, just use our ideal fee value for
// the average calculation.
if lastSentFee == 0 {
lastSentFee = ourIdealFee
}
avgFee := (lastSentFee + peerFee) / 2
switch {
case peerFee <= maxFee && peerFee >= minFee:
// Peer fee is in the accepted range.
return peerFee
case avgFee <= maxFee && avgFee >= minFee:
// The peer's fee is not in the accepted range, but the average
// fee is.
return avgFee
case avgFee > maxFee:
// TODO(halseth): We must ensure fee is not higher than the
// current fee on the commitment transaction.
// We cannot accept the average fee, as it is more than twice
// our own estimate. Set our proposed to the maximum we can
// accept.
return maxFee
default:
// Cannot accept the average, as we consider it too low.
return minFee
}
}
// handleClosingSigned is called when the a ClosingSigned message is received
// from the peer. If we are the initiator in the shutdown procedure, localReq
// should be set to the local close request. If we are the responder, it should
// be set to nil.
//
// This method sends the necessary ClosingSigned message to continue fee
// negotiation, and in case we agreed on a fee completes the channel close
// transaction, and then broadcasts it. It also performs channel cleanup (and
// reports status back to the caller if this was a local shutdown request).
//
// It returns the signature and the proposed fee included in the ClosingSigned
// sent to the peer.
//
// Following the broadcast, both the initiator and responder in the channel
// closure workflow should watch the blockchain for a confirmation of the
@ -1238,10 +1332,10 @@ func (p *peer) handleShutdownResponse(msg *lnwire.Shutdown,
// of an unresponsive remote party, the initiator can either choose to execute
// a force closure, or backoff for a period of time, and retry the cooperative
// closure.
func (p *peer) handleInitClosingSigned(req *htlcswitch.ChanClose,
msg *lnwire.ClosingSigned, deliveryScripts *closingScripts) {
chanID := lnwire.NewChanIDFromOutPoint(req.ChanPoint)
func (p *peer) handleClosingSigned(localReq *htlcswitch.ChanClose,
msg *lnwire.ClosingSigned, deliveryScripts *closingScripts,
lastSig []byte, lastFee uint64) ([]byte, uint64) {
chanID := msg.ChannelID
p.activeChanMtx.RLock()
channel, ok := p.activeChannels[chanID]
p.activeChanMtx.RUnlock()
@ -1249,71 +1343,66 @@ func (p *peer) handleInitClosingSigned(req *htlcswitch.ChanClose,
err := fmt.Errorf("unable to close channel, ChannelID(%v) is "+
"unknown", chanID)
peerLog.Errorf(err.Error())
req.Err <- err
return
if localReq != nil {
localReq.Err <- err
}
return nil, 0
}
// Calculate a fee rate that we believe to be fair and will ensure a
// timely confirmation.
//
// TODO(bvu): with a dynamic fee implementation, we will compare this
// to the fee proposed by the responder in their ClosingSigned message.
feeRate := p.server.cc.feeEstimator.EstimateFeePerWeight(1) * 1000
// We agree with the proposed channel close transaction and fee rate,
// so generate our signature.
initiatorSig, proposedFee, err := channel.CreateCloseProposal(
feeRate, deliveryScripts.localScript, deliveryScripts.remoteScript,
)
// We now consider the fee proposed by the peer, together with the fee
// we last proposed (if any). This method will in case more fee
// negotiation is necessary send a new ClosingSigned message to the peer
// with our new proposed fee. In case we can agree on a fee, it will
// assemble the close transaction, and we can go on to broadcasting it.
closeTx, ourSig, ourFee, err := p.negotiateFeeAndCreateCloseTx(channel,
msg, deliveryScripts, lastSig, lastFee)
if err != nil {
req.Err <- err
return
}
initSig := append(initiatorSig, byte(txscript.SigHashAll))
// Complete coop close transaction with the signatures of the close
// initiator and responder.
responderSig := msg.Signature
respSig := append(responderSig.Serialize(), byte(txscript.SigHashAll))
closeTx, err := channel.CompleteCooperativeClose(initSig, respSig,
deliveryScripts.localScript, deliveryScripts.remoteScript,
feeRate)
if err != nil {
req.Err <- err
// TODO(roasbeef): send ErrorGeneric to other side
return
if localReq != nil {
localReq.Err <- err
}
return nil, 0
}
// As we're the initiator of this channel shutdown procedure we'll now
// create a mirrored close signed message with our completed signature.
parsedSig, err := btcec.ParseSignature(initSig, btcec.S256())
if err != nil {
req.Err <- err
return
// If closeTx == nil it means that we did not agree on a fee, but we
// proposed a new fee to the peer. Return the signature used for this
// new proposal, and the fee we proposed, for use when we get a reponse.
if closeTx == nil {
return ourSig, ourFee
}
closingSigned := lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
p.queueMsg(closingSigned, nil)
// Finally, broadcast the closure transaction to the network.
// We agreed on a fee, and we can broadcast the closure transaction to
// the network.
peerLog.Infof("Broadcasting cooperative close tx: %v",
newLogClosure(func() string {
return spew.Sdump(closeTx)
}))
chanPoint := channel.ChannelPoint()
if err := p.server.cc.wallet.PublishTransaction(closeTx); err != nil {
peerLog.Errorf("channel close tx from "+
"ChannelPoint(%v) rejected: %v",
req.ChanPoint, err)
// TODO(roasbeef): send ErrorGeneric to other side
return
// TODO(halseth): Add relevant error types to the
// WalletController interface as this is quite fragile.
if strings.Contains(err.Error(), "already exists") ||
strings.Contains(err.Error(), "already have") {
peerLog.Infof("channel close tx from ChannelPoint(%v) "+
" already exist, probably broadcasted by peer: %v",
chanPoint, err)
} else {
peerLog.Errorf("channel close tx from ChannelPoint(%v) "+
" rejected: %v", chanPoint, err)
// TODO(roasbeef): send ErrorGeneric to other side
return nil, 0
}
}
// Once we've completed the cooperative channel closure, we'll wipe the
// channel so we reject any incoming forward or payment requests via
// this channel.
p.server.breachArbiter.settledContracts <- req.ChanPoint
p.server.breachArbiter.settledContracts <- chanPoint
if err := p.WipeChannel(channel); err != nil {
req.Err <- err
return
if localReq != nil {
localReq.Err <- err
}
return nil, 0
}
// TODO(roasbeef): also add closure height to summary
@ -1323,7 +1412,7 @@ func (p *peer) handleInitClosingSigned(req *htlcswitch.ChanClose,
closingTxid := closeTx.TxHash()
chanInfo := channel.StateSnapshot()
closeSummary := &channeldb.ChannelCloseSummary{
ChanPoint: *req.ChanPoint,
ChanPoint: *chanPoint,
ClosingTXID: closingTxid,
RemotePub: &chanInfo.RemoteIdentity,
Capacity: chanInfo.Capacity,
@ -1332,144 +1421,168 @@ func (p *peer) handleInitClosingSigned(req *htlcswitch.ChanClose,
IsPending: true,
}
if err := channel.DeleteState(closeSummary); err != nil {
req.Err <- err
return
if localReq != nil {
localReq.Err <- err
}
return nil, 0
}
// Update the caller with a new event detailing the current pending
// state of this request.
req.Updates <- &lnrpc.CloseStatusUpdate{
Update: &lnrpc.CloseStatusUpdate_ClosePending{
ClosePending: &lnrpc.PendingUpdate{
Txid: closingTxid[:],
// If this is a locally requested shutdown, update the caller with a new
// event detailing the current pending state of this request.
if localReq != nil {
localReq.Updates <- &lnrpc.CloseStatusUpdate{
Update: &lnrpc.CloseStatusUpdate_ClosePending{
ClosePending: &lnrpc.PendingUpdate{
Txid: closingTxid[:],
},
},
},
}
}
_, bestHeight, err := p.server.cc.chainIO.GetBestBlock()
if err != nil {
req.Err <- err
return
if localReq != nil {
localReq.Err <- err
}
return nil, 0
}
// Finally, launch a goroutine which will request to be notified by the
// ChainNotifier once the closure transaction obtains a single
// confirmation.
notifier := p.server.cc.chainNotifier
go waitForChanToClose(uint32(bestHeight), notifier, req.Err,
req.ChanPoint, &closingTxid, func() {
// If any error happens during waitForChanToClose, forard it to
// localReq. If this channel closure is not locally initiated, localReq
// will be nil, so just ignore the error.
errChan := make(chan error, 1)
if localReq != nil {
errChan = localReq.Err
}
go waitForChanToClose(uint32(bestHeight), notifier, errChan,
chanPoint, &closingTxid, func() {
// First, we'll mark the database as being fully closed
// so we'll no longer watch for its ultimate closure
// upon startup.
err := p.server.chanDB.MarkChanFullyClosed(req.ChanPoint)
err := p.server.chanDB.MarkChanFullyClosed(chanPoint)
if err != nil {
req.Err <- err
if localReq != nil {
localReq.Err <- err
}
return
}
// Respond to the local subsystem which requested the
// channel closure.
req.Updates <- &lnrpc.CloseStatusUpdate{
Update: &lnrpc.CloseStatusUpdate_ChanClose{
ChanClose: &lnrpc.ChannelCloseUpdate{
ClosingTxid: closingTxid[:],
Success: true,
if localReq != nil {
localReq.Updates <- &lnrpc.CloseStatusUpdate{
Update: &lnrpc.CloseStatusUpdate_ChanClose{
ChanClose: &lnrpc.ChannelCloseUpdate{
ClosingTxid: closingTxid[:],
Success: true,
},
},
},
}
}
})
return nil, 0
}
// handleResponseClosingSigned is called when the responder in a cooperative
// close workflow receives a ClosingSigned message. This function handles the
// finalization of the cooperative close from the perspective of the responder.
func (p *peer) handleResponseClosingSigned(msg *lnwire.ClosingSigned,
respSig []byte, deliveryScripts *closingScripts) {
// negotiateFeeAndCreateCloseTx takes into consideration the closing transaction
// fee proposed by the remote peer in the ClosingSigned message and our
// previously proposed fee (set to 0 if no previous), and continues the fee
// negotiation it process. In case the peer agreed on the same fee as we
// previously sent, it will assemble the close transaction and broadcast it. In
// case the peer propose a fee different from our previous proposal, but that
// can be accepted, a ClosingSigned message with the accepted fee is sent,
// before the closing transaction is broadcasted. In the case where we cannot
// accept the peer's proposed fee, a new fee proposal will be sent.
//
// TODO(halseth): In the case where we cannot accept the fee, and we cannot
// make more proposals, this method should return an error, and we should fail
// the channel.
func (p *peer) negotiateFeeAndCreateCloseTx(channel *lnwallet.LightningChannel,
msg *lnwire.ClosingSigned, deliveryScripts *closingScripts, ourSig []byte,
ourFeeProp uint64) (*wire.MsgTx, []byte, uint64, error) {
p.activeChanMtx.RLock()
channel, ok := p.activeChannels[msg.ChannelID]
p.activeChanMtx.RUnlock()
if !ok {
peerLog.Errorf("unable to close channel, ChannelID(%v) is "+
"unknown", msg.ChannelID)
return
peerFeeProposal := msg.FeeSatoshis
// If the fee proposed by the peer is different from what we proposed
// before (or we did not propose anything yet), we must check if we can
// accept the proposal, or if we should negotiate.
if peerFeeProposal != ourFeeProp {
// The peer has suggested a different fee from what we proposed.
// Let's calculate if this one is tolerable.
ourIdealFeeRate := p.server.cc.feeEstimator.
EstimateFeePerWeight(1) * 1000
ourIdealFee := channel.CalcFee(ourIdealFeeRate)
fee := calculateCompromiseFee(ourIdealFee, ourFeeProp,
peerFeeProposal)
// Our new proposed fee must be strictly between what we
// proposed before and what the peer proposed.
isAcceptable := false
if fee < peerFeeProposal && fee > ourFeeProp {
isAcceptable = true
}
if fee < ourFeeProp && fee > peerFeeProposal {
isAcceptable = true
}
if !isAcceptable {
// TODO(halseth): fail channel
}
// Since the compromise fee is different from the fee we last
// proposed, we must update our proposal.
// Create a new close proposal with the compromise fee, and
// send this to the peer.
closeSig, proposedFee, err := channel.CreateCloseProposal(fee,
deliveryScripts.localScript, deliveryScripts.remoteScript)
if err != nil {
peerLog.Errorf("unable to create close proposal: %v",
err)
return nil, nil, 0, err
}
parsedSig, err := btcec.ParseSignature(closeSig, btcec.S256())
if err != nil {
peerLog.Errorf("unable to parse signature: %v", err)
return nil, nil, 0, err
}
closingSigned := lnwire.NewClosingSigned(msg.ChannelID,
proposedFee, parsedSig)
p.queueMsg(closingSigned, nil)
// If the compromise fee was different from what the peer
// proposed, then we must return and wait for an answer, if not
// we can go on to complete the close transaction.
if fee != peerFeeProposal {
return nil, closeSig, proposedFee, nil
}
// We accept the fee proposed by the peer, so prepare our
// signature to complete the close transaction.
ourSig = append(closeSig, byte(txscript.SigHashAll))
}
// Now that we have the initiator's signature for the closure
// transaction, we can assemble the final closure transaction, complete
// with our signature.
initiatorSig := msg.Signature
initSig := append(initiatorSig.Serialize(), byte(txscript.SigHashAll))
// We agreed on a fee, and we have the peer's signature for this fee,
// so we can assemble the close tx.
peerSig := append(msg.Signature.Serialize(), byte(txscript.SigHashAll))
chanPoint := channel.ChannelPoint()
// Calculate our expected fee rate.
// TODO(roasbeef): should instead use the fee within the message
feeRate := p.server.cc.feeEstimator.EstimateFeePerWeight(1) * 1000
closeTx, err := channel.CompleteCooperativeClose(respSig, initSig,
closeTx, err := channel.CompleteCooperativeClose(ourSig, peerSig,
deliveryScripts.localScript, deliveryScripts.remoteScript,
feeRate)
peerFeeProposal)
if err != nil {
peerLog.Errorf("unable to complete cooperative "+
"close for ChannelPoint(%v): %v",
chanPoint, err)
// TODO(roasbeef): send ErrorGeneric to other side
return
return nil, nil, 0, err
}
closeTxid := closeTx.TxHash()
_, bestHeight, err := p.server.cc.chainIO.GetBestBlock()
if err != nil {
peerLog.Errorf("unable to get best height: %v", err)
}
// Once we've completed the cooperative channel closure, we'll wipe the
// channel so we reject any incoming forward or payment requests via
// this channel.
p.server.breachArbiter.settledContracts <- chanPoint
// We've just broadcast the transaction which closes the channel, so
// we'll wipe the channel from all our local indexes and also signal to
// the switch that this channel is now closed.
peerLog.Infof("ChannelPoint(%v) is now closed", chanPoint)
if err := p.WipeChannel(channel); err != nil {
peerLog.Errorf("unable to wipe channel: %v", err)
}
// Clear out the current channel state, marking the channel as being
// closed within the database.
chanInfo := channel.StateSnapshot()
closeSummary := &channeldb.ChannelCloseSummary{
ChanPoint: *chanPoint,
ClosingTXID: closeTxid,
RemotePub: &chanInfo.RemoteIdentity,
Capacity: chanInfo.Capacity,
SettledBalance: chanInfo.LocalBalance,
CloseType: channeldb.CooperativeClose,
IsPending: true,
}
if err := channel.DeleteState(closeSummary); err != nil {
peerLog.Errorf("unable to delete channel state: %v", err)
return
}
// Finally, we'll launch a goroutine to watch the network for the
// confirmation of the closing transaction, and mark the channel as
// such within the database (once it's confirmed").
notifier := p.server.cc.chainNotifier
go waitForChanToClose(uint32(bestHeight), notifier, nil, chanPoint,
&closeTxid, func() {
// Now that the closing transaction has been confirmed,
// we'll mark the database as being fully closed so now
// that we no longer watch for its ultimate closure
// upon startup.
err := p.server.chanDB.MarkChanFullyClosed(chanPoint)
if err != nil {
peerLog.Errorf("unable to mark channel "+
"as closed: %v", err)
return
}
},
)
return closeTx, nil, 0, nil
}
// waitForChanToClose uses the passed notifier to wait until the channel has

@ -1 +1,521 @@
package main
import (
"testing"
"time"
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
)
func disablePeerLogger(t *testing.T) {
peerLog = btclog.Disabled
srvrLog = btclog.Disabled
lnwallet.UseLogger(btclog.Disabled)
htlcswitch.UseLogger(btclog.Disabled)
channeldb.UseLogger(btclog.Disabled)
}
// TestPeerChannelClosureAcceptFeeResponder tests the shutdown responder's
// behavior if we can agree on the fee immediately.
func TestPeerChannelClosureAcceptFeeResponder(t *testing.T) {
disablePeerLogger(t)
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
responder, responderChan, initiatorChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
chanID := lnwire.NewChanIDFromOutPoint(responderChan.ChannelPoint())
// We send a shutdown request to Alice. She will now be the responding
// node in this shutdown procedure. We first expect Alice to answer this
// shutdown request with a Shutdown message.
responder.shutdownChanReqs <- lnwire.NewShutdown(chanID, dummyDeliveryScript)
var msg lnwire.Message
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive shutdown message")
}
shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok {
t.Fatalf("expected Shutdown message, got %T", msg)
}
respDeliveryScript := shutdownMsg.Address
// Alice will thereafter send a ClosingSigned message, indicating her
// proposed closing transaction fee.
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive ClosingSigned message")
}
responderClosingSigned, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
// We accept the fee, and send a ClosingSigned with the same fee back,
// so she knows we agreed.
peerFee := responderClosingSigned.FeeSatoshis
initiatorSig, proposedFee, err := initiatorChan.CreateCloseProposal(
peerFee, dummyDeliveryScript, respDeliveryScript)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
initSig := append(initiatorSig, byte(txscript.SigHashAll))
parsedSig, err := btcec.ParseSignature(initSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
responder.closingSignedChanReqs <- closingSigned
// The responder will now see that we agreed on the fee, and broadcast
// the closing transaction.
select {
case <-broadcastTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("closing tx not broadcast")
}
// And the initiator should be waiting for a confirmation notification.
notifier.confChannel <- &chainntnfs.TxConfirmation{}
}
// TestPeerChannelClosureAcceptFeeInitiator tests the shutdown initiator's
// behavior if we can agree on the fee immediately.
func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) {
disablePeerLogger(t)
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
initiator, initiatorChan, responderChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// We make the initiator send a shutdown request.
updateChan := make(chan *lnrpc.CloseStatusUpdate, 1)
errChan := make(chan error, 1)
closeCommand := &htlcswitch.ChanClose{
CloseType: htlcswitch.CloseRegular,
ChanPoint: initiatorChan.ChannelPoint(),
Updates: updateChan,
Err: errChan,
}
initiator.localCloseChanReqs <- closeCommand
// We should now be getting the shutdown request.
var msg lnwire.Message
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive shutdown request")
}
shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok {
t.Fatalf("expected Shutdown message, got %T", msg)
}
initiatorDeliveryScript := shutdownMsg.Address
// We'll answer the shutdown message with our own Shutdown, and then a
// ClosingSigned message.
chanID := shutdownMsg.ChannelID
initiator.shutdownChanReqs <- lnwire.NewShutdown(chanID,
dummyDeliveryScript)
estimator := lnwallet.StaticFeeEstimator{FeeRate: 50}
feeRate := estimator.EstimateFeePerWeight(1) * 1000
fee := responderChan.CalcFee(feeRate)
closeSig, proposedFee, err := responderChan.CreateCloseProposal(fee,
dummyDeliveryScript, initiatorDeliveryScript)
if err != nil {
t.Fatalf("unable to create close proposal: %v", err)
}
parsedSig, err := btcec.ParseSignature(closeSig, btcec.S256())
if err != nil {
t.Fatalf("unable to parse signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(shutdownMsg.ChannelID,
proposedFee, parsedSig)
initiator.closingSignedChanReqs <- closingSigned
// And we expect the initiator to accept the fee, and broadcast the
// closing transaction.
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed message")
}
closingSignedMsg, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
if closingSignedMsg.FeeSatoshis != proposedFee {
t.Fatalf("expected ClosingSigned fee to be %v, instead got %v",
proposedFee, closingSignedMsg.FeeSatoshis)
}
// The initiator will now see that we agreed on the fee, and broadcast
// the closing transaction.
select {
case <-broadcastTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("closing tx not broadcast")
}
// And the initiator should be waiting for a confirmation notification.
notifier.confChannel <- &chainntnfs.TxConfirmation{}
}
// TestPeerChannelClosureFeeNegotiationsResponder tests the shutdown responder's
// behavior in the case where we must do several rounds of fee negotiation
// before we agree on a fee.
func TestPeerChannelClosureFeeNegotiationsResponder(t *testing.T) {
disablePeerLogger(t)
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
responder, responderChan, initiatorChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
chanID := lnwire.NewChanIDFromOutPoint(responderChan.ChannelPoint())
// We send a shutdown request to Alice. She will now be the responding
// node in this shutdown procedure. We first expect Alice to answer this
// shutdown request with a Shutdown message.
responder.shutdownChanReqs <- lnwire.NewShutdown(chanID,
dummyDeliveryScript)
var msg lnwire.Message
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive shutdown message")
}
shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok {
t.Fatalf("expected Shutdown message, got %T", msg)
}
respDeliveryScript := shutdownMsg.Address
// Alice will thereafter send a ClosingSigned message, indicating her
// proposed closing transaction fee.
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed message")
}
responderClosingSigned, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
// We don't agree with the fee, and will send back one that's 2.5x.
preferredRespFee := responderClosingSigned.FeeSatoshis
increasedFee := uint64(float64(preferredRespFee) * 2.5)
initiatorSig, proposedFee, err := initiatorChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, respDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err := btcec.ParseSignature(initiatorSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
responder.closingSignedChanReqs <- closingSigned
// The responder will see the new fee we propose, but with current
// settings wont't accept anything over 2*FeeRate. We should get a new
// proposal back, which should have the average fee rate proposed.
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed message")
}
responderClosingSigned, ok = msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
avgFee := (preferredRespFee + increasedFee) / 2
peerFee := responderClosingSigned.FeeSatoshis
if peerFee != avgFee {
t.Fatalf("expected ClosingSigned with fee %v, got %v",
proposedFee, responderClosingSigned.FeeSatoshis)
}
// We try negotiating a 2.1x fee, which should also be rejected.
increasedFee = uint64(float64(preferredRespFee) * 2.1)
initiatorSig, proposedFee, err = initiatorChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, respDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err = btcec.ParseSignature(initiatorSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
responder.closingSignedChanReqs <- closingSigned
// It still won't be accepted, and we should get a new proposal, the
// average of what we proposed, and what they proposed last time.
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed message")
}
responderClosingSigned, ok = msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
avgFee = (peerFee + increasedFee) / 2
peerFee = responderClosingSigned.FeeSatoshis
if peerFee != avgFee {
t.Fatalf("expected ClosingSigned with fee %v, got %v",
proposedFee, responderClosingSigned.FeeSatoshis)
}
// Accept fee.
initiatorSig, proposedFee, err = initiatorChan.CreateCloseProposal(
peerFee, dummyDeliveryScript, respDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
initSig := append(initiatorSig, byte(txscript.SigHashAll))
parsedSig, err = btcec.ParseSignature(initSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
responder.closingSignedChanReqs <- closingSigned
// The responder will now see that we agreed on the fee, and broadcast
// the closing transaction.
select {
case <-broadcastTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("closing tx not broadcast")
}
// And the responder should be waiting for a confirmation notification.
notifier.confChannel <- &chainntnfs.TxConfirmation{}
}
// TestPeerChannelClosureFeeNegotiationsInitiator tests the shutdown initiator's
// behavior in the case where we must do several rounds of fee negotiation
// before we agree on a fee.
func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) {
disablePeerLogger(t)
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
initiator, initiatorChan, responderChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
// We make the initiator send a shutdown request.
updateChan := make(chan *lnrpc.CloseStatusUpdate, 1)
errChan := make(chan error, 1)
closeCommand := &htlcswitch.ChanClose{
CloseType: htlcswitch.CloseRegular,
ChanPoint: initiatorChan.ChannelPoint(),
Updates: updateChan,
Err: errChan,
}
initiator.localCloseChanReqs <- closeCommand
// We should now be getting the shutdown request.
var msg lnwire.Message
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive shutdown request")
}
shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok {
t.Fatalf("expected Shutdown message, got %T", msg)
}
initiatorDeliveryScript := shutdownMsg.Address
// We'll answer the shutdown message with our own Shutdown, and then a
// ClosingSigned message.
chanID := lnwire.NewChanIDFromOutPoint(initiatorChan.ChannelPoint())
respShutdown := lnwire.NewShutdown(chanID, dummyDeliveryScript)
initiator.shutdownChanReqs <- respShutdown
estimator := lnwallet.StaticFeeEstimator{FeeRate: 50}
initiatorIdealFeeRate := estimator.EstimateFeePerWeight(1) * 1000
initiatorIdealFee := responderChan.CalcFee(initiatorIdealFeeRate)
increasedFee := uint64(float64(initiatorIdealFee) * 2.5)
closeSig, proposedFee, err := responderChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, initiatorDeliveryScript,
)
if err != nil {
t.Fatalf("unable to create close proposal: %v", err)
}
parsedSig, err := btcec.ParseSignature(closeSig, btcec.S256())
if err != nil {
t.Fatalf("unable to parse signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(shutdownMsg.ChannelID,
proposedFee, parsedSig)
initiator.closingSignedChanReqs <- closingSigned
// And we expect the initiator to reject the fee, and suggest a lower
// one.
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed")
}
closingSignedMsg, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
avgFee := (initiatorIdealFee + increasedFee) / 2
peerFee := closingSignedMsg.FeeSatoshis
if peerFee != avgFee {
t.Fatalf("expected ClosingSigned fee to be %v, instead got %v",
avgFee, peerFee)
}
// We try negotiating a 2.1x fee, which should also be rejected.
increasedFee = uint64(float64(initiatorIdealFee) * 2.1)
responderSig, proposedFee, err := responderChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, initiatorDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err = btcec.ParseSignature(responderSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
initiator.closingSignedChanReqs <- closingSigned
// It still won't be accepted, and we should get a new proposal, the
// average of what we proposed, and what they proposed last time.
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed")
}
initiatorClosingSigned, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
avgFee = (peerFee + increasedFee) / 2
peerFee = initiatorClosingSigned.FeeSatoshis
if peerFee != avgFee {
t.Fatalf("expected ClosingSigned with fee %v, got %v",
proposedFee, initiatorClosingSigned.FeeSatoshis)
}
// Accept fee.
responderSig, proposedFee, err = responderChan.CreateCloseProposal(
peerFee, dummyDeliveryScript, initiatorDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
respSig := append(responderSig, byte(txscript.SigHashAll))
parsedSig, err = btcec.ParseSignature(respSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
initiator.closingSignedChanReqs <- closingSigned
// Wait for closing tx to be broadcasted.
select {
case <-broadcastTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("closing tx not broadcast")
}
}

268
test_utils.go Normal file

@ -0,0 +1,268 @@
package main
import (
"bytes"
"io/ioutil"
"math/rand"
"net"
"os"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
var (
alicesPrivKey = []byte{
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
}
bobsPrivKey = []byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
}
// Use a hard-coded HD seed.
testHdSeed = [32]byte{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
// Just use some arbitrary bytes as delivery script.
dummyDeliveryScript = alicesPrivKey[:]
)
// createTestPeer creates a channel between two nodes, and returns a peer for
// one of the nodes, together with the channel seen from both nodes.
func createTestPeer(notifier chainntnfs.ChainNotifier,
publTx chan *wire.MsgTx) (*peer, *lnwallet.LightningChannel,
*lnwallet.LightningChannel, func(), error) {
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
alicesPrivKey)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
bobsPrivKey)
channelCapacity := btcutil.Amount(10 * 1e8)
channelBal := channelCapacity / 2
aliceDustLimit := btcutil.Amount(200)
bobDustLimit := btcutil.Amount(1300)
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
prevOut := &wire.OutPoint{
Hash: chainhash.Hash(testHdSeed),
Index: 0,
}
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
aliceCfg := channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: aliceDustLimit,
MaxPendingAmount: btcutil.Amount(rand.Int63()),
ChanReserve: btcutil.Amount(rand.Int63()),
MinHTLC: btcutil.Amount(rand.Int63()),
MaxAcceptedHtlcs: uint16(rand.Int31()),
},
CsvDelay: uint16(csvTimeoutAlice),
MultiSigKey: aliceKeyPub,
RevocationBasePoint: aliceKeyPub,
PaymentBasePoint: aliceKeyPub,
DelayBasePoint: aliceKeyPub,
}
bobCfg := channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: bobDustLimit,
MaxPendingAmount: btcutil.Amount(rand.Int63()),
ChanReserve: btcutil.Amount(rand.Int63()),
MinHTLC: btcutil.Amount(rand.Int63()),
MaxAcceptedHtlcs: uint16(rand.Int31()),
},
CsvDelay: uint16(csvTimeoutBob),
MultiSigKey: bobKeyPub,
RevocationBasePoint: bobKeyPub,
PaymentBasePoint: bobKeyPub,
DelayBasePoint: bobKeyPub,
}
bobRoot := lnwallet.DeriveRevocationRoot(bobKeyPriv, testHdSeed, aliceKeyPub)
bobPreimageProducer := shachain.NewRevocationProducer(bobRoot)
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, nil, nil, err
}
bobCommitPoint := lnwallet.ComputeCommitmentPoint(bobFirstRevoke[:])
aliceRoot := lnwallet.DeriveRevocationRoot(aliceKeyPriv, testHdSeed, bobKeyPub)
alicePreimageProducer := shachain.NewRevocationProducer(aliceRoot)
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, nil, nil, err
}
aliceCommitPoint := lnwallet.ComputeCommitmentPoint(aliceFirstRevoke[:])
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(channelBal,
channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint,
fundingTxIn)
if err != nil {
return nil, nil, nil, nil, err
}
alicePath, err := ioutil.TempDir("", "alicedb")
dbAlice, err := channeldb.Open(alicePath)
if err != nil {
return nil, nil, nil, nil, err
}
bobPath, err := ioutil.TempDir("", "bobdb")
dbBob, err := channeldb.Open(bobPath)
if err != nil {
return nil, nil, nil, nil, err
}
var obsfucator [lnwallet.StateHintSize]byte
copy(obsfucator[:], aliceFirstRevoke[:])
estimator := &lnwallet.StaticFeeEstimator{FeeRate: 50}
feePerKw := btcutil.Amount(estimator.EstimateFeePerWeight(1) * 1000)
aliceChannelState := &channeldb.OpenChannel{
LocalChanCfg: aliceCfg,
RemoteChanCfg: bobCfg,
IdentityPub: aliceKeyPub,
FundingOutpoint: *prevOut,
ChanType: channeldb.SingleFunder,
FeePerKw: feePerKw,
IsInitiator: true,
Capacity: channelCapacity,
LocalBalance: channelBal,
RemoteBalance: channelBal,
CommitTx: *aliceCommitTx,
CommitSig: bytes.Repeat([]byte{1}, 71),
RemoteCurrentRevocation: bobCommitPoint,
RevocationProducer: alicePreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
Db: dbAlice,
}
addr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18555,
}
if err := aliceChannelState.SyncPending(addr, 0); err != nil {
return nil, nil, nil, nil, err
}
bobChannelState := &channeldb.OpenChannel{
LocalChanCfg: bobCfg,
RemoteChanCfg: aliceCfg,
IdentityPub: bobKeyPub,
FeePerKw: feePerKw,
FundingOutpoint: *prevOut,
ChanType: channeldb.SingleFunder,
IsInitiator: false,
Capacity: channelCapacity,
LocalBalance: channelBal,
RemoteBalance: channelBal,
CommitTx: *bobCommitTx,
CommitSig: bytes.Repeat([]byte{1}, 71),
RemoteCurrentRevocation: aliceCommitPoint,
RevocationProducer: bobPreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
Db: dbBob,
}
addr = &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18556,
}
if err := bobChannelState.SyncPending(addr, 0); err != nil {
return nil, nil, nil, nil, err
}
cleanUpFunc := func() {
os.RemoveAll(bobPath)
os.RemoveAll(alicePath)
}
aliceSigner := &mockSigner{aliceKeyPriv}
bobSigner := &mockSigner{bobKeyPriv}
channelAlice, err := lnwallet.NewLightningChannel(aliceSigner, notifier,
estimator, aliceChannelState)
if err != nil {
return nil, nil, nil, nil, err
}
channelBob, err := lnwallet.NewLightningChannel(bobSigner, notifier,
estimator, bobChannelState)
if err != nil {
return nil, nil, nil, nil, err
}
chainIO := &mockChainIO{}
wallet := &lnwallet.LightningWallet{
WalletController: &mockWalletController{
rootKey: aliceKeyPriv,
publishedTransactions: publTx,
},
}
cc := &chainControl{
feeEstimator: estimator,
chainIO: chainIO,
chainNotifier: notifier,
wallet: wallet,
}
breachArbiter := &breachArbiter{
settledContracts: make(chan *wire.OutPoint, 10),
}
s := &server{
chanDB: dbAlice,
cc: cc,
breachArbiter: breachArbiter,
}
s.htlcSwitch = htlcswitch.New(htlcswitch.Config{})
s.htlcSwitch.Start()
alicePeer := &peer{
server: s,
sendQueue: make(chan outgoinMsg, 1),
outgoingQueue: make(chan outgoinMsg, outgoingQueueLen),
activeChannels: make(map[lnwire.ChannelID]*lnwallet.LightningChannel),
newChannels: make(chan *newChannelMsg, 1),
localCloseChanReqs: make(chan *htlcswitch.ChanClose),
shutdownChanReqs: make(chan *lnwire.Shutdown),
closingSignedChanReqs: make(chan *lnwire.ClosingSigned),
localSharedFeatures: nil,
globalSharedFeatures: nil,
queueQuit: make(chan struct{}),
quit: make(chan struct{}),
}
chanID := lnwire.NewChanIDFromOutPoint(channelAlice.ChannelPoint())
alicePeer.activeChannels[chanID] = channelAlice
go alicePeer.channelManager()
return alicePeer, channelAlice, channelBob, cleanUpFunc, nil
}