decred.org/dcrdex@v1.0.5/client/db/types.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 "bytes" 8 "fmt" 9 "math" 10 "os" 11 "strconv" 12 "strings" 13 "time" 14 15 "decred.org/dcrdex/client/asset" 16 "decred.org/dcrdex/dex" 17 "decred.org/dcrdex/dex/config" 18 "decred.org/dcrdex/dex/encode" 19 "decred.org/dcrdex/dex/order" 20 "github.com/decred/dcrd/dcrec/secp256k1/v4" 21 "golang.org/x/crypto/blake2s" 22 ) 23 24 // Severity indicates the level of required action for a notification. The DEX 25 // db only stores notifications with Severity >= Success. 26 type Severity uint8 27 28 const ( 29 Ignorable Severity = iota 30 // Data notifications are not meant for display to the user. These 31 // notifications are used only for communication of information necessary for 32 // UI updates or other high-level state changes. 33 Data 34 // Poke notifications are not persistent across sessions. These should be 35 // displayed if the user has a live notification feed. They are not stored in 36 // the database. 37 Poke 38 // Success and higher are stored and can be recalled using DB.NotificationsN. 39 Success 40 WarningLevel 41 ErrorLevel 42 ) 43 44 const ( 45 ErrNoCredentials = dex.ErrorKind("no credentials have been stored") 46 ErrAcctNotFound = dex.ErrorKind("account not found") 47 ErrNoSeedGenTime = dex.ErrorKind("seed generation time has not been stored") 48 ) 49 50 // String satisfies fmt.Stringer for Severity. 51 func (s Severity) String() string { 52 switch s { 53 case Ignorable: 54 return "ignore" 55 case Data: 56 return "data" 57 case Poke: 58 return "poke" 59 case WarningLevel: 60 return "warning" 61 case ErrorLevel: 62 return "error" 63 case Success: 64 return "success" 65 } 66 return "unknown severity" 67 } 68 69 // PrimaryCredentials should be created during app initialization. Both the seed 70 // and the inner key (and technically the other two fields) should be generated 71 // with a cryptographically-secure prng. 72 type PrimaryCredentials struct { 73 // EncSeed is the root seed used to create a hierarchical deterministic 74 // key chain (see also dcrd/hdkeychain.NewMaster/ExtendedKey). 75 EncSeed []byte 76 // EncInnerKey is an encrypted encryption key. The inner key will never 77 // change. The inner key is encrypted with the outer key, which itself is 78 // based on the user's password. 79 EncInnerKey []byte 80 // InnerKeyParams are the key parameters for the inner key. 81 InnerKeyParams []byte 82 // OuterKeyParams are the key parameters for the outer key. 83 OuterKeyParams []byte 84 // Birthday is the time stamp associated with seed creation. 85 Birthday time.Time 86 // Version is the current PrimaryCredentials version. 87 Version uint16 88 } 89 90 // when updating to bonds, default to 42 (DCR) 91 const defaultBondAsset = 42 92 93 // BondUID generates a unique identifier from a bond's asset ID and coin ID. 94 func BondUID(assetID uint32, bondCoinID []byte) []byte { 95 return hashKey(append(uint32Bytes(assetID), bondCoinID...)) 96 } 97 98 // Bond is stored in a sub-bucket of an account bucket. The dex.Bytes type is 99 // used for certain fields so that the data marshals to/from hexadecimal. 100 type Bond struct { 101 Version uint16 `json:"ver"` 102 AssetID uint32 `json:"asset"` 103 CoinID dex.Bytes `json:"coinID"` 104 UnsignedTx dex.Bytes `json:"utx"` 105 SignedTx dex.Bytes `json:"stx"` // can be obtained from msgjson.Bond.CoinID 106 Data dex.Bytes `json:"data"` // e.g. redeem script 107 Amount uint64 `json:"amt"` 108 LockTime uint64 `json:"lockTime"` 109 KeyIndex uint32 `json:"keyIndex"` // child key index for HD path: m / hdKeyPurposeBonds / assetID' / bondIndex 110 RefundTx dex.Bytes `json:"refundTx"` // pays to wallet that created it - only a backup for emergency! 111 112 Confirmed bool `json:"confirmed"` // if reached required confs according to server, not in serialization 113 Refunded bool `json:"refunded"` // not in serialization 114 115 Strength uint32 `json:"strength"` 116 } 117 118 // UniqueID computes the bond's unique ID for keying purposes. 119 func (b *Bond) UniqueID() []byte { 120 return BondUID(b.AssetID, b.CoinID) 121 } 122 123 // Encode serialized the Bond. Confirmed and Refund are not included. 124 func (b *Bond) Encode() []byte { 125 return versionedBytes(2). 126 AddData(uint16Bytes(b.Version)). 127 AddData(uint32Bytes(b.AssetID)). 128 AddData(b.CoinID). 129 AddData(b.UnsignedTx). 130 AddData(b.SignedTx). 131 AddData(b.Data). 132 AddData(uint64Bytes(b.Amount)). 133 AddData(uint64Bytes(b.LockTime)). 134 AddData(uint32Bytes(b.KeyIndex)). 135 AddData(b.RefundTx). 136 AddData(uint32Bytes(b.Strength)) 137 // Confirmed and Refunded are not part of the encoding. 138 } 139 140 // DecodeBond decodes the versioned blob into a *Bond. 141 func DecodeBond(b []byte) (*Bond, error) { 142 ver, pushes, err := encode.DecodeBlob(b) 143 if err != nil { 144 return nil, err 145 } 146 switch ver { 147 case 0: 148 return decodeBond_v0(pushes) 149 case 1: 150 return decodeBond_v1(pushes) 151 case 2: 152 return decodeBond_v2(pushes) 153 } 154 return nil, fmt.Errorf("unknown Bond version %d", ver) 155 } 156 157 // decodeBond_v0 handles the unreleased v0 db.Bond format that did spend some 158 // notable time on master. The app will recognize the special KeyIndex value and 159 // use the RefundTx instead, if there were were any unspent bonds at time of 160 // upgrade, but this is mainly so we can decode the v0 blobs. 161 func decodeBond_v0(pushes [][]byte) (*Bond, error) { 162 if len(pushes) != 10 { 163 return nil, fmt.Errorf("decodeBond_v0: expected 10 data pushes, got %d", len(pushes)) 164 } 165 ver, assetIDB, coinID := pushes[0], pushes[1], pushes[2] 166 utx, stx := pushes[3], pushes[4] 167 data, amtB, lockTimeB := pushes[5], pushes[6], pushes[7] 168 // privKey := pushes[8] // in v0, so we will use the refundTx to handle this unreleased revision without deleting out DB files 169 refundTx := pushes[9] 170 return &Bond{ 171 Version: intCoder.Uint16(ver), 172 AssetID: intCoder.Uint32(assetIDB), 173 CoinID: coinID, 174 UnsignedTx: utx, 175 SignedTx: stx, 176 Data: data, 177 Amount: intCoder.Uint64(amtB), 178 LockTime: intCoder.Uint64(lockTimeB), 179 KeyIndex: math.MaxUint32, // special 180 RefundTx: refundTx, 181 }, nil 182 } 183 184 func decodeBond_v1(pushes [][]byte) (*Bond, error) { 185 if len(pushes) != 10 { 186 return nil, fmt.Errorf("decodeBond_v0: expected 10 data pushes, got %d", len(pushes)) 187 } 188 return decodeBond_v2(append(pushes, []byte{0, 0, 0, 0} /* uint32 strength */)) 189 } 190 191 func decodeBond_v2(pushes [][]byte) (*Bond, error) { 192 if len(pushes) != 11 { 193 return nil, fmt.Errorf("decodeBond_v0: expected 10 data pushes, got %d", len(pushes)) 194 } 195 ver, assetIDB, coinID := pushes[0], pushes[1], pushes[2] 196 utx, stx := pushes[3], pushes[4] 197 data, amtB, lockTimeB := pushes[5], pushes[6], pushes[7] 198 keyIndex, refundTx, strength := pushes[8], pushes[9], pushes[10] 199 return &Bond{ 200 Version: intCoder.Uint16(ver), 201 AssetID: intCoder.Uint32(assetIDB), 202 CoinID: coinID, 203 UnsignedTx: utx, 204 SignedTx: stx, 205 Data: data, 206 Amount: intCoder.Uint64(amtB), 207 LockTime: intCoder.Uint64(lockTimeB), 208 KeyIndex: intCoder.Uint32(keyIndex), 209 RefundTx: refundTx, 210 Strength: intCoder.Uint32(strength), 211 }, nil 212 } 213 214 // AccountInfo is information about an account on a Decred DEX. The database 215 // is designed for one account per server. 216 type AccountInfo struct { 217 // Host, Cert, and DEXPubKey identify the DEX server. 218 Host string 219 Cert []byte 220 DEXPubKey *secp256k1.PublicKey 221 222 // EncKeyV2 is an encrypted private key generated deterministically from the 223 // app seed. 224 EncKeyV2 []byte 225 // LegacyEncKey is an old-style non-hierarchical key that must be included 226 // when exporting the client credentials, since it cannot be regenerated 227 // automatically. 228 LegacyEncKey []byte 229 230 Bonds []*Bond 231 TargetTier uint64 // zero means no bond maintenance (allows actual tier to drop negative) 232 MaxBondedAmt uint64 233 PenaltyComps uint16 234 BondAsset uint32 // the asset to use when auto-posting bonds 235 Disabled bool // whether the account is disabled 236 237 // DEPRECATED reg fee data. Bond txns are in a sub-bucket. 238 // Left until we need to upgrade just for serialization simplicity. 239 LegacyFeeCoin []byte 240 LegacyFeeAssetID uint32 241 LegacyFeePaid bool 242 } 243 244 // Encode the AccountInfo as bytes. NOTE: remove deprecated fee fields and do a 245 // DB upgrade at some point. But how to deal with old accounts needing to store 246 // this data forever? 247 func (ai *AccountInfo) Encode() []byte { 248 return versionedBytes(4). 249 AddData([]byte(ai.Host)). 250 AddData(ai.Cert). 251 AddData(ai.DEXPubKey.SerializeCompressed()). 252 AddData(ai.EncKeyV2). 253 AddData(ai.LegacyEncKey). 254 AddData(encode.Uint64Bytes(ai.TargetTier)). 255 AddData(encode.Uint64Bytes(ai.MaxBondedAmt)). 256 AddData(encode.Uint32Bytes(ai.BondAsset)). 257 AddData(encode.Uint32Bytes(ai.LegacyFeeAssetID)). 258 AddData(ai.LegacyFeeCoin). 259 AddData(encode.Uint16Bytes(ai.PenaltyComps)) 260 } 261 262 // ViewOnly is true if account keys are not saved. 263 func (ai *AccountInfo) ViewOnly() bool { 264 return len(ai.EncKey()) == 0 265 } 266 267 // EncKey is the encrypted account private key. 268 func (ai *AccountInfo) EncKey() []byte { 269 if len(ai.EncKeyV2) > 0 { 270 return ai.EncKeyV2 271 } 272 return ai.LegacyEncKey 273 } 274 275 // DecodeAccountInfo decodes the versioned blob into an *AccountInfo. The byte 276 // slice fields of AccountInfo reference the underlying buffer of the the input. 277 func DecodeAccountInfo(b []byte) (*AccountInfo, error) { 278 ver, pushes, err := encode.DecodeBlob(b) 279 if err != nil { 280 return nil, err 281 } 282 switch ver { 283 case 0: 284 return decodeAccountInfo_v0(pushes) // caller must decode account proof 285 case 1: 286 return decodeAccountInfo_v1(pushes) 287 case 2: 288 return decodeAccountInfo_v2(pushes) 289 case 3: 290 return decodeAccountInfo_v3(pushes) 291 case 4: 292 return decodeAccountInfo_v4(pushes) 293 } 294 return nil, fmt.Errorf("unknown AccountInfo version %d", ver) 295 } 296 297 func decodeAccountInfo_v0(pushes [][]byte) (*AccountInfo, error) { 298 return decodeAccountInfo_v1(append(pushes, nil)) 299 } 300 301 func decodeAccountInfo_v1(pushes [][]byte) (*AccountInfo, error) { 302 if len(pushes) != 6 { 303 return nil, fmt.Errorf("decodeAccountInfo: expected 6 data pushes, got %d", len(pushes)) 304 } 305 hostB, legacyKeyB, dexB := pushes[0], pushes[1], pushes[2] 306 coinB, certB, v2Key := pushes[3], pushes[4], pushes[5] 307 pk, err := secp256k1.ParsePubKey(dexB) 308 if err != nil { 309 return nil, err 310 } 311 return &AccountInfo{ 312 Host: string(hostB), 313 Cert: certB, 314 DEXPubKey: pk, 315 EncKeyV2: v2Key, 316 LegacyEncKey: legacyKeyB, 317 LegacyFeeAssetID: 42, // only option at this version 318 LegacyFeeCoin: coinB, 319 // LegacyFeePaid comes from AccountProof. 320 }, nil 321 } 322 323 func decodeAccountInfo_v2(pushes [][]byte) (*AccountInfo, error) { 324 if len(pushes) != 7 { 325 return nil, fmt.Errorf("decodeAccountInfo: expected 7 data pushes, got %d", len(pushes)) 326 } 327 hostB, certB, dexPkB := pushes[0], pushes[1], pushes[2] // dex identity 328 v2Key, legacyKeyB := pushes[3], pushes[4] // account identity 329 regAssetB, coinB := pushes[5], pushes[6] // legacy reg fee data 330 pk, err := secp256k1.ParsePubKey(dexPkB) 331 if err != nil { 332 return nil, err 333 } 334 return &AccountInfo{ 335 Host: string(hostB), 336 Cert: certB, 337 DEXPubKey: pk, 338 EncKeyV2: v2Key, 339 LegacyEncKey: legacyKeyB, 340 // Bonds decoded by DecodeBond from separate pushes. 341 BondAsset: defaultBondAsset, 342 LegacyFeeAssetID: intCoder.Uint32(regAssetB), 343 LegacyFeeCoin: coinB, // NOTE: no longer in current serialization. 344 // LegacyFeePaid comes from AccountProof. 345 }, nil 346 } 347 348 func decodeAccountInfo_v3(pushes [][]byte) (*AccountInfo, error) { 349 if len(pushes) != 10 { 350 return nil, fmt.Errorf("decodeAccountInfo_v3: expected 10 data pushes, got %d", len(pushes)) 351 } 352 pushes = append(pushes, []byte{0, 0}) // 16-bit PenaltyComps 353 return decodeAccountInfo_v4(pushes) 354 } 355 356 func decodeAccountInfo_v4(pushes [][]byte) (*AccountInfo, error) { 357 if len(pushes) != 11 { 358 return nil, fmt.Errorf("decodeAccountInfo: expected 11 data pushes, got %d", len(pushes)) 359 } 360 hostB, certB, dexPkB := pushes[0], pushes[1], pushes[2] // dex identity 361 v2Key, legacyKeyB := pushes[3], pushes[4] // account identity 362 targetTierB, maxBondedB, bondAssetB := pushes[5], pushes[6], pushes[7] // bond options 363 regAssetB, coinB, penaltyComps := pushes[8], pushes[9], pushes[10] // legacy reg fee data 364 pk, err := secp256k1.ParsePubKey(dexPkB) 365 if err != nil { 366 return nil, err 367 } 368 return &AccountInfo{ 369 Host: string(hostB), 370 Cert: certB, 371 DEXPubKey: pk, 372 EncKeyV2: v2Key, 373 LegacyEncKey: legacyKeyB, 374 // Bonds decoded by DecodeBond from separate pushes. 375 TargetTier: intCoder.Uint64(targetTierB), 376 MaxBondedAmt: intCoder.Uint64(maxBondedB), 377 PenaltyComps: intCoder.Uint16(penaltyComps), 378 BondAsset: intCoder.Uint32(bondAssetB), 379 LegacyFeeAssetID: intCoder.Uint32(regAssetB), 380 LegacyFeeCoin: coinB, // NOTE: no longer in current serialization. 381 // LegacyFeePaid comes from AccountProof. 382 }, nil 383 } 384 385 // AccountProof is information necessary to prove that the DEX server accepted 386 // the account's fee payment. The fee coin is not part of the proof, since it 387 // is already stored as part of the AccountInfo blob. DEPRECATED. 388 type AccountProof struct{} 389 390 // Encode encodes the AccountProof to a versioned blob. 391 func (p *AccountProof) Encode() []byte { 392 return versionedBytes(1) 393 } 394 395 // DecodeAccountProof decodes the versioned blob to a *MatchProof. 396 func DecodeAccountProof(b []byte) (*AccountProof, error) { 397 return &AccountProof{}, nil 398 } 399 400 // MetaOrder is an order and its metadata. 401 type MetaOrder struct { 402 // MetaData is important auxiliary information about the order. 403 MetaData *OrderMetaData 404 // Order is the order. 405 Order order.Order 406 } 407 408 // OrderMetaData is important auxiliary information about an order. 409 type OrderMetaData struct { 410 // Status is the last known order status. 411 Status order.OrderStatus 412 // Host is the hostname of the server that this order is associated with. 413 Host string 414 // Proof is the signatures and other verification-related data for the order. 415 Proof OrderProof 416 // ChangeCoin is a change coin from a match. Change coins are "daisy-chained" 417 // for matches. All funding coins go into the first match, and the change coin 418 // from the initiation transaction is used to fund the next match. The 419 // change from that matches ini tx funds the next match, etc. 420 ChangeCoin order.CoinID 421 // LinkedOrder is used to specify the cancellation order for a trade, or 422 // vice-versa. 423 LinkedOrder order.OrderID 424 // SwapFeesPaid is the sum of the actual fees paid for all swaps. 425 SwapFeesPaid uint64 426 // RedemptionFeesPaid is the sum of the actual fees paid for all 427 // redemptions. 428 RedemptionFeesPaid uint64 429 // FundingFeesPaid is the fees paid when funding the order. This is > 0 430 // when funding the order required a split tx. 431 FundingFeesPaid uint64 432 433 // EpochDur is the epoch duration for the market at the time the order was 434 // submitted. When considered with the order's ServerTime, we also know the 435 // epoch index, which is helpful for determining if an epoch order should 436 // have been matched. WARNING: may load from DB as zero for older orders, in 437 // which case the current asset config should be used. 438 EpochDur uint64 439 440 // We store any variable information of each dex.Asset (the server's asset 441 // config at time of order). This includes: the max fee rates for swap and 442 // redeem, and the asset versions, and the required swap confirmations 443 // counts. 444 445 // FromSwapConf and ToSwapConf are the dex.Asset.SwapConf values at the time 446 // the order is submitted. WARNING: may load from DB as zero for older 447 // orders, in which case the current asset config should be used. 448 FromSwapConf uint32 449 ToSwapConf uint32 450 // MaxFeeRate is the dex.Asset.MaxFeeRate at the time of ordering. The rates 451 // assigned to matches will be validated against this value. 452 MaxFeeRate uint64 453 // RedeemMaxFeeRate is the dex.Asset.MaxFeeRate for the redemption asset at 454 // the time of ordering. This rate is used to reserve funds for redemption, 455 // and therefore this rate can be used when actually submitting a redemption 456 // transaction. 457 RedeemMaxFeeRate uint64 458 // FromVersion is the version of the from asset. 459 FromVersion uint32 460 // ToVersion is the version of the to asset. 461 ToVersion uint32 462 463 // Options are the options offered by the wallet and selected by the user. 464 Options map[string]string 465 // RedemptionReserves is the amount of funds reserved by the wallet to pay 466 // the transaction fees for all the possible redemptions in this order. 467 // The amount that should be locked at any point can be determined by 468 // checking the status of the order and the status of all matches related 469 // to this order, and determining how many more possible redemptions there 470 // could be. 471 RedemptionReserves uint64 472 // RedemptionRefunds is the amount of funds reserved by the wallet to pay 473 // the transaction fees for all the possible refunds in this order. 474 // The amount that should be locked at any point can be determined by 475 // checking the status of the order and the status of all matches related 476 // to this order, and determining how many more possible refunds there 477 // could be. 478 RefundReserves uint64 479 // AccelerationCoins keeps track of all the change coins generated from doing 480 // accelerations on this order. 481 AccelerationCoins []order.CoinID 482 } 483 484 // MetaMatch is a match and its metadata. 485 type MetaMatch struct { 486 // UserMatch is the match info. 487 *order.UserMatch 488 // MetaData is important auxiliary information about the match. 489 MetaData *MatchMetaData 490 } 491 492 // MatchOrderUniqueID is a unique ID for the match-order pair. 493 func (m *MetaMatch) MatchOrderUniqueID() []byte { 494 return hashKey(append(m.MatchID[:], m.OrderID[:]...)) 495 } 496 497 // MatchIsActive returns false (i.e. the match is inactive) if any: (1) status 498 // is MatchConfirmed OR InitSig unset, signaling a cancel order match, which is 499 // never active, (2) the match is refunded, or (3) it is revoked and this side 500 // of the match requires no further action like refund or auto-redeem. 501 func MatchIsActive(match *order.UserMatch, proof *MatchProof) bool { 502 // MatchComplete only means inactive if: (a) cancel order match or (b) the 503 // redeem request was accepted for trade orders. A cancel order match starts 504 // complete and has no InitSig as there is no swap negotiation. 505 // Unfortunately, an empty Address is not sufficient since taker cancel 506 // matches included the makers Address. 507 if match.Status == order.MatchConfirmed { 508 return false 509 } 510 511 // Cancel match 512 if match.Status == order.MatchComplete && len(proof.Auth.InitSig) == 0 { 513 return false 514 } 515 516 // Refunded matches are inactive regardless of status. 517 if len(proof.RefundCoin) > 0 { 518 return false 519 } 520 521 // Revoked matches may need to be refunded or auto-redeemed first. 522 if proof.IsRevoked() { 523 // - NewlyMatched requires no further action from either side 524 // - MakerSwapCast requires no further action from the taker 525 // - (TakerSwapCast requires action on both sides) 526 // Matches that make it to MakerRedeemed or MatchComplete must 527 // stay active until the redeem is confirmed. When the redeem 528 // is confirmed is up to the asset and differs between assets. 529 status, side := match.Status, match.Side 530 if status == order.NewlyMatched || 531 (status == order.MakerSwapCast && side == order.Taker) { 532 return false 533 } 534 } 535 return true 536 } 537 538 // MatchIsActiveV6Upgrade is the previous version of MatchIsActive that is 539 // required for the V6 upgrade of the DB. 540 func MatchIsActiveV6Upgrade(match *order.UserMatch, proof *MatchProof) bool { 541 // MatchComplete only means inactive if: (a) cancel order match or (b) the 542 // redeem request was accepted for trade orders. A cancel order match starts 543 // complete and has no InitSig as their is no swap negotiation. 544 // Unfortunately, an empty Address is not sufficient since taker cancel 545 // matches included the makers Address. 546 if match.Status == order.MatchComplete && (len(proof.Auth.RedeemSig) > 0 || // completed trade 547 len(proof.Auth.InitSig) == 0) { // completed cancel 548 return false 549 } 550 551 // Refunded matches are inactive regardless of status. 552 if len(proof.RefundCoin) > 0 { 553 return false 554 } 555 556 // Revoked matches may need to be refunded or auto-redeemed first. 557 if proof.IsRevoked() { 558 // - NewlyMatched requires no further action from either side 559 // - MakerSwapCast requires no further action from the taker 560 // - (TakerSwapCast requires action on both sides) 561 // - MakerRedeemed requires no further action from the maker 562 // - MatchComplete requires no further action. This happens if taker 563 // does not have server's ack of their redeem request (RedeemSig). 564 status, side := match.Status, match.Side 565 if status == order.NewlyMatched || status == order.MatchComplete || 566 (status == order.MakerSwapCast && side == order.Taker) || 567 (status == order.MakerRedeemed && side == order.Maker) { 568 return false 569 } 570 } 571 return true 572 } 573 574 // MatchMetaData is important auxiliary information about the match. 575 type MatchMetaData struct { 576 // Proof is the signatures and other verification-related data for the match. 577 Proof MatchProof 578 // DEX is the URL of the server that this match is associated with. 579 DEX string 580 // Base is the base asset of the exchange market. 581 Base uint32 582 // Quote is the quote asset of the exchange market. 583 Quote uint32 584 // Stamp is the match time (ms UNIX), according to the server's 'match' 585 // request timestamp. 586 Stamp uint64 587 // TODO: ReceiveTime uint64 -- local time stamp for match age and time display 588 } 589 590 // MatchAuth holds the DEX signatures and timestamps associated with the 591 // messages in the negotiation process. 592 type MatchAuth struct { 593 MatchSig []byte 594 MatchStamp uint64 595 InitSig []byte 596 InitStamp uint64 597 AuditSig []byte 598 AuditStamp uint64 599 RedeemSig []byte 600 RedeemStamp uint64 601 RedemptionSig []byte 602 RedemptionStamp uint64 603 } 604 605 // MatchProof is information related to the progression of the swap negotiation 606 // process. 607 type MatchProof struct { 608 ContractData []byte 609 CounterContract []byte 610 CounterTxData []byte 611 SecretHash []byte 612 Secret []byte 613 MakerSwap order.CoinID 614 MakerRedeem order.CoinID 615 TakerSwap order.CoinID 616 TakerRedeem order.CoinID 617 RefundCoin order.CoinID 618 Auth MatchAuth 619 ServerRevoked bool 620 SelfRevoked bool 621 // SwapFeeConfirmed indicate the fees for this match have been 622 // confirmed and the value added to the trade. 623 SwapFeeConfirmed bool 624 // RedemptionFeeConfirmed indicate the fees for this match have been 625 // confirmed and the value added to the trade. 626 RedemptionFeeConfirmed bool 627 } 628 629 func boolByte(b bool) []byte { 630 if b { 631 return []byte{1} 632 } 633 return []byte{0} 634 } 635 636 // MatchProofVer is the current serialization version of a MatchProof. 637 const ( 638 MatchProofVer = 3 639 matchProofPushes = 24 640 ) 641 642 // Encode encodes the MatchProof to a versioned blob. 643 func (p *MatchProof) Encode() []byte { 644 auth := p.Auth 645 return versionedBytes(MatchProofVer). 646 AddData(p.ContractData). 647 AddData(p.CounterContract). 648 AddData(p.SecretHash). 649 AddData(p.Secret). 650 AddData(p.MakerSwap). 651 AddData(p.MakerRedeem). 652 AddData(p.TakerSwap). 653 AddData(p.TakerRedeem). 654 AddData(p.RefundCoin). 655 AddData(auth.MatchSig). 656 AddData(uint64Bytes(auth.MatchStamp)). 657 AddData(auth.InitSig). 658 AddData(uint64Bytes(auth.InitStamp)). 659 AddData(auth.AuditSig). 660 AddData(uint64Bytes(auth.AuditStamp)). 661 AddData(auth.RedeemSig). 662 AddData(uint64Bytes(auth.RedeemStamp)). 663 AddData(auth.RedemptionSig). 664 AddData(uint64Bytes(auth.RedemptionStamp)). 665 AddData(boolByte(p.ServerRevoked)). 666 AddData(boolByte(p.SelfRevoked)). 667 AddData(p.CounterTxData). 668 AddData(boolByte(p.SwapFeeConfirmed)). 669 AddData(boolByte(p.RedemptionFeeConfirmed)) 670 } 671 672 // DecodeMatchProof decodes the versioned blob to a *MatchProof. 673 func DecodeMatchProof(b []byte) (*MatchProof, uint8, error) { 674 ver, pushes, err := encode.DecodeBlob(b, matchProofPushes) 675 if err != nil { 676 return nil, 0, err 677 } 678 switch ver { 679 case 3: // MatchProofVer 680 proof, err := decodeMatchProof_v3(pushes) 681 return proof, ver, err 682 case 2: 683 proof, err := decodeMatchProof_v2(pushes) 684 return proof, ver, err 685 case 1: 686 proof, err := decodeMatchProof_v1(pushes) 687 return proof, ver, err 688 case 0: 689 proof, err := decodeMatchProof_v0(pushes) 690 return proof, ver, err 691 } 692 return nil, ver, fmt.Errorf("unknown MatchProof version %d", ver) 693 } 694 695 func decodeMatchProof_v0(pushes [][]byte) (*MatchProof, error) { 696 pushes = append(pushes, encode.ByteFalse) 697 return decodeMatchProof_v1(pushes) 698 } 699 700 func decodeMatchProof_v1(pushes [][]byte) (*MatchProof, error) { 701 pushes = append(pushes, nil) 702 return decodeMatchProof_v2(pushes) 703 } 704 705 func decodeMatchProof_v2(pushes [][]byte) (*MatchProof, error) { 706 // Add the MatchProof SwapFeeConfirmed and RedemptionFeeConfirmed bytes. 707 // True because all fees until now are confirmed. 708 pushes = append(pushes, encode.ByteTrue, encode.ByteTrue) 709 return decodeMatchProof_v3(pushes) 710 } 711 712 func decodeMatchProof_v3(pushes [][]byte) (*MatchProof, error) { 713 if len(pushes) != matchProofPushes { 714 return nil, fmt.Errorf("DecodeMatchProof: expected %d pushes, got %d", 715 matchProofPushes, len(pushes)) 716 } 717 return &MatchProof{ 718 ContractData: pushes[0], 719 CounterContract: pushes[1], 720 CounterTxData: pushes[21], 721 SecretHash: pushes[2], 722 Secret: pushes[3], 723 MakerSwap: pushes[4], 724 MakerRedeem: pushes[5], 725 TakerSwap: pushes[6], 726 TakerRedeem: pushes[7], 727 RefundCoin: pushes[8], 728 Auth: MatchAuth{ 729 MatchSig: pushes[9], 730 MatchStamp: intCoder.Uint64(pushes[10]), 731 InitSig: pushes[11], 732 InitStamp: intCoder.Uint64(pushes[12]), 733 AuditSig: pushes[13], 734 AuditStamp: intCoder.Uint64(pushes[14]), 735 RedeemSig: pushes[15], 736 RedeemStamp: intCoder.Uint64(pushes[16]), 737 RedemptionSig: pushes[17], 738 RedemptionStamp: intCoder.Uint64(pushes[18]), 739 }, 740 ServerRevoked: bytes.Equal(pushes[19], encode.ByteTrue), 741 SelfRevoked: bytes.Equal(pushes[20], encode.ByteTrue), 742 SwapFeeConfirmed: bytes.Equal(pushes[21], encode.ByteTrue), 743 RedemptionFeeConfirmed: bytes.Equal(pushes[22], encode.ByteTrue), 744 }, nil 745 } 746 747 // IsRevoked is true if either ServerRevoked or SelfRevoked is true. 748 func (p *MatchProof) IsRevoked() bool { 749 return p.ServerRevoked || p.SelfRevoked 750 } 751 752 // OrderProof is information related to order authentication and matching. 753 type OrderProof struct { 754 DEXSig []byte 755 Preimage []byte 756 } 757 758 // Encode encodes the OrderProof to a versioned blob. 759 func (p *OrderProof) Encode() []byte { 760 return versionedBytes(0).AddData(p.DEXSig).AddData(p.Preimage) 761 } 762 763 // DecodeOrderProof decodes the versioned blob to an *OrderProof. 764 func DecodeOrderProof(b []byte) (*OrderProof, error) { 765 ver, pushes, err := encode.DecodeBlob(b) 766 if err != nil { 767 return nil, err 768 } 769 switch ver { 770 case 0: 771 return decodeOrderProof_v0(pushes) 772 } 773 return nil, fmt.Errorf("unknown OrderProof version %d", ver) 774 } 775 776 func decodeOrderProof_v0(pushes [][]byte) (*OrderProof, error) { 777 if len(pushes) != 2 { 778 return nil, fmt.Errorf("decodeOrderProof: expected 2 push, got %d", len(pushes)) 779 } 780 return &OrderProof{ 781 DEXSig: pushes[0], 782 Preimage: pushes[1], 783 }, nil 784 } 785 786 // encodeAssetBalance serializes an asset.Balance. 787 func encodeAssetBalance(bal *asset.Balance) []byte { 788 return versionedBytes(0). 789 AddData(uint64Bytes(bal.Available)). 790 AddData(uint64Bytes(bal.Immature)). 791 AddData(uint64Bytes(bal.Locked)) 792 } 793 794 // decodeAssetBalance deserializes an asset.Balance. 795 func decodeAssetBalance(b []byte) (*asset.Balance, error) { 796 ver, pushes, err := encode.DecodeBlob(b) 797 if err != nil { 798 return nil, err 799 } 800 switch ver { 801 case 0: 802 return decodeAssetBalance_v0(pushes) 803 } 804 return nil, fmt.Errorf("unknown Balance version %d", ver) 805 } 806 807 func decodeAssetBalance_v0(pushes [][]byte) (*asset.Balance, error) { 808 if len(pushes) != 3 { 809 return nil, fmt.Errorf("decodeBalance_v0: expected 3 push, got %d", len(pushes)) 810 } 811 return &asset.Balance{ 812 Available: intCoder.Uint64(pushes[0]), 813 Immature: intCoder.Uint64(pushes[1]), 814 Locked: intCoder.Uint64(pushes[2]), 815 }, nil 816 } 817 818 // Balance represents a wallet's balance in various contexts. 819 type Balance struct { 820 asset.Balance 821 Stamp time.Time `json:"stamp"` 822 } 823 824 // Encode encodes the Balance to a versioned blob. 825 func (b *Balance) Encode() []byte { 826 return versionedBytes(0). 827 AddData(encodeAssetBalance(&b.Balance)). 828 AddData(uint64Bytes(uint64(b.Stamp.UnixMilli()))) 829 } 830 831 // DecodeBalance decodes the versioned blob to a *Balance. 832 func DecodeBalance(b []byte) (*Balance, error) { 833 ver, pushes, err := encode.DecodeBlob(b) 834 if err != nil { 835 return nil, err 836 } 837 switch ver { 838 case 0: 839 return decodeBalance_v0(pushes) 840 } 841 return nil, fmt.Errorf("unknown Balance version %d", ver) 842 } 843 844 func decodeBalance_v0(pushes [][]byte) (*Balance, error) { 845 if len(pushes) < 2 { 846 return nil, fmt.Errorf("decodeBalances_v0: expected >= 2 pushes. got %d", len(pushes)) 847 } 848 if len(pushes)%2 != 0 { 849 return nil, fmt.Errorf("decodeBalances_v0: expected an even number of pushes, got %d", len(pushes)) 850 } 851 bal, err := decodeAssetBalance(pushes[0]) 852 if err != nil { 853 return nil, fmt.Errorf("decodeBalances_v0: error decoding zero conf balance: %w", err) 854 } 855 856 return &Balance{ 857 Balance: *bal, 858 Stamp: time.UnixMilli(int64(intCoder.Uint64(pushes[1]))), 859 }, nil 860 } 861 862 // Wallet is information necessary to create an asset.Wallet. 863 type Wallet struct { 864 AssetID uint32 865 Type string 866 Settings map[string]string 867 Balance *Balance 868 EncryptedPW []byte 869 Address string 870 Disabled bool 871 } 872 873 // Encode encodes the Wallet to a versioned blob. 874 func (w *Wallet) Encode() []byte { 875 return versionedBytes(1). 876 AddData(uint32Bytes(w.AssetID)). 877 AddData(config.Data(w.Settings)). 878 AddData(w.EncryptedPW). 879 AddData([]byte(w.Address)). 880 AddData([]byte(w.Type)) 881 } 882 883 // DecodeWallet decodes the versioned blob to a *Wallet. The Balance is NOT set; 884 // the caller must retrieve it. See for example makeWallet and DecodeBalance. 885 func DecodeWallet(b []byte) (*Wallet, error) { 886 ver, pushes, err := encode.DecodeBlob(b) 887 if err != nil { 888 return nil, err 889 } 890 switch ver { 891 case 0: 892 return decodeWallet_v0(pushes) 893 case 1: 894 return decodeWallet_v1(pushes) 895 } 896 return nil, fmt.Errorf("unknown DecodeWallet version %d", ver) 897 } 898 899 func decodeWallet_v0(pushes [][]byte) (*Wallet, error) { 900 // Add a push for wallet type. 901 pushes = append(pushes, []byte("")) 902 return decodeWallet_v1(pushes) 903 } 904 905 func decodeWallet_v1(pushes [][]byte) (*Wallet, error) { 906 if len(pushes) != 5 { 907 return nil, fmt.Errorf("decodeWallet_v1: expected 5 pushes, got %d", len(pushes)) 908 } 909 idB, settingsB, keyB := pushes[0], pushes[1], pushes[2] 910 addressB, typeB := pushes[3], pushes[4] 911 settings, err := config.Parse(settingsB) 912 if err != nil { 913 return nil, fmt.Errorf("unable to decode wallet settings") 914 } 915 return &Wallet{ 916 AssetID: intCoder.Uint32(idB), 917 Type: string(typeB), 918 Settings: settings, 919 EncryptedPW: keyB, 920 Address: string(addressB), 921 }, nil 922 } 923 924 // ID is the byte-encoded asset ID for this wallet. 925 func (w *Wallet) ID() []byte { 926 return uint32Bytes(w.AssetID) 927 } 928 929 // SID is a string representation of the wallet's asset ID. 930 func (w *Wallet) SID() string { 931 return strconv.Itoa(int(w.AssetID)) 932 } 933 934 func versionedBytes(v byte) encode.BuildyBytes { 935 return encode.BuildyBytes{v} 936 } 937 938 var uint64Bytes = encode.Uint64Bytes 939 var uint32Bytes = encode.Uint32Bytes 940 var uint16Bytes = encode.Uint16Bytes 941 var intCoder = encode.IntCoder 942 943 // AccountBackup represents a user account backup. 944 type AccountBackup struct { 945 KeyParams []byte 946 Accounts []*AccountInfo 947 } 948 949 // encodeDEXAccount serializes the details needed to backup a dex account. 950 func encodeDEXAccount(acct *AccountInfo) []byte { 951 return versionedBytes(1). 952 AddData([]byte(acct.Host)). 953 AddData(acct.LegacyEncKey). 954 AddData(acct.DEXPubKey.SerializeCompressed()). 955 AddData(acct.EncKeyV2) 956 } 957 958 // decodeDEXAccount decodes the versioned blob into an AccountInfo. 959 func decodeDEXAccount(acctB []byte) (*AccountInfo, error) { 960 ver, pushes, err := encode.DecodeBlob(acctB) 961 if err != nil { 962 return nil, err 963 } 964 965 switch ver { 966 case 0: 967 pushes = append(pushes, nil) 968 fallthrough 969 case 1: 970 if len(pushes) != 4 { 971 return nil, fmt.Errorf("expected 4 pushes, got %d", len(pushes)) 972 } 973 974 var ai AccountInfo 975 ai.Host = string(pushes[0]) 976 ai.LegacyEncKey = pushes[1] 977 ai.DEXPubKey, err = secp256k1.ParsePubKey(pushes[2]) 978 ai.EncKeyV2 = pushes[3] 979 if err != nil { 980 return nil, err 981 } 982 return &ai, nil 983 984 } 985 return nil, fmt.Errorf("unknown DEX account version %d", ver) 986 } 987 988 // Serialize encodes an account backup as bytes. 989 func (ab *AccountBackup) Serialize() []byte { 990 backup := versionedBytes(0).AddData(ab.KeyParams) 991 for _, acct := range ab.Accounts { 992 backup = backup.AddData(encodeDEXAccount(acct)) 993 } 994 return backup 995 } 996 997 // decodeAccountBackup decodes the versioned blob into an *AccountBackup. 998 func decodeAccountBackup(b []byte) (*AccountBackup, error) { 999 ver, pushes, err := encode.DecodeBlob(b) 1000 if err != nil { 1001 return nil, err 1002 } 1003 switch ver { 1004 case 0, 1: 1005 keyParams := pushes[0] 1006 accts := make([]*AccountInfo, 0, len(pushes)-1) 1007 for _, push := range pushes[1:] { 1008 ai, err := decodeDEXAccount(push) 1009 if err != nil { 1010 return nil, err 1011 } 1012 accts = append(accts, ai) 1013 } 1014 1015 return &AccountBackup{ 1016 KeyParams: keyParams, 1017 Accounts: accts, 1018 }, nil 1019 } 1020 return nil, fmt.Errorf("unknown AccountBackup version %d", ver) 1021 } 1022 1023 // Save persists an account backup to file. 1024 func (ab *AccountBackup) Save(path string) error { 1025 backup := ab.Serialize() 1026 return os.WriteFile(path, backup, 0o600) 1027 } 1028 1029 // RestoreAccountBackup generates a user account from a backup file. 1030 func RestoreAccountBackup(path string) (*AccountBackup, error) { 1031 backup, err := os.ReadFile(path) 1032 if err != nil { 1033 return nil, err 1034 } 1035 ab, err := decodeAccountBackup(backup) 1036 if err != nil { 1037 return nil, err 1038 } 1039 return ab, nil 1040 } 1041 1042 // Topic is a language-independent unique ID for a Notification. 1043 type Topic string 1044 1045 // Notification is information for the user that is typically meant for display, 1046 // and is persisted for recall across sessions. 1047 type Notification struct { 1048 NoteType string `json:"type"` 1049 TopicID Topic `json:"topic"` 1050 SubjectText string `json:"subject"` 1051 DetailText string `json:"details"` 1052 Severeness Severity `json:"severity"` 1053 TimeStamp uint64 `json:"stamp"` 1054 Ack bool `json:"acked"` 1055 Id dex.Bytes `json:"id"` 1056 } 1057 1058 // NewNotification is a constructor for a Notification. 1059 func NewNotification(noteType string, topic Topic, subject, details string, severity Severity) Notification { 1060 note := Notification{ 1061 NoteType: noteType, 1062 TopicID: topic, 1063 SubjectText: subject, 1064 DetailText: details, 1065 Severeness: severity, 1066 } 1067 note.Stamp() 1068 return note 1069 } 1070 1071 // ID is a unique ID based on a hash of the notification data. 1072 func (n *Notification) ID() dex.Bytes { 1073 return noteKey(n.Encode()) 1074 } 1075 1076 // Type is the notification type. 1077 func (n *Notification) Type() string { 1078 return n.NoteType 1079 } 1080 1081 // Topic is a language-independent unique ID for the Notification. 1082 func (n *Notification) Topic() Topic { 1083 return n.TopicID 1084 } 1085 1086 // Subject is a short description of the notification contents. 1087 func (n *Notification) Subject() string { 1088 return n.SubjectText 1089 } 1090 1091 // Details should contain more detailed information. 1092 func (n *Notification) Details() string { 1093 return n.DetailText 1094 } 1095 1096 // Severity is the notification severity. 1097 func (n *Notification) Severity() Severity { 1098 return n.Severeness 1099 } 1100 1101 // Time is the notification timestamp. The timestamp is set in NewNotification. 1102 func (n *Notification) Time() uint64 { 1103 return n.TimeStamp 1104 } 1105 1106 // Acked is true if the user has seen the notification. Acknowledgement is 1107 // recorded with DB.AckNotification. 1108 func (n *Notification) Acked() bool { 1109 return n.Ack 1110 } 1111 1112 // Stamp sets the notification timestamp. If NewNotification is used to 1113 // construct the Notification, the timestamp will already be set. 1114 func (n *Notification) Stamp() { 1115 n.TimeStamp = uint64(time.Now().UnixMilli()) 1116 n.Id = n.ID() 1117 } 1118 1119 // DBNote is a function to return the *Notification itself. It should really be 1120 // defined on the concrete types in core, but is ubiquitous so defined here for 1121 // convenience. 1122 func (n *Notification) DBNote() *Notification { 1123 return n 1124 } 1125 1126 // String generates a compact human-readable representation of the Notification 1127 // that is suitable for logging. For example: 1128 // 1129 // |SUCCESS| (fee payment) Fee paid - Waiting for 2 confirmations before trading at https://superdex.tld:7232 1130 // |DATA| (boring event) Subject without details 1131 func (n *Notification) String() string { 1132 // In case type and/or detail or empty strings, adjust the formatting to 1133 // avoid extra whitespace. 1134 var format strings.Builder 1135 format.WriteString("|%s| (%s)") // always nil error 1136 if len(n.DetailText) > 0 || len(n.SubjectText) > 0 { 1137 format.WriteString(" ") 1138 } 1139 format.WriteString("%s") 1140 if len(n.DetailText) > 0 && len(n.SubjectText) > 0 { 1141 format.WriteString(" - ") 1142 } 1143 format.WriteString("%s") 1144 1145 severity := strings.ToUpper(n.Severity().String()) 1146 return fmt.Sprintf(format.String(), severity, n.NoteType, n.SubjectText, n.DetailText) 1147 } 1148 1149 // DecodeNotification decodes the versioned blob to a *Notification. 1150 func DecodeNotification(b []byte) (*Notification, error) { 1151 ver, pushes, err := encode.DecodeBlob(b) 1152 if err != nil { 1153 return nil, err 1154 } 1155 switch ver { 1156 case 1: 1157 return decodeNotification_v1(pushes) 1158 case 0: 1159 return decodeNotification_v0(pushes) 1160 } 1161 return nil, fmt.Errorf("unknown DecodeNotification version %d", ver) 1162 } 1163 1164 func decodeNotification_v0(pushes [][]byte) (*Notification, error) { 1165 return decodeNotification_v1(append(pushes, []byte{})) 1166 } 1167 1168 func decodeNotification_v1(pushes [][]byte) (*Notification, error) { 1169 if len(pushes) != 6 { 1170 return nil, fmt.Errorf("decodeNotification_v0: expected 5 pushes, got %d", len(pushes)) 1171 } 1172 if len(pushes[3]) != 1 { 1173 return nil, fmt.Errorf("decodeNotification_v0: severity push is supposed to be length 1. got %d", len(pushes[2])) 1174 } 1175 1176 return &Notification{ 1177 NoteType: string(pushes[0]), 1178 TopicID: Topic(string(pushes[5])), 1179 SubjectText: string(pushes[1]), 1180 DetailText: string(pushes[2]), 1181 Severeness: Severity(pushes[3][0]), 1182 TimeStamp: intCoder.Uint64(pushes[4]), 1183 }, nil 1184 } 1185 1186 // Encode encodes the Notification to a versioned blob. 1187 func (n *Notification) Encode() []byte { 1188 return versionedBytes(1). 1189 AddData([]byte(n.NoteType)). 1190 AddData([]byte(n.SubjectText)). 1191 AddData([]byte(n.DetailText)). 1192 AddData([]byte{byte(n.Severeness)}). 1193 AddData(uint64Bytes(n.TimeStamp)). 1194 AddData([]byte(n.TopicID)) 1195 } 1196 1197 type OrderFilterMarket struct { 1198 Base uint32 1199 Quote uint32 1200 } 1201 1202 // OrderFilter is used to limit the results returned by a query to (DB).Orders. 1203 type OrderFilter struct { 1204 // N is the number of orders to return in the set. 1205 N int 1206 // Offset can be used to shift the window of the time-sorted orders such 1207 // that any orders that would sort to index <= the order specified by Offset 1208 // will be rejected. 1209 Offset order.OrderID 1210 // Hosts is a list of acceptable hosts. A zero-length Hosts means all 1211 // hosts are accepted. 1212 Hosts []string 1213 // Assets is a list of BIP IDs for acceptable assets. A zero-length Assets 1214 // means all assets are accepted. 1215 Assets []uint32 1216 // Market limits results to a specific market. 1217 Market *OrderFilterMarket 1218 // Statuses is a list of acceptable statuses. A zero-length Statuses means 1219 // all statuses are accepted. 1220 Statuses []order.OrderStatus 1221 } 1222 1223 // noteKeySize must be <= 32. 1224 const noteKeySize = 8 1225 1226 // noteKey creates a unique key from the hash of the supplied bytes. 1227 func noteKey(b []byte) []byte { 1228 h := blake2s.Sum256(b) 1229 return h[:noteKeySize] 1230 } 1231 1232 // hashKey creates a unique key from the hash of the supplied bytes. 1233 func hashKey(b []byte) []byte { 1234 h := blake2s.Sum256(b) 1235 return h[:] 1236 }