decred.org/dcrdex@v1.0.5/server/db/interface.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package db 5 6 import ( 7 "context" 8 "time" 9 10 "decred.org/dcrdex/dex" 11 "decred.org/dcrdex/dex/candles" 12 "decred.org/dcrdex/dex/order" 13 "decred.org/dcrdex/server/account" 14 ) 15 16 // EpochResults represents the outcome of epoch order processing, including 17 // preimage collection, and computation of commitment checksum and shuffle seed. 18 // MatchTime is the time at which order matching is executed. 19 type EpochResults struct { 20 MktBase, MktQuote uint32 21 Idx int64 22 Dur int64 23 MatchTime int64 24 CSum []byte 25 Seed []byte 26 OrdersRevealed []order.OrderID 27 OrdersMissed []order.OrderID 28 MatchVolume uint64 29 QuoteVolume uint64 30 BookBuys uint64 31 BookBuys5 uint64 32 BookBuys25 uint64 33 BookSells uint64 34 BookSells5 uint64 35 BookSells25 uint64 36 HighRate uint64 37 LowRate uint64 38 StartRate uint64 39 EndRate uint64 40 } 41 42 // OrderStatus is the current status of an order. 43 type OrderStatus struct { 44 ID order.OrderID 45 Status order.OrderStatus 46 } 47 48 // PreimageResult is the outcome of preimage collection for an order in an epoch 49 // that closed at a certain time. 50 type PreimageResult struct { 51 Miss bool 52 Time int64 53 ID order.OrderID 54 } 55 56 // KeyIndexer are the functions required to track an extended public key and 57 // derived children by index. 58 type KeyIndexer interface { 59 KeyIndex(xpub string) (uint32, error) 60 SetKeyIndex(idx uint32, xpub string) error 61 } 62 63 // DEXArchivist will be composed of several different interfaces. Starting with 64 // OrderArchiver. 65 type DEXArchivist interface { 66 // LastErr should returns any fatal or unexpected error encountered by the 67 // archivist backend. This may be used to check if the database had an 68 // unrecoverable error (disconnect, etc.). 69 LastErr() error 70 71 // Fatal provides select semantics like Context.Done when there is a fatal 72 // backend error. Use LastErr to get the error. 73 Fatal() <-chan struct{} 74 75 // Close should gracefully shutdown the backend, returning when complete. 76 Close() error 77 78 // InsertEpoch stores the results of a newly-processed epoch. 79 InsertEpoch(ed *EpochResults) error 80 81 // LastEpochRate gets the EndRate of the last EpochResults inserted for the 82 // market. If the database is empty, no error and a rate of zero are 83 // returned. 84 LastEpochRate(b, q uint32) (uint64, error) 85 86 // LoadEpochStats reads all market epoch history from the database. 87 LoadEpochStats(uint32, uint32, []*candles.Cache) error 88 LastCandleEndStamp(base, quote uint32, candleDur uint64) (uint64, error) 89 InsertCandles(base, quote uint32, dur uint64, cs []*candles.Candle) error 90 91 OrderArchiver 92 AccountArchiver 93 KeyIndexer 94 MatchArchiver 95 SwapArchiver 96 } 97 98 // OrderArchiver is the interface required for storage and retrieval of all 99 // order data. 100 type OrderArchiver interface { 101 // Order retrieves an order with the given OrderID, stored for the market 102 // specified by the given base and quote assets. 103 Order(oid order.OrderID, base, quote uint32) (order.Order, order.OrderStatus, error) 104 105 // BookOrders returns all book orders for a market. 106 BookOrders(base, quote uint32) ([]*order.LimitOrder, error) 107 108 // EpochOrders returns all epoch orders for a market. 109 EpochOrders(base, quote uint32) ([]order.Order, error) 110 111 // FlushBook revokes all booked orders for a market. 112 FlushBook(base, quote uint32) (sellsRemoved, buysRemoved []order.OrderID, err error) 113 114 // ActiveOrderCoins retrieves a CoinID slice for each active order. 115 ActiveOrderCoins(base, quote uint32) (baseCoins, quoteCoins map[order.OrderID][]order.CoinID, err error) 116 117 // UserOrders retrieves all orders for the given account in the market 118 // specified by a base and quote asset. 119 UserOrders(ctx context.Context, aid account.AccountID, base, quote uint32) ([]order.Order, []order.OrderStatus, error) 120 121 // UserOrderStatuses retrieves the statuses and filled amounts of the orders 122 // with the provided order IDs for the given account in the market specified 123 // by a base and quote asset. 124 // The number and ordering of the returned statuses is not necessarily the 125 // same as the number and ordering of the provided order IDs. It is not an 126 // error if any or all of the provided order IDs cannot be found for the 127 // given account in the specified market. 128 UserOrderStatuses(aid account.AccountID, base, quote uint32, oids []order.OrderID) ([]*OrderStatus, error) 129 130 // ActiveUserOrderStatuses retrieves the statuses and filled amounts of all 131 // active orders for a user across all markets. 132 ActiveUserOrderStatuses(aid account.AccountID) ([]*OrderStatus, error) 133 134 // CompletedUserOrders retrieves the N most recently completed orders for a 135 // user across all markets. 136 CompletedUserOrders(aid account.AccountID, N int) (oids []order.OrderID, compTimes []int64, err error) 137 138 // PreimageStats retrieves the N most recent results of preimage requests 139 // for the user across all markets. 140 PreimageStats(user account.AccountID, lastN int) ([]*PreimageResult, error) 141 142 // ExecutedCancelsForUser retrieves up to N executed cancel orders for a 143 // given user. These may be user-initiated cancels, or cancels created by 144 // the server (revokes). Executed cancel orders from all markets are 145 // returned. 146 ExecutedCancelsForUser(aid account.AccountID, N int) ([]*CancelRecord, error) 147 148 // OrderWithCommit searches all markets' trade and cancel orders, both 149 // active and archived, for an order with the given Commitment. 150 OrderWithCommit(ctx context.Context, commit order.Commitment) (found bool, oid order.OrderID, err error) 151 152 // OrderStatus gets the status, ID, and filled amount of the given order. 153 OrderStatus(order.Order) (order.OrderStatus, order.OrderType, int64, error) 154 155 // NewEpochOrder stores a new order with epoch status. Such orders are 156 // pending execution or insertion on a book (standing limit orders with a 157 // remaining unfilled amount). For trade orders, the epoch gap should be 158 // db.EpochGapNA, while for cancel orders it is the number of epochs since 159 // the targeted order was placed, as described in the docs for CancelRecord. 160 NewEpochOrder(ord order.Order, epochIdx, epochDur int64, epochGap int32) error 161 162 // StorePreimage stores the preimage associated with an existing order. 163 StorePreimage(ord order.Order, pi order.Preimage) error 164 165 // BookOrder books the given order. If the order was already stored (i.e. 166 // NewEpochOrder), it's status and filled amount are updated, otherwise it 167 // is inserted. See also UpdateOrderFilled. 168 BookOrder(*order.LimitOrder) error 169 170 // ExecuteOrder puts the order into the executed state, and sets the filled 171 // amount for market and limit orders. For unmatched cancel orders, use 172 // FailCancelOrder instead. 173 ExecuteOrder(ord order.Order) error 174 175 // CancelOrder puts a limit order into the canceled state. Market orders 176 // must use ExecuteOrder since they may not be canceled. Similarly, cancel 177 // orders must use ExecuteOrder or FailCancelOrder. Orders that are 178 // terminated by the DEX rather than via a cancel order are considered 179 // "revoked", and RevokeOrder should be used to set this status. 180 CancelOrder(*order.LimitOrder) error 181 182 // RevokeOrder puts an order into the revoked state, and generates a cancel 183 // order to record the action. Orders should be revoked by the DEX according 184 // to policy on failed orders. For canceling an order that was matched with 185 // a cancel order, use CancelOrder. 186 RevokeOrder(order.Order) (cancelID order.OrderID, t time.Time, err error) 187 188 // RevokeOrderUncounted is like RevokeOrder except that the generated cancel 189 // order will not be counted against the user. i.e. ExecutedCancelsForUser 190 // should not return the cancel orders created this way. 191 RevokeOrderUncounted(order.Order) (cancelID order.OrderID, t time.Time, err error) 192 193 // NewArchivedCancel stores a cancel order directly in the executed state. This 194 // is used for orders that are canceled when the market is suspended, and therefore 195 // do not need to be matched. 196 NewArchivedCancel(ord *order.CancelOrder, epochID, epochDur int64) error 197 198 // FailCancelOrder puts an unmatched cancel order into the executed state. 199 // For matched cancel orders, use ExecuteOrder. 200 FailCancelOrder(*order.CancelOrder) error 201 202 // UpdateOrderFilled updates the filled amount of the given order. This 203 // function applies only to limit orders, not cancel or market orders. The 204 // filled amount of a market order should be updated by ExecuteOrder. 205 UpdateOrderFilled(*order.LimitOrder) error 206 207 // UpdateOrderStatus updates the status and filled amount of the given 208 // order. 209 UpdateOrderStatus(order.Order, order.OrderStatus) error 210 211 // SetOrderCompleteTime sets the successful completion time for an existing 212 // order. This will follow the final step in swap negotiation, for an order 213 // that is not on the book. 214 SetOrderCompleteTime(ord order.Order, compTimeMs int64) error 215 } 216 217 // Account holds data returned by Accounts. 218 type Account struct { 219 AccountID account.AccountID `json:"accountid"` 220 Pubkey dex.Bytes `json:"pubkey"` 221 } 222 223 // Bond represents a time-locked fidelity bond posted by a user. 224 type Bond struct { 225 Version uint16 226 AssetID uint32 227 CoinID []byte 228 Amount int64 229 Strength uint32 // Amount / <bond increment at time of acceptance> 230 LockTime int64 231 232 // Will we need to store asset-specific data, like the redeem script for 233 // UTXO assets or a contract address or bond key for account assets? Or will 234 // that info be conveyed by Version and CoinID? 235 // 236 // Data []byte 237 } 238 239 // AccountArchiver is the interface required for storage and retrieval of all 240 // account data. 241 type AccountArchiver interface { 242 // Account retrieves the account information for the specified account ID. A 243 // nil pointer will be returned for unknown or closed accounts. Bond and 244 // registration fee payment status is returned as well. A bond is active if 245 // its lockTime is after the lockTimeThresh Time, which should be 246 // time.Now().Add(bondExpiry). The legacy bool return refers to the legacy 247 // registration fee system, and legacyPaid indicates if the account has a 248 // recorded fee coin (paid legacy fee). 249 Account(acctID account.AccountID, lockTimeThresh time.Time) (acct *account.Account, activeBonds []*Bond) 250 251 // CreateAccountWithBond creates a new account with the given bond. This is 252 // used for the new postbond request protocol. The bond tx should be 253 // fully-confirmed. 254 CreateAccountWithBond(acct *account.Account, bond *Bond) error 255 256 // AddBond stores a new Bond, which is uniquely identified by (asset ID, 257 // coin ID), for an existing account. 258 AddBond(acct account.AccountID, bond *Bond) error 259 260 // DeleteBond deletes a bond which should generally be expired. 261 DeleteBond(assetID uint32, coinID []byte) error 262 263 FetchPrepaidBond(bondCoinID []byte) (strength uint32, lockTime int64, err error) 264 DeletePrepaidBond(coinID []byte) error 265 StorePrepaidBonds(coinIDs [][]byte, strength uint32, lockTime int64) error 266 267 // AccountInfo returns data for an account. 268 AccountInfo(account.AccountID) (*Account, error) 269 } 270 271 // MatchData represents an order pair match, but with just the order IDs instead 272 // of the full orders. The actual orders may be retrieved by ID. 273 type MatchData struct { 274 ID order.MatchID 275 Taker order.OrderID 276 TakerAcct account.AccountID 277 TakerAddr string 278 TakerSell bool 279 Maker order.OrderID 280 MakerAcct account.AccountID 281 MakerAddr string 282 Epoch order.EpochID 283 Quantity uint64 284 Rate uint64 285 BaseRate uint64 286 QuoteRate uint64 287 Active bool // match negotiation in progress, not yet completed or failed 288 Status order.MatchStatus // note that failed swaps, where Active=false, can have any status 289 } 290 291 // MatchDataWithCoins pairs MatchData (embedded) with the encode swap and redeem 292 // coin IDs blobs for both maker and taker. 293 type MatchDataWithCoins struct { 294 MatchData 295 MakerSwapCoin []byte 296 MakerRedeemCoin []byte 297 TakerSwapCoin []byte 298 TakerRedeemCoin []byte 299 } 300 301 // MatchStatus is the current status of a match, its known contracts and coin 302 // IDs, and its secret, if known. 303 type MatchStatus struct { 304 ID order.MatchID 305 Status order.MatchStatus 306 MakerContract []byte 307 TakerContract []byte 308 MakerSwap []byte 309 TakerSwap []byte 310 MakerRedeem []byte 311 TakerRedeem []byte 312 Secret []byte 313 Active bool 314 TakerSell bool 315 IsTaker bool 316 IsMaker bool 317 } 318 319 // SwapData contains the data generated by the clients during swap negotiation. 320 type SwapData struct { 321 SigMatchAckMaker []byte 322 SigMatchAckTaker []byte 323 ContractA []byte // contains the secret hash used by both parties 324 ContractACoinID []byte 325 ContractATime int64 326 ContractAAckSig []byte // B's signature of contract A data 327 ContractB []byte 328 ContractBCoinID []byte 329 ContractBTime int64 330 ContractBAckSig []byte // A's signature of contract B data 331 RedeemACoinID []byte 332 RedeemASecret []byte // the secret revealed in A's redeem, also used in B's redeem 333 RedeemATime int64 334 RedeemAAckSig []byte // B's signature of redeem A data 335 RedeemBCoinID []byte 336 RedeemBTime int64 337 } 338 339 // SwapDataFull combines a MatchData, SwapData, and the Base/Quote asset IDs. 340 type SwapDataFull struct { 341 Base, Quote uint32 342 *MatchData 343 *SwapData 344 } 345 346 // MarketMatchID designates a MatchID for a certain market by the market's 347 // base-quote asset IDs. 348 type MarketMatchID struct { 349 order.MatchID 350 Base, Quote uint32 // market 351 } 352 353 // MatchID constructs a MarketMatchID from an order.Match. 354 func MatchID(match *order.Match) MarketMatchID { 355 return MarketMatchID{ 356 MatchID: match.ID(), 357 Base: match.Maker.BaseAsset, // same for taker's redeem as BaseAsset refers to the market 358 Quote: match.Maker.QuoteAsset, 359 } 360 } 361 362 // MatchOutcome pairs an inactive match's status with a timestamp. In the case 363 // of a successful match for the user, this is when their redeem was received. 364 // In the case of an at-fault match failure for the user, this corresponds to 365 // the time of the previous match action. The previous action times are: match 366 // time, swap txn validated times, and initiator redeem validated time. Note 367 // that this does not directly correspond to match revocation times where 368 // inaction deadline references the time when the swap txns reach the required 369 // confirms. These times must match the reference times provided to the auth 370 // manager when registering new swap outcomes. 371 type MatchOutcome struct { 372 Status order.MatchStatus 373 ID order.MatchID 374 Fail bool // taker must reach MatchComplete, maker succeeds at MakerRedeemed 375 Time int64 376 Value uint64 377 Base, Quote uint32 // the market 378 } 379 380 // MatchFail is a failed match and the effect on the user's score 381 type MatchFail struct { 382 ID order.MatchID 383 Status order.MatchStatus 384 } 385 386 // MatchArchiver is the interface required for storage and retrieval of all 387 // match data. 388 type MatchArchiver interface { 389 InsertMatch(match *order.Match) error 390 MatchByID(mid order.MatchID, base, quote uint32) (*MatchData, error) 391 UserMatches(aid account.AccountID, base, quote uint32) ([]*MatchData, error) 392 CompletedAndAtFaultMatchStats(aid account.AccountID, lastN int) ([]*MatchOutcome, error) 393 UserMatchFails(aid account.AccountID, lastN int) ([]*MatchFail, error) 394 ForgiveMatchFail(mid order.MatchID) (bool, error) 395 AllActiveUserMatches(aid account.AccountID) ([]*MatchData, error) 396 MarketMatches(base, quote uint32) ([]*MatchDataWithCoins, error) 397 MarketMatchesStreaming(base, quote uint32, includeInactive bool, N int64, f func(*MatchDataWithCoins) error) (int, error) 398 MatchStatuses(aid account.AccountID, base, quote uint32, matchIDs []order.MatchID) ([]*MatchStatus, error) 399 } 400 401 // SwapArchiver is the interface required for storage and retrieval of swap 402 // counterparty data. 403 // 404 // In the swap process, the counterparties are: 405 // - Initiator or party A on chain X. This is the maker in the DEX. 406 // - Participant or party B on chain Y. This is the taker in the DEX. 407 // 408 // For each match, a successful swap will generate the following data that must 409 // be stored: 410 // - 5 client signatures. Both parties sign the data to acknowledge (1) the 411 // match ack, and (2) the counterparty's contract script and contract 412 // transaction. Plus the taker acks the maker's redemption transaction. 413 // - 2 swap contracts and the associated transaction outputs (more generally, 414 // coinIDs), one on each party's blockchain. 415 // - 2 redemption transaction outputs (coinIDs). 416 // 417 // The methods for saving this data are defined below in the order in which the 418 // data is expected from the parties. 419 type SwapArchiver interface { 420 // ActiveSwaps loads the full details for all active swaps across all markets. 421 ActiveSwaps() ([]*SwapDataFull, error) 422 423 // SwapData retrieves the swap/match status and the current SwapData. 424 SwapData(mid MarketMatchID) (order.MatchStatus, *SwapData, error) 425 426 // Match acknowledgement message signatures. 427 428 // SaveMatchAckSigA records the match data acknowledgement signature from 429 // swap party A (the initiator), which is the maker in the DEX. 430 SaveMatchAckSigA(mid MarketMatchID, sig []byte) error 431 432 // SaveMatchAckSigB records the match data acknowledgement signature from 433 // swap party B (the participant), which is the taker in the DEX. 434 SaveMatchAckSigB(mid MarketMatchID, sig []byte) error 435 436 // Swap contracts, and counterparty audit acknowledgement signatures. 437 438 // SaveContractA records party A's swap contract script and the coinID (e.g. 439 // transaction output) containing the contract on chain X. Note that this 440 // contract contains the secret hash. 441 SaveContractA(mid MarketMatchID, contract []byte, coinID []byte, timestamp int64) error 442 443 // SaveAuditAckSigB records party B's signature acknowledging their audit of 444 // A's swap contract. 445 SaveAuditAckSigB(mid MarketMatchID, sig []byte) error 446 447 // SaveContractB records party B's swap contract script and the coinID (e.g. 448 // transaction output) containing the contract on chain Y. 449 SaveContractB(mid MarketMatchID, contract []byte, coinID []byte, timestamp int64) error 450 451 // SaveAuditAckSigA records party A's signature acknowledging their audit of 452 // B's swap contract. 453 SaveAuditAckSigA(mid MarketMatchID, sig []byte) error 454 455 // Redemption transactions, and counterparty acknowledgement signatures. 456 457 // SaveRedeemA records party A's redemption coinID (e.g. transaction 458 // output), which spends party B's swap contract on chain Y. Note that this 459 // transaction will contain the secret, which party B extracts. 460 SaveRedeemA(mid MarketMatchID, coinID, secret []byte, timestamp int64) error 461 462 // SaveRedeemAckSigB records party B's signature acknowledging party A's 463 // redemption, which spent their swap contract on chain Y and revealed the 464 // secret. 465 SaveRedeemAckSigB(mid MarketMatchID, sig []byte) error 466 467 // SaveRedeemB records party B's redemption coinID (e.g. transaction 468 // output), which spends party A's swap contract on chain X. This should 469 // also flag the match as inactive. 470 SaveRedeemB(mid MarketMatchID, coinID []byte, timestamp int64) error 471 472 // SetMatchInactive sets the swap as done/inactive. This can be because of a 473 // failed or successfully completed swap, but in practice this will be used 474 // for failed swaps since SaveRedeemB flags the swap as done/inactive. If 475 // the match is being marked as inactive prior to MatchComplete (the match 476 // was revoked) but the user is not at fault, the forgive bool may be set to 477 // true so the outcome will not count against the user who would have the 478 // next action in the swap. 479 SetMatchInactive(mid MarketMatchID, forgive bool) error 480 } 481 482 // ValidateOrder ensures that the order with the given status for the specified 483 // market is sensible. This function is in the database package because the 484 // concept of a valid order-status-market state is dependent on the semantics of 485 // order archival. The ServerTime may not be set yet, so the OrderID cannot be 486 // computed. 487 func ValidateOrder(ord order.Order, status order.OrderStatus, mkt *dex.MarketInfo) bool { 488 // Orders with status OrderStatusUnknown should never reach the database. 489 if status == order.OrderStatusUnknown { 490 return false 491 } 492 493 // Bad MarketInfo! 494 if mkt.Base == mkt.Quote { 495 panic("MarketInfo specifies market with same base and quote assets") 496 } 497 498 return order.ValidateOrder(ord, status, mkt.LotSize) == nil 499 } 500 501 // EpochGapNA is a specifier for an epoch gap (epochs between limit order and 502 // cancel order) when such a designation doesn't apply in-context. For instance, 503 // revocations are treated in many places like cancel orders, but there is no 504 // reason to consider the epoch gap. 505 const EpochGapNA int32 = -1 506 507 // CancelRecord is info about a cancel order and when it matched. 508 type CancelRecord struct { 509 ID order.OrderID 510 TargetID order.OrderID 511 MatchTime int64 512 // EpochGap is the number of epochs passed since the targeted trade order 513 // was placed, where 0 means canceled in the same epoch, 1 means canceled in 514 // the next epoch, etc. 515 EpochGap int32 516 }