From 1e7c00a5866134069817d68bfc822f5aaf5cb7b5 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 30 Jun 2022 22:17:32 +0200 Subject: [PATCH] order+rpcserver: use account version in fee estimation --- order/batch_verifier.go | 2 +- order/interfaces.go | 25 ++++++++++++++++++------- order/interfaces_test.go | 10 ++++++---- order/tradingfees.go | 28 ++++++++++++++++++++++------ rpcserver.go | 7 +++++++ 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/order/batch_verifier.go b/order/batch_verifier.go index 95ff393..c042320 100644 --- a/order/batch_verifier.go +++ b/order/batch_verifier.go @@ -230,7 +230,7 @@ func (v *batchVerifier) Verify(batch *Batch, bestHeight uint32) error { // Now that we know how many channels were created from the // given account, let's also account for the chain fees. - tally.ChainFees(batch.BatchTxFeeRate) + tally.ChainFees(batch.BatchTxFeeRate, acct.Version) // Even if the account output is dust, we should arrive at the // same number with our tally as the server. diff --git a/order/interfaces.go b/order/interfaces.go index 41cafcc..e165845 100644 --- a/order/interfaces.go +++ b/order/interfaces.go @@ -275,8 +275,12 @@ type Order interface { Digest() ([hashSize]byte, error) // ReservedValue returns the maximum value that could be deducted from - // the account if the order is is matched, and therefore has to be - // reserved to ensure the trader can afford it. + // the account if the order is matched, and therefore has to be + // reserved to ensure the trader can afford it. This always uses the + // worst-case fee estimation using the version 0 p2wsh script witness + // size calculation for the account spend. + // + // TODO(guggero): Update to be more precise for p2tr accounts. ReservedValue(feeSchedule terms.FeeSchedule) btcutil.Amount } @@ -453,10 +457,13 @@ func (a *Ask) Digest() ([hashSize]byte, error) { // account if the given order is matched under the worst case fee conditions. // This usually means the order is partially matched with the minimum match // size, all in different batches, leading to maximum chain and execution fees -// being paid. +// being paid. This always uses the worst-case fee estimation using the version +// 0 p2wsh script witness size calculation for the account spend. // // The passed function should be set to either calculate the maker or taker // balance delta for a single match of the given amount. +// +// TODO(guggero): Update to be more precise for p2tr accounts. func reservedValue(o Order, perMatchDelta func(btcutil.Amount) btcutil.Amount) btcutil.Amount { @@ -490,9 +497,13 @@ func reservedValue(o Order, // Subtract the worst case chain fee from the balance. maxFeeRate := o.Details().MaxBatchFeeRate - balanceDelta -= maxNumMatches * EstimateTraderFee(1, maxFeeRate) + balanceDelta -= maxNumMatches * EstimateTraderFee( + 1, maxFeeRate, account.VersionInitialNoVersion, + ) if rem > 0 { - balanceDelta -= EstimateTraderFee(1, maxFeeRate) + balanceDelta -= EstimateTraderFee( + 1, maxFeeRate, account.VersionInitialNoVersion, + ) } // If the balance delta is negative, meaning this order will decrease @@ -507,7 +518,7 @@ func reservedValue(o Order, } // ReservedValue returns the maximum value that could be deducted from a single -// account if the ask is is matched under the worst case fee conditions. +// account if the ask is matched under the worst case fee conditions. func (a *Ask) ReservedValue(feeSchedule terms.FeeSchedule) btcutil.Amount { // For an ask the clearing price will be no lower than the ask's fixed // rate, resulting in the smallest gain for the asker. @@ -681,7 +692,7 @@ func (b *Bid) Digest() ([hashSize]byte, error) { } // ReservedValue returns the maximum value that could be deducted from a single -// account if the bid is is matched under the worst case fee conditions. +// account if the bid is matched under the worst case fee conditions. func (b *Bid) ReservedValue(feeSchedule terms.FeeSchedule) btcutil.Amount { // For a bid, the final clearing price is never higher that the bid's // fixed rate, resulting in the highest possible premium paid by the diff --git a/order/interfaces_test.go b/order/interfaces_test.go index 8401fad..ef4b260 100644 --- a/order/interfaces_test.go +++ b/order/interfaces_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcutil" + "github.com/lightninglabs/pool/account" "github.com/lightninglabs/pool/terms" ) @@ -15,8 +16,9 @@ func TestOrderReservedValue(t *testing.T) { simpleFeeSchedule := terms.NewLinearFeeSchedule(1, 100) testCases := []struct { - name string - order Order + name string + order Order + accountVersion account.Version }{ { name: "bid 1 unit", @@ -236,7 +238,7 @@ func TestOrderReservedValue(t *testing.T) { LumpSumPremium(amt, o.LeaseDuration) exeFee := executionFee(amt, simpleFeeSchedule) chainFee := EstimateTraderFee( - 1, o.MaxBatchFeeRate, + 1, o.MaxBatchFeeRate, tc.accountVersion, ) // For bids the lump sum, chain fee and the @@ -274,7 +276,7 @@ func TestOrderReservedValue(t *testing.T) { LumpSumPremium(amt, 144) exeFee := executionFee(amt, simpleFeeSchedule) chainFee := EstimateTraderFee( - 1, o.MaxBatchFeeRate, + 1, o.MaxBatchFeeRate, tc.accountVersion, ) // For asks the amount itself, the chain fee diff --git a/order/tradingfees.go b/order/tradingfees.go index 9742455..0af4d10 100644 --- a/order/tradingfees.go +++ b/order/tradingfees.go @@ -3,6 +3,7 @@ package order import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcutil" + "github.com/lightninglabs/pool/account" "github.com/lightninglabs/pool/poolscript" "github.com/lightninglabs/pool/terms" "github.com/lightningnetwork/lnd/input" @@ -65,8 +66,8 @@ func PerBlockPremium(amt btcutil.Amount, fixedRate uint32) float64 { // EstimateTraderFee calculates the chain fees a trader has to pay for their // part of a batch transaction. The more outputs a trader creates (channels), // the higher fee they will pay. -func EstimateTraderFee(numTraderChans uint32, - feeRate chainfee.SatPerKWeight) btcutil.Amount { +func EstimateTraderFee(numTraderChans uint32, feeRate chainfee.SatPerKWeight, + accountVersion account.Version) btcutil.Amount { var weightEstimate int64 @@ -90,7 +91,13 @@ func EstimateTraderFee(numTraderChans uint32, // Finally, we tack on the size of the witness spending the account // outpoint. - weightEstimate += poolscript.MultiSigWitnessSize + switch accountVersion { + case account.VersionTaprootEnabled: + weightEstimate += poolscript.TaprootMultiSigWitnessSize + + default: + weightEstimate += poolscript.MultiSigWitnessSize + } return feeRate.FeeForWeight(weightEstimate) } @@ -124,7 +131,12 @@ func NewQuote(amt, minChanAmt btcutil.Amount, rate FixedRatePremium, exeFee := schedule.BaseFee() + schedule.ExecutionFee(amt) maxNumMatches := amt / minChanAmt - chainFee := maxNumMatches * EstimateTraderFee(1, maxBatchFeeRate) + + // For an order quote we always return the worst case fees, which means + // with a legacy account. + chainFee := maxNumMatches * EstimateTraderFee( + 1, maxBatchFeeRate, account.VersionInitialNoVersion, + ) return &Quote{ TotalPremium: rate.LumpSumPremium(amt, leaseDuration), @@ -251,8 +263,12 @@ func (t *AccountTally) CalcTakerDelta(feeSchedule terms.FeeSchedule, // ChainFees estimates the chain fees that need to be paid for the number of // channels created for this account and subtracts that value from the ending // balance. -func (t *AccountTally) ChainFees(feeRate chainfee.SatPerKWeight) { - chainFeesDue := EstimateTraderFee(t.NumChansCreated, feeRate) +func (t *AccountTally) ChainFees(feeRate chainfee.SatPerKWeight, + accountVersion account.Version) { + + chainFeesDue := EstimateTraderFee( + t.NumChansCreated, feeRate, accountVersion, + ) t.EndingBalance -= chainFeesDue } diff --git a/rpcserver.go b/rpcserver.go index e76999c..dc23319 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2270,8 +2270,15 @@ func (s *rpcServer) prepareLeasesResponse(ctx context.Context, // Estimate the chain fees paid for the number of // channels created in this batch and tally them. + // + // TODO(guggero): This is just an approximation! We + // should properly calculate the fees _per account_ as + // that's what we do on the server side. Then we can + // also take a look at the actual account version at the + // time of the batch. chainFee := order.EstimateTraderFee( uint32(numChans), batch.BatchTxFeeRate, + account.VersionInitialNoVersion, ) // We'll need to compute the chain fee paid for each