market

package
v0.5.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2022 License: BlueOak-1.0.0 Imports: 26 Imported by: 1

Documentation

Index

Constants

View Source
const (
	ErrMarketNotRunning       = Error("market not running")
	ErrInvalidOrder           = Error("order failed validation")
	ErrInvalidCommitment      = Error("order commitment invalid")
	ErrEpochMissed            = Error("order unexpectedly missed its intended epoch")
	ErrDuplicateOrder         = Error("order already in epoch") // maybe remove since this is ill defined
	ErrQuantityTooHigh        = Error("order quantity exceeds user limit")
	ErrDuplicateCancelOrder   = Error("equivalent cancel order already in epoch")
	ErrTooManyCancelOrders    = Error("too many cancel orders in current epoch")
	ErrCancelNotPermitted     = Error("cancel order account does not match targeted order account")
	ErrTargetNotActive        = Error("target order not active on this market")
	ErrTargetNotCancelable    = Error("targeted order is not a limit order with standing time-in-force")
	ErrSuspendedAccount       = Error("suspended account")
	ErrMalformedOrderResponse = Error("malformed order response")
	ErrInternalServer         = Error("internal server error")
)
View Source
const (

	// ZeroConfFeeRateThreshold is multiplied by the last known fee rate for an
	// asset to attain a minimum fee rate acceptable for zero-conf funding
	// coins.
	ZeroConfFeeRateThreshold = 0.9
)

Variables

This section is empty.

Functions

func DisableLog

func DisableLog()

DisableLog disables all library log output. Logging output is disabled by default until UseLogger is called.

func OrderToMsgOrder added in v0.2.0

func OrderToMsgOrder(ord order.Order, mkt string) (*msgjson.BookOrderNote, error)

OrderToMsgOrder converts an order.Order into a *msgjson.BookOrderNote.

func UseLogger

func UseLogger(logger slog.Logger)

UseLogger uses a specified Logger to output package logging info.

Types

type AuthManager

type AuthManager interface {
	Route(route string, handler func(account.AccountID, *msgjson.Message) *msgjson.Error)
	Auth(user account.AccountID, msg, sig []byte) error
	Suspended(user account.AccountID) (found, suspended bool)
	Sign(...msgjson.Signable)
	Send(account.AccountID, *msgjson.Message) error
	Request(account.AccountID, *msgjson.Message, func(comms.Link, *msgjson.Message)) error
	RequestWithTimeout(account.AccountID, *msgjson.Message, func(comms.Link, *msgjson.Message), time.Duration, func()) error
	PreimageSuccess(user account.AccountID, refTime time.Time, oid order.OrderID)
	MissedPreimage(user account.AccountID, refTime time.Time, oid order.OrderID)
	RecordCancel(user account.AccountID, oid, target order.OrderID, t time.Time)
	RecordCompletedOrder(user account.AccountID, oid order.OrderID, t time.Time)
	UserSettlingLimit(user account.AccountID, mkt *dex.MarketInfo) int64
}

The AuthManager handles client-related actions, including authorization and communications.

type Balancer added in v0.5.0

type Balancer interface {
	// CheckBalance checks that the address's account has sufficient balance to
	// trade the outgoing number of lots (totaling qty) and incoming number of
	// redeems.
	CheckBalance(acctAddr string, assetID, redeemAssetID uint32, qty, lots uint64, redeems int) bool
}

Balancer provides a method to check that an account on an account-based asset has sufficient balance.

type BookRouter

type BookRouter struct {
	// contains filtered or unexported fields
}

BookRouter handles order book subscriptions, syncing the market with a group of subscribers, and maintaining an intermediate copy of the orderbook in message payload format for quick, full-book syncing.

func NewBookRouter

func NewBookRouter(sources map[string]BookSource, feeSource FeeSource) *BookRouter

NewBookRouter is a constructor for a BookRouter. Routes are registered with comms and a monitoring goroutine is started for each BookSource specified. The input sources is a mapping of market names to sources for order and epoch queue information.

func (*BookRouter) Book added in v0.2.0

func (r *BookRouter) Book(mktName string) (*msgjson.OrderBook, error)

Book creates a copy of the book as a *msgjson.OrderBook.

func (*BookRouter) Run

func (r *BookRouter) Run(ctx context.Context)

Run implements dex.Runner, and is blocking.

type BookSource

type BookSource interface {
	Book() (epoch int64, buys []*order.LimitOrder, sells []*order.LimitOrder)
	OrderFeed() <-chan *updateSignal
	Base() uint32
	Quote() uint32
}

BookSource is a source of a market's order book and a feed of updates to the order book and epoch queue.

type Config added in v0.2.0

type Config struct {
	MarketInfo      *dex.MarketInfo
	Storage         Storage
	Swapper         Swapper
	AuthManager     AuthManager
	FeeFetcherBase  FeeFetcher
	CoinLockerBase  coinlock.CoinLocker
	FeeFetcherQuote FeeFetcher
	CoinLockerQuote coinlock.CoinLocker
	DataCollector   DataCollector
	Balancer        Balancer
}

Config is the Market configuration.

type DEXBalancer added in v0.5.0

type DEXBalancer struct {
	// contains filtered or unexported fields
}

BackedBalancer is an asset manager that is capable of querying the entire DEX for the balance required to fulfill new + existing orders and outstanding redemptions.

func NewDEXBalancer added in v0.5.0

func NewDEXBalancer(tunnels map[string]PendingAccounter, assets map[uint32]*asset.BackedAsset, matchNegotiator MatchNegotiator) (*DEXBalancer, error)

NewDEXBalancer is a constructor for a DEXBalancer. Provided assets will be filtered for those that are account-based. The matchNegotiator is satisfied by the *Swapper.

func (*DEXBalancer) CheckBalance added in v0.5.0

func (b *DEXBalancer) CheckBalance(acctAddr string, assetID, redeemAssetID uint32, qty, lots uint64, redeems int) bool

CheckBalance checks if there is sufficient balance to support the specified new funding and redemptions, given the existing orders throughout DEX that fund from or redeem to the specified account address for the account-based asset. It is an internally logged error to call CheckBalance for a non-account-based asset or an asset that was not provided to the constructor. Because these assets may have a base chain as well as degenerate tokens, we need to consider outstanding orders and matches across all "fee family" assets. It is acceptable to call CheckBalance with qty = 0, lots = 0, redeems > 0 and assetID = redeemAssetID, as might be the case when checking that a user has sufficient balance to redeem an order's matches.

type DataCollector added in v0.2.0

type DataCollector interface {
	ReportEpoch(base, quote uint32, epochIdx uint64, stats *matcher.MatchCycleStats) (*msgjson.Spot, error)
}

type EpochQueue

type EpochQueue struct {
	// Epoch is the epoch index.
	Epoch    int64
	Duration int64
	// Start and End define the time range of the epoch as [Start,End).
	Start, End time.Time
	// Orders holds the epoch queue orders in a map for quick lookups.
	Orders map[order.OrderID]order.Order
	// UserCancels counts the number of cancel orders per user.
	UserCancels map[account.AccountID]uint32
	// CancelTargets maps known targeted order IDs with the CancelOrder
	CancelTargets map[order.OrderID]*order.CancelOrder
}

EpochQueue represents an epoch order queue. The methods are NOT thread safe by design.

func NewEpoch

func NewEpoch(idx int64, duration int64) *EpochQueue

NewEpoch creates an epoch with the given index and duration in milliseconds.

func (*EpochQueue) IncludesTime

func (eq *EpochQueue) IncludesTime(t time.Time) bool

IncludesTime checks if the given time falls in the epoch.

func (*EpochQueue) Insert

func (eq *EpochQueue) Insert(ord order.Order)

Stores an order in the Order slice, overwriting and pre-existing order.

func (*EpochQueue) OrderSlice

func (eq *EpochQueue) OrderSlice() []order.Order

OrderSlice extracts the orders in a slice. The slice ordering is random.

type Error

type Error string

Error is just a basic error.

func (Error) Error

func (e Error) Error() string

Error satisfies the error interface.

type FeeFetcher added in v0.2.0

type FeeFetcher interface {
	FeeRate(context.Context) uint64
	SwapFeeRate(context.Context) uint64
	LastRate() uint64
	MaxFeeRate() uint64
}

FeeFetcher is a fee fetcher for fetching fees. Fees are fickle, so fetch fees with FeeFetcher fairly frequently.

type FeeSource added in v0.2.0

type FeeSource interface {
	LastRate(assetID uint32) (feeRate uint64)
}

FeeSource is a source of the last reported tx fee rate estimate for an asset.

type Market

type Market struct {
	// contains filtered or unexported fields
}

Market is the market manager. It should not be overly involved with details of accounts and authentication. Via the account package it should request account status with new orders, verification of order signatures. The Market should also perform various account package callbacks such as order status updates so that the account package code can keep various data up-to-date, including order status, history, cancellation statistics, etc.

The Market performs the following:

  1. Receive and validate new order data (amounts vs. lot size, check fees, utxos, sufficient market buy buffer, etc.).
  2. Put incoming orders into the current epoch queue.
  3. Maintain an order book, which must also implement matcher.Booker.
  4. Initiate order matching with matcher.Match(book, currentQueue)
  5. During and/or after matching: * update the book (remove orders, add new standing orders, etc.) * retire/archive the epoch queue * publish the matches (and order book changes?) * initiate swaps for each match (possibly groups of related matches)
  6. Cycle the epochs.
  7. Record all events with the archivist.

func NewMarket

func NewMarket(cfg *Config) (*Market, error)

NewMarket creates a new Market for the provided base and quote assets, with an epoch cycling at given duration in milliseconds.

func (*Market) AccountPending added in v0.5.0

func (m *Market) AccountPending(acctAddr string, assetID uint32) (qty, lots uint64, redeems int)

AccountPending sums the orders quantities that pay to or from the specified account address.

func (*Market) Base

func (m *Market) Base() uint32

Base is the base asset ID.

func (*Market) Book

func (m *Market) Book() (epoch int64, buys, sells []*order.LimitOrder)

Book retrieves the market's current order book and the current epoch index. If the Market is not yet running or the start epoch has not yet begun, the epoch index will be zero.

func (*Market) Cancelable

func (m *Market) Cancelable(oid order.OrderID) bool

Cancelable determines if an order is a limit order with time-in-force standing that is in either the epoch queue or in the order book.

func (*Market) CancelableBy

func (m *Market) CancelableBy(oid order.OrderID, aid account.AccountID) (bool, error)

CancelableBy determines if an order is cancelable by a certain account. This means: (1) an order in the book or epoch queue, (2) type limit with time-in-force standing (implied for book orders), and (3) AccountID field matching the provided account ID.

func (*Market) CheckUnfilled

func (m *Market) CheckUnfilled(assetID uint32, user account.AccountID) (unbooked []*order.LimitOrder)

CheckUnfilled checks unfilled book orders belonging to a user and funded by coins for a given asset to ensure that their funding coins are not spent. If any of an order's funding coins are spent, the order is unbooked (removed from the in-memory book, revoked in the DB, a cancellation marked against the user, coins unlocked, and orderbook subscribers notified). See Unbook for details.

func (*Market) CoinLocked

func (m *Market) CoinLocked(asset uint32, coin coinlock.CoinID) bool

CoinLocked checks if a coin is locked. The asset is specified since we should not assume that a CoinID for one asset cannot be made to match another asset's CoinID.

func (*Market) EpochDuration

func (m *Market) EpochDuration() uint64

EpochDuration returns the Market's epoch duration in milliseconds.

func (*Market) FeedDone

func (m *Market) FeedDone(feed <-chan *updateSignal) bool

FeedDone informs the market that the caller is finished receiving from the given channel, which should have been obtained from OrderFeed. If the channel was a registered order feed channel from OrderFeed, it is closed and removed so that no further signals will be send on the channel.

func (*Market) LotSize added in v0.4.0

func (m *Market) LotSize() uint64

LotSize returns the market's lot size in units of the base asset.

func (*Market) MarketBuyBuffer

func (m *Market) MarketBuyBuffer() float64

MarketBuyBuffer returns the Market's market-buy buffer.

func (*Market) MidGap

func (m *Market) MidGap() uint64

MidGap returns the mid-gap market rate, which is ths rate halfway between the best buy order and the best sell order in the order book. If one side has no orders, the best order rate on other side is returned. If both sides have no orders, 0 is returned.

func (*Market) OrderFeed

func (m *Market) OrderFeed() <-chan *updateSignal

OrderFeed provides a new order book update channel. Channels provided before the market starts and while a market is running are both valid. When the market stops, channels are closed (invalidated), and new channels should be requested if the market starts again.

func (*Market) PurgeBook

func (m *Market) PurgeBook()

PurgeBook flushes all booked orders from the in-memory book and persistent storage. In terms of storage, this means changing orders with status booked to status revoked.

func (*Market) Quote

func (m *Market) Quote() uint32

Quote is the quote asset ID.

func (*Market) RateStep added in v0.4.0

func (m *Market) RateStep() uint64

RateStep returns the market's rate step in units of the quote asset.

func (*Market) ResumeEpoch

func (m *Market) ResumeEpoch(asSoonAs time.Time) (startEpochIdx int64)

ResumeEpoch gets the next available resume epoch index for the currently configured epoch duration for the market and the provided earliest allowable start time. The market must be running, otherwise the zero index is returned.

func (*Market) Run

func (m *Market) Run(ctx context.Context)

Run is the main order processing loop, which takes new orders, notifies book subscribers, and cycles the epochs. The caller should cancel the provided Context to stop the market. The outgoing order feed channels persist after Run returns for possible Market resume, and for Swapper's unbook callback to function using sendToFeeds.

func (*Market) Running

func (m *Market) Running() bool

Running indicates is the market is accepting new orders. This will return false when suspended, but false does not necessarily mean Run has stopped since a start epoch may be set. Note that this method is of limited use and communicating subsystems shouldn't rely on the result for correct operation since a market could start or stop. Rather, they should infer or be informed of market status rather than rely on this.

TODO: Instead of using Running in OrderRouter and DEX, these types should track statuses (known suspend times).

func (*Market) ScaleFeeRate added in v0.2.0

func (m *Market) ScaleFeeRate(assetID uint32, feeRate uint64) uint64

ScaleFeeRate scales the provided fee rate with the given asset's swap fee rate scale factor, which is 1.0 by default.

func (*Market) SetFeeRateScale added in v0.2.0

func (m *Market) SetFeeRateScale(assetID uint32, scale float64)

SetFeeRateScale sets a swap fee scale factor for the given asset. SetFeeRateScale should be called regardless of whether the Market is suspended.

func (*Market) SetStartEpochIdx

func (m *Market) SetStartEpochIdx(startEpochIdx int64)

SetStartEpochIdx sets the starting epoch index. This should generally be called before Run, or Start used to specify the index at the same time.

func (*Market) Start

func (m *Market) Start(ctx context.Context, startEpochIdx int64)

Start begins order processing with a starting epoch index. See also SetStartEpochIdx and Run. Stop the Market by cancelling the context.

func (*Market) Status

func (m *Market) Status() *Status

Status returns the current operating state of the Market.

func (*Market) SubmitOrder

func (m *Market) SubmitOrder(rec *orderRecord) error

SubmitOrder submits a new order for inclusion into the current epoch. This is the synchronous version of SubmitOrderAsync.

func (*Market) SubmitOrderAsync

func (m *Market) SubmitOrderAsync(rec *orderRecord) <-chan error

SubmitOrderAsync submits a new order for inclusion into the current epoch. When submission is completed, an error value will be sent on the channel. This is the asynchronous version of SubmitOrder.

func (*Market) Suspend

func (m *Market) Suspend(asSoonAs time.Time, persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time)

Suspend requests the market to gracefully suspend epoch cycling as soon as the given time, always allowing the epoch including that time to complete. If the time is before the current epoch, the current epoch will be the last.

func (*Market) SuspendASAP

func (m *Market) SuspendASAP(persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time)

SuspendASAP suspends requests the market to gracefully suspend epoch cycling as soon as possible, always allowing an active epoch to close. See also Suspend.

func (*Market) SwapDone added in v0.2.0

func (m *Market) SwapDone(ord order.Order, match *order.Match, fail bool)

SwapDone registers a match for a given order as being finished. Whether the match was a successful or failed swap is indicated by fail. This is used to (1) register completed orders for cancellation rate purposes, and (2) to unbook at-fault limit orders.

Implementation note: Orders that have failed a swap or were canceled (see processReadyEpoch) are removed from the settling map regardless of any amount still setting for such orders.

func (*Market) Unbook

func (m *Market) Unbook(lo *order.LimitOrder) bool

Unbook allows the DEX manager to remove a booked order. This does: (1) remove the order from the in-memory book, (2) unlock funding order coins, (3) set the order's status in the DB to "revoked", (4) inform the auth manager of the action for cancellation ratio accounting, and (5) send an 'unbook' notification to subscribers of this market's order book. Note that this presently treats the user as at-fault by counting the revocation in the user's cancellation statistics.

func (*Market) UnbookUserOrders

func (m *Market) UnbookUserOrders(user account.AccountID)

UnbookUserOrders unbooks all orders belonging to a user, unlocks the coins that were used to fund the unbooked orders, changes the orders' statuses to revoked in the DB, and notifies orderbook subscribers.

type MarketTunnel

type MarketTunnel interface {
	// SubmitOrder submits the order to the market for insertion into the epoch
	// queue.
	SubmitOrder(*orderRecord) error
	// MidGap returns the mid-gap market rate, which is ths rate halfway between
	// the best buy order and the best sell order in the order book.
	MidGap() uint64
	// MarketBuyBuffer is a coefficient that when multiplied by the market's lot
	// size specifies the minimum required amount for a market buy order.
	MarketBuyBuffer() float64
	// LotSize is the market's lot size in units of the base asset.
	LotSize() uint64
	// RateStep is the market's rate step in units of the quote asset.
	RateStep() uint64
	// CoinLocked should return true if the CoinID is currently a funding Coin
	// for an active DEX order. This is required for Coin validation to prevent
	// a user from submitting multiple orders spending the same Coin. This
	// method will likely need to check all orders currently in the epoch queue,
	// the order book, and the swap monitor, since UTXOs will still be unspent
	// according to the asset backends until the client broadcasts their
	// initialization transaction.
	//
	// DRAFT NOTE: This function could also potentially be handled by persistent
	// storage, since active orders and active matches are tracked there.
	CoinLocked(assetID uint32, coinID order.CoinID) bool
	// Cancelable determines whether an order is cancelable. A cancelable order
	// is a limit order with time-in-force standing either in the epoch queue or
	// in the order book.
	Cancelable(order.OrderID) bool

	// Suspend suspends the market as soon as a given time, returning the final
	// epoch index and and time at which that epoch closes.
	Suspend(asSoonAs time.Time, persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time)

	// Running indicates is the market is accepting new orders. This will return
	// false when suspended, but false does not necessarily mean Run has stopped
	// since a start epoch may be set.
	Running() bool

	// CheckUnfilled checks a user's unfilled book orders that are funded by
	// coins for a given asset to ensure that their funding coins are not spent.
	// If any of an unfilled order's funding coins are spent, the order is
	// unbooked (removed from the in-memory book, revoked in the DB, a
	// cancellation marked against the user, coins unlocked, and orderbook
	// subscribers notified). See Unbook for details.
	CheckUnfilled(assetID uint32, user account.AccountID) (unbooked []*order.LimitOrder)
}

MarketTunnel is a connection to a market.

type MatchNegotiator added in v0.5.0

type MatchNegotiator interface {
	// AccountStats collects stats about pending matches for account's address
	// on an account-based asset. qty is the total pending outgoing quantity,
	// swaps is the number matches with oustanding swaps funded by the account,
	// and redeem is the number of matches with outstanding redemptions that pay
	// to the account.
	AccountStats(acctAddr string, assetID uint32) (qty, swaps uint64, redeems int)
}

MatchNegotiator can view match-reserved funds for an account-based asset's address. MatchNegotiator is satisfied by *Swapper.

type OrderRouter

type OrderRouter struct {
	// contains filtered or unexported fields
}

OrderRouter handles the 'limit', 'market', and 'cancel' DEX routes. These are authenticated routes used for placing and canceling orders.

func NewOrderRouter

func NewOrderRouter(cfg *OrderRouterConfig) *OrderRouter

NewOrderRouter is a constructor for an OrderRouter.

func (*OrderRouter) Run

func (r *OrderRouter) Run(ctx context.Context)

func (*OrderRouter) Suspend

func (r *OrderRouter) Suspend(asSoonAs time.Time, persistBooks bool) map[string]*SuspendEpoch

Suspend is like SuspendMarket, but for all known markets.

func (*OrderRouter) SuspendMarket

func (r *OrderRouter) SuspendMarket(mktName string, asSoonAs time.Time, persistBooks bool) *SuspendEpoch

SuspendMarket schedules a suspension of a given market, with the option to persist the orders on the book (or purge the book automatically on market shutdown). The scheduled final epoch and suspend time are returned. Note that OrderRouter is a proxy for this request to the ultimate Market. This is done because OrderRouter is the entry point for new orders into the market. TODO: track running, suspended, and scheduled-suspended markets, appropriately blocking order submission according to the schedule rather than just checking Market.Running prior to submitting incoming orders to the Market.

type OrderRouterConfig

type OrderRouterConfig struct {
	AuthManager AuthManager
	Assets      map[uint32]*asset.BackedAsset
	Markets     map[string]MarketTunnel
	FeeSource   FeeSource
	DEXBalancer *DEXBalancer
}

OrderRouterConfig is the configuration settings for an OrderRouter.

type PendingAccounter added in v0.5.0

type PendingAccounter interface {
	// AccountPending retreives the total pending order-reserved quantity for
	// the asset, as well as the number of possible pending redemptions
	// (a.k.a. ordered lots).
	AccountPending(acctAddr string, assetID uint32) (qty, lots uint64, redeems int)
	// Base is the base asset ID.
	Base() uint32
	// Quote is the quote asset ID.
	Quote() uint32
}

PendingAccounter can view order-reserved funds for an account-based asset's address. PendingAccounter is satisfied by *Market.

type Status

type Status struct {
	Running       bool
	EpochDuration uint64 // to compute times from epoch inds
	ActiveEpoch   int64
	StartEpoch    int64
	SuspendEpoch  int64
	PersistBook   bool
	Base, Quote   uint32
}

Status describes the operation state of the Market.

type Storage added in v0.2.0

type Storage interface {
	db.OrderArchiver
	LastErr() error
	Fatal() <-chan struct{}
	Close() error
	InsertEpoch(ed *db.EpochResults) error
	MarketMatches(base, quote uint32) ([]*db.MatchDataWithCoins, error)
	InsertMatch(match *order.Match) error
}

Storage is the DB interface required by Market.

type SuspendEpoch

type SuspendEpoch struct {
	Idx int64
	End time.Time
}

SuspendEpoch holds the index and end time of final epoch marking the suspension of a market.

type Swapper

type Swapper interface {
	Negotiate(matchSets []*order.MatchSet)
	CheckUnspent(ctx context.Context, asset uint32, coinID []byte) error
	UserSwappingAmt(user account.AccountID, base, quote uint32) (amt, count uint64)
	ChainsSynced(base, quote uint32) (bool, error)
}

Swapper coordinates atomic swaps for one or more matchsets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL