lnwallet: adopt simple fee structure for commitment transactions

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.
This commit is contained in:
Olaoluwa Osuntokun 2016-09-12 19:05:56 -07:00
parent ff70b3afa9
commit 49ce1040d4
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
5 changed files with 69 additions and 30 deletions

@ -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,

@ -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)
}

@ -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)
}

@ -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{

@ -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
}