pool/order/interfaces.go
2023-10-25 10:04:23 +02:00

1084 lines
33 KiB
Go

package order
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"net"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/pool/account"
"github.com/lightninglabs/pool/auctioneerrpc"
"github.com/lightninglabs/pool/codec"
"github.com/lightninglabs/pool/sidecar"
"github.com/lightninglabs/pool/terms"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
// Nonce is a 32 byte pseudo randomly generated unique order ID.
type Nonce [32]byte
// The size of a SHA256 checksum in bytes.
//
// Note: this matches the sha256.Size definition. However, mockgen
// complains about not being able to find the sha256 package. When
// that bug is fixed, we can change back to [sha256.Size]byte.
const hashSize = 32
// 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
// VersionNodeTierMinMatch is the order version that added recognition
// of the new node tier and min matchable order size fields.
VersionNodeTierMinMatch Version = 1
// VersionLeaseDurationBuckets is the order version that added use of
// multiple lease durations. Only orders with this version are allowed
// to use lease durations outside of the default/legacy 2016 block
// duration.
VersionLeaseDurationBuckets Version = 2
// VersionSelfChanBalance is the order version that added use of the
// self channel balance field. Only orders with this version are allowed
// to use the self channel balance field.
VersionSelfChanBalance Version = 3
// VersionSidecarChannel is the order version that added sidecar
// channels for bid orders. Only orders with this version are allowed
// to set the sidecar ticket field on bid orders. Since sidecar orders
// also add the feature of push amounts on the leased channels, this
// affects makers as well. Makers that don't want to support leasing out
// channels with a push amount (because it might screw up their
// accounting or whatever) can opt out by explicitly submitting their
// ask orders with a version previous to this one.
VersionSidecarChannel Version = 4
// VersionChannelType is the order version that added use of the channel
// type field. Only orders with this version are allowed to use the
// channel type field.
VersionChannelType Version = 5
)
// 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
}
}
// MatchState describes the distinct phases an order goes through as seen by the
// trader daemon. These states are not persisted on the orders themselves but
// rather as events with timestamps so a user can track what's happening to
// their orders.
type MatchState uint8
const (
// MatchStatePrepare is the state an order is in after the
// OrderMatchPrepare message was received initially.
MatchStatePrepare MatchState = 0
// MatchStateAccepted is the state an order is in after the
// OrderMatchPrepare message was processed successfully and the batch
// was accepted.
MatchStateAccepted MatchState = 1
// MatchStateRejected is the state an order is in after the trader
// rejected it, either as an answer to a OrderMatchSignBegin or
// OrderMatchFinalize message from the auctioneer.
MatchStateRejected MatchState = 2
// MatchStateSigned is the state an order is in after the
// OrderMatchSignBegin message was processed successfully.
MatchStateSigned MatchState = 3
// MatchStateFinalized is the state an order is in after the
// OrderMatchFinalize message was processed successfully.
MatchStateFinalized MatchState = 4
)
// String returns a human-readable string representation of the match state.
func (s MatchState) String() string {
switch s {
case MatchStatePrepare:
return "prepare"
case MatchStateAccepted:
return "accepted"
case MatchStateSigned:
return "signed"
case MatchStateFinalized:
return "finalized"
case MatchStateRejected:
return "rejected"
default:
return fmt.Sprintf("unknown<%d>", s)
}
}
// ChannelType is a numerical type that represents all possible channel types
// that are supported to be opened through the auction process.
type ChannelType uint8
// NOTE: We avoid the use of iota as this type is stored on disk.
const (
// ChannelTypePeerDependent denotes that the resulting channel type from
// an order match will depend on the shared features between its
// participants.
ChannelTypePeerDependent ChannelType = 0
// ChannelTypeScriptEnforced represents a new channel type that builds
// upon the anchors commitment format to enforce the maturity of a
// leased channel in the commitment and HTLC outputs that pay directly
// to the channel initiator.
ChannelTypeScriptEnforced ChannelType = 1
// ChannelTypeSimpleTaproot represents a channel type that uses a
// Pay-To-Taproot funding output.
ChannelTypeSimpleTaproot ChannelType = 2
)
// ChannelAnnouncementConstraints is a numerical type used to denote if the
// channels created from a match can be announced or not.
type ChannelAnnouncementConstraints uint8
const (
// AnnouncementNoPreference denotes that the resulting channels can be
// announced or not.
AnnouncementNoPreference ChannelAnnouncementConstraints = 0
// OnlyAnnounced denotes that the resulting channels must be announced
// to the network.
OnlyAnnounced ChannelAnnouncementConstraints = 1
// OnlyUnannounced denotes that the resulting channels must not be
// announced to the network.
OnlyUnannounced ChannelAnnouncementConstraints = 2
)
// ChannelConfirmationConstraints is a numerical type used to denote if the
// channels created from a match is zero conf or not.
type ChannelConfirmationConstraints uint8
const (
// ConfirmationNoPreference denotes that the resulting channel can be
// zero conf or not.
ConfirmationNoPreference ChannelConfirmationConstraints = 0
// OnlyConfirmed denotes that the resulting channels must be confirmed
// onchain before start routing.
OnlyConfirmed ChannelConfirmationConstraints = 1
// OnlyZeroConf denotes that the resulting channels can be used
// without having to wait for onchain confirmations.
OnlyZeroConf ChannelConfirmationConstraints = 2
)
// AuctionType is a numerical type used to denote in what auction market should
// this order be considered in.
type AuctionType uint32
const (
// BTCInboundLiquidity is an auction type where the bidder pays the
// asker a premium to get btc inbound liquidity from him.
BTCInboundLiquidity AuctionType = 0
// BTCOutboundLiquidity is an auction type where the bidder pays the
// asker a premium to accept a btc channel from the bidder.
BTCOutboundLiquidity AuctionType = 1
)
// String returns a human readable string representation of the auction type.
func (a AuctionType) String() string {
switch a {
case BTCInboundLiquidity:
return "btc_inbound_liquidity"
case BTCOutboundLiquidity:
return "btc_outbound_liquidity"
default:
return fmt.Sprintf("unknown<%d>", a)
}
}
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
)
// MatchAnnouncementConstraints returns true when the asker announcement
// constraints match the bidder announcement preferences.
func MatchAnnouncementConstraints(asker ChannelAnnouncementConstraints,
unannounced bool) bool {
switch {
case asker == AnnouncementNoPreference:
return true
case asker == OnlyAnnounced && !unannounced:
return true
case asker == OnlyUnannounced && unannounced:
return true
default:
return false
}
}
// MatchZeroConfConstraints returns true when the asker confirmation
// constraints match the bidder confirmation preferences.
func MatchZeroConfConstraints(asker ChannelConfirmationConstraints,
zeroConf bool) bool {
switch {
case asker == ConfirmationNoPreference:
return true
case asker == OnlyConfirmed && !zeroConf:
return true
case asker == OnlyZeroConf && zeroConf:
return true
default:
return false
}
}
// 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() ([hashSize]byte, error)
// 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.
ReservedValue(feeSchedule terms.FeeSchedule,
accountVersion account.Version) 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
// AuctionType is the market where this offer should be considered in.
AuctionType AuctionType
// 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 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
// LeaseDuration identifies how long this order wishes to acquire or
// lease out capital in the Lightning Network for.
LeaseDuration uint32
// MinUnitsMatch signals the minimum number of units that must be
// matched against an order.
MinUnitsMatch SupplyUnit
// ChannelType denotes the channel type that must be used for the
// resulting matched channels.
ChannelType ChannelType
// AllowedNodeIDs is the list of node ids this order is allowed to
// match with.
AllowedNodeIDs [][33]byte
// NotAllowedNodeIDs is the list of node ids this order is not allowed
// to match with.
NotAllowedNodeIDs [][33]byte
// IsPublic is the flag used to signal if the details of this order can
// be shared in public marketplaces or not.
IsPublic bool
}
// 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: VersionLeaseDurationBuckets,
}
}
// 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: VersionLeaseDurationBuckets,
}
}
// 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
// AnnouncementConstraints specifies the constraints for the matched
// channels in terms of announced/unannounced.
AnnouncementConstraints ChannelAnnouncementConstraints
// ConfirmationConstraints specifies the constraints for the matched
// channels in terms of confirmed/zero conf.
ConfirmationConstraints ChannelConfirmationConstraints
}
// 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() ([hashSize]byte, error) {
var (
msg bytes.Buffer
result [hashSize]byte
)
switch a.Kit.Version {
case VersionDefault:
err := codec.WriteElements(
&msg, a.nonce[:], uint32(a.Version), a.FixedRate,
a.Amt, a.LeaseDuration, uint64(a.MaxBatchFeeRate),
)
if err != nil {
return result, err
}
case VersionNodeTierMinMatch, VersionLeaseDurationBuckets,
VersionSelfChanBalance, VersionSidecarChannel:
err := codec.WriteElements(
&msg, a.nonce[:], uint32(a.Version), a.FixedRate,
a.Amt, a.LeaseDuration, uint64(a.MaxBatchFeeRate),
uint32(a.MinUnitsMatch),
)
if err != nil {
return result, err
}
case VersionChannelType:
err := codec.WriteElements(
&msg, a.nonce[:], uint32(a.Version), a.FixedRate,
a.Amt, a.LeaseDuration, uint64(a.MaxBatchFeeRate),
uint32(a.MinUnitsMatch), uint8(a.ChannelType),
)
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,
accountVersion account.Version) 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 its minimum possible
// units per batch. This situation results in the most chain and
// execution fees possible.
totalSats := o.Details().UnitsUnfulfilled.ToSatoshis()
minMatchSize := o.Details().MinUnitsMatch.ToSatoshis()
maxNumMatches := totalSats / minMatchSize
// We handle the case where the last match consumes the remainder of
// the order size.
rem := btcutil.Amount(0)
if maxNumMatches*minMatchSize < totalSats {
maxNumMatches--
rem = totalSats - maxNumMatches*minMatchSize
}
// 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)
if rem > 0 {
balanceDelta += perMatchDelta(rem)
}
// Subtract the worst case chain fee from the balance.
maxFeeRate := o.Details().MaxBatchFeeRate
balanceDelta -= maxNumMatches * EstimateTraderFee(
1, maxFeeRate, accountVersion,
)
if rem > 0 {
balanceDelta -= EstimateTraderFee(1, maxFeeRate, accountVersion)
}
// 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 matched under the worst case fee conditions.
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)
return reservedValue(a, func(amt btcutil.Amount) btcutil.Amount {
delta, _, _ := makerDelta(
feeSchedule, clearingPrice, amt, amt, a.LeaseDuration,
)
return delta
}, accountVersion)
}
// NodeTier an enum-like variable that presents which "tier" a node is in. A
// higher tier is better. Node tiers are used to allow clients to express their
// preference w.r.t the "quality" of a node they wish to buy channels from.
type NodeTier uint32
const (
// NodeTierDefault only exists in-memory as allows users to specify
// that they want to opt-into the default "node tier". The
// DefaultMinNodeTier constant should point to what the current default
// node tier is.
NodeTierDefault NodeTier = 0
// NodeTier0 is the tier for nodes which may not be explicitly ranked.
// Orders submitted with this min tier express that they don't care
// about the "quality" of the node they're matched with.
NodeTier0 NodeTier = 1
// NodeTier1 is the "base" node tier. Nodes on this tier are considered
// to be relatively good. We have this be the first value in the enum
// so it can be the default within the codebase and for order
// submission/matching.
NodeTier1 NodeTier = 2
)
// DefaultMinNodeTier is the default node tier. With this current value, Bids
// will default to only matching with nodes in the first tier and above.
const DefaultMinNodeTier = NodeTier1
// String returns the string representation of the target NodeTier.
func (n NodeTier) String() string {
switch n {
case NodeTier0:
return "NodeTier0"
case NodeTier1:
return "NodeTier1"
case NodeTierDefault:
return "NodeTierDefault"
default:
return fmt.Sprintf("UnknownNodeTier(%v)", uint32(n))
}
}
// 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
// MinNodeTier is the minimum node tier that this order should be
// matched with. Only Asks backed by nodes on this tier or above will
// be matched with this bid.
MinNodeTier NodeTier
// SelfChanBalance is the initial outbound balance that should be added
// to the channel resulting from matching this bid by moving additional
// funds from the taker's account into the channel.
SelfChanBalance btcutil.Amount
// SidecarTicket indicates, if non-nil, that the channel being purchased
// with this bid should be opened to a node other than the caller's
// node. The lease recipient is another Pool (light) node that
// authenticates itself to the auctioneer using the information in this
// ticket (the information exchange between bidder and lease recipient
// happens out of band). This will only be used if the order version is
// VersionSidecarChannel or greater.
SidecarTicket *sidecar.Ticket
// UnannouncedChannel signals if the resulting channel needs to be
// announced or not.
UnannouncedChannel bool
// ZeroConfChannel signals if the resulting channels need to be zero
// conf or not.
ZeroConfChannel bool
}
// 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() ([hashSize]byte, error) {
var (
msg bytes.Buffer
result [hashSize]byte
)
switch b.Kit.Version {
case VersionDefault:
err := codec.WriteElements(
&msg, b.nonce[:], uint32(b.Version), b.FixedRate,
b.Amt, b.LeaseDuration, uint64(b.MaxBatchFeeRate),
)
if err != nil {
return result, err
}
case VersionNodeTierMinMatch, VersionLeaseDurationBuckets:
err := codec.WriteElements(
&msg, b.nonce[:], uint32(b.Version), b.FixedRate,
b.Amt, b.LeaseDuration, uint64(b.MaxBatchFeeRate),
uint32(b.MinNodeTier), uint32(b.MinUnitsMatch),
)
if err != nil {
return result, err
}
case VersionSelfChanBalance:
err := codec.WriteElements(
&msg, b.nonce[:], uint32(b.Version), b.FixedRate,
b.Amt, b.LeaseDuration, uint64(b.MaxBatchFeeRate),
uint32(b.MinNodeTier), uint32(b.MinUnitsMatch),
uint64(b.SelfChanBalance),
)
if err != nil {
return result, err
}
case VersionSidecarChannel:
var isSidecar uint8
if b.SidecarTicket != nil {
isSidecar = 1
}
err := codec.WriteElements(
&msg, b.nonce[:], uint32(b.Version), b.FixedRate,
b.Amt, b.LeaseDuration, uint64(b.MaxBatchFeeRate),
uint32(b.MinNodeTier), uint32(b.MinUnitsMatch),
uint64(b.SelfChanBalance), isSidecar,
)
if err != nil {
return result, err
}
case VersionChannelType:
var isSidecar uint8
if b.SidecarTicket != nil {
isSidecar = 1
}
err := codec.WriteElements(
&msg, b.nonce[:], uint32(b.Version), b.FixedRate,
b.Amt, b.LeaseDuration, uint64(b.MaxBatchFeeRate),
uint32(b.MinNodeTier), uint32(b.MinUnitsMatch),
uint64(b.SelfChanBalance), isSidecar,
uint8(b.ChannelType),
)
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 matched under the worst case fee conditions.
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.
clearingPrice := FixedRatePremium(b.FixedRate)
return reservedValue(b, func(amt btcutil.Amount) btcutil.Amount {
premiumAmt := amt
if b.Details().AuctionType == BTCOutboundLiquidity {
premiumAmt += b.SelfChanBalance
}
delta, _, _ := takerDelta(
feeSchedule, clearingPrice, premiumAmt,
b.SelfChanBalance, b.LeaseDuration,
)
return delta
}, accountVersion)
}
// CheckOfferParams makes sure the offer parameters of an offer 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 {
// Only multiples of 100k sats are allowed in the outbound
// market.
if pushAmt == 0 || pushAmt%baseSupplyUnit != 0 {
return fmt.Errorf("self balance must be a positive "+
"multiple of %d", baseSupplyUnit)
}
}
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 {
if auctionType != BTCInboundLiquidity {
return fmt.Errorf("%s market does not support sidecar tickets",
auctionType)
}
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 {
if b.Version < VersionSelfChanBalance {
return fmt.Errorf("cannot use self chan balance with old " +
"order version")
}
if err := CheckOfferParams(
b.AuctionType, b.Amt, b.SelfChanBalance, BaseSupplyUnit,
); err != nil {
return fmt.Errorf("invalid self chan balance: %v", err)
}
if b.Units != b.MinUnitsMatch {
return fmt.Errorf("to use self chan balance the min units " +
"match must be equal to the order amount in units")
}
if b.AuctionType == BTCOutboundLiquidity &&
b.SelfChanBalance < BaseSupplyUnit {
return fmt.Errorf("to participate in the outbound liquidity " +
"market the self chan balance should be at least " +
"100k sats")
}
return nil
}
// 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)
// DeleteOrder removes the order with the given Nonce.
//
// Note: this method deletes the order without checking if it is
// referenced somewhere else (e.g. pending batch).
DeleteOrder(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(_ *Batch, 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 *auctioneerrpc.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
}
// Manager is the interface a manager implements to deal with
// the orders.
type Manager interface {
// Start starts all concurrent tasks the manager is responsible for.
Start() error
// Stop stops all concurrent tasks the manager is responsible for.
Stop()
// PrepareOrder validates an order, signs it and then stores it locally.
PrepareOrder(ctx context.Context, order Order, acct *account.Account,
terms *terms.AuctioneerTerms) (*ServerOrderParams, error)
// OrderMatchValidate verifies an incoming batch is sane before
// accepting it.
OrderMatchValidate(batch *Batch, bestHeight uint32) error
// HasPendingBatch returns whether a pending batch is currently being
// processed.
HasPendingBatch() bool
// PendingBatch returns the current pending batch being validated.
PendingBatch() *Batch
// BatchSign returns the witness stack of all account inputs in a batch
// that belong to the trader.
BatchSign() (BatchSignature, AccountNonces, error)
// BatchFinalize marks a batch as complete upon receiving the finalize
// message from the auctioneer.
BatchFinalize(batchID BatchID) error
// OurNodePubkey returns our lnd node's public identity key or an error
// if the manager wasn't fully started yet.
OurNodePubkey() ([33]byte, error)
}