Skip to content

Commit

Permalink
multi: update order/batch verification for outbound liquidity auctions
Browse files Browse the repository at this point in the history
  • Loading branch information
positiveblue committed Oct 6, 2022
1 parent 17bdb49 commit dd2a017
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 79 deletions.
5 changes: 3 additions & 2 deletions cmd/pool/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,8 +696,9 @@ func parseBaseBid(ctx *cli.Context) (*poolrpc.Bid, *sidecar.Ticket, error) {
if ctx.IsSet("self_chan_balance") {
bid.SelfChanBalance = ctx.Uint64("self_chan_balance")
bidAmt := btcutil.Amount(bid.Details.Amt)
err := sidecar.CheckOfferParams(
bidAmt, btcutil.Amount(bid.SelfChanBalance),
err := order.CheckOfferParams(
order.AuctionType(bid.Details.AuctionType), bidAmt,
btcutil.Amount(bid.SelfChanBalance),
order.BaseSupplyUnit,
)
if err != nil {
Expand Down
4 changes: 3 additions & 1 deletion funding/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,9 @@ func (m *Manager) OfferSidecar(ctx context.Context, capacity,
bid *order.Bid, auto bool) (*sidecar.Ticket, error) {

// Make sure the capacity and push amounts are sane.
err := sidecar.CheckOfferParams(capacity, pushAmt, order.BaseSupplyUnit)
err := order.CheckOfferParams(
bid.AuctionType, capacity, pushAmt, order.BaseSupplyUnit,
)
if err != nil {
return nil, err
}
Expand Down
45 changes: 42 additions & 3 deletions funding/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -805,28 +805,67 @@ func TestOfferSidecarValidation(t *testing.T) {
name string
capacity btcutil.Amount
pushAmt btcutil.Amount
bid *order.Bid
expectedErr string
}{{
name: "empty capacity",
bid: &order.Bid{},
expectedErr: "channel capacity must be positive multiple of",
}, {
name: "invalid capacity",
capacity: 123,
bid: &order.Bid{},
expectedErr: "channel capacity must be positive multiple of",
}, {
name: "invalid push amount",
capacity: 100000,
pushAmt: 100001,
bid: &order.Bid{},
expectedErr: "self channel balance must be smaller than " +
"or equal to capacity",
}, {
name: "invalid push amount for outbound market",
capacity: 100000,
pushAmt: 0,
bid: &order.Bid{
Kit: order.Kit{
AuctionType: order.BTCOutboundLiquidity,
},
},
expectedErr: "self channel balance must be greater",
}}

privKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
desc := &keychain.KeyDescriptor{
PubKey: privKey.PubKey(),
}

// We'll need a formally valid signature to pass the parsing. So we'll
// just create a dummy signature from a random key pair.
hash := sha256.New()
_, _ = hash.Write([]byte("foo"))
digest := hash.Sum(nil)
sig := ecdsa.Sign(privKey, digest)

h.mgr.cfg.NodePubKey = privKey.PubKey()
h.signerMock.Signature = sig.Serialize()
var nodeKeyRaw [33]byte
copy(nodeKeyRaw[:], privKey.PubKey().SerializeCompressed())

for _, testCase := range negativeCases {

_, err := h.mgr.OfferSidecar(
context.Background(), testCase.capacity,
testCase.pushAmt, 2016, nil, nil, false,
testCase.pushAmt, 2016, desc, testCase.bid, false,
)
require.Error(t, err)
require.Contains(t, err.Error(), testCase.expectedErr)
fmt.Println(testCase.expectedErr)
if testCase.expectedErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), testCase.expectedErr)
continue
}
require.NoError(t, err)
}
}

Expand Down
31 changes: 25 additions & 6 deletions order/batch_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ func (v *batchVerifier) Verify(batch *Batch, bestHeight uint32) error {
),
}

case unitsFilled < ourOrder.Details().MinUnitsMatch:
case ourOrder.Details().AuctionType != BTCOutboundLiquidity &&
unitsFilled < ourOrder.Details().MinUnitsMatch:

return &MismatchErr{
msg: fmt.Sprintf("invalid units to be filled "+
"for order %v. matched %d units, but "+
Expand Down Expand Up @@ -283,6 +285,13 @@ func (v *batchVerifier) validateMatchedOrder(tally *AccountTally,
"orders", ourOrder.Nonce())
}

// Auction types must match.
auctionType := ourOrder.Details().AuctionType
if auctionType != otherOrder.Order.Details().AuctionType {
return fmt.Errorf("order %v did not match the same auction "+
"type", ourOrder.Nonce())
}

// Make sure we weren't matched to our own order.
if otherOrder.NodeKey == v.ourNodePubkey {
return fmt.Errorf("other order is an order from our node")
Expand All @@ -305,10 +314,15 @@ func (v *batchVerifier) validateMatchedOrder(tally *AccountTally,
return fmt.Errorf("ask price greater than bid price")
}

makerAmt := otherOrder.UnitsFilled.ToSatoshis()
premiumAmt := makerAmt
if auctionType == BTCOutboundLiquidity {
premiumAmt += other.SelfChanBalance
}

// This match checks out, deduct it from the account's balance.
tally.CalcMakerDelta(
executionFee, clearingPrice,
otherOrder.UnitsFilled.ToSatoshis(),
executionFee, clearingPrice, makerAmt, premiumAmt,
other.LeaseDuration,
)

Expand All @@ -324,11 +338,16 @@ func (v *batchVerifier) validateMatchedOrder(tally *AccountTally,
return fmt.Errorf("ask price greater than bid price")
}

takerAmt := ours.SelfChanBalance
premiumAmt := otherOrder.UnitsFilled.ToSatoshis()
if auctionType == BTCOutboundLiquidity {
premiumAmt += takerAmt
}

// This match checks out, deduct it from the account's balance.
tally.CalcTakerDelta(
executionFee, clearingPrice,
otherOrder.UnitsFilled.ToSatoshis(),
ours.SelfChanBalance, ours.LeaseDuration,
executionFee, clearingPrice, takerAmt, premiumAmt,
ours.LeaseDuration,
)
}

Expand Down
44 changes: 39 additions & 5 deletions order/batch_verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,40 @@ func TestBatchVerifier(t *testing.T) {
return v.Verify(b, bestHeight)
},
},
{
name: "auction type mismatch",
expectedErr: "did not match the same auction type",
doVerify: func(v BatchVerifier, a *Ask, b1, b2 *Bid,
b *Batch) error {

a.Details().AuctionType = BTCInboundLiquidity
b1.Details().AuctionType = BTCOutboundLiquidity
return v.Verify(b, bestHeight)
},
},
{
// TODO(positiveblue): make a more specific test for
// btc outbound liquidity market.
name: "premiums are based on aucton type",
expectedErr: "ending balance 934",
doVerify: func(v BatchVerifier, a *Ask, b1, b2 *Bid,
b *Batch) error {

a.Details().AuctionType = BTCOutboundLiquidity
b1.Details().AuctionType = BTCOutboundLiquidity
b2.Details().AuctionType = BTCOutboundLiquidity
b.AccountDiffs[0].EndingBalance = 395_089
b.BatchTX.TxOut[2].Value = 395_089

// NOTE: the ask and bids would not be valid
// orders for the outbound liquidity market
// because they bigger than one unit but we are
// testing that premiums are calculated
// properly here.
b.AccountDiffs[1].EndingBalance = 934
return v.Verify(b, bestHeight)
},
},
}

// Run through all the test cases, creating a new, valid batch each
Expand Down Expand Up @@ -430,7 +464,7 @@ func TestBatchVerifier(t *testing.T) {
// 2000 * (200_000 * 5000 / 1_000_000_000) = 1000 sats premium
LeaseDuration: leaseDuration,
}),
SelfChanBalance: 50,
SelfChanBalance: 100_000,
}
pkScript := scriptForAcct(t, bigAcct)
// If account extension is supported we create the pkScript with
Expand All @@ -456,7 +490,7 @@ func TestBatchVerifier(t *testing.T) {
// Channel output for channel between ask and
// bid2.
{
Value: 200_050,
Value: 300_000,
PkScript: scriptForChan(
t, walletKit,
ask.MultiSigKeyLocator,
Expand All @@ -469,8 +503,8 @@ func TestBatchVerifier(t *testing.T) {
// bid1ExecFee - bid2ExecFee - chainFees
// - bid2SelfChanBalance
// 500_000 - 1000 - 1000 -
// 1_110 - 1_110 - 186 - 50
Value: 495_544,
// 1_110 - 1_110 - 186 - 100_000
Value: 395_594,
PkScript: pkScript,
},
},
Expand All @@ -485,7 +519,7 @@ func TestBatchVerifier(t *testing.T) {
AccountKey: acctKeyBig,
EndingState: stateRecreated,
OutpointIndex: 2,
EndingBalance: 495_544,
EndingBalance: 395_594,
},
{
AccountKeyRaw: acctIDSmall,
Expand Down
60 changes: 57 additions & 3 deletions order/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ func (a *Ask) ReservedValue(feeSchedule terms.FeeSchedule,

return reservedValue(a, func(amt btcutil.Amount) btcutil.Amount {
delta, _, _ := makerDelta(
feeSchedule, clearingPrice, amt, a.LeaseDuration,
feeSchedule, clearingPrice, amt, amt, a.LeaseDuration,
)
return delta
}, accountVersion)
Expand Down Expand Up @@ -824,6 +824,10 @@ func (b *Bid) ReservedValue(feeSchedule terms.FeeSchedule,
clearingPrice := FixedRatePremium(b.FixedRate)

return reservedValue(b, func(amt btcutil.Amount) btcutil.Amount {
if b.Details().AuctionType == BTCOutboundLiquidity {
amt += b.SelfChanBalance
}

delta, _, _ := takerDelta(
feeSchedule, clearingPrice, amt, b.SelfChanBalance,
b.LeaseDuration,
Expand All @@ -832,6 +836,56 @@ func (b *Bid) ReservedValue(feeSchedule terms.FeeSchedule,
}, accountVersion)
}

// CheckOfferParams makes sure the offer parameters of a sidecar ticket are
// valid and sane.
func CheckOfferParams(auctionType AuctionType, capacity, pushAmt,
baseSupplyUnit btcutil.Amount) error {

if capacity == 0 || capacity%baseSupplyUnit != 0 {
return fmt.Errorf("channel capacity must be positive multiple "+
"of %d", baseSupplyUnit)
}

if auctionType == BTCInboundLiquidity && pushAmt > capacity {
return fmt.Errorf("self channel balance must be smaller than " +
"or equal to capacity")
}

if auctionType == BTCOutboundLiquidity && pushAmt < baseSupplyUnit {
return fmt.Errorf("for participating in the outbound " +
"liquidity market self channel balance must be " +
"greater or equal than 100k")
}

return nil
}

// CheckOfferParamsForOrder makes sure that the order parameters in a
// sidecar offer are formally valid, sane and match the order parameters.
func CheckOfferParamsForOrder(auctionType AuctionType, offer sidecar.Offer,
bidAmt, bidMinUnitsMatch, baseSupplyUnit btcutil.Amount) error {

err := CheckOfferParams(
auctionType, offer.Capacity, offer.PushAmt, baseSupplyUnit,
)
if err != nil {
return err
}

if offer.Capacity != bidAmt {
return fmt.Errorf("invalid bid amount %v, must match sidecar "+
"ticket's capacity %v", bidAmt, offer.Capacity)
}

if offer.Capacity != bidMinUnitsMatch*baseSupplyUnit {
return fmt.Errorf("invalid min units match %v, must match "+
"sidecar ticket's capacity %v",
bidMinUnitsMatch*baseSupplyUnit, offer.Capacity)
}

return nil
}

// ValidateSelfChanBalance makes sure that all conditions to use the
// SelfChanBalance field on a bid order are met.
func (b *Bid) ValidateSelfChanBalance() error {
Expand All @@ -840,8 +894,8 @@ func (b *Bid) ValidateSelfChanBalance() error {
"order version")
}

if err := sidecar.CheckOfferParams(
b.Amt, b.SelfChanBalance, BaseSupplyUnit,
if err := CheckOfferParams(
b.AuctionType, b.Amt, b.SelfChanBalance, BaseSupplyUnit,
); err != nil {
return fmt.Errorf("invalid self chan balance: %v", err)
}
Expand Down
4 changes: 2 additions & 2 deletions order/interfaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)

// TestOrderReservedValue checks orders' ReservedValue merhod returning the
// TestOrderReservedValue checks orders' ReservedValue method returning the
// expected worst case value.
func TestOrderReservedValue(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -240,7 +240,7 @@ func TestOrderReservedValue(t *testing.T) {
1, o.MaxBatchFeeRate, v,
)

// For bids the lump sum, chain fee and the
// For bids the lump sum, chain fee and the
// execution fee must be reserved.
expValue += lumpSum + chainFee + exeFee
}
Expand Down
5 changes: 3 additions & 2 deletions order/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,9 @@ func (m *manager) validateAndSignTicketForOrder(ctx context.Context,

// The signature is valid! Let's now make sure the offer and the order
// parameters actually match.
err := sidecar.CheckOfferParamsForOrder(
o, bid.Amt, btcutil.Amount(bid.MinUnitsMatch), BaseSupplyUnit,
err := CheckOfferParamsForOrder(
bid.AuctionType, o, bid.Amt, btcutil.Amount(bid.MinUnitsMatch),
BaseSupplyUnit,
)
if err != nil {
return err
Expand Down
4 changes: 3 additions & 1 deletion order/rpc_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ func ParseRPCOrder(version, leaseDuration uint32,
return nil, errors.New("min units match must be greater than 0")

// The min units match must not exceed the total order units.
case details.MinUnitsMatch > uint32(kit.Units):
case kit.AuctionType != BTCOutboundLiquidity &&
details.MinUnitsMatch > uint32(kit.Units):

return nil, errors.New("min units match must not exceed " +
"total order units")
}
Expand Down
Loading

0 comments on commit dd2a017

Please sign in to comment.