multi: calculate locked value based on account version

This commit is contained in:
Oliver Gugger 2022-07-05 21:49:10 +02:00
parent d109f913c4
commit 701b999463
No known key found for this signature in database
GPG key ID: 8E4256593F177720
7 changed files with 70 additions and 52 deletions

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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)
})
}
}

View file

@ -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 {

View file

@ -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.

View file

@ -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),