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:
parent
ff70b3afa9
commit
49ce1040d4
@ -743,7 +743,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
|||||||
msg.channelType,
|
msg.channelType,
|
||||||
msg.coinType,
|
msg.coinType,
|
||||||
0, // TODO(roasbeef): grab from fee estimation model
|
0, // TODO(roasbeef): grab from fee estimation model
|
||||||
contribution.FundingAmount,
|
capacity,
|
||||||
contribution.CsvDelay,
|
contribution.CsvDelay,
|
||||||
contribution.CommitKey,
|
contribution.CommitKey,
|
||||||
contribution.MultiSigKey,
|
contribution.MultiSigKey,
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/fastsha256"
|
"github.com/btcsuite/fastsha256"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/elkrem"
|
"github.com/lightningnetwork/lnd/elkrem"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
@ -68,11 +69,33 @@ func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]
|
|||||||
|
|
||||||
return sig[:len(sig)-1], nil
|
return sig[:len(sig)-1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) {
|
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) {
|
||||||
return nil, nil
|
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
|
// initRevocationWindows simulates a new channel being opened within the p2p
|
||||||
// network by populating the initial revocation windows of the passed
|
// network by populating the initial revocation windows of the passed
|
||||||
// commitment state machines.
|
// commitment state machines.
|
||||||
@ -254,11 +277,13 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
|
|||||||
aliceSigner := &mockSigner{aliceKeyPriv}
|
aliceSigner := &mockSigner{aliceKeyPriv}
|
||||||
bobSigner := &mockSigner{bobKeyPriv}
|
bobSigner := &mockSigner{bobKeyPriv}
|
||||||
|
|
||||||
channelAlice, err := NewLightningChannel(aliceSigner, nil, nil, aliceChannelState)
|
notifier := &mockNotfier{}
|
||||||
|
|
||||||
|
channelAlice, err := NewLightningChannel(aliceSigner, nil, notifier, aliceChannelState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
channelBob, err := NewLightningChannel(bobSigner, nil, nil, bobChannelState)
|
channelBob, err := NewLightningChannel(bobSigner, nil, notifier, bobChannelState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
@ -672,11 +697,12 @@ func TestStateUpdatePersistence(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to fetch channel: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
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")
|
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.
|
// Alice responds with her output, change addr, multi-sig key and signatures.
|
||||||
// Bob then responds with his signatures.
|
// Bob then responds with his signatures.
|
||||||
bobsSigs, err := bobNode.signFundingTx(fundingTx)
|
bobsSigs, err := bobNode.signFundingTx(fundingTx)
|
||||||
@ -439,7 +442,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, wallet *lnwallet
|
|||||||
commitSig, err := bobNode.signCommitTx(
|
commitSig, err := bobNode.signCommitTx(
|
||||||
chanReservation.LocalCommitTx(),
|
chanReservation.LocalCommitTx(),
|
||||||
chanReservation.FundingRedeemScript(),
|
chanReservation.FundingRedeemScript(),
|
||||||
10e8)
|
chanCapacity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bob is unable to sign alice's commit tx: %v", err)
|
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(
|
bobCommitSig, err := bobNode.signCommitTx(
|
||||||
chanReservation.LocalCommitTx(),
|
chanReservation.LocalCommitTx(),
|
||||||
chanReservation.FundingRedeemScript(),
|
chanReservation.FundingRedeemScript(),
|
||||||
int64(fundingAmt))
|
// TODO(roasbeef): account for current hard-coded fee, need to
|
||||||
|
// remove bobNode entirely
|
||||||
|
int64(fundingAmt)+5000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bob is unable to sign alice's commit tx: %v", err)
|
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(
|
fundingRedeemScript, multiOut, err := lnwallet.GenFundingPkScript(
|
||||||
ourContribution.MultiSigKey.SerializeCompressed(),
|
ourContribution.MultiSigKey.SerializeCompressed(),
|
||||||
bobContribution.MultiSigKey.SerializeCompressed(),
|
bobContribution.MultiSigKey.SerializeCompressed(),
|
||||||
int64(capacity))
|
// TODO(roasbeef): account for hard-coded fee, remove bob node
|
||||||
|
int64(capacity)+5000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to generate multi-sig output: %v", err)
|
t.Fatalf("unable to generate multi-sig output: %v", err)
|
||||||
}
|
}
|
||||||
@ -837,7 +843,8 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness,
|
|||||||
}
|
}
|
||||||
txsort.InPlaceSort(aliceCommitTx)
|
txsort.InPlaceSort(aliceCommitTx)
|
||||||
bobCommitSig, err := bobNode.signCommitTx(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 {
|
if err != nil {
|
||||||
t.Fatalf("unable to sign alice's commit tx: %v", err)
|
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 {
|
if fundingAmt == 0 {
|
||||||
ourBalance = 0
|
ourBalance = 0
|
||||||
theirBalance = capacity
|
theirBalance = capacity - commitFee
|
||||||
} else {
|
} else {
|
||||||
ourBalance = fundingAmt
|
// TODO(roasbeef): need to rework fee structure in general and
|
||||||
theirBalance = capacity - fundingAmt
|
// 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{
|
return &ChannelReservation{
|
||||||
|
@ -36,6 +36,8 @@ const (
|
|||||||
// TODO(roasbeef): should instead be child to make room for future
|
// TODO(roasbeef): should instead be child to make room for future
|
||||||
// rotations, etc.
|
// rotations, etc.
|
||||||
identityKeyIndex = hdkeychain.HardenedKeyStart + 2
|
identityKeyIndex = hdkeychain.HardenedKeyStart + 2
|
||||||
|
|
||||||
|
commitFee = 5000
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -485,7 +487,8 @@ func (l *LightningWallet) InitChannelReservation(capacity,
|
|||||||
// validate a funding reservation request.
|
// validate a funding reservation request.
|
||||||
func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg) {
|
func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg) {
|
||||||
id := atomic.AddUint64(&l.nextFundingID, 1)
|
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)
|
req.minFeeRate, l, id, req.numConfs)
|
||||||
|
|
||||||
// Grab the mutex on the ChannelReservation to ensure thead-safety
|
// 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
|
// don't need to perform any coin selection. Otherwise, attempt to
|
||||||
// obtain enough coins to meet the required funding amount.
|
// obtain enough coins to meet the required funding amount.
|
||||||
if req.fundingAmount != 0 {
|
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)
|
feeRate := uint64(10)
|
||||||
if err := l.selectCoinsAndChange(feeRate, req.fundingAmount,
|
amt := req.fundingAmount + commitFee
|
||||||
ourContribution); err != nil {
|
err := l.selectCoinsAndChange(feeRate, amt, ourContribution)
|
||||||
|
if err != nil {
|
||||||
req.err <- err
|
req.err <- err
|
||||||
req.resp <- nil
|
req.resp <- nil
|
||||||
return
|
return
|
||||||
@ -1190,35 +1195,32 @@ out:
|
|||||||
|
|
||||||
// selectCoinsAndChange performs coin selection in order to obtain witness
|
// selectCoinsAndChange performs coin selection in order to obtain witness
|
||||||
// outputs which sum to at least 'numCoins' amount of satoshis. If coin
|
// outputs which sum to at least 'numCoins' amount of satoshis. If coin
|
||||||
// selection is succesful/possible, then the selected coins are available within
|
// selection is succesful/possible, then the selected coins are available
|
||||||
// the passed contribution's inputs. If necessary, a change address will also be
|
// within the passed contribution's inputs. If necessary, a change address will
|
||||||
// generated.
|
// also be generated.
|
||||||
// TODO(roasbeef): remove hardcoded fees and req'd confs for outputs.
|
// TODO(roasbeef): remove hardcoded fees and req'd confs for outputs.
|
||||||
func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amount,
|
func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amount,
|
||||||
contribution *ChannelContribution) error {
|
contribution *ChannelContribution) error {
|
||||||
|
|
||||||
// We hold the coin select mutex while querying for outputs, and
|
// We hold the coin select mutex while querying for outputs, and
|
||||||
// performing coin selection in order to avoid inadvertent double spends
|
// performing coin selection in order to avoid inadvertent double
|
||||||
// accross funding transactions.
|
// spends accross funding transactions.
|
||||||
// NOTE: We don't use defer her so we can properly release the lock
|
|
||||||
// when we encounter an error condition.
|
|
||||||
l.coinSelectMtx.Lock()
|
l.coinSelectMtx.Lock()
|
||||||
|
defer l.coinSelectMtx.Unlock()
|
||||||
|
|
||||||
// Find all unlocked unspent witness outputs with greater than 1
|
// Find all unlocked unspent witness outputs with greater than 1
|
||||||
// confirmation.
|
// confirmation.
|
||||||
// TODO(roasbeef): make num confs a configuration paramter
|
// TODO(roasbeef): make num confs a configuration paramter
|
||||||
coins, err := l.ListUnspentWitness(1)
|
coins, err := l.ListUnspentWitness(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.coinSelectMtx.Unlock()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peform coin selection over our available, unlocked unspent outputs
|
// Peform coin selection over our available, unlocked unspent outputs
|
||||||
// in order to find enough coins to meet the funding amount requirements.
|
// in order to find enough coins to meet the funding amount
|
||||||
// TODO(roasbeef): take in sat/byte
|
// requirements.
|
||||||
selectedCoins, changeAmt, err := coinSelect(feeRate, amt, coins)
|
selectedCoins, changeAmt, err := coinSelect(feeRate, amt, coins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.coinSelectMtx.Unlock()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1254,8 +1256,6 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amoun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
l.coinSelectMtx.Unlock()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user