Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fee negotiation on channel cooperative shutdown. #225

Merged
merged 2 commits into from
Aug 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
149 changes: 4 additions & 145 deletions fundingmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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{})

Expand Down Expand Up @@ -860,7 +719,7 @@ func TestFundingManagerNormalWorkflow(t *testing.T) {
}

func TestFundingManagerRestartBehavior(t *testing.T) {
disableLogger(t)
disableFndgLogger(t)

shutdownChannel := make(chan struct{})

Expand Down Expand Up @@ -1095,7 +954,7 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
}

func TestFundingManagerFundingTimeout(t *testing.T) {
disableLogger(t)
disableFndgLogger(t)

shutdownChannel := make(chan struct{})

Expand Down
16 changes: 10 additions & 6 deletions lnwallet/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -3671,22 +3671,21 @@ 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
}

// 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

Expand Down Expand Up @@ -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()
Expand All @@ -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

Expand Down Expand Up @@ -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
}
32 changes: 18 additions & 14 deletions lnwallet/channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -759,15 +761,15 @@ 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)
}
bobCloseSha := aliceCloseTx.TxHash()

bobCloseTx, err := aliceChannel.CompleteCooperativeClose(
aliceCloseSig, bobCloseSig, aliceDeliveryScript,
bobDeliveryScript, aliceFeeRate)
bobDeliveryScript, aliceFee)
if err != nil {
t.Fatalf("unable to complete bob cooperative close: %v", err)
}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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)
}
Expand Down