diff --git a/breacharbiter.go b/breacharbiter.go index 4c444710..1f93c6fe 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 5cfd233c..1042a936 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 90da7f6b..ef56993a 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 b0a2e558..ad433a24 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 84fbb1d5..1a5d0c6e 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 3579cbad..6159a02c 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 63e1ec5d..f6bfe7eb 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 15cd3790..e415e6de 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 912de771..74a5fe54 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 3a9ff20f..a245e878 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 0753fe62..24698ed9 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) } }