mirror of
https://github.com/lightninglabs/pool.git
synced 2026-08-15 12:50:29 +02:00
As a preparation to avoid package import cycles when introducing more generic terms, we move the FeeSchedule interface and its LinearFeeSchedule implementation to the terms package.
544 lines
16 KiB
Go
544 lines
16 KiB
Go
package order
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightninglabs/llm/account"
|
|
"github.com/lightninglabs/llm/clmrpc"
|
|
"github.com/lightninglabs/llm/terms"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
)
|
|
|
|
// Nonce is a 32 byte pseudo randomly generated unique order ID.
|
|
type Nonce [32]byte
|
|
|
|
// String returns the hex encoded representation of the nonce.
|
|
func (n Nonce) String() string {
|
|
return hex.EncodeToString(n[:])
|
|
}
|
|
|
|
// Version is the version of an order. We don't use iota for the constants due
|
|
// to the order type being persisted to disk.
|
|
type Version uint32
|
|
|
|
const (
|
|
// VersionDefault is the default initial version of orders.
|
|
VersionDefault Version = 0
|
|
)
|
|
|
|
// Type is the type of an order. We don't use iota for the constants due to the
|
|
// order type being persisted to disk.
|
|
type Type uint8
|
|
|
|
const (
|
|
// TypeAsk is the constant to represent the "ask" order type.
|
|
TypeAsk Type = 0
|
|
|
|
// TypeBid is the constant to represent the "bid" order type.
|
|
TypeBid Type = 1
|
|
)
|
|
|
|
// String returns a human read-able string describing the passed order type.
|
|
func (t Type) String() string {
|
|
switch t {
|
|
|
|
case TypeAsk:
|
|
return "Ask"
|
|
|
|
case TypeBid:
|
|
return "Bid"
|
|
|
|
default:
|
|
return "<unknown>"
|
|
}
|
|
}
|
|
|
|
// State describes the different possible states of an order. We don't use iota
|
|
// for the constants due to the order state being persisted to disk.
|
|
type State uint8
|
|
|
|
const (
|
|
// StateSubmitted is the state an order has after it's been submitted
|
|
// successfully.
|
|
StateSubmitted State = 0
|
|
|
|
// StateCleared is the state an order has after it's been accepted as
|
|
// part of a batch but has not been executed yet.
|
|
StateCleared State = 1
|
|
|
|
// StatePartiallyFilled is the state an order has after some but not all
|
|
// parts of it have been filled.
|
|
StatePartiallyFilled State = 2
|
|
|
|
// StateExecuted is the state an order has after it has been matched
|
|
// with another order in the order book and fully processed.
|
|
StateExecuted State = 3
|
|
|
|
// StateCanceled is the state an order has after a user cancels the
|
|
// order manually.
|
|
StateCanceled State = 4
|
|
|
|
// StateExpired is the state an order has after it's maximum lifetime
|
|
// has passed.
|
|
StateExpired State = 5
|
|
|
|
// StateFailed is the state an order has if any irrecoverable error
|
|
// happens in its lifetime.
|
|
StateFailed State = 6
|
|
)
|
|
|
|
// String returns a human readable string representation of the order state.
|
|
func (s State) String() string {
|
|
switch s {
|
|
case StateSubmitted:
|
|
return "submitted"
|
|
|
|
case StateCleared:
|
|
return "cleared"
|
|
|
|
case StatePartiallyFilled:
|
|
return "partially_filled"
|
|
|
|
case StateExecuted:
|
|
return "executed"
|
|
|
|
case StateCanceled:
|
|
return "canceled"
|
|
|
|
case StateExpired:
|
|
return "expired"
|
|
|
|
case StateFailed:
|
|
return "failed"
|
|
|
|
default:
|
|
return fmt.Sprintf("unknown<%d>", s)
|
|
}
|
|
}
|
|
|
|
// Archived returns true if the order is in a state that is considered to be
|
|
// fully executed and no more modifications will be done to it.
|
|
func (s State) Archived() bool {
|
|
switch s {
|
|
case StateExecuted, StateCanceled, StateExpired, StateFailed:
|
|
return true
|
|
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
var (
|
|
// ErrInsufficientBalance is the error that is returned if an account
|
|
// has insufficient balance to perform a requested action.
|
|
ErrInsufficientBalance = errors.New("insufficient account balance")
|
|
|
|
// ZeroNonce is used to find out if a user-provided nonce is empty.
|
|
ZeroNonce Nonce
|
|
)
|
|
|
|
// Order is an interface to allow generic handling of both ask and bid orders
|
|
// by both store and manager.
|
|
type Order interface {
|
|
// Nonce is the unique identifier of each order and MUST be created by
|
|
// hashing a new random preimage for each new order. The nonce is what
|
|
// is signed in the order signature.
|
|
Nonce() Nonce
|
|
|
|
// Details returns the Kit of the order.
|
|
Details() *Kit
|
|
|
|
// Type returns the order type.
|
|
Type() Type
|
|
|
|
// Digest returns a deterministic SHA256 hash over the contents of an
|
|
// order. Deterministic in this context means that if two orders have
|
|
// the same content, their digest have to be identical as well.
|
|
Digest() ([sha256.Size]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.
|
|
ReservedValue(feeSchedule terms.FeeSchedule) btcutil.Amount
|
|
}
|
|
|
|
// Kit stores all the common fields that are used to express the decision to
|
|
// participate in the auction process. A kit is always wrapped by either a bid
|
|
// or an ask.
|
|
type Kit struct {
|
|
// nonce is the hash of the preimage and acts as the unique identifier
|
|
// of an order.
|
|
nonce Nonce
|
|
|
|
// Preimage is the randomly generated preimage to the nonce hash. It is
|
|
// only known to the trader client.
|
|
Preimage lntypes.Preimage
|
|
|
|
// Version is the feature version of this order. Can be used to
|
|
// distinguish between certain feature sets or to signal feature flags.
|
|
Version Version
|
|
|
|
// State is the current state the order is in as it was last seen by the
|
|
// client. The real state is tracked on the auction server, so this can
|
|
// be out of sync if there was no connection for a while.
|
|
State State
|
|
|
|
// FixedRate is the fixed order rate expressed in parts per million.
|
|
FixedRate uint32
|
|
|
|
// Amt is the order amount in satoshis.
|
|
Amt btcutil.Amount
|
|
|
|
// Units the total amount of units that the target amount maps to.
|
|
Units SupplyUnit
|
|
|
|
// UnitsUnfulfilled is the number of units that have not been filled yet
|
|
// and are still available for matching against other orders.
|
|
UnitsUnfulfilled SupplyUnit
|
|
|
|
// MultiSigKeyLocator is the key locator used to obtain the multi sig
|
|
// key. This will be needed for operations that require a signature
|
|
// under said key and will therefore only be known to the trader client.
|
|
// This key will only be derived from the connected lnd after the order
|
|
// has been formally validated.
|
|
MultiSigKeyLocator keychain.KeyLocator
|
|
|
|
// MaxBatchFeeRate is is the maximum fee rate the trader is willing to
|
|
// pay for the batch transaction, in sat/kW.
|
|
MaxBatchFeeRate chainfee.SatPerKWeight
|
|
|
|
// AcctKey is key of the account the order belongs to.
|
|
AcctKey [33]byte
|
|
}
|
|
|
|
// Nonce is the unique identifier of each order and MUST be created by hashing a
|
|
// new random preimage for each new order. The nonce is what is signed in the
|
|
// order signature.
|
|
//
|
|
// NOTE: This method is part of the Order interface.
|
|
func (k *Kit) Nonce() Nonce {
|
|
return k.nonce
|
|
}
|
|
|
|
// Details returns the Kit of the order.
|
|
//
|
|
// NOTE: This method is part of the Order interface.
|
|
func (k *Kit) Details() *Kit {
|
|
return k
|
|
}
|
|
|
|
// NewKitWithPreimage creates a new kit by hashing the preimage to generate the
|
|
// unique nonce.
|
|
func NewKitWithPreimage(preimage lntypes.Preimage) *Kit {
|
|
var nonce Nonce
|
|
hash := preimage.Hash()
|
|
copy(nonce[:], hash[:])
|
|
return &Kit{
|
|
nonce: nonce,
|
|
Preimage: preimage,
|
|
Version: VersionDefault,
|
|
}
|
|
}
|
|
|
|
// NewKit creates a new kit from a nonce in case the preimage is not known.
|
|
func NewKit(nonce Nonce) *Kit {
|
|
return &Kit{
|
|
nonce: nonce,
|
|
Version: VersionDefault,
|
|
}
|
|
}
|
|
|
|
// Ask is the specific order type representing the willingness of an auction
|
|
// participant to lend out their funds by opening channels to other auction
|
|
// participants.
|
|
type Ask struct {
|
|
// Kit contains all the common order parameters.
|
|
Kit
|
|
|
|
// MaxDuration is the maximum number of blocks the liquidity provider is
|
|
// willing to provide the channel funds for.
|
|
MaxDuration uint32
|
|
}
|
|
|
|
// Type returns the order type.
|
|
//
|
|
// NOTE: This method is part of the Order interface.
|
|
func (a *Ask) Type() Type {
|
|
return TypeAsk
|
|
}
|
|
|
|
// Digest returns a deterministic SHA256 hash over the contents of an ask order.
|
|
// Deterministic in this context means that if two orders have the same content,
|
|
// their digest have to be identical as well.
|
|
//
|
|
// NOTE: This method is part of the Order interface.
|
|
func (a *Ask) Digest() ([sha256.Size]byte, error) {
|
|
var (
|
|
msg bytes.Buffer
|
|
result [sha256.Size]byte
|
|
)
|
|
switch a.Kit.Version {
|
|
case VersionDefault:
|
|
err := lnwire.WriteElements(
|
|
&msg, a.nonce[:], uint32(a.Version), a.FixedRate,
|
|
a.Amt, a.MaxDuration, uint64(a.MaxBatchFeeRate),
|
|
)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
default:
|
|
return result, fmt.Errorf("unknown version %d", a.Kit.Version)
|
|
}
|
|
return sha256.Sum256(msg.Bytes()), nil
|
|
}
|
|
|
|
// reservedValue returns the maximum value that could be deducted from a single
|
|
// 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.
|
|
//
|
|
// The passed function should be set to either calculate the maker or taker
|
|
// balance delta for a single match of the given amount.
|
|
func reservedValue(o Order,
|
|
perMatchDelta func(btcutil.Amount) btcutil.Amount) btcutil.Amount {
|
|
|
|
// If this order is in a state where it cannot be matched, return 0.
|
|
if o.Details().State.Archived() {
|
|
return 0
|
|
}
|
|
|
|
// The situation where the trader needs to pay the largest amount of
|
|
// fees is when the order gets partially matched by the base supply
|
|
// unit per batch. This situation results in the most chain and
|
|
// execution fees possible.
|
|
minMatchSize := btcutil.Amount(BaseSupplyUnit)
|
|
maxNumMatches := btcutil.Amount(o.Details().UnitsUnfulfilled)
|
|
|
|
// We'll calculate the worst case possible wrt. fees paid by the
|
|
// account if the order is filled by minimum size matched.
|
|
balanceDelta := maxNumMatches * perMatchDelta(minMatchSize)
|
|
|
|
// Subtract the worst case chain fee from the balance.
|
|
balanceDelta -= maxNumMatches * EstimateTraderFee(
|
|
1, o.Details().MaxBatchFeeRate,
|
|
)
|
|
|
|
// If the balance delta is negative, meaning this order will decrease
|
|
// the balance, the reserved value is the negative balance delta.
|
|
if balanceDelta < 0 {
|
|
return -balanceDelta
|
|
}
|
|
|
|
// Otherwise this order will increase the balance if matched, and we
|
|
// don't have to reserve any amount.
|
|
return 0
|
|
}
|
|
|
|
// 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.
|
|
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.
|
|
clearingPrice := FixedRatePremium(a.FixedRate)
|
|
|
|
// The premium paid to the asker is at its lowest when the min duration
|
|
// matched is only 144 block.
|
|
minDuration := uint32(MinimumOrderDurationBlocks)
|
|
return reservedValue(a, func(amt btcutil.Amount) btcutil.Amount {
|
|
delta, _, _ := makerDelta(
|
|
feeSchedule, clearingPrice, amt, minDuration,
|
|
)
|
|
return delta
|
|
})
|
|
}
|
|
|
|
// Bid is the specific order type representing the willingness of an auction
|
|
// participant to pay for inbound liquidity provided by other auction
|
|
// participants.
|
|
type Bid struct {
|
|
// Kit contains all the common order parameters.
|
|
Kit
|
|
|
|
// MinDuration is the minimal duration the channel resulting from this
|
|
// bid should be kept open, expressed in blocks.
|
|
MinDuration uint32
|
|
}
|
|
|
|
// Type returns the order type.
|
|
//
|
|
// NOTE: This method is part of the Order interface.
|
|
func (b *Bid) Type() Type {
|
|
return TypeBid
|
|
}
|
|
|
|
// Digest returns a deterministic SHA256 hash over the contents of a bid order.
|
|
// Deterministic in this context means that if two orders have the same content,
|
|
// their digest have to be identical as well.
|
|
//
|
|
// NOTE: This method is part of the Order interface.
|
|
func (b *Bid) Digest() ([sha256.Size]byte, error) {
|
|
var (
|
|
msg bytes.Buffer
|
|
result [sha256.Size]byte
|
|
)
|
|
switch b.Kit.Version {
|
|
case VersionDefault:
|
|
err := lnwire.WriteElements(
|
|
&msg, b.nonce[:], uint32(b.Version), b.FixedRate,
|
|
b.Amt, b.MinDuration, uint64(b.MaxBatchFeeRate),
|
|
)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
default:
|
|
return result, fmt.Errorf("unknown version %d", b.Kit.Version)
|
|
}
|
|
return sha256.Sum256(msg.Bytes()), nil
|
|
}
|
|
|
|
// 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.
|
|
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 bu the bidder.
|
|
clearingPrice := FixedRatePremium(b.FixedRate)
|
|
|
|
// The bidder always pay a premium based on the
|
|
// bid's min duration.
|
|
minDuration := b.MinDuration
|
|
return reservedValue(b, func(amt btcutil.Amount) btcutil.Amount {
|
|
delta, _, _ := takerDelta(
|
|
feeSchedule, clearingPrice, amt, minDuration,
|
|
)
|
|
return delta
|
|
})
|
|
}
|
|
|
|
// This is a compile time check to make certain that both Ask and Bid implement
|
|
// the Order interface.
|
|
var _ Order = (*Ask)(nil)
|
|
var _ Order = (*Bid)(nil)
|
|
|
|
// Modifier abstracts the modification of an account through a function.
|
|
type Modifier func(*Kit)
|
|
|
|
// StateModifier is a functional option that modifies the state of an order.
|
|
func StateModifier(state State) Modifier {
|
|
return func(order *Kit) {
|
|
order.State = state
|
|
}
|
|
}
|
|
|
|
// UnitsFulfilledModifier is a functional option that modifies the number of
|
|
// unfulfilled units of an order.
|
|
func UnitsFulfilledModifier(newUnfulfilledUnits SupplyUnit) Modifier {
|
|
return func(order *Kit) {
|
|
order.UnitsUnfulfilled = newUnfulfilledUnits
|
|
}
|
|
}
|
|
|
|
// Store is the interface a store has to implement to support persisting orders.
|
|
type Store interface {
|
|
// SubmitOrder stores an order by using the orders's nonce as an
|
|
// identifier. If an order with the given nonce already exists in the
|
|
// store, ErrOrderExists is returned.
|
|
SubmitOrder(Order) error
|
|
|
|
// UpdateOrder updates an order in the database according to the given
|
|
// modifiers.
|
|
UpdateOrder(Nonce, ...Modifier) error
|
|
|
|
// UpdateOrders atomically updates a list of orders in the database
|
|
// according to the given modifiers.
|
|
UpdateOrders([]Nonce, [][]Modifier) error
|
|
|
|
// GetOrder returns an order by looking up the nonce. If no order with
|
|
// that nonce exists in the store, ErrNoOrder is returned.
|
|
GetOrder(Nonce) (Order, error)
|
|
|
|
// GetOrders returns all orders that are currently known to the store.
|
|
GetOrders() ([]Order, error)
|
|
|
|
// DelOrder removes the order with the given nonce from the local store.
|
|
DelOrder(Nonce) error
|
|
|
|
// StorePendingBatch atomically stages all modified orders/accounts as a
|
|
// result of a pending batch. If any single operation fails, the whole
|
|
// set of changes is rolled back. Once the batch has been
|
|
// finalized/confirmed on-chain, then the stage modifications will be
|
|
// applied atomically as a result of MarkBatchComplete.
|
|
StorePendingBatch(_ BatchID, _ *wire.MsgTx, orders []Nonce,
|
|
orderModifiers [][]Modifier, accounts []*account.Account,
|
|
accountModifiers [][]account.Modifier) error
|
|
|
|
// MarkBatchComplete marks a pending batch as complete, applying any
|
|
// staged modifications necessary, and allowing a trader to participate
|
|
// in a new batch. If a pending batch is not found, ErrNoPendingBatch is
|
|
// returned.
|
|
MarkBatchComplete() error
|
|
}
|
|
|
|
// UserError is an error type that is returned if an action fails because of
|
|
// an invalid action or information provided by the user.
|
|
type UserError struct {
|
|
FailMsg string
|
|
Details *clmrpc.InvalidOrder
|
|
}
|
|
|
|
// Error returns the string representation of the underlying failure message.
|
|
func (e *UserError) Error() string {
|
|
return e.FailMsg
|
|
}
|
|
|
|
// A compile-time constraint to ensure UserError implements the error interface.
|
|
var _ error = (*UserError)(nil)
|
|
|
|
// ServerOrderParams is the list of values that we have to send to the server
|
|
// when submitting an order that doesn't need to be persisted in the local DB.
|
|
type ServerOrderParams struct {
|
|
// MultiSigKey is a key of the node creating the order that will be used
|
|
// to craft the channel funding TX's 2-of-2 multi signature output.
|
|
MultiSigKey [33]byte
|
|
|
|
// NodePubkey is the identity public key of the node submitting the
|
|
// order.
|
|
NodePubkey [33]byte
|
|
|
|
// Addrs is a list of network addresses through which the node
|
|
// submitting the order can be reached.
|
|
Addrs []net.Addr
|
|
|
|
// RawSig is the raw signature over the order digest signed with the
|
|
// trader's account key.
|
|
RawSig []byte
|
|
}
|
|
|
|
// PendingChanKey calculates the pending channel ID to be used for funding
|
|
// purposes for a given bid and ask. The pending channel ID must be unique, so
|
|
// we use the hash of the concatenation of the two nonces: sha256(askNonce ||
|
|
// bidNonce).
|
|
func PendingChanKey(askNonce, bidNonce Nonce) [32]byte {
|
|
var pid [32]byte
|
|
|
|
h := sha256.New()
|
|
_, _ = h.Write(askNonce[:])
|
|
_, _ = h.Write(bidNonce[:])
|
|
|
|
copy(pid[:], h.Sum(nil))
|
|
|
|
return pid
|
|
}
|