From abe2e502d5ed1a593f8135779e986cca505df943 Mon Sep 17 00:00:00 2001 From: bryanvu Date: Mon, 1 May 2017 00:27:12 -0700 Subject: [PATCH] lnwallet: add FeeEstimator interface, StaticFeeEstimator implementation This commit adds the FeeEstimator interface, which can be used for future fee calculation implementations. Currently, there is only the StaticFeeEstimator implementation, which returns the same fee rate for any transaction. --- breacharbiter.go | 6 ++++-- fundingmanager.go | 7 ++++++- lnd.go | 5 +++-- lnwallet/channel.go | 8 +++++++- lnwallet/channel_test.go | 11 +++++++---- lnwallet/interface.go | 20 ++++++++++++++++++++ lnwallet/interface_test.go | 3 ++- lnwallet/wallet.go | 34 +++++++++++++++++++++++++++++++++- peer.go | 2 +- rpcserver.go | 2 +- server.go | 27 ++++++++++++++++----------- 11 files changed, 100 insertions(+), 25 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index 4c444710e..1f93c6fec 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -28,6 +28,7 @@ type breachArbiter struct { notifier chainntnfs.ChainNotifier chainIO lnwallet.BlockChainIO htlcSwitch *htlcSwitch + estimator lnwallet.FeeEstimator // breachObservers is a map which tracks all the active breach // observers we're currently managing. The key of the map is the @@ -64,7 +65,7 @@ type breachArbiter struct { // its dependent objects. func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB, notifier chainntnfs.ChainNotifier, h *htlcSwitch, - chain lnwallet.BlockChainIO) *breachArbiter { + chain lnwallet.BlockChainIO, fe lnwallet.FeeEstimator) *breachArbiter { return &breachArbiter{ wallet: wallet, @@ -72,6 +73,7 @@ func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB, notifier: notifier, chainIO: chain, htlcSwitch: h, + estimator: fe, breachObservers: make(map[wire.OutPoint]chan struct{}), breachedContracts: make(chan *retributionInfo), @@ -110,7 +112,7 @@ func (b *breachArbiter) Start() error { len(activeChannels)) for i, chanState := range activeChannels { channel, err := lnwallet.NewLightningChannel(nil, b.notifier, - chanState) + b.estimator, chanState) if err != nil { brarLog.Errorf("unable to load channel from "+ "disk: %v", err) diff --git a/fundingmanager.go b/fundingmanager.go index 5cfd233c9..1042a9362 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -130,6 +130,10 @@ type fundingConfig struct { // funds from on-chain transaction outputs into Lightning channels. Wallet *lnwallet.LightningWallet + // FeeEstimator calculates appropriate fee rates based on historical + // transaction information. + FeeEstimator lnwallet.FeeEstimator + // ArbiterChan allows the FundingManager to notify the BreachArbiter // that a new channel has been created that should be observed to // ensure that the channel counterparty hasn't broadcasted an invalid @@ -959,7 +963,8 @@ func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.Open // With the channel marked open, we'll create the state-machine object // which wraps the database state. - channel, err := lnwallet.NewLightningChannel(nil, nil, completeChan) + channel, err := lnwallet.NewLightningChannel(nil, nil, + f.cfg.FeeEstimator, completeChan) if err != nil { fndgLog.Errorf("error creating new lightning channel: %v", err) return diff --git a/lnd.go b/lnd.go index 90da7f6b0..ef56993a4 100644 --- a/lnd.go +++ b/lnd.go @@ -149,11 +149,12 @@ func lndMain() error { signer := wc bio := wc fundingSigner := wc + estimator := lnwallet.StaticFeeEstimator{FeeRate: 250} // Create, and start the lnwallet, which handles the core payment // channel logic, and exposes control via proxy state machines. wallet, err := lnwallet.NewLightningWallet(chanDB, notifier, wc, signer, - bio, activeNetParams.Params) + bio, estimator, activeNetParams.Params) if err != nil { fmt.Printf("unable to create wallet: %v\n", err) return err @@ -191,7 +192,7 @@ func lndMain() error { // With all the relevant chains initialized, we can finally start the // server itself. server, err := newServer(defaultListenAddrs, notifier, bio, - fundingSigner, wallet, chanDB, chainView) + fundingSigner, wallet, estimator, chanDB, chainView) if err != nil { srvrLog.Errorf("unable to create server: %v\n", err) return err diff --git a/lnwallet/channel.go b/lnwallet/channel.go index b0a2e5583..ad433a249 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -571,6 +571,10 @@ type LightningChannel struct { status channelState + // feeEstimator is used to calculate the fee rate for the channel's + // commitment and cooperative close transactions. + feeEstimator FeeEstimator + // Capcity is the total capacity of this channel. Capacity btcutil.Amount @@ -679,11 +683,13 @@ type LightningChannel struct { // automatically persist pertinent state to the database in an efficient // manner. func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier, - state *channeldb.OpenChannel) (*LightningChannel, error) { + fe FeeEstimator, state *channeldb.OpenChannel) (*LightningChannel, + error) { lc := &LightningChannel{ signer: signer, channelEvents: events, + feeEstimator: fe, currentHeight: state.NumUpdates, remoteCommitChain: newCommitmentChain(state.NumUpdates), localCommitChain: newCommitmentChain(state.NumUpdates), diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 84fbb1d5f..1a5d0c6ee 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -306,12 +306,15 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan bobSigner := &mockSigner{bobKeyPriv} notifier := &mockNotfier{} + estimator := &StaticFeeEstimator{50, 6} - channelAlice, err := NewLightningChannel(aliceSigner, notifier, aliceChannelState) + channelAlice, err := NewLightningChannel(aliceSigner, notifier, + estimator, aliceChannelState) if err != nil { return nil, nil, nil, err } - channelBob, err := NewLightningChannel(bobSigner, notifier, bobChannelState) + channelBob, err := NewLightningChannel(bobSigner, notifier, + estimator, bobChannelState) if err != nil { return nil, nil, nil, err } @@ -1362,12 +1365,12 @@ func TestStateUpdatePersistence(t *testing.T) { } notifier := aliceChannel.channelEvents aliceChannelNew, err := NewLightningChannel(aliceChannel.signer, - notifier, aliceChannels[0]) + notifier, aliceChannel.feeEstimator, aliceChannels[0]) if err != nil { t.Fatalf("unable to create new channel: %v", err) } bobChannelNew, err := NewLightningChannel(bobChannel.signer, notifier, - bobChannels[0]) + bobChannel.feeEstimator, bobChannels[0]) if err != nil { t.Fatalf("unable to create new channel: %v", err) } diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 3579cbadd..6159a02c0 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -302,6 +302,26 @@ type MessageSigner interface { SignMessage(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error) } +// FeeEstimator provides the ability to estimate on-chain transaction fees for +// various combinations of transaction sizes and desired confirmation time +// (measured by number of blocks). +type FeeEstimator interface { + // EstimateFeePerByte takes in a target for the number of blocks until + // an initial confirmation and returns the estimated fee expressed in + // satoshis/byte. + EstimateFeePerByte(numBlocks uint32) uint64 + + // EstimateFeePerWeight takes in a target for the number of blocks until + // an initial confirmation and returns the estimated fee expressed in + // satoshis/weight. + EstimateFeePerWeight(numBlocks uint32) uint64 + + // EstimateConfirmation will return the number of blocks expected for a + // transaction to be confirmed given a fee rate in satoshis per + // byte. + EstimateConfirmation(satPerByte int64) uint32 +} + // WalletDriver represents a "driver" for a particular concrete // WalletController implementation. A driver is identified by a globally unique // string identifier along with a 'New()' method which is responsible for diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index 63e1ec5d1..f6bfe7eb2 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -339,8 +339,9 @@ func createTestWallet(tempTestDir string, miningNode *rpctest.Harness, return nil, err } + estimator := lnwallet.StaticFeeEstimator{FeeRate: 250} wallet, err := lnwallet.NewLightningWallet(cdb, notifier, wc, signer, - bio, netParams) + bio, estimator, netParams) if err != nil { return nil, err } diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 15cd37900..e415e6de2 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -273,6 +273,10 @@ type LightningWallet struct { // update the commitment state. Signer Signer + // FeeEstimator is the implementation that the wallet will use for the + // calculation of on-chain transaction fees. + FeeEstimator FeeEstimator + // ChainIO is an instance of the BlockChainIO interface. ChainIO is // used to lookup the existence of outputs within the UTXO set. ChainIO BlockChainIO @@ -320,12 +324,13 @@ type LightningWallet struct { // initialized/started before being passed as a function arugment. func NewLightningWallet(cdb *channeldb.DB, notifier chainntnfs.ChainNotifier, wallet WalletController, signer Signer, bio BlockChainIO, - netParams *chaincfg.Params) (*LightningWallet, error) { + fe FeeEstimator, netParams *chaincfg.Params) (*LightningWallet, error) { return &LightningWallet{ chainNotifier: notifier, Signer: signer, WalletController: wallet, + FeeEstimator: fe, ChainIO: bio, ChannelDB: cdb, msgChan: make(chan interface{}, msgBufferSize), @@ -490,6 +495,7 @@ func (l *LightningWallet) InitChannelReservation(capacity, errChan := make(chan error, 1) respChan := make(chan *ChannelReservation, 1) + minFeeRate := btcutil.Amount(l.FeeEstimator.EstimateFeePerWeight(1)) l.msgChan <- &initFundingReserveMsg{ capacity: capacity, @@ -497,6 +503,7 @@ func (l *LightningWallet) InitChannelReservation(capacity, fundingAmount: ourFundAmt, csvDelay: csvDelay, ourDustLimit: ourDustLimit, + minFeeRate: minFeeRate, pushSat: pushSat, nodeID: theirID, nodeAddr: theirAddr, @@ -1434,3 +1441,28 @@ func coinSelect(feeRate uint64, amt btcutil.Amount, return selectedUtxos, changeAmt, nil } } + +// StaticFeeEstimator will return a static value for all fee calculation +// requests. It is designed to be replaced by a proper fee calcuation +// implementation. +type StaticFeeEstimator struct { + FeeRate uint64 + Confirmation uint32 +} + +// EstimateFeePerByte will return a static value for fee calculations. +func (e StaticFeeEstimator) EstimateFeePerByte(numBlocks uint32) uint64 { + return e.FeeRate +} + +// EstimateFeePerWeight will return a static value for fee calculations. +func (e StaticFeeEstimator) EstimateFeePerWeight(numBlocks uint32) uint64 { + return e.FeeRate * 4 +} + +// EstimateConfirmation will return a static value representing the estimated +// number of blocks that will be required to confirm a transaction for the +// given fee rate. +func (e StaticFeeEstimator) EstimateConfirmation(satPerByte int64) uint32 { + return e.Confirmation +} diff --git a/peer.go b/peer.go index 912de7719..74a5fe540 100644 --- a/peer.go +++ b/peer.go @@ -288,7 +288,7 @@ func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error { } lnChan, err := lnwallet.NewLightningChannel(p.server.lnwallet.Signer, - p.server.chainNotifier, dbChan) + p.server.chainNotifier, p.server.feeEstimator, dbChan) if err != nil { return err } diff --git a/rpcserver.go b/rpcserver.go index 3a9ff20fe..a245e8786 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -688,7 +688,7 @@ func (r *rpcServer) fetchActiveChannel(chanPoint wire.OutPoint) (*lnwallet.Light // Otherwise, we create a fully populated channel state machine which // uses the db channel as backing storage. return lnwallet.NewLightningChannel(r.server.lnwallet.Signer, nil, - dbChan) + lnwallet.StaticFeeEstimator{FeeRate: 250}, dbChan) } // forceCloseChan executes a unilateral close of the target channel by diff --git a/server.go b/server.go index 0753fe626..24698ed93 100644 --- a/server.go +++ b/server.go @@ -62,8 +62,9 @@ type server struct { chainNotifier chainntnfs.ChainNotifier - bio lnwallet.BlockChainIO - lnwallet *lnwallet.LightningWallet + bio lnwallet.BlockChainIO + lnwallet *lnwallet.LightningWallet + feeEstimator lnwallet.FeeEstimator fundingMgr *fundingManager chanDB *channeldb.DB @@ -108,8 +109,8 @@ type server struct { // passed listener address. func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, bio lnwallet.BlockChainIO, fundingSigner lnwallet.MessageSigner, - wallet *lnwallet.LightningWallet, chanDB *channeldb.DB, - chainView chainview.FilteredChainView) (*server, error) { + wallet *lnwallet.LightningWallet, estimator lnwallet.FeeEstimator, + chanDB *channeldb.DB, chainView chainview.FilteredChainView) (*server, error) { privKey, err := wallet.GetIdentitykey() if err != nil { @@ -131,6 +132,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, bio: bio, chainNotifier: notifier, chanDB: chanDB, + feeEstimator: estimator, invoices: newInvoiceRegistry(chanDB), utxoNursery: newUtxoNursery(chanDB, notifier, wallet), @@ -264,7 +266,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, s.rpcServer = newRPCServer(s) s.breachArbiter = newBreachArbiter(wallet, chanDB, notifier, - s.htlcSwitch, s.bio) + s.htlcSwitch, s.bio, s.feeEstimator) var chanIDSeed [32]byte if _, err := rand.Read(chanIDSeed[:]); err != nil { @@ -272,10 +274,11 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, } s.fundingMgr, err = newFundingManager(fundingConfig{ - IDKey: s.identityPriv.PubKey(), - Wallet: wallet, - ChainIO: s.bio, - Notifier: s.chainNotifier, + IDKey: s.identityPriv.PubKey(), + Wallet: wallet, + ChainIO: s.bio, + Notifier: s.chainNotifier, + FeeEstimator: s.feeEstimator, SignMessage: func(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error) { if pubKey.IsEqual(s.identityPriv.PubKey()) { return s.nodeSigner.SignMessage(pubKey, msg) @@ -300,8 +303,10 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, for _, channel := range dbChannels { if chanID.IsChanPoint(channel.ChanID) { - return lnwallet.NewLightningChannel(wallet.Signer, - notifier, channel) + return lnwallet.NewLightningChannel( + wallet.Signer, notifier, + lnwallet.StaticFeeEstimator{FeeRate: 250}, + channel) } }