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.
This commit is contained in:
bryanvu 2017-05-01 00:27:12 -07:00 committed by Olaoluwa Osuntokun
parent 320bed7e6b
commit abe2e502d5
11 changed files with 100 additions and 25 deletions

View File

@ -28,6 +28,7 @@ type breachArbiter struct {
notifier chainntnfs.ChainNotifier notifier chainntnfs.ChainNotifier
chainIO lnwallet.BlockChainIO chainIO lnwallet.BlockChainIO
htlcSwitch *htlcSwitch htlcSwitch *htlcSwitch
estimator lnwallet.FeeEstimator
// breachObservers is a map which tracks all the active breach // breachObservers is a map which tracks all the active breach
// observers we're currently managing. The key of the map is the // observers we're currently managing. The key of the map is the
@ -64,7 +65,7 @@ type breachArbiter struct {
// its dependent objects. // its dependent objects.
func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB, func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB,
notifier chainntnfs.ChainNotifier, h *htlcSwitch, notifier chainntnfs.ChainNotifier, h *htlcSwitch,
chain lnwallet.BlockChainIO) *breachArbiter { chain lnwallet.BlockChainIO, fe lnwallet.FeeEstimator) *breachArbiter {
return &breachArbiter{ return &breachArbiter{
wallet: wallet, wallet: wallet,
@ -72,6 +73,7 @@ func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB,
notifier: notifier, notifier: notifier,
chainIO: chain, chainIO: chain,
htlcSwitch: h, htlcSwitch: h,
estimator: fe,
breachObservers: make(map[wire.OutPoint]chan struct{}), breachObservers: make(map[wire.OutPoint]chan struct{}),
breachedContracts: make(chan *retributionInfo), breachedContracts: make(chan *retributionInfo),
@ -110,7 +112,7 @@ func (b *breachArbiter) Start() error {
len(activeChannels)) len(activeChannels))
for i, chanState := range activeChannels { for i, chanState := range activeChannels {
channel, err := lnwallet.NewLightningChannel(nil, b.notifier, channel, err := lnwallet.NewLightningChannel(nil, b.notifier,
chanState) b.estimator, chanState)
if err != nil { if err != nil {
brarLog.Errorf("unable to load channel from "+ brarLog.Errorf("unable to load channel from "+
"disk: %v", err) "disk: %v", err)

View File

@ -130,6 +130,10 @@ type fundingConfig struct {
// funds from on-chain transaction outputs into Lightning channels. // funds from on-chain transaction outputs into Lightning channels.
Wallet *lnwallet.LightningWallet Wallet *lnwallet.LightningWallet
// FeeEstimator calculates appropriate fee rates based on historical
// transaction information.
FeeEstimator lnwallet.FeeEstimator
// ArbiterChan allows the FundingManager to notify the BreachArbiter // ArbiterChan allows the FundingManager to notify the BreachArbiter
// that a new channel has been created that should be observed to // that a new channel has been created that should be observed to
// ensure that the channel counterparty hasn't broadcasted an invalid // 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 // With the channel marked open, we'll create the state-machine object
// which wraps the database state. // 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 { if err != nil {
fndgLog.Errorf("error creating new lightning channel: %v", err) fndgLog.Errorf("error creating new lightning channel: %v", err)
return return

5
lnd.go
View File

@ -149,11 +149,12 @@ func lndMain() error {
signer := wc signer := wc
bio := wc bio := wc
fundingSigner := wc fundingSigner := wc
estimator := lnwallet.StaticFeeEstimator{FeeRate: 250}
// Create, and start the lnwallet, which handles the core payment // Create, and start the lnwallet, which handles the core payment
// channel logic, and exposes control via proxy state machines. // channel logic, and exposes control via proxy state machines.
wallet, err := lnwallet.NewLightningWallet(chanDB, notifier, wc, signer, wallet, err := lnwallet.NewLightningWallet(chanDB, notifier, wc, signer,
bio, activeNetParams.Params) bio, estimator, activeNetParams.Params)
if err != nil { if err != nil {
fmt.Printf("unable to create wallet: %v\n", err) fmt.Printf("unable to create wallet: %v\n", err)
return err return err
@ -191,7 +192,7 @@ func lndMain() error {
// With all the relevant chains initialized, we can finally start the // With all the relevant chains initialized, we can finally start the
// server itself. // server itself.
server, err := newServer(defaultListenAddrs, notifier, bio, server, err := newServer(defaultListenAddrs, notifier, bio,
fundingSigner, wallet, chanDB, chainView) fundingSigner, wallet, estimator, chanDB, chainView)
if err != nil { if err != nil {
srvrLog.Errorf("unable to create server: %v\n", err) srvrLog.Errorf("unable to create server: %v\n", err)
return err return err

View File

@ -571,6 +571,10 @@ type LightningChannel struct {
status channelState 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. // Capcity is the total capacity of this channel.
Capacity btcutil.Amount Capacity btcutil.Amount
@ -679,11 +683,13 @@ type LightningChannel struct {
// automatically persist pertinent state to the database in an efficient // automatically persist pertinent state to the database in an efficient
// manner. // manner.
func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier, func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier,
state *channeldb.OpenChannel) (*LightningChannel, error) { fe FeeEstimator, state *channeldb.OpenChannel) (*LightningChannel,
error) {
lc := &LightningChannel{ lc := &LightningChannel{
signer: signer, signer: signer,
channelEvents: events, channelEvents: events,
feeEstimator: fe,
currentHeight: state.NumUpdates, currentHeight: state.NumUpdates,
remoteCommitChain: newCommitmentChain(state.NumUpdates), remoteCommitChain: newCommitmentChain(state.NumUpdates),
localCommitChain: newCommitmentChain(state.NumUpdates), localCommitChain: newCommitmentChain(state.NumUpdates),

View File

@ -306,12 +306,15 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
bobSigner := &mockSigner{bobKeyPriv} bobSigner := &mockSigner{bobKeyPriv}
notifier := &mockNotfier{} notifier := &mockNotfier{}
estimator := &StaticFeeEstimator{50, 6}
channelAlice, err := NewLightningChannel(aliceSigner, notifier, aliceChannelState) channelAlice, err := NewLightningChannel(aliceSigner, notifier,
estimator, aliceChannelState)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
channelBob, err := NewLightningChannel(bobSigner, notifier, bobChannelState) channelBob, err := NewLightningChannel(bobSigner, notifier,
estimator, bobChannelState)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@ -1362,12 +1365,12 @@ func TestStateUpdatePersistence(t *testing.T) {
} }
notifier := aliceChannel.channelEvents notifier := aliceChannel.channelEvents
aliceChannelNew, err := NewLightningChannel(aliceChannel.signer, aliceChannelNew, err := NewLightningChannel(aliceChannel.signer,
notifier, aliceChannels[0]) notifier, aliceChannel.feeEstimator, 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, notifier, bobChannelNew, err := NewLightningChannel(bobChannel.signer, notifier,
bobChannels[0]) bobChannel.feeEstimator, 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)
} }

View File

@ -302,6 +302,26 @@ type MessageSigner interface {
SignMessage(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error) 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 // WalletDriver represents a "driver" for a particular concrete
// WalletController implementation. A driver is identified by a globally unique // WalletController implementation. A driver is identified by a globally unique
// string identifier along with a 'New()' method which is responsible for // string identifier along with a 'New()' method which is responsible for

View File

@ -339,8 +339,9 @@ func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
return nil, err return nil, err
} }
estimator := lnwallet.StaticFeeEstimator{FeeRate: 250}
wallet, err := lnwallet.NewLightningWallet(cdb, notifier, wc, signer, wallet, err := lnwallet.NewLightningWallet(cdb, notifier, wc, signer,
bio, netParams) bio, estimator, netParams)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -273,6 +273,10 @@ type LightningWallet struct {
// update the commitment state. // update the commitment state.
Signer Signer 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 // ChainIO is an instance of the BlockChainIO interface. ChainIO is
// used to lookup the existence of outputs within the UTXO set. // used to lookup the existence of outputs within the UTXO set.
ChainIO BlockChainIO ChainIO BlockChainIO
@ -320,12 +324,13 @@ type LightningWallet struct {
// initialized/started before being passed as a function arugment. // initialized/started before being passed as a function arugment.
func NewLightningWallet(cdb *channeldb.DB, notifier chainntnfs.ChainNotifier, func NewLightningWallet(cdb *channeldb.DB, notifier chainntnfs.ChainNotifier,
wallet WalletController, signer Signer, bio BlockChainIO, wallet WalletController, signer Signer, bio BlockChainIO,
netParams *chaincfg.Params) (*LightningWallet, error) { fe FeeEstimator, netParams *chaincfg.Params) (*LightningWallet, error) {
return &LightningWallet{ return &LightningWallet{
chainNotifier: notifier, chainNotifier: notifier,
Signer: signer, Signer: signer,
WalletController: wallet, WalletController: wallet,
FeeEstimator: fe,
ChainIO: bio, ChainIO: bio,
ChannelDB: cdb, ChannelDB: cdb,
msgChan: make(chan interface{}, msgBufferSize), msgChan: make(chan interface{}, msgBufferSize),
@ -490,6 +495,7 @@ func (l *LightningWallet) InitChannelReservation(capacity,
errChan := make(chan error, 1) errChan := make(chan error, 1)
respChan := make(chan *ChannelReservation, 1) respChan := make(chan *ChannelReservation, 1)
minFeeRate := btcutil.Amount(l.FeeEstimator.EstimateFeePerWeight(1))
l.msgChan <- &initFundingReserveMsg{ l.msgChan <- &initFundingReserveMsg{
capacity: capacity, capacity: capacity,
@ -497,6 +503,7 @@ func (l *LightningWallet) InitChannelReservation(capacity,
fundingAmount: ourFundAmt, fundingAmount: ourFundAmt,
csvDelay: csvDelay, csvDelay: csvDelay,
ourDustLimit: ourDustLimit, ourDustLimit: ourDustLimit,
minFeeRate: minFeeRate,
pushSat: pushSat, pushSat: pushSat,
nodeID: theirID, nodeID: theirID,
nodeAddr: theirAddr, nodeAddr: theirAddr,
@ -1434,3 +1441,28 @@ func coinSelect(feeRate uint64, amt btcutil.Amount,
return selectedUtxos, changeAmt, nil 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
}

View File

@ -288,7 +288,7 @@ func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error {
} }
lnChan, err := lnwallet.NewLightningChannel(p.server.lnwallet.Signer, lnChan, err := lnwallet.NewLightningChannel(p.server.lnwallet.Signer,
p.server.chainNotifier, dbChan) p.server.chainNotifier, p.server.feeEstimator, dbChan)
if err != nil { if err != nil {
return err return err
} }

View File

@ -688,7 +688,7 @@ func (r *rpcServer) fetchActiveChannel(chanPoint wire.OutPoint) (*lnwallet.Light
// Otherwise, we create a fully populated channel state machine which // Otherwise, we create a fully populated channel state machine which
// uses the db channel as backing storage. // uses the db channel as backing storage.
return lnwallet.NewLightningChannel(r.server.lnwallet.Signer, nil, return lnwallet.NewLightningChannel(r.server.lnwallet.Signer, nil,
dbChan) lnwallet.StaticFeeEstimator{FeeRate: 250}, dbChan)
} }
// forceCloseChan executes a unilateral close of the target channel by // forceCloseChan executes a unilateral close of the target channel by

View File

@ -64,6 +64,7 @@ type server struct {
bio lnwallet.BlockChainIO bio lnwallet.BlockChainIO
lnwallet *lnwallet.LightningWallet lnwallet *lnwallet.LightningWallet
feeEstimator lnwallet.FeeEstimator
fundingMgr *fundingManager fundingMgr *fundingManager
chanDB *channeldb.DB chanDB *channeldb.DB
@ -108,8 +109,8 @@ type server struct {
// passed listener address. // passed listener address.
func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
bio lnwallet.BlockChainIO, fundingSigner lnwallet.MessageSigner, bio lnwallet.BlockChainIO, fundingSigner lnwallet.MessageSigner,
wallet *lnwallet.LightningWallet, chanDB *channeldb.DB, wallet *lnwallet.LightningWallet, estimator lnwallet.FeeEstimator,
chainView chainview.FilteredChainView) (*server, error) { chanDB *channeldb.DB, chainView chainview.FilteredChainView) (*server, error) {
privKey, err := wallet.GetIdentitykey() privKey, err := wallet.GetIdentitykey()
if err != nil { if err != nil {
@ -131,6 +132,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
bio: bio, bio: bio,
chainNotifier: notifier, chainNotifier: notifier,
chanDB: chanDB, chanDB: chanDB,
feeEstimator: estimator,
invoices: newInvoiceRegistry(chanDB), invoices: newInvoiceRegistry(chanDB),
utxoNursery: newUtxoNursery(chanDB, notifier, wallet), utxoNursery: newUtxoNursery(chanDB, notifier, wallet),
@ -264,7 +266,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
s.rpcServer = newRPCServer(s) s.rpcServer = newRPCServer(s)
s.breachArbiter = newBreachArbiter(wallet, chanDB, notifier, s.breachArbiter = newBreachArbiter(wallet, chanDB, notifier,
s.htlcSwitch, s.bio) s.htlcSwitch, s.bio, s.feeEstimator)
var chanIDSeed [32]byte var chanIDSeed [32]byte
if _, err := rand.Read(chanIDSeed[:]); err != nil { if _, err := rand.Read(chanIDSeed[:]); err != nil {
@ -276,6 +278,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
Wallet: wallet, Wallet: wallet,
ChainIO: s.bio, ChainIO: s.bio,
Notifier: s.chainNotifier, Notifier: s.chainNotifier,
FeeEstimator: s.feeEstimator,
SignMessage: func(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error) { SignMessage: func(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error) {
if pubKey.IsEqual(s.identityPriv.PubKey()) { if pubKey.IsEqual(s.identityPriv.PubKey()) {
return s.nodeSigner.SignMessage(pubKey, msg) return s.nodeSigner.SignMessage(pubKey, msg)
@ -300,8 +303,10 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
for _, channel := range dbChannels { for _, channel := range dbChannels {
if chanID.IsChanPoint(channel.ChanID) { if chanID.IsChanPoint(channel.ChanID) {
return lnwallet.NewLightningChannel(wallet.Signer, return lnwallet.NewLightningChannel(
notifier, channel) wallet.Signer, notifier,
lnwallet.StaticFeeEstimator{FeeRate: 250},
channel)
} }
} }