From 701b999463ede037066f07e57eec995e03d90dd7 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 5 Jul 2022 21:49:10 +0200 Subject: [PATCH] multi: calculate locked value based on account version --- cmd/pool/debug.go | 2 ++ marshaler.go | 4 +++- order/interfaces.go | 36 +++++++++++++---------------- order/interfaces_test.go | 49 ++++++++++++++++++++++------------------ order/manager.go | 4 ++-- order/mock_interfaces.go | 8 +++---- rpcserver.go | 19 +++++++++++++--- 7 files changed, 70 insertions(+), 52 deletions(-) diff --git a/cmd/pool/debug.go b/cmd/pool/debug.go index 9c3a57b..1a4362b 100644 --- a/cmd/pool/debug.go +++ b/cmd/pool/debug.go @@ -8,6 +8,7 @@ import ( "path/filepath" "github.com/lightninglabs/pool" + "github.com/lightninglabs/pool/account" "github.com/lightninglabs/pool/auctioneer" "github.com/lightninglabs/pool/clientdb" "github.com/lightninglabs/pool/order" @@ -123,6 +124,7 @@ func dumpOrders(ctx *cli.Context) error { UnitsUnfulfilled: uint32(dbDetails.UnitsUnfulfilled), ReservedValueSat: uint64(dbOrder.ReservedValue( terms.NewLinearFeeSchedule(0, 0), + account.VersionInitialNoVersion, )), CreationTimestampNs: uint64(0), MinUnitsMatch: uint32( diff --git a/marshaler.go b/marshaler.go index 64c2a2b..b566f71 100644 --- a/marshaler.go +++ b/marshaler.go @@ -85,7 +85,9 @@ func (m *marshaler) MarshallAccountsWithAvailableBalance(ctx context.Context, continue } - debitAmt += o.ReservedValue(auctionFeeSchedule) + debitAmt += o.ReservedValue( + auctionFeeSchedule, acct.Version, + ) } accountDebits[acctKey] = debitAmt diff --git a/order/interfaces.go b/order/interfaces.go index 89756a1..8f8214b 100644 --- a/order/interfaces.go +++ b/order/interfaces.go @@ -276,12 +276,9 @@ type Order interface { // ReservedValue returns the maximum value that could be deducted from // 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 + // reserved to ensure the trader can afford it. + ReservedValue(feeSchedule terms.FeeSchedule, + accountVersion account.Version) btcutil.Amount } // Kit stores all the common fields that are used to express the decision to @@ -457,15 +454,12 @@ 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. This always uses the worst-case fee estimation using the version -// 0 p2wsh script witness size calculation for the account spend. +// being paid. // // 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 { +func reservedValue(o Order, perMatchDelta func(btcutil.Amount) btcutil.Amount, + accountVersion account.Version) btcutil.Amount { // If this order is in a state where it cannot be matched, return 0. if o.Details().State.Archived() { @@ -498,12 +492,10 @@ func reservedValue(o Order, // Subtract the worst case chain fee from the balance. maxFeeRate := o.Details().MaxBatchFeeRate balanceDelta -= maxNumMatches * EstimateTraderFee( - 1, maxFeeRate, account.VersionInitialNoVersion, + 1, maxFeeRate, accountVersion, ) if rem > 0 { - balanceDelta -= EstimateTraderFee( - 1, maxFeeRate, account.VersionInitialNoVersion, - ) + balanceDelta -= EstimateTraderFee(1, maxFeeRate, accountVersion) } // If the balance delta is negative, meaning this order will decrease @@ -519,7 +511,9 @@ func reservedValue(o Order, // ReservedValue returns the maximum value that could be deducted from a single // account if the ask is matched under the worst case fee conditions. -func (a *Ask) ReservedValue(feeSchedule terms.FeeSchedule) btcutil.Amount { +func (a *Ask) ReservedValue(feeSchedule terms.FeeSchedule, + accountVersion account.Version) 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. clearingPrice := FixedRatePremium(a.FixedRate) @@ -529,7 +523,7 @@ func (a *Ask) ReservedValue(feeSchedule terms.FeeSchedule) btcutil.Amount { feeSchedule, clearingPrice, amt, a.LeaseDuration, ) return delta - }) + }, accountVersion) } // NodeTier an enum-like variable that presents which "tier" a node is in. A @@ -693,7 +687,9 @@ func (b *Bid) Digest() ([hashSize]byte, error) { // ReservedValue returns the maximum value that could be deducted from a single // account if the bid is matched under the worst case fee conditions. -func (b *Bid) ReservedValue(feeSchedule terms.FeeSchedule) btcutil.Amount { +func (b *Bid) ReservedValue(feeSchedule terms.FeeSchedule, + accountVersion account.Version) 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 // bidder. @@ -705,7 +701,7 @@ func (b *Bid) ReservedValue(feeSchedule terms.FeeSchedule) btcutil.Amount { b.LeaseDuration, ) return delta - }) + }, accountVersion) } // ValidateSelfChanBalance makes sure that all conditions to use the diff --git a/order/interfaces_test.go b/order/interfaces_test.go index ef4b260..d0b797a 100644 --- a/order/interfaces_test.go +++ b/order/interfaces_test.go @@ -15,11 +15,11 @@ func TestOrderReservedValue(t *testing.T) { simpleFeeSchedule := terms.NewLinearFeeSchedule(1, 100) - testCases := []struct { - name string - order Order - accountVersion account.Version - }{ + type testCase struct { + name string + order Order + } + testCases := []*testCase{ { name: "bid 1 unit", order: &Bid{ @@ -204,9 +204,7 @@ func TestOrderReservedValue(t *testing.T) { }, } - for i, tc := range testCases { - tc := tc - + runTestCase := func(t *testing.T, tc *testCase, v account.Version) { // Count the worst case we will expect. var expValue btcutil.Amount @@ -238,7 +236,7 @@ func TestOrderReservedValue(t *testing.T) { LumpSumPremium(amt, o.LeaseDuration) exeFee := executionFee(amt, simpleFeeSchedule) chainFee := EstimateTraderFee( - 1, o.MaxBatchFeeRate, tc.accountVersion, + 1, o.MaxBatchFeeRate, v, ) // For bids the lump sum, chain fee and the @@ -276,7 +274,7 @@ func TestOrderReservedValue(t *testing.T) { LumpSumPremium(amt, 144) exeFee := executionFee(amt, simpleFeeSchedule) chainFee := EstimateTraderFee( - 1, o.MaxBatchFeeRate, tc.accountVersion, + 1, o.MaxBatchFeeRate, v, ) // For asks the amount itself, the chain fee @@ -294,18 +292,25 @@ func TestOrderReservedValue(t *testing.T) { expValue = 0 } - // Check the value returned. - i := i - t.Run(tc.name, func(t *testing.T) { - val := tc.order.ReservedValue(simpleFeeSchedule) - if val < 0 { - t.Fatalf("reserved value cannot be "+ - "negative: %v", val) - } - if val != expValue { - t.Fatalf("test #%v: expected reserved value "+ - "%v, got '%v'", i, expValue, val) - } + val := tc.order.ReservedValue(simpleFeeSchedule, v) + if val < 0 { + t.Fatalf("reserved value cannot be "+ + "negative: %v", val) + } + if val != expValue { + t.Fatalf("%s: expected reserved value "+ + "%v, got '%v'", tc.name, expValue, val) + } + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name+"/version_0", func(t *testing.T) { + runTestCase(t, tc, account.VersionInitialNoVersion) + }) + t.Run(tc.name+"/version_1", func(t *testing.T) { + runTestCase(t, tc, account.VersionTaprootEnabled) }) } } diff --git a/order/manager.go b/order/manager.go index 8897bf2..6375e2d 100644 --- a/order/manager.go +++ b/order/manager.go @@ -317,7 +317,7 @@ func (m *manager) validateOrder(order Order, acct *account.Account, var acctKey [33]byte copy(acctKey[:], acct.TraderKey.PubKey.SerializeCompressed()) feeSchedule := terms.FeeSchedule() - reserved := order.ReservedValue(feeSchedule) + reserved := order.ReservedValue(feeSchedule, acct.Version) for _, o := range dbOrders { // Only tally the reserved balance if this order was submitted // by this account. @@ -325,7 +325,7 @@ func (m *manager) validateOrder(order Order, acct *account.Account, continue } - reserved += o.ReservedValue(feeSchedule) + reserved += o.ReservedValue(feeSchedule, acct.Version) } if acct.Value < reserved { diff --git a/order/mock_interfaces.go b/order/mock_interfaces.go index 8722a37..b160a2d 100644 --- a/order/mock_interfaces.go +++ b/order/mock_interfaces.go @@ -81,17 +81,17 @@ func (mr *MockOrderMockRecorder) Nonce() *gomock.Call { } // ReservedValue mocks base method. -func (m *MockOrder) ReservedValue(feeSchedule terms.FeeSchedule) btcutil.Amount { +func (m *MockOrder) ReservedValue(feeSchedule terms.FeeSchedule, accountVersion account.Version) btcutil.Amount { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReservedValue", feeSchedule) + ret := m.ctrl.Call(m, "ReservedValue", feeSchedule, accountVersion) ret0, _ := ret[0].(btcutil.Amount) return ret0 } // ReservedValue indicates an expected call of ReservedValue. -func (mr *MockOrderMockRecorder) ReservedValue(feeSchedule interface{}) *gomock.Call { +func (mr *MockOrderMockRecorder) ReservedValue(feeSchedule, accountVersion interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReservedValue", reflect.TypeOf((*MockOrder)(nil).ReservedValue), feeSchedule) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReservedValue", reflect.TypeOf((*MockOrder)(nil).ReservedValue), feeSchedule, accountVersion) } // Type mocks base method. diff --git a/rpcserver.go b/rpcserver.go index 5da3e3f..1202263 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1564,6 +1564,19 @@ func (s *rpcServer) ListOrders(ctx context.Context, feeSchedule = auctioneerTerms.FeeSchedule() } + // We also need a map of all account versions for the locked value + // estimation below. + accountVersions := make(map[[33]byte]account.Version) + allAccounts, err := s.server.db.Accounts() + if err != nil { + return nil, fmt.Errorf("error querying accounts: %v", err) + } + for _, acct := range allAccounts { + var rawKey [33]byte + copy(rawKey[:], acct.TraderKey.PubKey.SerializeCompressed()) + accountVersions[rawKey] = acct.Version + } + // The RPC is split by order type so we have to separate them now. asks := make([]*poolrpc.Ask, 0, len(creationEvents)) bids := make([]*poolrpc.Bid, 0, len(creationEvents)) @@ -1616,9 +1629,9 @@ func (s *rpcServer) ListOrders(ctx context.Context, State: orderState, Units: uint32(dbDetails.Units), UnitsUnfulfilled: uint32(dbDetails.UnitsUnfulfilled), - ReservedValueSat: uint64( - dbOrder.ReservedValue(feeSchedule), - ), + ReservedValueSat: uint64(dbOrder.ReservedValue( + feeSchedule, accountVersions[dbDetails.AcctKey], + )), CreationTimestampNs: uint64(evt.Timestamp().UnixNano()), Events: rpcEvents, MinUnitsMatch: uint32(dbOrder.Details().MinUnitsMatch),