From b3d5d7ab9cf7249263433c8c2d92d3a1eb64f647 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Jul 2018 18:37:05 -0700 Subject: [PATCH 1/4] lnwallet: switch fee estimation to return sat/kw fee rates In this commit, we modify our FeeEstimator interface to return an estimated fee rate in sat/kw. Recently, due to low fees on the network, users have been experiencing failures broadcasting transactions due to not meeting specific fee requirements. This was happening more often than not, as the estimated fee returned by backend nodes (bitcoind and btcd) only takes into account vbytes, rather than weight. The fees returned are also expressed in sat/kb, so we must take care that we do not lose precision while converting to sat/kw. In the event that this happens, a fee floor of 253 sat/kw has been added. This fee rate originates from bitcoind rounding up the conversion from weight to vbytes. --- lnwallet/fee_estimator.go | 230 ++++++++++++++++++--------------- lnwallet/fee_estimator_test.go | 95 +++++++------- 2 files changed, 175 insertions(+), 150 deletions(-) diff --git a/lnwallet/fee_estimator.go b/lnwallet/fee_estimator.go index 078e6db71..c0574c5dd 100644 --- a/lnwallet/fee_estimator.go +++ b/lnwallet/fee_estimator.go @@ -8,38 +8,49 @@ import ( "github.com/btcsuite/btcutil" ) -// SatPerVByte represents a fee rate in satoshis per vbyte. -type SatPerVByte btcutil.Amount +const ( + // FeePerKwFloor is the lowest fee rate in sat/kw that we should use for + // determining transaction fees. + FeePerKwFloor SatPerKWeight = 253 +) -// FeeForVSize calculates the fee resulting from this fee rate and -// the given vsize in vbytes. -func (s SatPerVByte) FeeForVSize(vbytes int64) btcutil.Amount { - return btcutil.Amount(s) * btcutil.Amount(vbytes) +// SatPerKVByte represents a fee rate in sat/kb. +type SatPerKVByte btcutil.Amount + +// FeeForVSize calculates the fee resulting from this fee rate and the given +// vsize in vbytes. +func (s SatPerKVByte) FeeForVSize(vbytes int64) btcutil.Amount { + return btcutil.Amount(s) * btcutil.Amount(vbytes) / 1000 } -// FeePerKWeight converts the fee rate into SatPerKWeight. -func (s SatPerVByte) FeePerKWeight() SatPerKWeight { - return SatPerKWeight(s * 1000 / blockchain.WitnessScaleFactor) +// FeePerKWeight converts the current fee rate from sat/kb to sat/kw. +func (s SatPerKVByte) FeePerKWeight() SatPerKWeight { + return SatPerKWeight(s / blockchain.WitnessScaleFactor) } -// SatPerKWeight represents a fee rate in satoshis per kilo weight unit. +// SatPerKWeight represents a fee rate in sat/kw. type SatPerKWeight btcutil.Amount -// FeeForWeight calculates the fee resulting from this fee rate and the -// given weight in weight units (wu). +// FeeForWeight calculates the fee resulting from this fee rate and the given +// weight in weight units (wu). func (s SatPerKWeight) FeeForWeight(wu int64) btcutil.Amount { // The resulting fee is rounded down, as specified in BOLT#03. return btcutil.Amount(s) * btcutil.Amount(wu) / 1000 } +// FeePerKVByte converts the current fee rate from sat/kw to sat/kb. +func (s SatPerKWeight) FeePerKVByte() SatPerKVByte { + return SatPerKVByte(s * blockchain.WitnessScaleFactor) +} + // 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 { - // EstimateFeePerVSize takes in a target for the number of blocks until - // an initial confirmation and returns the estimated fee expressed in - // satoshis/vbyte. - EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) + // EstimateFeePerKW takes in a target for the number of blocks until an + // initial confirmation and returns the estimated fee expressed in + // sat/kw. + EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) // Start signals the FeeEstimator to start any processes or goroutines // it needs to perform its duty. @@ -54,16 +65,16 @@ type FeeEstimator interface { // requests. It is designed to be replaced by a proper fee calculation // implementation. type StaticFeeEstimator struct { - // FeeRate is the static fee rate in satoshis-per-vbyte that will be + // FeePerKW is the static fee rate in satoshis-per-vbyte that will be // returned by this fee estimator. - FeeRate SatPerVByte + FeePerKW SatPerKWeight } -// EstimateFeePerVSize will return a static value for fee calculations. +// EstimateFeePerKW will return a static value for fee calculations. // // NOTE: This method is part of the FeeEstimator interface. -func (e StaticFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { - return e.FeeRate, nil +func (e StaticFeeEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) { + return e.FeePerKW, nil } // Start signals the FeeEstimator to start any processes or goroutines @@ -90,16 +101,16 @@ var _ FeeEstimator = (*StaticFeeEstimator)(nil) // by the RPC interface of an active btcd node. This implementation will proxy // any fee estimation requests to btcd's RPC interface. type BtcdFeeEstimator struct { - // fallBackFeeRate is the fall back fee rate in satoshis per vbyte that - // is returned if the fee estimator does not yet have enough data to - // actually produce fee estimates. - fallBackFeeRate SatPerVByte + // fallbackFeePerKW is the fall back fee rate in sat/kw that is returned + // if the fee estimator does not yet have enough data to actually + // produce fee estimates. + fallbackFeePerKW SatPerKWeight - // minFeeRate is the minimum relay fee, in sat/vbyte, of the backend - // node. This will be used as the default fee rate of a transaction when - // the estimated fee rate is too low to allow the transaction to - // propagate through the network. - minFeeRate SatPerVByte + // minFeePerKW is the minimum fee, in sat/kw, that we should enforce. + // This will be used as the default fee rate for a transaction when the + // estimated fee rate is too low to allow the transaction to propagate + // through the network. + minFeePerKW SatPerKWeight btcdConn *rpcclient.Client } @@ -110,7 +121,7 @@ type BtcdFeeEstimator struct { // the occasion that the estimator has insufficient data, or returns zero for a // fee estimate. func NewBtcdFeeEstimator(rpcConfig rpcclient.ConnConfig, - fallBackFeeRate SatPerVByte) (*BtcdFeeEstimator, error) { + fallBackFeeRate SatPerKWeight) (*BtcdFeeEstimator, error) { rpcConfig.DisableConnectOnNew = true rpcConfig.DisableAutoReconnect = false @@ -120,8 +131,8 @@ func NewBtcdFeeEstimator(rpcConfig rpcclient.ConnConfig, } return &BtcdFeeEstimator{ - fallBackFeeRate: fallBackFeeRate, - btcdConn: chainConn, + fallbackFeePerKW: fallBackFeeRate, + btcdConn: chainConn, }, nil } @@ -146,9 +157,20 @@ func (b *BtcdFeeEstimator) Start() error { return err } - // The fee rate is expressed in sat/KB, so we'll manually convert it to - // our desired sat/vbyte rate. - b.minFeeRate = SatPerVByte(relayFee / 1000) + // The fee rate is expressed in sat/kb, so we'll manually convert it to + // our desired sat/kw rate. + minRelayFeePerKw := SatPerKVByte(relayFee).FeePerKWeight() + + // By default, we'll use the backend node's minimum relay fee as the + // minimum fee rate we'll propose for transacations. However, if this + // happens to be lower than our fee floor, we'll enforce that instead. + b.minFeePerKW = minRelayFeePerKw + if b.minFeePerKW < FeePerKwFloor { + b.minFeePerKW = FeePerKwFloor + } + + walletLog.Debugf("Using minimum fee rate of %v sat/kw", + int64(b.minFeePerKW)) return nil } @@ -163,13 +185,12 @@ func (b *BtcdFeeEstimator) Stop() error { return nil } -// EstimateFeePerVSize takes in a target for the number of blocks until an -// initial confirmation and returns the estimated fee expressed in -// satoshis/vbyte. +// EstimateFeePerKW takes in a target for the number of blocks until an initial +// confirmation and returns the estimated fee expressed in sat/kw. // // NOTE: This method is part of the FeeEstimator interface. -func (b *BtcdFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { - feeEstimate, err := b.fetchEstimatePerVSize(numBlocks) +func (b *BtcdFeeEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) { + feeEstimate, err := b.fetchEstimate(numBlocks) switch { // If the estimator doesn't have enough data, or returns an error, then // to return a proper value, then we'll return the default fall back @@ -179,16 +200,15 @@ func (b *BtcdFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, e fallthrough case feeEstimate == 0: - return b.fallBackFeeRate, nil + return b.fallbackFeePerKW, nil } return feeEstimate, nil } // fetchEstimate returns a fee estimate for a transaction to be confirmed in -// confTarget blocks. The estimate is returned in sat/vbyte. -func (b *BtcdFeeEstimator) fetchEstimatePerVSize( - confTarget uint32) (SatPerVByte, error) { +// confTarget blocks. The estimate is returned in sat/kw. +func (b *BtcdFeeEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, error) { // First, we'll fetch the estimate for our confirmation target. btcPerKB, err := b.btcdConn.EstimateFee(int64(confTarget)) if err != nil { @@ -202,23 +222,21 @@ func (b *BtcdFeeEstimator) fetchEstimatePerVSize( return 0, err } - // The value returned is expressed in fees per KB, while we want - // fee-per-byte, so we'll divide by 1000 to map to satoshis-per-byte - // before returning the estimate. - satPerByte := SatPerVByte(satPerKB / 1000) + // Since we use fee rates in sat/kw internally, we'll convert the + // estimated fee rate from its sat/kb representation to sat/kw. + satPerKw := SatPerKVByte(satPerKB).FeePerKWeight() - // Before proceeding, we'll make sure that this fee rate respects the - // minimum relay fee set on the backend node. - if satPerByte < b.minFeeRate { - walletLog.Debugf("Using backend node's minimum relay fee rate "+ - "of %v sat/vbyte", b.minFeeRate) - satPerByte = b.minFeeRate + // Finally, we'll enforce our fee floor. + if satPerKw < b.minFeePerKW { + walletLog.Debugf("Estimated fee rate of %v sat/kw is too low, "+ + "using fee floor of %v sat/kw instead", b.minFeePerKW) + satPerKw = b.minFeePerKW } - walletLog.Debugf("Returning %v sat/vbyte for conf target of %v", - int64(satPerByte), confTarget) + walletLog.Debugf("Returning %v sat/kw for conf target of %v", + int64(satPerKw), confTarget) - return satPerByte, nil + return satPerKw, nil } // A compile-time assertion to ensure that BtcdFeeEstimator implements the @@ -229,16 +247,16 @@ var _ FeeEstimator = (*BtcdFeeEstimator)(nil) // backed by the RPC interface of an active bitcoind node. This implementation // will proxy any fee estimation requests to bitcoind's RPC interface. type BitcoindFeeEstimator struct { - // fallBackFeeRate is the fall back fee rate in satoshis per vbyte that - // is returned if the fee estimator does not yet have enough data to - // actually produce fee estimates. - fallBackFeeRate SatPerVByte + // fallbackFeePerKW is the fallback fee rate in sat/kw that is returned + // if the fee estimator does not yet have enough data to actually + // produce fee estimates. + fallbackFeePerKW SatPerKWeight - // minFeeRate is the minimum relay fee, in sat/vbyte, of the backend - // node. This will be used as the default fee rate of a transaction when - // the estimated fee rate is too low to allow the transaction to - // propagate through the network. - minFeeRate SatPerVByte + // minFeePerKW is the minimum fee, in sat/kw, that we should enforce. + // This will be used as the default fee rate for a transaction when the + // estimated fee rate is too low to allow the transaction to propagate + // through the network. + minFeePerKW SatPerKWeight bitcoindConn *rpcclient.Client } @@ -249,7 +267,7 @@ type BitcoindFeeEstimator struct { // is used in the occasion that the estimator has insufficient data, or returns // zero for a fee estimate. func NewBitcoindFeeEstimator(rpcConfig rpcclient.ConnConfig, - fallBackFeeRate SatPerVByte) (*BitcoindFeeEstimator, error) { + fallBackFeeRate SatPerKWeight) (*BitcoindFeeEstimator, error) { rpcConfig.DisableConnectOnNew = true rpcConfig.DisableAutoReconnect = false @@ -261,8 +279,8 @@ func NewBitcoindFeeEstimator(rpcConfig rpcclient.ConnConfig, } return &BitcoindFeeEstimator{ - fallBackFeeRate: fallBackFeeRate, - bitcoindConn: chainConn, + fallbackFeePerKW: fallBackFeeRate, + bitcoindConn: chainConn, }, nil } @@ -293,9 +311,20 @@ func (b *BitcoindFeeEstimator) Start() error { return err } - // The fee rate is expressed in sat/KB, so we'll manually convert it to - // our desired sat/vbyte rate. - b.minFeeRate = SatPerVByte(relayFee / 1000) + // The fee rate is expressed in sat/kb, so we'll manually convert it to + // our desired sat/kw rate. + minRelayFeePerKw := SatPerKVByte(relayFee).FeePerKWeight() + + // By default, we'll use the backend node's minimum relay fee as the + // minimum fee rate we'll propose for transacations. However, if this + // happens to be lower than our fee floor, we'll enforce that instead. + b.minFeePerKW = minRelayFeePerKw + if b.minFeePerKW < FeePerKwFloor { + b.minFeePerKW = FeePerKwFloor + } + + walletLog.Debugf("Using minimum fee rate of %v sat/kw", + int64(b.minFeePerKW)) return nil } @@ -308,13 +337,12 @@ func (b *BitcoindFeeEstimator) Stop() error { return nil } -// EstimateFeePerVSize takes in a target for the number of blocks until an -// initial confirmation and returns the estimated fee expressed in -// satoshis/vbyte. +// EstimateFeePerKW takes in a target for the number of blocks until an initial +// confirmation and returns the estimated fee expressed in sat/kw. // // NOTE: This method is part of the FeeEstimator interface. -func (b *BitcoindFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByte, error) { - feeEstimate, err := b.fetchEstimatePerVSize(numBlocks) +func (b *BitcoindFeeEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) { + feeEstimate, err := b.fetchEstimate(numBlocks) switch { // If the estimator doesn't have enough data, or returns an error, then // to return a proper value, then we'll return the default fall back @@ -324,16 +352,15 @@ func (b *BitcoindFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (SatPerVByt fallthrough case feeEstimate == 0: - return b.fallBackFeeRate, nil + return b.fallbackFeePerKW, nil } return feeEstimate, nil } -// fetchEstimatePerVSize returns a fee estimate for a transaction to be confirmed in -// confTarget blocks. The estimate is returned in sat/vbyte. -func (b *BitcoindFeeEstimator) fetchEstimatePerVSize( - confTarget uint32) (SatPerVByte, error) { +// fetchEstimate returns a fee estimate for a transaction to be confirmed in +// confTarget blocks. The estimate is returned in sat/kw. +func (b *BitcoindFeeEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, error) { // First, we'll send an "estimatesmartfee" command as a raw request, // since it isn't supported by btcd but is available in bitcoind. target, err := json.Marshal(uint64(confTarget)) @@ -341,45 +368,44 @@ func (b *BitcoindFeeEstimator) fetchEstimatePerVSize( return 0, err } // TODO: Allow selection of economical/conservative modifiers. - resp, err := b.bitcoindConn.RawRequest("estimatesmartfee", - []json.RawMessage{target}) + resp, err := b.bitcoindConn.RawRequest( + "estimatesmartfee", []json.RawMessage{target}, + ) if err != nil { return 0, err } // Next, we'll parse the response to get the BTC per KB. feeEstimate := struct { - Feerate float64 `json:"feerate"` + FeeRate float64 `json:"feerate"` }{} err = json.Unmarshal(resp, &feeEstimate) if err != nil { return 0, err } - // Next, we'll convert the returned value to satoshis, as it's - // currently returned in BTC. - satPerKB, err := btcutil.NewAmount(feeEstimate.Feerate) + // Next, we'll convert the returned value to satoshis, as it's currently + // returned in BTC. + satPerKB, err := btcutil.NewAmount(feeEstimate.FeeRate) if err != nil { return 0, err } - // The value returned is expressed in fees per KB, while we want - // fee-per-byte, so we'll divide by 1000 to map to satoshis-per-byte - // before returning the estimate. - satPerByte := SatPerVByte(satPerKB / 1000) + // Since we use fee rates in sat/kw internally, we'll convert the + // estimated fee rate from its sat/kb representation to sat/kw. + satPerKw := SatPerKVByte(satPerKB).FeePerKWeight() - // Before proceeding, we'll make sure that this fee rate respects the - // minimum relay fee set on the backend node. - if satPerByte < b.minFeeRate { - walletLog.Debugf("Using backend node's minimum relay fee rate "+ - "of %v sat/vbyte", b.minFeeRate) - satPerByte = b.minFeeRate + // Finally, we'll enforce our fee floor. + if satPerKw < b.minFeePerKW { + walletLog.Debugf("Estimated fee rate of %v sat/kw is too low, "+ + "using fee floor of %v sat/kw instead", b.minFeePerKW) + satPerKw = b.minFeePerKW } - walletLog.Debugf("Returning %v sat/vbyte for conf target of %v", - int64(satPerByte), confTarget) + walletLog.Debugf("Returning %v sat/kw for conf target of %v", + int64(satPerKw), confTarget) - return satPerByte, nil + return satPerKw, nil } // A compile-time assertion to ensure that BitcoindFeeEstimator implements the diff --git a/lnwallet/fee_estimator_test.go b/lnwallet/fee_estimator_test.go index 0fa97eadb..d4608b9b2 100644 --- a/lnwallet/fee_estimator_test.go +++ b/lnwallet/fee_estimator_test.go @@ -13,57 +13,56 @@ import ( func TestFeeRateTypes(t *testing.T) { t.Parallel() - // Let our fee rate be 100 sat/vbyte. - feePerVSize := lnwallet.SatPerVByte(100) + // We'll be calculating the transaction fees for the given measurements + // using different fee rates and expecting them to match. + const vsize = 300 + const weight = vsize * 4 - // It is also equivalent to 25000 sat/kw. - feePerKw := feePerVSize.FeePerKWeight() - if feePerKw != 25000 { - t.Fatalf("expected %d sat/kw, got %d sat/kw", 25000, - feePerKw) - } - - const txVSize = 300 - - // We'll now run through a set of values for the fee per vsize type, - // making sure the conversion to sat/kw and fee calculation is done - // correctly. - for f := lnwallet.SatPerVByte(0); f <= 40; f++ { - fPerKw := f.FeePerKWeight() - - // The kw is always 250*vsize. - if fPerKw != lnwallet.SatPerKWeight(f*250) { - t.Fatalf("expected %d sat/kw, got %d sat/kw, when "+ - "converting %d sat/vbyte", f*250, fPerKw, f) + // Test the conversion from sat/kw to sat/kb. + for feePerKw := lnwallet.SatPerKWeight(250); feePerKw < 10000; feePerKw += 50 { + feePerKB := feePerKw.FeePerKVByte() + if feePerKB != lnwallet.SatPerKVByte(feePerKw*4) { + t.Fatalf("expected %d sat/kb, got %d sat/kb when "+ + "converting from %d sat/kw", feePerKw*4, + feePerKB, feePerKw) } - // The tx fee should simply be f*txvsize. - fee := f.FeeForVSize(txVSize) - if fee != btcutil.Amount(f*txVSize) { - t.Fatalf("expected tx fee to be %d sat, was %d sat", - f*txVSize, fee) + // The resulting transaction fee should be the same when using + // both rates. + expectedFee := btcutil.Amount(feePerKw * weight / 1000) + fee1 := feePerKw.FeeForWeight(weight) + if fee1 != expectedFee { + t.Fatalf("expected fee of %d sats, got %d sats", + expectedFee, fee1) } - - // The weight is 4*vsize. Fee calculation from the fee/kw - // should result in the same fee. - fee2 := fPerKw.FeeForWeight(txVSize * 4) - if fee != fee2 { - t.Fatalf("fee calculated from vsize (%d) not equal "+ - "fee calculated from weight (%d)", fee, fee2) + fee2 := feePerKB.FeeForVSize(vsize) + if fee2 != expectedFee { + t.Fatalf("expected fee of %d sats, got %d sats", + expectedFee, fee2) } } - // Do the same for fee per kw. - for f := lnwallet.SatPerKWeight(0); f < 1500; f++ { - weight := int64(txVSize * 4) + // Test the conversion from sat/kb to sat/kw. + for feePerKB := lnwallet.SatPerKVByte(1000); feePerKB < 40000; feePerKB += 1000 { + feePerKw := feePerKB.FeePerKWeight() + if feePerKw != lnwallet.SatPerKWeight(feePerKB/4) { + t.Fatalf("expected %d sat/kw, got %d sat/kw when "+ + "converting from %d sat/kb", feePerKB/4, + feePerKw, feePerKB) + } - // The expected fee is weight*f / 1000, since the fee is - // denominated per 1000 wu. - expFee := btcutil.Amount(weight) * btcutil.Amount(f) / 1000 - fee := f.FeeForWeight(weight) - if fee != expFee { - t.Fatalf("expected fee to be %d sat, was %d", - fee, expFee) + // The resulting transaction fee should be the same when using + // both rates. + expectedFee := btcutil.Amount(feePerKB * vsize / 1000) + fee1 := feePerKB.FeeForVSize(vsize) + if fee1 != expectedFee { + t.Fatalf("expected fee of %d sats, got %d sats", + expectedFee, fee1) + } + fee2 := feePerKw.FeeForWeight(weight) + if fee2 != expectedFee { + t.Fatalf("expected fee of %d sats, got %d sats", + expectedFee, fee2) } } } @@ -73,22 +72,22 @@ func TestFeeRateTypes(t *testing.T) { func TestStaticFeeEstimator(t *testing.T) { t.Parallel() - const feePerVSize = 100 + const feePerKw = lnwallet.FeePerKwFloor feeEstimator := &lnwallet.StaticFeeEstimator{ - FeeRate: feePerVSize, + FeePerKW: feePerKw, } if err := feeEstimator.Start(); err != nil { t.Fatalf("unable to start fee estimator: %v", err) } defer feeEstimator.Stop() - feeRate, err := feeEstimator.EstimateFeePerVSize(6) + feeRate, err := feeEstimator.EstimateFeePerKW(6) if err != nil { t.Fatalf("unable to get fee rate: %v", err) } - if feeRate != feePerVSize { - t.Fatalf("expected fee rate %v, got %v", feePerVSize, feeRate) + if feeRate != feePerKw { + t.Fatalf("expected fee rate %v, got %v", feePerKw, feeRate) } } From a63677a381450bc7c871c596cc1b6db0d7610e51 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Jul 2018 18:27:51 -0700 Subject: [PATCH 2/4] multi: switch to transaction weight to calculate transaction fees Due to a recent change within the codebase to return estimated fee rates in sat/kw, this commit ensures that we use this fee rate properly by calculing a transaction's fees using its weight. This includes all of the different transactions that are created within lnd (funding, sweeps, etc.). On-chain transactions still rely on a sat/vbyte fee rate since it's required by btcwallet. --- breacharbiter.go | 10 +++--- contractcourt/contract_resolvers.go | 28 ++++++++--------- lnwallet/wallet.go | 47 +++++++++++++++-------------- utxonursery.go | 10 +++--- 4 files changed, 48 insertions(+), 47 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index 0862acd82..a5ea1a5f9 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -1015,13 +1015,13 @@ func (b *breachArbiter) createJusticeTx( spendableOutputs = append(spendableOutputs, input) } - txVSize := int64(weightEstimate.VSize()) - return b.sweepSpendableOutputsTxn(txVSize, spendableOutputs...) + txWeight := int64(weightEstimate.Weight()) + return b.sweepSpendableOutputsTxn(txWeight, spendableOutputs...) } // sweepSpendableOutputsTxn creates a signed transaction from a sequence of // spendable outputs by sweeping the funds into a single p2wkh output. -func (b *breachArbiter) sweepSpendableOutputsTxn(txVSize int64, +func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64, inputs ...SpendableOutput) (*wire.MsgTx, error) { // First, we obtain a new public key script from the wallet which we'll @@ -1041,11 +1041,11 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txVSize int64, // We'll actually attempt to target inclusion within the next two // blocks as we'd like to sweep these funds back into our wallet ASAP. - feePerVSize, err := b.cfg.Estimator.EstimateFeePerVSize(2) + feePerKw, err := b.cfg.Estimator.EstimateFeePerKW(2) if err != nil { return nil, err } - txFee := feePerVSize.FeeForVSize(txVSize) + txFee := feePerKw.FeeForWeight(txWeight) // TODO(roasbeef): already start to siphon their funds into fees sweepAmt := int64(totalAmt - txFee) diff --git a/contractcourt/contract_resolvers.go b/contractcourt/contract_resolvers.go index 086b8d26c..b525b5dfd 100644 --- a/contractcourt/contract_resolvers.go +++ b/contractcourt/contract_resolvers.go @@ -451,27 +451,27 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) { return nil, err } - // With out address obtained, we'll query for an + // With our address obtained, we'll query for an // estimate to be confirmed at ease. // // TODO(roasbeef): signal up if fee would be too large // to sweep singly, need to batch - feePerVSize, err := h.FeeEstimator.EstimateFeePerVSize(6) + feePerKw, err := h.FeeEstimator.EstimateFeePerKW(6) if err != nil { return nil, err } - log.Debugf("%T(%x): using %v sat/vbyte to sweep htlc"+ + log.Debugf("%T(%x): using %v sat/kw to sweep htlc"+ "incoming+remote htlc confirmed", h, - h.payHash[:], int64(feePerVSize)) + h.payHash[:], int64(feePerKw)) // Using a weight estimator, we'll compute the total // fee required, and from that the value we'll end up // with. - totalVSize := (&lnwallet.TxWeightEstimator{}). + totalWeight := (&lnwallet.TxWeightEstimator{}). AddWitnessInput(lnwallet.OfferedHtlcSuccessWitnessSize). - AddP2WKHOutput().VSize() - totalFees := feePerVSize.FeeForVSize(int64(totalVSize)) + AddP2WKHOutput().Weight() + totalFees := feePerKw.FeeForWeight(int64(totalWeight)) sweepAmt := h.htlcResolution.SweepSignDesc.Output.Value - int64(totalFees) @@ -1253,18 +1253,18 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { // First, we'll estimate the total weight so we can compute // fees properly. We'll use a lax estimate, as this output is // in no immediate danger. - feePerVSize, err := c.FeeEstimator.EstimateFeePerVSize(6) + feePerKw, err := c.FeeEstimator.EstimateFeePerKW(6) if err != nil { return nil, err } - log.Debugf("%T(%v): using %v sat/vsize for sweep tx", c, - c.chanPoint, int64(feePerVSize)) + log.Debugf("%T(%v): using %v sat/kw for sweep tx", c, + c.chanPoint, int64(feePerKw)) - totalVSize := (&lnwallet.TxWeightEstimator{}). - AddP2PKHInput(). - AddP2WKHOutput().VSize() - totalFees := feePerVSize.FeeForVSize(int64(totalVSize)) + totalWeight := (&lnwallet.TxWeightEstimator{}). + AddP2WKHInput(). + AddP2WKHOutput().Weight() + totalFees := feePerKw.FeeForWeight(int64(totalWeight)) sweepAmt := signDesc.Output.Value - int64(totalFees) c.sweepTx = wire.NewMsgTx(2) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 8a2d9b1f2..4f86ba946 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -81,9 +81,9 @@ type initFundingReserveMsg struct { // paying some multiple of the accepted base fee rate of the network. commitFeePerKw SatPerKWeight - // fundingFeePerVSize is the fee rate in sat/vbyte to use for the - // initial funding transaction. - fundingFeePerVSize SatPerVByte + // fundingFeePerKw is the fee rate in sat/kw to use for the initial + // funding transaction. + fundingFeePerKw SatPerKWeight // pushMSat is the number of milli-satoshis that should be pushed over // the responder as part of the initial channel creation. @@ -413,7 +413,7 @@ out: // commitment transaction is valid. func (l *LightningWallet) InitChannelReservation( capacity, ourFundAmt btcutil.Amount, pushMSat lnwire.MilliSatoshi, - commitFeePerKw SatPerKWeight, fundingFeePerVSize SatPerVByte, + commitFeePerKw SatPerKWeight, fundingFeePerKw SatPerKWeight, theirID *btcec.PublicKey, theirAddr net.Addr, chainHash *chainhash.Hash, flags lnwire.FundingFlag) (*ChannelReservation, error) { @@ -421,17 +421,17 @@ func (l *LightningWallet) InitChannelReservation( respChan := make(chan *ChannelReservation, 1) l.msgChan <- &initFundingReserveMsg{ - chainHash: chainHash, - nodeID: theirID, - nodeAddr: theirAddr, - fundingAmount: ourFundAmt, - capacity: capacity, - commitFeePerKw: commitFeePerKw, - fundingFeePerVSize: fundingFeePerVSize, - pushMSat: pushMSat, - flags: flags, - err: errChan, - resp: respChan, + chainHash: chainHash, + nodeID: theirID, + nodeAddr: theirAddr, + fundingAmount: ourFundAmt, + capacity: capacity, + commitFeePerKw: commitFeePerKw, + fundingFeePerKw: fundingFeePerKw, + pushMSat: pushMSat, + flags: flags, + err: errChan, + resp: respChan, } return <-respChan, <-errChan @@ -479,10 +479,10 @@ 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 { - // Coin selection is done on the basis of sat-per-vbyte, we'll - // use the passed sat/vbyte passed in to perform coin selection. + // Coin selection is done on the basis of sat/kw, so we'll use + // the fee rate passed in to perform coin selection. err := l.selectCoinsAndChange( - req.fundingFeePerVSize, req.fundingAmount, + req.fundingFeePerKw, req.fundingAmount, reservation.ourContribution, ) if err != nil { @@ -1276,7 +1276,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { // 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 SatPerVByte, +func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerKWeight, amt btcutil.Amount, contribution *ChannelContribution) error { // We hold the coin select mutex while querying for outputs, and @@ -1286,7 +1286,7 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate SatPerVByte, defer l.coinSelectMtx.Unlock() walletLog.Infof("Performing funding tx coin selection using %v "+ - "sat/vbyte as fee rate", int64(feeRate)) + "sat/kw as fee rate", int64(feeRate)) // Find all unlocked unspent witness outputs with greater than 1 // confirmation. @@ -1395,9 +1395,9 @@ func selectInputs(amt btcutil.Amount, coins []*Utxo) (btcutil.Amount, []*Utxo, e // coinSelect attempts to select a sufficient amount of coins, including a // change output to fund amt satoshis, adhering to the specified fee rate. The -// specified fee rate should be expressed in sat/vbyte for coin selection to +// specified fee rate should be expressed in sat/kw for coin selection to // function properly. -func coinSelect(feeRate SatPerVByte, amt btcutil.Amount, +func coinSelect(feeRate SatPerKWeight, amt btcutil.Amount, coins []*Utxo) ([]*Utxo, btcutil.Amount, error) { amtNeeded := amt @@ -1441,7 +1441,8 @@ func coinSelect(feeRate SatPerVByte, amt btcutil.Amount, // amount isn't enough to pay fees, then increase the requested // coin amount by the estimate required fee, performing another // round of coin selection. - requiredFee := feeRate.FeeForVSize(int64(weightEstimate.VSize())) + totalWeight := int64(weightEstimate.Weight()) + requiredFee := feeRate.FeeForWeight(totalWeight) if overShootAmt < requiredFee { amtNeeded = amt + requiredFee continue diff --git a/utxonursery.go b/utxonursery.go index ec2424d3e..eba9de757 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -994,15 +994,15 @@ func (u *utxoNursery) createSweepTx(kgtnOutputs []kidOutput, utxnLog.Infof("Creating sweep transaction for %v CSV inputs, %v CLTV "+ "inputs", len(csvOutputs), len(cltvOutputs)) - txVSize := int64(weightEstimate.VSize()) - return u.populateSweepTx(txVSize, classHeight, csvOutputs, cltvOutputs) + txWeight := int64(weightEstimate.Weight()) + return u.populateSweepTx(txWeight, classHeight, csvOutputs, cltvOutputs) } // populateSweepTx populate the final sweeping transaction with all witnesses // in place for all inputs using the provided txn fee. The created transaction // has a single output sending all the funds back to the source wallet, after // accounting for the fee estimate. -func (u *utxoNursery) populateSweepTx(txVSize int64, classHeight uint32, +func (u *utxoNursery) populateSweepTx(txWeight int64, classHeight uint32, csvInputs []CsvSpendableOutput, cltvInputs []SpendableOutput) (*wire.MsgTx, error) { @@ -1022,11 +1022,11 @@ func (u *utxoNursery) populateSweepTx(txVSize int64, classHeight uint32, } // Using the txn weight estimate, compute the required txn fee. - feePerVSize, err := u.cfg.Estimator.EstimateFeePerVSize(6) + feePerKw, err := u.cfg.Estimator.EstimateFeePerKW(6) if err != nil { return nil, err } - txFee := feePerVSize.FeeForVSize(txVSize) + txFee := feePerKw.FeeForWeight(txWeight) // Sweep as much possible, after subtracting txn fees. sweepAmt := int64(totalSum - txFee) From 9d2eeb63049fa1cfca35ebd4b6e76bb2a3a938bc Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Jul 2018 18:20:58 -0700 Subject: [PATCH 3/4] multi: update to latest fee estimation interface --- breacharbiter_test.go | 7 +- chainregistry.go | 26 ++++--- fundingmanager.go | 30 ++------- fundingmanager_test.go | 2 +- htlcswitch/link.go | 24 +------ htlcswitch/link_test.go | 47 ++++--------- htlcswitch/mock.go | 6 +- htlcswitch/test_utils.go | 9 +-- lntest/harness.go | 6 +- lnwallet/btcwallet/btcwallet.go | 8 +-- lnwallet/channel_test.go | 5 +- lnwallet/interface.go | 12 ++-- lnwallet/interface_test.go | 101 +++++++++++++++------------- lnwallet/test_utils.go | 5 +- mock.go | 2 +- peer.go | 9 +-- peer_test.go | 13 ++-- pilot.go | 7 +- routing/chainview/interface_test.go | 2 +- server.go | 28 ++++---- test_utils.go | 5 +- 21 files changed, 150 insertions(+), 204 deletions(-) diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 03de6b16f..8f6c90a33 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -1351,7 +1351,7 @@ func createTestArbiter(t *testing.T, contractBreaches chan *ContractBreachEvent, ba := newBreachArbiter(&BreachConfig{ CloseLink: func(_ *wire.OutPoint, _ htlcswitch.ChannelCloseType) {}, DB: db, - Estimator: &lnwallet.StaticFeeEstimator{FeeRate: 50}, + Estimator: &lnwallet.StaticFeeEstimator{FeePerKW: 12500}, GenSweepScript: func() ([]byte, error) { return nil, nil }, ContractBreaches: contractBreaches, Signer: signer, @@ -1491,12 +1491,11 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa return nil, nil, nil, err } - estimator := &lnwallet.StaticFeeEstimator{FeeRate: 50} - feePerVSize, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 12500} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { return nil, nil, nil, err } - feePerKw := feePerVSize.FeePerKWeight() // TODO(roasbeef): need to factor in commit fee? aliceCommit := channeldb.ChannelCommitment{ diff --git a/chainregistry.go b/chainregistry.go index 873efaa27..efde6fba7 100644 --- a/chainregistry.go +++ b/chainregistry.go @@ -37,15 +37,21 @@ const ( defaultBitcoinBaseFeeMSat = lnwire.MilliSatoshi(1000) defaultBitcoinFeeRate = lnwire.MilliSatoshi(1) defaultBitcoinTimeLockDelta = 144 - defaultBitcoinStaticFeeRate = lnwallet.SatPerVByte(50) defaultLitecoinMinHTLCMSat = lnwire.MilliSatoshi(1000) defaultLitecoinBaseFeeMSat = lnwire.MilliSatoshi(1000) defaultLitecoinFeeRate = lnwire.MilliSatoshi(1) defaultLitecoinTimeLockDelta = 576 - defaultLitecoinStaticFeeRate = lnwallet.SatPerVByte(200) defaultLitecoinDustLimit = btcutil.Amount(54600) + // defaultBitcoinStaticFeePerKW is the fee rate of 50 sat/vbyte + // expressed in sat/kw. + defaultBitcoinStaticFeePerKW = lnwallet.SatPerKWeight(12500) + + // defaultLitecoinStaticFeePerKW is the fee rate of 200 sat/vbyte + // expressed in sat/kw. + defaultLitecoinStaticFeePerKW = lnwallet.SatPerKWeight(50000) + // btcToLtcConversionRate is a fixed ratio used in order to scale up // payments when running on the Litecoin chain. btcToLtcConversionRate = 60 @@ -141,7 +147,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, TimeLockDelta: cfg.Bitcoin.TimeLockDelta, } cc.feeEstimator = lnwallet.StaticFeeEstimator{ - FeeRate: defaultBitcoinStaticFeeRate, + FeePerKW: defaultBitcoinStaticFeePerKW, } case litecoinChain: cc.routingPolicy = htlcswitch.ForwardingPolicy{ @@ -151,7 +157,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, TimeLockDelta: cfg.Litecoin.TimeLockDelta, } cc.feeEstimator = lnwallet.StaticFeeEstimator{ - FeeRate: defaultLitecoinStaticFeeRate, + FeePerKW: defaultLitecoinStaticFeePerKW, } default: return nil, nil, fmt.Errorf("Default routing policy for "+ @@ -337,9 +343,9 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, // if we're using bitcoind as a backend, then we can // use live fee estimates, rather than a statically // coded value. - fallBackFeeRate := lnwallet.SatPerVByte(25) + fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000) cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator( - *rpcConfig, fallBackFeeRate, + *rpcConfig, fallBackFeeRate.FeePerKWeight(), ) if err != nil { return nil, nil, err @@ -354,9 +360,9 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, // if we're using litecoind as a backend, then we can // use live fee estimates, rather than a statically // coded value. - fallBackFeeRate := lnwallet.SatPerVByte(25) + fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000) cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator( - *rpcConfig, fallBackFeeRate, + *rpcConfig, fallBackFeeRate.FeePerKWeight(), ) if err != nil { return nil, nil, err @@ -457,9 +463,9 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, // if we're using btcd as a backend, then we can use // live fee estimates, rather than a statically coded // value. - fallBackFeeRate := lnwallet.SatPerVByte(25) + fallBackFeeRate := lnwallet.SatPerKVByte(25 * 1000) cc.feeEstimator, err = lnwallet.NewBtcdFeeEstimator( - *rpcConfig, fallBackFeeRate, + *rpcConfig, fallBackFeeRate.FeePerKWeight(), ) if err != nil { return nil, nil, err diff --git a/fundingmanager.go b/fundingmanager.go index fa414545a..8576205c0 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -8,10 +8,6 @@ import ( "sync/atomic" "time" - "google.golang.org/grpc" - - "golang.org/x/crypto/salsa20" - "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -28,6 +24,8 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" + "golang.org/x/crypto/salsa20" + "google.golang.org/grpc" ) const ( @@ -68,11 +66,6 @@ const ( // currently accepted on the Litecoin chain within the Lightning // Protocol. maxLtcFundingAmount = maxBtcFundingAmount * btcToLtcConversionRate - - // minCommitFeePerKw is the smallest fee rate that we should propose - // for a new fee update. We'll use this as a fee floor when proposing - // and accepting updates. - minCommitFeePerKw = 253 ) var ( @@ -1028,8 +1021,8 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { reservation, err := f.cfg.Wallet.InitChannelReservation( amt, 0, msg.PushAmount, lnwallet.SatPerKWeight(msg.FeePerKiloWeight), 0, - fmsg.peer.IdentityKey(), fmsg.peer.Address(), - &chainHash, msg.ChannelFlags, + fmsg.peer.IdentityKey(), fmsg.peer.Address(), &chainHash, + msg.ChannelFlags, ) if err != nil { fndgLog.Errorf("Unable to initialize reservation: %v", err) @@ -2543,23 +2536,12 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // commitment transaction confirmed by the next few blocks (conf target // of 3). We target the near blocks here to ensure that we'll be able // to execute a timely unilateral channel closure if needed. - feePerVSize, err := f.cfg.FeeEstimator.EstimateFeePerVSize(3) + commitFeePerKw, err := f.cfg.FeeEstimator.EstimateFeePerKW(3) if err != nil { msg.err <- err return } - // If the converted fee-per-kw is below the current widely used policy - // floor, then we'll use the floor instead. - commitFeePerKw := feePerVSize.FeePerKWeight() - if commitFeePerKw < minCommitFeePerKw { - fndgLog.Infof("Proposed fee rate of %v sat/kw is below min "+ - "of %v sat/kw, using fee floor", int64(commitFeePerKw), - int64(minCommitFeePerKw)) - - commitFeePerKw = minCommitFeePerKw - } - // We set the channel flags to indicate whether we want this channel to // be announced to the network. var channelFlags lnwire.FundingFlag @@ -2573,7 +2555,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // request will fail, and be aborted. reservation, err := f.cfg.Wallet.InitChannelReservation( capacity, localAmt, msg.pushAmt, commitFeePerKw, - msg.fundingFeePerVSize, peerKey, msg.peer.Address(), + msg.fundingFeePerKw, peerKey, msg.peer.Address(), &msg.chainHash, channelFlags, ) if err != nil { diff --git a/fundingmanager_test.go b/fundingmanager_test.go index deb08f0f2..d37f21954 100644 --- a/fundingmanager_test.go +++ b/fundingmanager_test.go @@ -235,7 +235,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, addr *lnwire.NetAddress, tempTestDir string) (*testNode, error) { netParams := activeNetParams.Params - estimator := lnwallet.StaticFeeEstimator{FeeRate: 250} + estimator := lnwallet.StaticFeeEstimator{FeePerKW: 62500} chainNotifier := &mockNotifier{ oneConfChannel: make(chan *chainntnfs.TxConfirmation, 1), diff --git a/htlcswitch/link.go b/htlcswitch/link.go index b75f3af51..df33da84e 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -35,11 +35,6 @@ const ( // TODO(roasbeef): must be < default delta expiryGraceDelta = 2 - // minCommitFeePerKw is the smallest fee rate that we should propose - // for a new fee update. We'll use this as a fee floor when proposing - // and accepting updates. - minCommitFeePerKw = 253 - // DefaultMinLinkFeeUpdateTimeout represents the minimum interval in // which a link should propose to update its commitment fee rate. DefaultMinLinkFeeUpdateTimeout = 10 * time.Minute @@ -495,26 +490,13 @@ func (l *channelLink) EligibleToForward() bool { // this is the native rate used when computing the fee for commitment // transactions, and the second-level HTLC transactions. func (l *channelLink) sampleNetworkFee() (lnwallet.SatPerKWeight, error) { - // We'll first query for the sat/vbyte recommended to be confirmed - // within 3 blocks. - feePerVSize, err := l.cfg.FeeEstimator.EstimateFeePerVSize(3) + // We'll first query for the sat/kw recommended to be confirmed within 3 + // blocks. + feePerKw, err := l.cfg.FeeEstimator.EstimateFeePerKW(3) if err != nil { return 0, err } - // Once we have this fee rate, we'll convert to sat-per-kw. - feePerKw := feePerVSize.FeePerKWeight() - - // If the returned feePerKw is less than the current widely used - // policy, then we'll use that instead as a floor. - if feePerKw < minCommitFeePerKw { - log.Debugf("Proposed fee rate of %v sat/kw is below min "+ - "of %v sat/kw, using fee floor", int64(feePerKw), - int64(minCommitFeePerKw)) - - feePerKw = minCommitFeePerKw - } - log.Debugf("ChannelLink(%v): sampled fee rate for 3 block conf: %v "+ "sat/kw", l, int64(feePerKw)) diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index bd4dc5972..24b80530a 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -1726,14 +1726,11 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) { coreLink.cfg.HodlMask = hodl.MaskFromFlags(hodl.ExitSettle) coreLink.cfg.DebugHTLC = true - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() htlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(lnwallet.HtlcWeight), ) @@ -2140,14 +2137,11 @@ func TestChannelLinkBandwidthConsistencyOverflow(t *testing.T) { aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() var htlcID uint64 addLinkHTLC := func(id uint64, amt lnwire.MilliSatoshi) [32]byte { @@ -2390,17 +2384,15 @@ func TestChannelLinkTrimCircuitsPending(t *testing.T) { // Compute the static fees that will be used to determine the // correctness of Alice's bandwidth when forwarding HTLCs. - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } defaultCommitFee := alice.channel.StateSnapshot().CommitFee htlcFee := lnwire.NewMSatFromSatoshis( - feeRate.FeePerKWeight().FeeForWeight(lnwallet.HtlcWeight), + feePerKw.FeeForWeight(lnwallet.HtlcWeight), ) // The starting bandwidth of the channel should be exactly the amount @@ -2666,17 +2658,15 @@ func TestChannelLinkTrimCircuitsNoCommit(t *testing.T) { // Compute the static fees that will be used to determine the // correctness of Alice's bandwidth when forwarding HTLCs. - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } defaultCommitFee := alice.channel.StateSnapshot().CommitFee htlcFee := lnwire.NewMSatFromSatoshis( - feeRate.FeePerKWeight().FeeForWeight(lnwallet.HtlcWeight), + feePerKw.FeeForWeight(lnwallet.HtlcWeight), ) // The starting bandwidth of the channel should be exactly the amount @@ -2926,14 +2916,11 @@ func TestChannelLinkBandwidthChanReserve(t *testing.T) { aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() htlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(lnwallet.HtlcWeight), ) @@ -3460,15 +3447,9 @@ func TestChannelLinkUpdateCommitFee(t *testing.T) { startingFeeRate := channels.aliceToBob.CommitFeeRate() - // Convert starting fee rate to sat/vbyte. This is usually a - // lossy conversion, but since the startingFeeRate is - // 6000 sat/kw in this case, we won't lose precision. - startingFeeRateSatPerVByte := lnwallet.SatPerVByte( - startingFeeRate * 4 / 1000) - // Next, we'll send the first fee rate response to Alice. select { - case n.feeEstimator.byteFeeIn <- startingFeeRateSatPerVByte: + case n.feeEstimator.byteFeeIn <- startingFeeRate: case <-time.After(time.Second * 5): t.Fatalf("alice didn't query for the new network fee") } @@ -3497,7 +3478,7 @@ func TestChannelLinkUpdateCommitFee(t *testing.T) { // fee update. newFeeRate := startingFeeRate * 3 select { - case n.feeEstimator.byteFeeIn <- startingFeeRateSatPerVByte * 3: + case n.feeEstimator.byteFeeIn <- newFeeRate: case <-time.After(time.Second * 5): t.Fatalf("alice didn't query for the new network fee") } diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index 9d3a29389..3dcfa8763 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -58,12 +58,14 @@ func (m *mockPreimageCache) SubscribeUpdates() *contractcourt.WitnessSubscriptio } type mockFeeEstimator struct { - byteFeeIn chan lnwallet.SatPerVByte + byteFeeIn chan lnwallet.SatPerKWeight quit chan struct{} } -func (m *mockFeeEstimator) EstimateFeePerVSize(numBlocks uint32) (lnwallet.SatPerVByte, error) { +func (m *mockFeeEstimator) EstimateFeePerKW( + numBlocks uint32) (lnwallet.SatPerKWeight, error) { + select { case feeRate := <-m.byteFeeIn: return feeRate, nil diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index 46bf718f0..7838e1292 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -271,14 +271,11 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, return nil, nil, nil, nil, err } - estimator := &lnwallet.StaticFeeEstimator{ - FeeRate: 24, - } - feePerVSize, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { return nil, nil, nil, nil, err } - feePerKw := feePerVSize.FeePerKWeight() commitFee := feePerKw.FeeForWeight(724) const broadcastHeight = 1 @@ -873,7 +870,7 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel, carolDecoder := newMockIteratorDecoder() feeEstimator := &mockFeeEstimator{ - byteFeeIn: make(chan lnwallet.SatPerVByte), + byteFeeIn: make(chan lnwallet.SatPerKWeight), quit: make(chan struct{}), } diff --git a/lntest/harness.go b/lntest/harness.go index 517645b2d..60c0f4ef4 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -172,7 +172,8 @@ func (n *NetworkHarness) SetUp(lndArgs []string) error { PkScript: addrScript, Value: btcutil.SatoshiPerBitcoin, } - if _, err := n.Miner.SendOutputs([]*wire.TxOut{output}, 30); err != nil { + _, err = n.Miner.SendOutputs([]*wire.TxOut{output}, 7500) + if err != nil { return err } } @@ -1159,7 +1160,8 @@ func (n *NetworkHarness) sendCoins(ctx context.Context, amt btcutil.Amount, PkScript: addrScript, Value: int64(amt), } - if _, err := n.Miner.SendOutputs([]*wire.TxOut{output}, 30); err != nil { + _, err = n.Miner.SendOutputs([]*wire.TxOut{output}, 7500) + if err != nil { return err } diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index cc946e428..92bcbfa25 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -272,11 +272,11 @@ func (b *BtcWallet) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) { // // This is a part of the WalletController interface. func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut, - feeRate lnwallet.SatPerVByte) (*chainhash.Hash, error) { + feeRate lnwallet.SatPerKWeight) (*chainhash.Hash, error) { - // The fee rate is passed in using units of sat/vbyte, so we'll scale - // this up to sat/KB as the SendOutputs method requires this unit. - feeSatPerKB := btcutil.Amount(feeRate * 1000) + // Convert our fee rate from sat/kw to sat/kb since it's required by + // SendOutputs. + feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte()) return b.wallet.SendOutputs(outputs, defaultAccount, 1, feeSatPerKB) } diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 9da2ee919..b596befbb 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -1131,12 +1131,11 @@ func TestHTLCSigNumber(t *testing.T) { } // Calculate two values that will be below and above Bob's dust limit. - estimator := &StaticFeeEstimator{24} - feePerVSize, err := estimator.EstimateFeePerVSize(1) + estimator := &StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to get fee: %v", err) } - feePerKw := feePerVSize.FeePerKWeight() belowDust := btcutil.Amount(500) + htlcTimeoutFee(feePerKw) aboveDust := btcutil.Amount(1400) + htlcSuccessFee(feePerKw) diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 0aad560ad..073cd9137 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -154,13 +154,13 @@ type WalletController interface { // error should be returned. GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) - // SendOutputs funds, signs, and broadcasts a Bitcoin transaction - // paying out to the specified outputs. In the case the wallet has - // insufficient funds, or the outputs are non-standard, an error should - // be returned. This method also takes the target fee expressed in - // sat/vbyte that should be used when crafting the transaction. + // SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying + // out to the specified outputs. In the case the wallet has insufficient + // funds, or the outputs are non-standard, an error should be returned. + // This method also takes the target fee expressed in sat/kw that should + // be used when crafting the transaction. SendOutputs(outputs []*wire.TxOut, - feeRate SatPerVByte) (*chainhash.Hash, error) + feeRate SatPerKWeight) (*chainhash.Hash, error) // ListUnspentWitness returns all unspent outputs which are version 0 // witness programs. The 'confirms' parameter indicates the minimum diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index eb3d3b507..4f304bc15 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -186,7 +186,7 @@ func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet, Value: int64(satoshiPerOutput), PkScript: script, } - if _, err := miner.SendOutputs([]*wire.TxOut{output}, 10); err != nil { + if _, err := miner.SendOutputs([]*wire.TxOut{output}, 2500); err != nil { return err } } @@ -249,7 +249,7 @@ func createTestWallet(tempTestDir string, miningNode *rpctest.Harness, WalletController: wc, Signer: signer, ChainIO: bio, - FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 10}, + FeeEstimator: lnwallet.StaticFeeEstimator{FeePerKW: 2500}, DefaultConstraints: channeldb.ChannelConstraints{ DustLimit: 500, MaxPendingAmount: lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) * 100, @@ -290,14 +290,14 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, // Alice initiates a channel funded with 5 BTC for each side, so 10 BTC // total. She also generates 2 BTC in change. - feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) + feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() aliceChanReservation, err := alice.InitChannelReservation( - fundingAmount*2, fundingAmount, 0, feePerKw, feeRate, - bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel) + fundingAmount*2, fundingAmount, 0, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, lnwire.FFAnnounceChannel, + ) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) } @@ -325,9 +325,10 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, // Bob does the same, generating his own contribution. He then also // receives' Alice's contribution, and consumes that so we can continue // the funding process. - bobChanReservation, err := bob.InitChannelReservation(fundingAmount*2, - fundingAmount, 0, feePerKw, feeRate, alicePub, aliceAddr, - chainHash, lnwire.FFAnnounceChannel) + bobChanReservation, err := bob.InitChannelReservation( + fundingAmount*2, fundingAmount, 0, feePerKw, feePerKw, alicePub, + aliceAddr, chainHash, lnwire.FFAnnounceChannel, + ) if err != nil { t.Fatalf("bob unable to init channel reservation: %v", err) } @@ -471,14 +472,13 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, if err != nil { t.Fatalf("unable to create amt: %v", err) } - feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) + feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() - _, err = alice.InitChannelReservation(fundingAmount, - fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, - lnwire.FFAnnounceChannel, + _, err = alice.InitChannelReservation( + fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, lnwire.FFAnnounceChannel, ) if err != nil { t.Fatalf("unable to initialize funding reservation 1: %v", err) @@ -491,9 +491,10 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, if err != nil { t.Fatalf("unable to create amt: %v", err) } - failedReservation, err := alice.InitChannelReservation(amt, amt, 0, - feePerKw, feeRate, bobPub, bobAddr, chainHash, - lnwire.FFAnnounceChannel) + failedReservation, err := alice.InitChannelReservation( + amt, amt, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash, + lnwire.FFAnnounceChannel, + ) if err == nil { t.Fatalf("not error returned, should fail on coin selection") } @@ -508,28 +509,28 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, alice, _ *lnwallet.LightningWallet, t *testing.T) { - feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) + feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() // Create a reservation for 44 BTC. fundingAmount, err := btcutil.NewAmount(44) if err != nil { t.Fatalf("unable to create amt: %v", err) } - chanReservation, err := alice.InitChannelReservation(fundingAmount, - fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, - lnwire.FFAnnounceChannel) + chanReservation, err := alice.InitChannelReservation( + fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, lnwire.FFAnnounceChannel, + ) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) } // Attempt to create another channel with 44 BTC, this should fail. - _, err = alice.InitChannelReservation(fundingAmount, - fundingAmount, 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, - lnwire.FFAnnounceChannel, + _, err = alice.InitChannelReservation( + fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, lnwire.FFAnnounceChannel, ) if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok { t.Fatalf("coin selection succeeded should have insufficient funds: %v", @@ -560,7 +561,7 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, // Request to fund a new channel should now succeed. _, err = alice.InitChannelReservation(fundingAmount, fundingAmount, - 0, feePerKw, feeRate, bobPub, bobAddr, chainHash, + 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) @@ -570,15 +571,15 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, func testCancelNonExistentReservation(miner *rpctest.Harness, alice, _ *lnwallet.LightningWallet, t *testing.T) { - feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) + feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } // Create our own reservation, give it some ID. res, err := lnwallet.NewChannelReservation( - 10000, 10000, feeRate.FeePerKWeight(), alice, - 22, 10, &testHdSeed, lnwire.FFAnnounceChannel, + 10000, 10000, feePerKw, alice, 22, 10, &testHdSeed, + lnwire.FFAnnounceChannel, ) if err != nil { t.Fatalf("unable to create res: %v", err) @@ -597,14 +598,17 @@ func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness, // We'll attempt to create a new reservation with an extremely high fee // rate. This should push our balance into the negative and result in a // failure to create the reservation. - fundingAmount, err := btcutil.NewAmount(4) + const numBTC = 4 + fundingAmount, err := btcutil.NewAmount(numBTC) if err != nil { t.Fatalf("unable to create amt: %v", err) } - feePerVSize := lnwallet.SatPerVByte(btcutil.SatoshiPerBitcoin * 4 / 100) - feePerKw := feePerVSize.FeePerKWeight() + + feePerKw := lnwallet.SatPerKWeight( + numBTC * numBTC * btcutil.SatoshiPerBitcoin, + ) _, err = alice.InitChannelReservation( - fundingAmount, fundingAmount, 0, feePerKw, feePerVSize, bobPub, + fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel, ) switch { @@ -672,14 +676,14 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness, t.Fatalf("unable to create amt: %v", err) } pushAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) - feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerVSize(1) + feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() - aliceChanReservation, err := alice.InitChannelReservation(fundingAmt, - fundingAmt, pushAmt, feePerKw, feeRate, bobPub, bobAddr, chainHash, - lnwire.FFAnnounceChannel) + aliceChanReservation, err := alice.InitChannelReservation( + fundingAmt, fundingAmt, pushAmt, feePerKw, feePerKw, bobPub, + bobAddr, chainHash, lnwire.FFAnnounceChannel, + ) if err != nil { t.Fatalf("unable to init channel reservation: %v", err) } @@ -707,9 +711,10 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness, // Next, Bob receives the initial request, generates a corresponding // reservation initiation, then consume Alice's contribution. - bobChanReservation, err := bob.InitChannelReservation(fundingAmt, 0, - pushAmt, feePerKw, feeRate, alicePub, aliceAddr, chainHash, - lnwire.FFAnnounceChannel) + bobChanReservation, err := bob.InitChannelReservation( + fundingAmt, 0, pushAmt, feePerKw, feePerKw, alicePub, aliceAddr, + chainHash, lnwire.FFAnnounceChannel, + ) if err != nil { t.Fatalf("unable to create bob reservation: %v", err) } @@ -891,7 +896,7 @@ func testListTransactionDetails(miner *rpctest.Harness, Value: outputAmt, PkScript: script, } - txid, err := miner.SendOutputs([]*wire.TxOut{output}, 10) + txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500) if err != nil { t.Fatalf("unable to send coinbase: %v", err) } @@ -994,7 +999,7 @@ func testListTransactionDetails(miner *rpctest.Harness, t.Fatalf("unable to make output script: %v", err) } burnOutput := wire.NewTxOut(outputAmt, outputScript) - burnTXID, err := alice.SendOutputs([]*wire.TxOut{burnOutput}, 10) + burnTXID, err := alice.SendOutputs([]*wire.TxOut{burnOutput}, 2500) if err != nil { t.Fatalf("unable to create burn tx: %v", err) } @@ -1108,7 +1113,7 @@ func testTransactionSubscriptions(miner *rpctest.Harness, Value: outputAmt, PkScript: script, } - txid, err := miner.SendOutputs([]*wire.TxOut{output}, 10) + txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500) if err != nil { t.Fatalf("unable to send coinbase: %v", err) } @@ -1308,7 +1313,7 @@ func testPublishTransaction(r *rpctest.Harness, Value: btcutil.SatoshiPerBitcoin, PkScript: keyScript, } - txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 10) + txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 2500) if err != nil { t.Fatalf("unable to create output: %v", err) } @@ -1553,7 +1558,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness, Value: btcutil.SatoshiPerBitcoin, PkScript: keyScript, } - txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 10) + txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 2500) if err != nil { t.Fatalf("unable to create output: %v", err) } @@ -1679,7 +1684,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet, Value: 1e8, PkScript: script, } - txid, err := w.SendOutputs([]*wire.TxOut{output}, 10) + txid, err := w.SendOutputs([]*wire.TxOut{output}, 2500) if err != nil { t.Fatalf("unable to send outputs: %v", err) } @@ -2073,7 +2078,7 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, } case "neutrino": - feeEstimator = lnwallet.StaticFeeEstimator{FeeRate: 250} + feeEstimator = lnwallet.StaticFeeEstimator{FeePerKW: 62500} // Set some package-level variable to speed up // operation for tests. diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index 03cd85135..c11aa69d4 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -229,12 +229,11 @@ func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error) return nil, nil, nil, err } - estimator := &StaticFeeEstimator{24} - feePerVSize, err := estimator.EstimateFeePerVSize(1) + estimator := &StaticFeeEstimator{FeePerKW: 6000} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { return nil, nil, nil, err } - feePerKw := feePerVSize.FeePerKWeight() commitFee := calcStaticFee(0) aliceCommit := channeldb.ChannelCommitment{ diff --git a/mock.go b/mock.go index cd0461e8c..e4b563ad7 100644 --- a/mock.go +++ b/mock.go @@ -224,7 +224,7 @@ func (*mockWalletController) GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, e } func (*mockWalletController) SendOutputs(outputs []*wire.TxOut, - _ lnwallet.SatPerVByte) (*chainhash.Hash, error) { + _ lnwallet.SatPerKWeight) (*chainhash.Hash, error) { return nil, nil } diff --git a/peer.go b/peer.go index ffc4cd286..09a172913 100644 --- a/peer.go +++ b/peer.go @@ -1658,18 +1658,13 @@ func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, e // In order to begin fee negotiations, we'll first compute our // target ideal fee-per-kw. We'll set this to a lax value, as // we weren't the ones that initiated the channel closure. - feePerVSize, err := p.server.cc.feeEstimator.EstimateFeePerVSize(6) + feePerKw, err := p.server.cc.feeEstimator.EstimateFeePerKW(6) if err != nil { peerLog.Errorf("unable to query fee estimator: %v", err) return nil, fmt.Errorf("unable to estimate fee") } - // We'll then convert the sat per weight to sat per k/w as this - // is the native unit used within the protocol when dealing - // with fees. - targetFeePerKw := feePerVSize.FeePerKWeight() - _, startingHeight, err := p.server.cc.chainIO.GetBestBlock() if err != nil { peerLog.Errorf("unable to obtain best block: %v", err) @@ -1685,7 +1680,7 @@ func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, e quit: p.quit, }, deliveryAddr, - targetFeePerKw, + feePerKw, uint32(startingHeight), nil, ) diff --git a/peer_test.go b/peer_test.go index 2b7112870..ffeb5951c 100644 --- a/peer_test.go +++ b/peer_test.go @@ -169,12 +169,11 @@ func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) { dummyDeliveryScript), } - estimator := lnwallet.StaticFeeEstimator{FeeRate: 50} - feeRate, err := estimator.EstimateFeePerVSize(1) + estimator := lnwallet.StaticFeeEstimator{FeePerKW: 12500} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - feePerKw := feeRate.FeePerKWeight() fee := responderChan.CalcFee(feePerKw) closeSig, _, _, err := responderChan.CreateCloseProposal(fee, dummyDeliveryScript, initiatorDeliveryScript) @@ -460,14 +459,12 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) { msg: respShutdown, } - estimator := lnwallet.StaticFeeEstimator{FeeRate: 50} - initiatorIdealFeeRate, err := estimator.EstimateFeePerVSize(1) + estimator := lnwallet.StaticFeeEstimator{FeePerKW: 12500} + initiatorIdealFeeRate, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } - initiatorIdealFee := responderChan.CalcFee( - initiatorIdealFeeRate.FeePerKWeight(), - ) + initiatorIdealFee := responderChan.CalcFee(initiatorIdealFeeRate) increasedFee := btcutil.Amount(float64(initiatorIdealFee) * 2.5) closeSig, _, _, err := responderChan.CreateCloseProposal( increasedFee, dummyDeliveryScript, initiatorDeliveryScript, diff --git a/pilot.go b/pilot.go index e956359ee..b1d8f0a90 100644 --- a/pilot.go +++ b/pilot.go @@ -80,7 +80,7 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey, // With the connection established, we'll now establish our connection // to the target peer, waiting for the first update before we exit. - feePerVSize, err := c.server.cc.feeEstimator.EstimateFeePerVSize(3) + feePerKw, err := c.server.cc.feeEstimator.EstimateFeePerKW(3) if err != nil { return err } @@ -88,8 +88,9 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey, // TODO(halseth): make configurable? minHtlc := lnwire.NewMSatFromSatoshis(1) - updateStream, errChan := c.server.OpenChannel(target, amt, 0, - minHtlc, feePerVSize, false, 0) + updateStream, errChan := c.server.OpenChannel( + target, amt, 0, minHtlc, feePerKw, false, 0, + ) select { case err := <-errChan: diff --git a/routing/chainview/interface_test.go b/routing/chainview/interface_test.go index 7691daf14..d00950347 100644 --- a/routing/chainview/interface_test.go +++ b/routing/chainview/interface_test.go @@ -92,7 +92,7 @@ func getTestTXID(miner *rpctest.Harness) (*chainhash.Hash, error) { PkScript: script, }, } - return miner.SendOutputs(outputs, 10) + return miner.SendOutputs(outputs, 2500) } func locateOutput(tx *wire.MsgTx, script []byte) (*wire.OutPoint, *wire.TxOut, error) { diff --git a/server.go b/server.go index 233fe2d54..76d54512f 100644 --- a/server.go +++ b/server.go @@ -2537,7 +2537,7 @@ type openChanReq struct { pushAmt lnwire.MilliSatoshi - fundingFeePerVSize lnwallet.SatPerVByte + fundingFeePerKw lnwallet.SatPerKWeight private bool @@ -2685,7 +2685,7 @@ func (s *server) DisconnectPeer(pubKey *btcec.PublicKey) error { // NOTE: This function is safe for concurrent access. func (s *server) OpenChannel(nodeKey *btcec.PublicKey, localAmt btcutil.Amount, pushAmt, minHtlc lnwire.MilliSatoshi, - fundingFeePerVSize lnwallet.SatPerVByte, private bool, + fundingFeePerKw lnwallet.SatPerKWeight, private bool, remoteCsvDelay uint16) (chan *lnrpc.OpenStatusUpdate, chan error) { // The updateChan will have a buffer of 2, since we expect a @@ -2723,9 +2723,9 @@ func (s *server) OpenChannel(nodeKey *btcec.PublicKey, // If the fee rate wasn't specified, then we'll use a default // confirmation target. - if fundingFeePerVSize == 0 { + if fundingFeePerKw == 0 { estimator := s.cc.feeEstimator - fundingFeePerVSize, err = estimator.EstimateFeePerVSize(6) + fundingFeePerKw, err = estimator.EstimateFeePerKW(6) if err != nil { errChan <- err return updateChan, errChan @@ -2737,16 +2737,16 @@ func (s *server) OpenChannel(nodeKey *btcec.PublicKey, // instead of blocking on this request which is exported as a // synchronous request to the outside world. req := &openChanReq{ - targetPubkey: nodeKey, - chainHash: *activeNetParams.GenesisHash, - localFundingAmt: localAmt, - fundingFeePerVSize: fundingFeePerVSize, - pushAmt: pushAmt, - private: private, - minHtlc: minHtlc, - remoteCsvDelay: remoteCsvDelay, - updates: updateChan, - err: errChan, + targetPubkey: nodeKey, + chainHash: *activeNetParams.GenesisHash, + localFundingAmt: localAmt, + fundingFeePerKw: fundingFeePerKw, + pushAmt: pushAmt, + private: private, + minHtlc: minHtlc, + remoteCsvDelay: remoteCsvDelay, + updates: updateChan, + err: errChan, } // TODO(roasbeef): pass in chan that's closed if/when funding succeeds diff --git a/test_utils.go b/test_utils.go index 0de7f0f80..f6338bfb5 100644 --- a/test_utils.go +++ b/test_utils.go @@ -201,12 +201,11 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, return nil, nil, nil, nil, err } - estimator := &lnwallet.StaticFeeEstimator{FeeRate: 50} - feePerVSize, err := estimator.EstimateFeePerVSize(1) + estimator := &lnwallet.StaticFeeEstimator{FeePerKW: 12500} + feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { return nil, nil, nil, nil, err } - feePerKw := feePerVSize.FeePerKWeight() // TODO(roasbeef): need to factor in commit fee? aliceCommit := channeldb.ChannelCommitment{ From 336f1c156eeec4175d74f3d8670863e5e77e68af Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Jul 2018 18:39:38 -0700 Subject: [PATCH 4/4] rpcserver: convert sat/vbyte fee rates input by the user to sat/kw In this commit, we explicitly convert sat/vbyte fee rates input by the user to sat/kw. We do this as users are typically more accustomed to sat/vbyte fee rates, rather than sat/kw. --- rpcserver.go | 80 +++++++++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 74ee82757..acf58921d 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -12,11 +12,8 @@ import ( "sort" "strings" "sync" - "time" - - "gopkg.in/macaroon-bakery.v2/bakery" - "sync/atomic" + "time" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec" @@ -37,6 +34,7 @@ import ( "github.com/lightningnetwork/lnd/zpay32" "github.com/tv42/zbase32" "golang.org/x/net/context" + "gopkg.in/macaroon-bakery.v2/bakery" ) const ( @@ -402,7 +400,7 @@ func addrPairsToOutputs(addrPairs map[string]int64) ([]*wire.TxOut, error) { // more addresses specified in the passed payment map. The payment map maps an // address to a specified output value to be sent to that address. func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64, - feeRate lnwallet.SatPerVByte) (*chainhash.Hash, error) { + feeRate lnwallet.SatPerKWeight) (*chainhash.Hash, error) { outputs, err := addrPairsToOutputs(paymentMap) if err != nil { @@ -412,18 +410,18 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64, return r.server.cc.wallet.SendOutputs(outputs, feeRate) } -// determineFeePerVSize will determine the fee in sat/vbyte that should be paid -// given an estimator, a confirmation target, and a manual value for sat/byte. -// A value is chosen based on the two free parameters as one, or both of them -// can be zero. -func determineFeePerVSize(feeEstimator lnwallet.FeeEstimator, targetConf int32, - feePerByte int64) (lnwallet.SatPerVByte, error) { +// determineFeePerKw will determine the fee in sat/kw that should be paid given +// an estimator, a confirmation target, and a manual value for sat/byte. A value +// is chosen based on the two free parameters as one, or both of them can be +// zero. +func determineFeePerKw(feeEstimator lnwallet.FeeEstimator, targetConf int32, + feePerByte int64) (lnwallet.SatPerKWeight, error) { switch { // If the target number of confirmations is set, then we'll use that to // consult our fee estimator for an adequate fee. case targetConf != 0: - feePerVSize, err := feeEstimator.EstimateFeePerVSize( + feePerKw, err := feeEstimator.EstimateFeePerKW( uint32(targetConf), ) if err != nil { @@ -431,22 +429,24 @@ func determineFeePerVSize(feeEstimator lnwallet.FeeEstimator, targetConf int32, "estimator: %v", err) } - return feePerVSize, nil + return feePerKw, nil // If a manual sat/byte fee rate is set, then we'll use that directly. + // We'll need to convert it to sat/kw as this is what we use internally. case feePerByte != 0: - return lnwallet.SatPerVByte(feePerByte), nil + feePerKB := lnwallet.SatPerKVByte(feePerByte * 1000) + return feePerKB.FeePerKWeight(), nil // Otherwise, we'll attempt a relaxed confirmation target for the // transaction default: - feePerVSize, err := feeEstimator.EstimateFeePerVSize(6) + feePerKw, err := feeEstimator.EstimateFeePerKW(6) if err != nil { - return 0, fmt.Errorf("unable to query fee "+ - "estimator: %v", err) + return 0, fmt.Errorf("unable to query fee estimator: "+ + "%v", err) } - return feePerVSize, nil + return feePerKw, nil } } @@ -457,18 +457,18 @@ func (r *rpcServer) SendCoins(ctx context.Context, // Based on the passed fee related parameters, we'll determine an // appropriate fee rate for this transaction. - feeRate, err := determineFeePerVSize( + feePerKw, err := determineFeePerKw( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return nil, err } - rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/vbyte=%v", - in.Addr, btcutil.Amount(in.Amount), int64(feeRate)) + rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/kw=%v", in.Addr, + btcutil.Amount(in.Amount), int64(feePerKw)) paymentMap := map[string]int64{in.Addr: in.Amount} - txid, err := r.sendCoinsOnChain(paymentMap, feeRate) + txid, err := r.sendCoinsOnChain(paymentMap, feePerKw) if err != nil { return nil, err } @@ -484,18 +484,18 @@ func (r *rpcServer) SendMany(ctx context.Context, in *lnrpc.SendManyRequest) (*lnrpc.SendManyResponse, error) { // Based on the passed fee related parameters, we'll determine an - // approriate fee rate for this transaction. - feeRate, err := determineFeePerVSize( + // appropriate fee rate for this transaction. + feePerKw, err := determineFeePerKw( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return nil, err } - rpcsLog.Infof("[sendmany] outputs=%v, sat/vbyte=%v", - spew.Sdump(in.AddrToAmount), int64(feeRate)) + rpcsLog.Infof("[sendmany] outputs=%v, sat/kw=%v", + spew.Sdump(in.AddrToAmount), int64(feePerKw)) - txid, err := r.sendCoinsOnChain(in.AddrToAmount, feeRate) + txid, err := r.sendCoinsOnChain(in.AddrToAmount, feePerKw) if err != nil { return nil, err } @@ -794,15 +794,15 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest, // Based on the passed fee related parameters, we'll determine an // appropriate fee rate for the funding transaction. - feeRate, err := determineFeePerVSize( + feeRate, err := determineFeePerKw( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return err } - rpcsLog.Debugf("[openchannel]: using fee of %v sat/vbyte for funding "+ - "tx", int64(feeRate)) + rpcsLog.Debugf("[openchannel]: using fee of %v sat/kw for funding tx", + int64(feeRate)) // Instruct the server to trigger the necessary events to attempt to // open a new channel. A stream is returned in place, this stream will @@ -925,14 +925,14 @@ func (r *rpcServer) OpenChannelSync(ctx context.Context, // Based on the passed fee related parameters, we'll determine an // appropriate fee rate for the funding transaction. - feeRate, err := determineFeePerVSize( + feeRate, err := determineFeePerKw( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return nil, err } - rpcsLog.Tracef("[openchannel] target sat/vbyte for funding tx: %v", + rpcsLog.Tracef("[openchannel] target sat/kw for funding tx: %v", int64(feeRate)) updateChan, errChan := r.server.OpenChannel( @@ -1109,25 +1109,16 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, // Based on the passed fee related parameters, we'll determine // an appropriate fee rate for the cooperative closure // transaction. - feeRate, err := determineFeePerVSize( + feeRate, err := determineFeePerKw( r.server.cc.feeEstimator, in.TargetConf, in.SatPerByte, ) if err != nil { return err } - rpcsLog.Debugf("Target sat/vbyte for closing transaction: %v", + rpcsLog.Debugf("Target sat/kw for closing transaction: %v", int64(feeRate)) - if feeRate == 0 { - // If the fee rate returned isn't usable, then we'll - // fall back to a lax fee estimate. - feeRate, err = r.server.cc.feeEstimator.EstimateFeePerVSize(6) - if err != nil { - return err - } - } - // Before we attempt the cooperative channel closure, we'll // examine the channel to ensure that it doesn't have a // lingering HTLC. @@ -1140,9 +1131,8 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, // cooperative channel closure. So we'll forward the request to // the htlc switch which will handle the negotiation and // broadcast details. - feePerKw := feeRate.FeePerKWeight() updateChan, errChan = r.server.htlcSwitch.CloseLink( - chanPoint, htlcswitch.CloseRegular, feePerKw, + chanPoint, htlcswitch.CloseRegular, feeRate, ) } out: