From 49ce1040d410b018bee44679206f89ba60b859af Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 12 Sep 2016 19:05:56 -0700 Subject: [PATCH] lnwallet: adopt simple fee structure for commitment transactions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit modifies the prior funding workflow to account for fees when creating the funding output. As a stop gap, the current fee for the commitment transaction is now hard-coded at 5k satoshis. Once the fee models are in place this should instead be some high multiple of the current “average” fee rate within the network, continuing, the proper fee should be adjusted from the commitment transaction has outputs are added/removed. --- fundingmanager.go | 2 +- lnwallet/channel_test.go | 36 +++++++++++++++++++++++++++++++----- lnwallet/interface_test.go | 15 +++++++++++---- lnwallet/reservation.go | 12 +++++++++--- lnwallet/wallet.go | 34 +++++++++++++++++----------------- 5 files changed, 69 insertions(+), 30 deletions(-) diff --git a/fundingmanager.go b/fundingmanager.go index c38f13f8..9a2eacbf 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -743,7 +743,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { msg.channelType, msg.coinType, 0, // TODO(roasbeef): grab from fee estimation model - contribution.FundingAmount, + capacity, contribution.CsvDelay, contribution.CommitKey, contribution.MultiSigKey, diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 2df97f49..dc38be40 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/fastsha256" "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/elkrem" "github.com/lightningnetwork/lnd/lnwire" @@ -68,11 +69,33 @@ func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([] return sig[:len(sig)-1], nil } - func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) { return nil, nil } +type mockNotfier struct { +} + +func (m *mockNotfier) RegisterConfirmationsNtfn(txid *wire.ShaHash, numConfs uint32) (*chainntnfs.ConfirmationEvent, error) { + return nil, 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) (*chainntnfs.SpendEvent, error) { + return &chainntnfs.SpendEvent{ + Spend: make(chan *chainntnfs.SpendDetail), + }, nil +} + // initRevocationWindows simulates a new channel being opened within the p2p // network by populating the initial revocation windows of the passed // commitment state machines. @@ -254,11 +277,13 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan aliceSigner := &mockSigner{aliceKeyPriv} bobSigner := &mockSigner{bobKeyPriv} - channelAlice, err := NewLightningChannel(aliceSigner, nil, nil, aliceChannelState) + notifier := &mockNotfier{} + + channelAlice, err := NewLightningChannel(aliceSigner, nil, notifier, aliceChannelState) if err != nil { return nil, nil, nil, err } - channelBob, err := NewLightningChannel(bobSigner, nil, nil, bobChannelState) + channelBob, err := NewLightningChannel(bobSigner, nil, notifier, bobChannelState) if err != nil { return nil, nil, nil, err } @@ -672,11 +697,12 @@ func TestStateUpdatePersistence(t *testing.T) { if err != nil { t.Fatalf("unable to fetch channel: %v", err) } - aliceChannelNew, err := NewLightningChannel(aliceChannel.signer, nil, nil, aliceChannels[0]) + notifier := aliceChannel.channelEvents + aliceChannelNew, err := NewLightningChannel(aliceChannel.signer, nil, notifier, aliceChannels[0]) if err != nil { t.Fatalf("unable to create new channel: %v", err) } - bobChannelNew, err := NewLightningChannel(bobChannel.signer, nil, nil, bobChannels[0]) + bobChannelNew, err := NewLightningChannel(bobChannel.signer, nil, notifier, bobChannels[0]) if err != nil { t.Fatalf("unable to create new channel: %v", err) } diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index 04725dab..ff9b5489 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -430,6 +430,9 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, wallet *lnwallet 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) @@ -439,7 +442,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, wallet *lnwallet commitSig, err := bobNode.signCommitTx( chanReservation.LocalCommitTx(), chanReservation.FundingRedeemScript(), - 10e8) + chanCapacity) if err != nil { t.Fatalf("bob is unable to sign alice's commit tx: %v", err) } @@ -690,7 +693,9 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, bobCommitSig, err := bobNode.signCommitTx( chanReservation.LocalCommitTx(), chanReservation.FundingRedeemScript(), - int64(fundingAmt)) + // 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) } @@ -804,7 +809,8 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, fundingRedeemScript, multiOut, err := lnwallet.GenFundingPkScript( ourContribution.MultiSigKey.SerializeCompressed(), bobContribution.MultiSigKey.SerializeCompressed(), - int64(capacity)) + // 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) } @@ -837,7 +843,8 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, } txsort.InPlaceSort(aliceCommitTx) bobCommitSig, err := bobNode.signCommitTx(aliceCommitTx, - fundingRedeemScript, int64(capacity)) + // 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) } diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index e59dbf97..286f1db2 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -141,10 +141,16 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut if fundingAmt == 0 { ourBalance = 0 - theirBalance = capacity + theirBalance = capacity - commitFee } else { - ourBalance = fundingAmt - theirBalance = capacity - fundingAmt + // TODO(roasbeef): need to rework fee structure in general and + // also when we "unlock" dual funder within the daemon + if capacity == fundingAmt+commitFee { // Single funder + ourBalance = capacity - commitFee + } else { + ourBalance = fundingAmt - commitFee + } + theirBalance = capacity - fundingAmt - commitFee } return &ChannelReservation{ diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 529f4dac..ecced934 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -36,6 +36,8 @@ const ( // TODO(roasbeef): should instead be child to make room for future // rotations, etc. identityKeyIndex = hdkeychain.HardenedKeyStart + 2 + + commitFee = 5000 ) var ( @@ -485,7 +487,8 @@ func (l *LightningWallet) InitChannelReservation(capacity, // validate a funding reservation request. func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg) { id := atomic.AddUint64(&l.nextFundingID, 1) - reservation := NewChannelReservation(req.capacity, req.fundingAmount, + totalCapacity := req.capacity + commitFee + reservation := NewChannelReservation(totalCapacity, req.fundingAmount, req.minFeeRate, l, id, req.numConfs) // Grab the mutex on the ChannelReservation to ensure thead-safety @@ -501,10 +504,12 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg // don't need to perform any coin selection. Otherwise, attempt to // obtain enough coins to meet the required funding amount. if req.fundingAmount != 0 { - // TODO(roasbeef): consult model for proper fee rate + // TODO(roasbeef): consult model for proper fee rate on funding + // tx feeRate := uint64(10) - if err := l.selectCoinsAndChange(feeRate, req.fundingAmount, - ourContribution); err != nil { + amt := req.fundingAmount + commitFee + err := l.selectCoinsAndChange(feeRate, amt, ourContribution) + if err != nil { req.err <- err req.resp <- nil return @@ -1190,35 +1195,32 @@ out: // selectCoinsAndChange performs coin selection in order to obtain witness // outputs which sum to at least 'numCoins' amount of satoshis. If coin -// selection is succesful/possible, then the selected coins are available within -// the passed contribution's inputs. If necessary, a change address will also be -// generated. +// selection is succesful/possible, then the selected coins are available +// within the passed contribution's inputs. If necessary, a change address will +// also be generated. // TODO(roasbeef): remove hardcoded fees and req'd confs for outputs. func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amount, contribution *ChannelContribution) error { // We hold the coin select mutex while querying for outputs, and - // performing coin selection in order to avoid inadvertent double spends - // accross funding transactions. - // NOTE: We don't use defer her so we can properly release the lock - // when we encounter an error condition. + // performing coin selection in order to avoid inadvertent double + // spends accross funding transactions. l.coinSelectMtx.Lock() + defer l.coinSelectMtx.Unlock() // Find all unlocked unspent witness outputs with greater than 1 // confirmation. // TODO(roasbeef): make num confs a configuration paramter coins, err := l.ListUnspentWitness(1) if err != nil { - l.coinSelectMtx.Unlock() return err } // Peform coin selection over our available, unlocked unspent outputs - // in order to find enough coins to meet the funding amount requirements. - // TODO(roasbeef): take in sat/byte + // in order to find enough coins to meet the funding amount + // requirements. selectedCoins, changeAmt, err := coinSelect(feeRate, amt, coins) if err != nil { - l.coinSelectMtx.Unlock() return err } @@ -1254,8 +1256,6 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amoun } } - l.coinSelectMtx.Unlock() - return nil }