decred.org/dcrdex@v1.0.5/client/db/bolt/db.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 bolt 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 "errors" 11 "fmt" 12 "io/fs" 13 "os" 14 "path/filepath" 15 "sort" 16 "strings" 17 "time" 18 19 "decred.org/dcrdex/client/db" 20 dexdb "decred.org/dcrdex/client/db" 21 "decred.org/dcrdex/dex" 22 "decred.org/dcrdex/dex/config" 23 "decred.org/dcrdex/dex/encode" 24 "decred.org/dcrdex/dex/encrypt" 25 "decred.org/dcrdex/dex/order" 26 "go.etcd.io/bbolt" 27 ) 28 29 // getCopy returns a copy of the value for the given key and provided bucket. If 30 // the key is not found or if an empty slice was loaded, nil is returned. Thus, 31 // use bkt.Get(key) == nil directly to test for existence of the key. This 32 // function should be used instead of bbolt.(*Bucket).Get when the read value 33 // needs to be kept after the transaction, at which time the buffer from Get is 34 // no longer safe to use. 35 func getCopy(bkt *bbolt.Bucket, key []byte) []byte { 36 b := bkt.Get(key) 37 if len(b) == 0 { 38 return nil 39 } 40 val := make([]byte, len(b)) 41 copy(val, b) 42 return val 43 } 44 45 // Short names for some commonly used imported functions. 46 var ( 47 intCoder = encode.IntCoder 48 bEqual = bytes.Equal 49 uint16Bytes = encode.Uint16Bytes 50 uint32Bytes = encode.Uint32Bytes 51 uint64Bytes = encode.Uint64Bytes 52 ) 53 54 // Bolt works on []byte keys and values. These are some commonly used key and 55 // value encodings. 56 var ( 57 // bucket keys 58 appBucket = []byte("appBucket") 59 accountsBucket = []byte("accounts") 60 bondIndexesBucket = []byte("bondIndexes") 61 bondsSubBucket = []byte("bonds") // sub bucket of accounts 62 activeOrdersBucket = []byte("activeOrders") 63 archivedOrdersBucket = []byte("orders") 64 activeMatchesBucket = []byte("activeMatches") 65 archivedMatchesBucket = []byte("matches") 66 botProgramsBucket = []byte("botPrograms") 67 walletsBucket = []byte("wallets") 68 notesBucket = []byte("notes") 69 pokesBucket = []byte("pokes") 70 credentialsBucket = []byte("credentials") 71 72 // value keys 73 versionKey = []byte("version") 74 linkedKey = []byte("linked") 75 feeProofKey = []byte("feecoin") 76 statusKey = []byte("status") 77 baseKey = []byte("base") 78 quoteKey = []byte("quote") 79 orderKey = []byte("order") 80 matchKey = []byte("match") 81 orderIDKey = []byte("orderID") 82 matchIDKey = []byte("matchID") 83 proofKey = []byte("proof") 84 activeKey = []byte("active") 85 bondKey = []byte("bond") 86 confirmedKey = []byte("confirmed") 87 refundedKey = []byte("refunded") 88 lockTimeKey = []byte("lockTime") 89 dexKey = []byte("dex") 90 updateTimeKey = []byte("utime") 91 accountKey = []byte("account") 92 balanceKey = []byte("balance") 93 walletKey = []byte("wallet") 94 changeKey = []byte("change") 95 noteKey = []byte("note") 96 pokesKey = []byte("pokesKey") 97 stampKey = []byte("stamp") 98 severityKey = []byte("severity") 99 ackKey = []byte("ack") 100 swapFeesKey = []byte("swapFees") 101 maxFeeRateKey = []byte("maxFeeRate") 102 redeemMaxFeeRateKey = []byte("redeemMaxFeeRate") 103 redemptionFeesKey = []byte("redeemFees") 104 fundingFeesKey = []byte("fundingFees") 105 accelerationsKey = []byte("accelerations") 106 typeKey = []byte("type") 107 seedGenTimeKey = []byte("seedGenTime") 108 encSeedKey = []byte("encSeed") 109 encInnerKeyKey = []byte("encInnerKey") 110 innerKeyParamsKey = []byte("innerKeyParams") 111 outerKeyParamsKey = []byte("outerKeyParams") 112 birthdayKey = []byte("birthday") 113 credsVersionKey = []byte("credsVersion") 114 legacyKeyParamsKey = []byte("keyParams") 115 epochDurKey = []byte("epochDur") 116 fromVersionKey = []byte("fromVersion") 117 toVersionKey = []byte("toVersion") 118 fromSwapConfKey = []byte("fromSwapConf") 119 toSwapConfKey = []byte("toSwapConf") 120 optionsKey = []byte("options") 121 redemptionReservesKey = []byte("redemptionReservesKey") 122 refundReservesKey = []byte("refundReservesKey") 123 disabledRateSourceKey = []byte("disabledRateSources") 124 walletDisabledKey = []byte("walletDisabled") 125 programKey = []byte("program") 126 langKey = []byte("lang") 127 128 // values 129 byteTrue = encode.ByteTrue 130 byteFalse = encode.ByteFalse 131 byteEpoch = uint16Bytes(uint16(order.OrderStatusEpoch)) 132 byteBooked = uint16Bytes(uint16(order.OrderStatusBooked)) 133 134 backupDir = "backup" 135 ) 136 137 // Opts is a set of options for the DB. 138 type Opts struct { 139 BackupOnShutdown bool // default is true 140 PruneArchive uint64 141 } 142 143 var defaultOpts = Opts{ 144 BackupOnShutdown: true, 145 } 146 147 // BoltDB is a bbolt-based database backend for Bison Wallet. BoltDB satisfies 148 // the db.DB interface defined at decred.org/dcrdex/client/db. 149 type BoltDB struct { 150 *bbolt.DB 151 opts Opts 152 log dex.Logger 153 } 154 155 // Check that BoltDB satisfies the db.DB interface. 156 var _ dexdb.DB = (*BoltDB)(nil) 157 158 // NewDB is a constructor for a *BoltDB. 159 func NewDB(dbPath string, logger dex.Logger, opts ...Opts) (dexdb.DB, error) { 160 _, err := os.Stat(dbPath) 161 isNew := os.IsNotExist(err) 162 163 db, err := bbolt.Open(dbPath, 0600, &bbolt.Options{Timeout: 3 * time.Second}) 164 if err != nil { 165 if errors.Is(err, bbolt.ErrTimeout) { 166 err = fmt.Errorf("%w, could happen when database is already being used by another process", err) 167 } 168 return nil, err 169 } 170 171 // Release the file lock on exit. 172 bdb := &BoltDB{ 173 DB: db, 174 opts: defaultOpts, 175 log: logger, 176 } 177 if len(opts) > 0 { 178 bdb.opts = opts[0] 179 } 180 181 if err = bdb.makeTopLevelBuckets([][]byte{ 182 appBucket, accountsBucket, bondIndexesBucket, 183 activeOrdersBucket, archivedOrdersBucket, 184 activeMatchesBucket, archivedMatchesBucket, 185 walletsBucket, notesBucket, credentialsBucket, 186 botProgramsBucket, pokesBucket, 187 }); err != nil { 188 return nil, err 189 } 190 191 // If the db is a new one, initialize it with the current DB version. 192 if isNew { 193 err := bdb.DB.Update(func(dbTx *bbolt.Tx) error { 194 bkt := dbTx.Bucket(appBucket) 195 if bkt == nil { 196 return fmt.Errorf("app bucket not found") 197 } 198 199 versionB := encode.Uint32Bytes(DBVersion) 200 err := bkt.Put(versionKey, versionB) 201 if err != nil { 202 return err 203 } 204 205 return nil 206 }) 207 if err != nil { 208 return nil, err 209 } 210 211 bdb.log.Infof("Created and started database (version = %d, file = %s)", DBVersion, dbPath) 212 213 return bdb, nil 214 } 215 216 err = bdb.upgradeDB() 217 if err != nil { 218 return nil, err 219 } 220 221 if bdb.opts.PruneArchive > 0 { 222 bdb.log.Info("Pruning the order archive") 223 bdb.pruneArchivedOrders(bdb.opts.PruneArchive) 224 } 225 226 bdb.log.Infof("Started database (version = %d, file = %s)", DBVersion, dbPath) 227 228 return bdb, nil 229 } 230 231 func (db *BoltDB) fileSize(path string) int64 { 232 stat, err := os.Stat(path) 233 if err != nil { 234 db.log.Errorf("Failed to stat %v: %v", path, err) 235 return 0 236 } 237 return stat.Size() 238 } 239 240 // Run waits for context cancellation and closes the database. 241 func (db *BoltDB) Run(ctx context.Context) { 242 <-ctx.Done() // wait for shutdown to backup and compact 243 244 // Create a backup in the backups folder. 245 if db.opts.BackupOnShutdown { 246 db.log.Infof("Backing up database...") 247 if err := db.Backup(); err != nil { 248 db.log.Errorf("Unable to backup database: %v", err) 249 } 250 } 251 252 // Only compact the current DB file if there is excessive free space, in 253 // terms of bytes AND relative to total DB size. 254 const byteThresh = 1 << 18 // 256 KiB free 255 const pctThresh = 0.05 // 5% free 256 var dbSize int64 257 _ = db.View(func(tx *bbolt.Tx) error { 258 dbSize = tx.Size() // db size including free bytes 259 return nil 260 }) 261 dbStats := db.Stats() 262 // FreeAlloc is (FreePageN + PendingPageN) * db.Info().PageSize 263 // FreelistInuse seems to be page header overhead (much smaller). Add them. 264 // https://github.com/etcd-io/bbolt/blob/020684ea1eb7b5574a8007c8d69605b1de8d9ec4/tx.go#L308-L309 265 freeBytes := int64(dbStats.FreeAlloc + dbStats.FreelistInuse) 266 pctFree := float64(freeBytes) / float64(dbSize) 267 db.log.Debugf("Total DB size %d bytes, %d bytes unused (%.2f%%)", 268 dbSize, freeBytes, 100*pctFree) 269 // Only compact if free space is at least the byte threshold AND that fee 270 // space accounts for a significant percent of the file. 271 if freeBytes < byteThresh || pctFree < pctThresh { 272 db.Close() 273 return 274 } 275 276 // TODO: If we never see trouble with the compacted DB files when bisonw 277 // starts up, we can just compact to the backups folder and copy that backup 278 // file back on top of the main DB file after it is closed. For now, the 279 // backup above is a direct (not compacted) copy. 280 281 // Compact the database by writing into a temporary file, closing the source 282 // DB, and overwriting the original with the compacted temporary file. 283 db.log.Infof("Compacting database to reclaim at least %d bytes...", freeBytes) 284 srcPath := db.Path() // before db.Close 285 compFile := srcPath + ".tmp" // deterministic on *same fs* 286 err := db.BackupTo(compFile, true, true) // overwrite and compact 287 if err != nil { 288 db.Close() 289 db.log.Errorf("Unable to compact database: %v", err) 290 return 291 } 292 293 db.Close() // close db file at srcPath 294 295 initSize, compSize := db.fileSize(srcPath), db.fileSize(compFile) 296 db.log.Infof("Compacted database file from %v => %v bytes (%.2f%% reduction)", 297 initSize, compSize, 100*float64(initSize-compSize)/float64(initSize)) 298 299 err = os.Rename(compFile, srcPath) // compFile => srcPath 300 if err != nil { 301 db.log.Errorf("Unable to switch to compacted database: %v", err) 302 return 303 } 304 } 305 306 // Recrypt re-encrypts the wallet passwords and account private keys. As a 307 // convenience, the provided *PrimaryCredentials are stored under the same 308 // transaction. 309 func (db *BoltDB) Recrypt(creds *dexdb.PrimaryCredentials, oldCrypter, newCrypter encrypt.Crypter) (walletUpdates map[uint32][]byte, acctUpdates map[string][]byte, err error) { 310 if err := validateCreds(creds); err != nil { 311 return nil, nil, err 312 } 313 314 walletUpdates = make(map[uint32][]byte) 315 acctUpdates = make(map[string][]byte) 316 317 return walletUpdates, acctUpdates, db.Update(func(tx *bbolt.Tx) error { 318 // Updates accounts and wallets. 319 wallets := tx.Bucket(walletsBucket) 320 if wallets == nil { 321 return fmt.Errorf("no wallets bucket") 322 } 323 324 accounts := tx.Bucket(accountsBucket) 325 if accounts == nil { 326 return fmt.Errorf("no accounts bucket") 327 } 328 329 if err := wallets.ForEach(func(wid, _ []byte) error { 330 wBkt := wallets.Bucket(wid) 331 w, err := makeWallet(wBkt) 332 if err != nil { 333 return err 334 } 335 if len(w.EncryptedPW) == 0 { 336 return nil 337 } 338 339 pw, err := oldCrypter.Decrypt(w.EncryptedPW) 340 if err != nil { 341 return fmt.Errorf("Decrypt error: %w", err) 342 } 343 w.EncryptedPW, err = newCrypter.Encrypt(pw) 344 if err != nil { 345 return fmt.Errorf("Encrypt error: %w", err) 346 } 347 err = wBkt.Put(walletKey, w.Encode()) 348 if err != nil { 349 return err 350 } 351 walletUpdates[w.AssetID] = w.EncryptedPW 352 return nil 353 354 }); err != nil { 355 return fmt.Errorf("wallets update error: %w", err) 356 } 357 358 err = accounts.ForEach(func(hostB, _ []byte) error { 359 acct := accounts.Bucket(hostB) 360 if acct == nil { 361 return fmt.Errorf("account bucket %s value not a nested bucket", string(hostB)) 362 } 363 acctB := getCopy(acct, accountKey) 364 if acctB == nil { 365 return fmt.Errorf("empty account found for %s", string(hostB)) 366 } 367 acctInfo, err := dexdb.DecodeAccountInfo(acctB) 368 if err != nil { 369 return err 370 } 371 if len(acctInfo.LegacyEncKey) != 0 { 372 privB, err := oldCrypter.Decrypt(acctInfo.LegacyEncKey) 373 if err != nil { 374 return err 375 } 376 377 acctInfo.LegacyEncKey, err = newCrypter.Encrypt(privB) 378 if err != nil { 379 return err 380 } 381 acctUpdates[acctInfo.Host] = acctInfo.LegacyEncKey 382 } else if len(acctInfo.EncKeyV2) > 0 { 383 privB, err := oldCrypter.Decrypt(acctInfo.EncKeyV2) 384 if err != nil { 385 return err 386 } 387 acctInfo.EncKeyV2, err = newCrypter.Encrypt(privB) 388 if err != nil { 389 return err 390 } 391 acctUpdates[acctInfo.Host] = acctInfo.EncKeyV2 392 } 393 394 return acct.Put(accountKey, acctInfo.Encode()) 395 }) 396 if err != nil { 397 return fmt.Errorf("accounts update error: %w", err) 398 } 399 400 // Store the new credentials. 401 return db.setCreds(tx, creds) 402 }) 403 } 404 405 // SetPrimaryCredentials validates and stores the PrimaryCredentials. 406 func (db *BoltDB) SetPrimaryCredentials(creds *dexdb.PrimaryCredentials) error { 407 if err := validateCreds(creds); err != nil { 408 return err 409 } 410 411 return db.Update(func(tx *bbolt.Tx) error { 412 return db.setCreds(tx, creds) 413 }) 414 } 415 416 func (db *BoltDB) pruneArchivedOrders(prunedSize uint64) error { 417 418 return db.Update(func(tx *bbolt.Tx) error { 419 archivedOB := tx.Bucket(archivedOrdersBucket) 420 if archivedOB == nil { 421 return fmt.Errorf("failed to open %s bucket", string(archivedOrdersBucket)) 422 } 423 424 nOrds := uint64(archivedOB.Stats().BucketN - 1 /* BucketN includes top bucket */) 425 if nOrds <= prunedSize { 426 return nil 427 } 428 429 // We won't delete any orders with active matches. 430 activeMatches := tx.Bucket(activeMatchesBucket) 431 if activeMatches == nil { 432 return fmt.Errorf("failed to open %s bucket", string(activeMatchesBucket)) 433 } 434 oidsWithActiveMatches := make(map[order.OrderID]struct{}, 0) 435 if err := activeMatches.ForEach(func(k, _ []byte) error { 436 mBkt := activeMatches.Bucket(k) 437 if mBkt == nil { 438 return fmt.Errorf("error getting match bucket %x", k) 439 } 440 var oid order.OrderID 441 copy(oid[:], mBkt.Get(orderIDKey)) 442 oidsWithActiveMatches[oid] = struct{}{} 443 return nil 444 }); err != nil { 445 return fmt.Errorf("error building active match order ID index: %w", err) 446 } 447 448 toClear := int(nOrds - prunedSize) 449 450 type orderStamp struct { 451 oid []byte 452 stamp uint64 453 } 454 deletes := make([]*orderStamp, 0, toClear) 455 sortDeletes := func() { 456 sort.Slice(deletes, func(i, j int) bool { 457 return deletes[i].stamp < deletes[j].stamp 458 }) 459 } 460 var sortedAtCapacity bool 461 if err := archivedOB.ForEach(func(oidB, v []byte) error { 462 var oid order.OrderID 463 copy(oid[:], oidB) 464 if _, found := oidsWithActiveMatches[oid]; found { 465 return nil 466 } 467 oBkt := archivedOB.Bucket(oidB) 468 if oBkt == nil { 469 return fmt.Errorf("no order bucket iterated order %x", oidB) 470 } 471 stampB := oBkt.Get(updateTimeKey) 472 if stampB == nil { 473 // Highly improbable. 474 stampB = make([]byte, 8) 475 } 476 stamp := intCoder.Uint64(stampB) 477 if len(deletes) < toClear { 478 deletes = append(deletes, &orderStamp{ 479 stamp: stamp, 480 oid: oidB, 481 }) 482 return nil 483 } 484 if !sortedAtCapacity { 485 // Make sure the last element is the newest one once we hit 486 // capacity. 487 sortDeletes() 488 sortedAtCapacity = true 489 } 490 if stamp > deletes[len(deletes)-1].stamp { 491 return nil 492 } 493 deletes[len(deletes)-1] = &orderStamp{ 494 stamp: stamp, 495 oid: oidB, 496 } 497 sortDeletes() 498 return nil 499 }); err != nil { 500 return fmt.Errorf("archive iteration error: %v", err) 501 } 502 503 deletedOrders := make(map[order.OrderID]struct{}) 504 for _, del := range deletes { 505 var oid order.OrderID 506 copy(oid[:], del.oid) 507 deletedOrders[oid] = struct{}{} 508 if err := archivedOB.DeleteBucket(del.oid); err != nil { 509 return fmt.Errorf("error deleting archived order %q: %v", del.oid, err) 510 } 511 } 512 513 matchesToDelete := make([][]byte, 0, prunedSize /* just avoid some allocs if we can */) 514 archivedMatches := tx.Bucket(archivedMatchesBucket) 515 if archivedMatches == nil { 516 return errors.New("no archived match bucket") 517 } 518 if err := archivedMatches.ForEach(func(k, _ []byte) error { 519 matchBkt := archivedMatches.Bucket(k) 520 if matchBkt == nil { 521 return fmt.Errorf("no bucket found for %x during iteration", k) 522 } 523 var oid order.OrderID 524 copy(oid[:], matchBkt.Get(orderIDKey)) 525 if _, found := deletedOrders[oid]; found { 526 matchesToDelete = append(matchesToDelete, k) 527 } 528 return nil 529 }); err != nil { 530 return fmt.Errorf("error finding matches to prune: %w", err) 531 } 532 for i := range matchesToDelete { 533 if err := archivedMatches.DeleteBucket(matchesToDelete[i]); err != nil { 534 return fmt.Errorf("error deleting pruned match %x: %w", matchesToDelete[i], err) 535 } 536 } 537 return nil 538 }) 539 } 540 541 // validateCreds checks that the PrimaryCredentials fields are properly 542 // populated. 543 func validateCreds(creds *dexdb.PrimaryCredentials) error { 544 if len(creds.EncSeed) == 0 { 545 return errors.New("EncSeed not set") 546 } 547 if len(creds.EncInnerKey) == 0 { 548 return errors.New("EncInnerKey not set") 549 } 550 if len(creds.InnerKeyParams) == 0 { 551 return errors.New("InnerKeyParams not set") 552 } 553 if len(creds.OuterKeyParams) == 0 { 554 return errors.New("OuterKeyParams not set") 555 } 556 return nil 557 } 558 559 // primaryCreds reconstructs the *PrimaryCredentials. 560 func (db *BoltDB) primaryCreds() (creds *dexdb.PrimaryCredentials, err error) { 561 return creds, db.View(func(tx *bbolt.Tx) error { 562 bkt := tx.Bucket(credentialsBucket) 563 if bkt == nil { 564 return errors.New("no credentials bucket") 565 } 566 if bkt.Stats().KeyN == 0 { 567 return dexdb.ErrNoCredentials 568 } 569 570 versionB := getCopy(bkt, credsVersionKey) 571 if len(versionB) != 2 { 572 versionB = []byte{0x00, 0x00} 573 } 574 575 bdayB := getCopy(bkt, birthdayKey) 576 bday := time.Time{} 577 if len(bdayB) > 0 { 578 bday = time.Unix(int64(intCoder.Uint64(bdayB)), 0) 579 } 580 581 creds = &dexdb.PrimaryCredentials{ 582 EncSeed: getCopy(bkt, encSeedKey), 583 EncInnerKey: getCopy(bkt, encInnerKeyKey), 584 InnerKeyParams: getCopy(bkt, innerKeyParamsKey), 585 OuterKeyParams: getCopy(bkt, outerKeyParamsKey), 586 Birthday: bday, 587 Version: intCoder.Uint16(versionB), 588 } 589 return nil 590 }) 591 } 592 593 // setCreds stores the *PrimaryCredentials. 594 func (db *BoltDB) setCreds(tx *bbolt.Tx, creds *dexdb.PrimaryCredentials) error { 595 bkt := tx.Bucket(credentialsBucket) 596 if bkt == nil { 597 return errors.New("no credentials bucket") 598 } 599 return newBucketPutter(bkt). 600 put(encSeedKey, creds.EncSeed). 601 put(encInnerKeyKey, creds.EncInnerKey). 602 put(innerKeyParamsKey, creds.InnerKeyParams). 603 put(outerKeyParamsKey, creds.OuterKeyParams). 604 put(birthdayKey, uint64Bytes(uint64(creds.Birthday.Unix()))). 605 put(credsVersionKey, uint16Bytes(creds.Version)). 606 err() 607 } 608 609 // PrimaryCredentials retrieves the *PrimaryCredentials, if they are stored. It 610 // is an error if none have been stored. 611 func (db *BoltDB) PrimaryCredentials() (creds *dexdb.PrimaryCredentials, err error) { 612 return db.primaryCreds() 613 } 614 615 // SetSeedGenerationTime stores the time the app seed was generated. 616 func (db *BoltDB) SetSeedGenerationTime(time uint64) error { 617 return db.Update(func(tx *bbolt.Tx) error { 618 bkt := tx.Bucket(credentialsBucket) 619 if bkt == nil { 620 return errors.New("no credentials bucket") 621 } 622 623 return newBucketPutter(bkt). 624 put(seedGenTimeKey, uint64Bytes(time)). 625 err() 626 }) 627 } 628 629 // SeedGenerationTime returns the time the app seed was generated, if it was 630 // stored. It returns dexdb.ErrNoSeedGenTime if it was not stored. 631 func (db *BoltDB) SeedGenerationTime() (uint64, error) { 632 var seedGenTime uint64 633 return seedGenTime, db.View(func(tx *bbolt.Tx) error { 634 bkt := tx.Bucket(credentialsBucket) 635 if bkt == nil { 636 return errors.New("no credentials bucket") 637 } 638 639 seedGenTimeBytes := bkt.Get(seedGenTimeKey) 640 if seedGenTimeBytes == nil { 641 return dexdb.ErrNoSeedGenTime 642 } 643 if len(seedGenTimeBytes) != 8 { 644 return fmt.Errorf("seed generation time length %v, expected 8", len(seedGenTimeBytes)) 645 } 646 647 seedGenTime = intCoder.Uint64(seedGenTimeBytes) 648 return nil 649 }) 650 } 651 652 // ListAccounts returns a list of DEX URLs. The DB is designed to have a single 653 // account per DEX, so the account itself is identified by the DEX URL. 654 func (db *BoltDB) ListAccounts() ([]string, error) { 655 var urls []string 656 return urls, db.acctsView(func(accts *bbolt.Bucket) error { 657 c := accts.Cursor() 658 for acct, _ := c.First(); acct != nil; acct, _ = c.Next() { 659 acctBkt := accts.Bucket(acct) 660 if acctBkt == nil { 661 return fmt.Errorf("account bucket %s value not a nested bucket", string(acct)) 662 } 663 if bEqual(acctBkt.Get(activeKey), byteTrue) { 664 urls = append(urls, string(acct)) 665 } 666 } 667 return nil 668 }) 669 } 670 671 func loadAccountInfo(acct *bbolt.Bucket, log dex.Logger) (*db.AccountInfo, error) { 672 acctB := getCopy(acct, accountKey) 673 if acctB == nil { 674 return nil, fmt.Errorf("empty account") 675 } 676 acctInfo, err := dexdb.DecodeAccountInfo(acctB) 677 if err != nil { 678 return nil, err 679 } 680 681 acctInfo.Disabled = bytes.Equal(acct.Get(activeKey), byteFalse) 682 683 bondsBkt := acct.Bucket(bondsSubBucket) 684 if bondsBkt == nil { 685 return acctInfo, nil // no bonds, OK for legacy account 686 } 687 688 c := bondsBkt.Cursor() 689 for bondUID, _ := c.First(); bondUID != nil; bondUID, _ = c.Next() { 690 bond := bondsBkt.Bucket(bondUID) 691 if acct == nil { 692 return nil, fmt.Errorf("bond sub-bucket %x not a nested bucket", bondUID) 693 } 694 dbBond, err := dexdb.DecodeBond(getCopy(bond, bondKey)) 695 if err != nil { 696 log.Errorf("Invalid bond data encoding: %v", err) 697 continue 698 } 699 dbBond.Confirmed = bEqual(bond.Get(confirmedKey), byteTrue) 700 dbBond.Refunded = bEqual(bond.Get(refundedKey), byteTrue) 701 acctInfo.Bonds = append(acctInfo.Bonds, dbBond) 702 } 703 704 return acctInfo, nil 705 } 706 707 // Accounts returns a list of DEX Accounts. The DB is designed to have a single 708 // account per DEX, so the account itself is identified by the DEX host. TODO: 709 // allow bonds filter based on lockTime. 710 func (db *BoltDB) Accounts() ([]*dexdb.AccountInfo, error) { 711 var accounts []*dexdb.AccountInfo 712 return accounts, db.acctsView(func(accts *bbolt.Bucket) error { 713 c := accts.Cursor() 714 for acctKey, _ := c.First(); acctKey != nil; acctKey, _ = c.Next() { 715 acct := accts.Bucket(acctKey) 716 if acct == nil { 717 return fmt.Errorf("account bucket %s value not a nested bucket", string(acctKey)) 718 } 719 acctInfo, err := loadAccountInfo(acct, db.log) 720 if err != nil { 721 return err 722 } 723 accounts = append(accounts, acctInfo) 724 } 725 return nil 726 }) 727 } 728 729 // Account gets the AccountInfo associated with the specified DEX address. 730 func (db *BoltDB) Account(url string) (*dexdb.AccountInfo, error) { 731 var acctInfo *dexdb.AccountInfo 732 acctKey := []byte(url) 733 return acctInfo, db.acctsView(func(accts *bbolt.Bucket) (err error) { 734 acct := accts.Bucket(acctKey) 735 if acct == nil { 736 return dexdb.ErrAcctNotFound 737 } 738 acctInfo, err = loadAccountInfo(acct, db.log) 739 return 740 }) 741 } 742 743 // CreateAccount saves the AccountInfo. If an account already exists for this 744 // DEX, it will return an error. 745 func (db *BoltDB) CreateAccount(ai *dexdb.AccountInfo) error { 746 if ai.Host == "" { 747 return fmt.Errorf("empty host not allowed") 748 } 749 if ai.DEXPubKey == nil { 750 return fmt.Errorf("nil DEXPubKey not allowed") 751 } 752 return db.acctsUpdate(func(accts *bbolt.Bucket) error { 753 acct, err := accts.CreateBucket([]byte(ai.Host)) 754 if err != nil { 755 return fmt.Errorf("failed to create account bucket: %w", err) 756 } 757 758 err = acct.Put(accountKey, ai.Encode()) 759 if err != nil { 760 return fmt.Errorf("accountKey put error: %w", err) 761 } 762 err = acct.Put(activeKey, byteTrue) 763 if err != nil { 764 return fmt.Errorf("activeKey put error: %w", err) 765 } 766 767 bonds, err := acct.CreateBucket(bondsSubBucket) 768 if err != nil { 769 return fmt.Errorf("unable to create bonds sub-bucket for account for %s: %w", ai.Host, err) 770 } 771 772 for _, bond := range ai.Bonds { 773 bondUID := bond.UniqueID() 774 bondBkt, err := bonds.CreateBucketIfNotExists(bondUID) 775 if err != nil { 776 return fmt.Errorf("failed to create bond %x bucket: %w", bondUID, err) 777 } 778 779 err = db.storeBond(bondBkt, bond) 780 if err != nil { 781 return err 782 } 783 } 784 785 return nil 786 }) 787 } 788 789 // NextBondKeyIndex returns the next bond key index and increments the stored 790 // value so that subsequent calls will always return a higher index. 791 func (db *BoltDB) NextBondKeyIndex(assetID uint32) (uint32, error) { 792 var bondIndex uint32 793 return bondIndex, db.Update(func(tx *bbolt.Tx) error { 794 bkt := tx.Bucket(bondIndexesBucket) 795 if bkt == nil { 796 return errors.New("no bond indexes bucket") 797 } 798 799 thisBondIdxKey := uint32Bytes(assetID) 800 bondIndexB := bkt.Get(thisBondIdxKey) 801 if len(bondIndexB) != 0 { 802 bondIndex = intCoder.Uint32(bondIndexB) 803 } 804 return bkt.Put(thisBondIdxKey, uint32Bytes(bondIndex+1)) 805 }) 806 } 807 808 // UpdateAccountInfo updates the account info for an existing account with 809 // the same Host as the parameter. If no account exists with this host, 810 // an error is returned. 811 func (db *BoltDB) UpdateAccountInfo(ai *dexdb.AccountInfo) error { 812 return db.acctsUpdate(func(accts *bbolt.Bucket) error { 813 acct := accts.Bucket([]byte(ai.Host)) 814 if acct == nil { 815 return fmt.Errorf("account not found for %s", ai.Host) 816 } 817 818 err := acct.Put(accountKey, ai.Encode()) 819 if err != nil { 820 return fmt.Errorf("accountKey put error: %w", err) 821 } 822 823 bonds, err := acct.CreateBucketIfNotExists(bondsSubBucket) 824 if err != nil { 825 return fmt.Errorf("unable to create bonds sub-bucket for account for %s: %w", ai.Host, err) 826 } 827 828 for _, bond := range ai.Bonds { 829 bondUID := bond.UniqueID() 830 bondBkt, err := bonds.CreateBucketIfNotExists(bondUID) 831 if err != nil { 832 return fmt.Errorf("failed to create bond %x bucket: %w", bondUID, err) 833 } 834 835 err = db.storeBond(bondBkt, bond) 836 if err != nil { 837 return err 838 } 839 } 840 841 return nil 842 }) 843 } 844 845 // ToggleAccountStatus enables or disables the account associated with the given 846 // host. 847 func (db *BoltDB) ToggleAccountStatus(host string, disable bool) error { 848 return db.acctsUpdate(func(accts *bbolt.Bucket) error { 849 acct := accts.Bucket([]byte(host)) 850 if acct == nil { 851 return fmt.Errorf("account not found for %s", host) 852 } 853 854 newStatus := byteTrue 855 if disable { 856 newStatus = byteFalse 857 } 858 859 if bytes.Equal(acct.Get(activeKey), newStatus) { 860 msg := "account is already enabled" 861 if disable { 862 msg = "account is already disabled" 863 } 864 return errors.New(msg) 865 } 866 867 err := acct.Put(activeKey, newStatus) 868 if err != nil { 869 return fmt.Errorf("accountKey put error: %w", err) 870 } 871 return nil 872 }) 873 } 874 875 // acctsView is a convenience function for reading from the account bucket. 876 func (db *BoltDB) acctsView(f bucketFunc) error { 877 return db.withBucket(accountsBucket, db.View, f) 878 } 879 880 // acctsUpdate is a convenience function for updating the account bucket. 881 func (db *BoltDB) acctsUpdate(f bucketFunc) error { 882 return db.withBucket(accountsBucket, db.Update, f) 883 } 884 885 func (db *BoltDB) storeBond(bondBkt *bbolt.Bucket, bond *db.Bond) error { 886 err := bondBkt.Put(bondKey, bond.Encode()) 887 if err != nil { 888 return fmt.Errorf("bondKey put error: %w", err) 889 } 890 891 confirmed := encode.ByteFalse 892 if bond.Confirmed { 893 confirmed = encode.ByteTrue 894 } 895 err = bondBkt.Put(confirmedKey, confirmed) 896 if err != nil { 897 return fmt.Errorf("confirmedKey put error: %w", err) 898 } 899 900 refunded := encode.ByteFalse 901 if bond.Refunded { 902 refunded = encode.ByteTrue 903 } 904 err = bondBkt.Put(refundedKey, refunded) 905 if err != nil { 906 return fmt.Errorf("refundedKey put error: %w", err) 907 } 908 909 err = bondBkt.Put(lockTimeKey, uint64Bytes(bond.LockTime)) // also in bond encoding 910 if err != nil { 911 return fmt.Errorf("lockTimeKey put error: %w", err) 912 } 913 914 return nil 915 } 916 917 // AddBond saves a new Bond or updates an existing bond for an existing DEX 918 // account. 919 func (db *BoltDB) AddBond(host string, bond *db.Bond) error { 920 acctKey := []byte(host) 921 return db.acctsUpdate(func(accts *bbolt.Bucket) error { 922 acct := accts.Bucket(acctKey) 923 if acct == nil { 924 return fmt.Errorf("account not found for %s", host) 925 } 926 927 bonds, err := acct.CreateBucketIfNotExists(bondsSubBucket) 928 if err != nil { 929 return fmt.Errorf("unable to access bonds sub-bucket for account for %s: %w", host, err) 930 } 931 932 bondUID := bond.UniqueID() 933 bondBkt, err := bonds.CreateBucketIfNotExists(bondUID) 934 if err != nil { 935 return fmt.Errorf("failed to create bond %x bucket: %w", bondUID, err) 936 } 937 938 return db.storeBond(bondBkt, bond) 939 }) 940 } 941 942 func (db *BoltDB) setBondFlag(host string, assetID uint32, bondCoinID []byte, flagKey []byte) error { 943 acctKey := []byte(host) 944 return db.acctsUpdate(func(accts *bbolt.Bucket) error { 945 acct := accts.Bucket(acctKey) 946 if acct == nil { 947 return fmt.Errorf("account not found for %s", host) 948 } 949 950 bonds := acct.Bucket(bondsSubBucket) 951 if bonds == nil { 952 return fmt.Errorf("bonds sub-bucket not found for account for %s", host) 953 } 954 955 bondUID := dexdb.BondUID(assetID, bondCoinID) 956 bondBkt := bonds.Bucket(bondUID) 957 if bondBkt == nil { 958 return fmt.Errorf("bond bucket does not exist: %x", bondUID) 959 } 960 961 return bondBkt.Put(flagKey, byteTrue) 962 }) 963 } 964 965 // ConfirmBond marks a DEX account bond as confirmed by the DEX. 966 func (db *BoltDB) ConfirmBond(host string, assetID uint32, bondCoinID []byte) error { 967 return db.setBondFlag(host, assetID, bondCoinID, confirmedKey) 968 } 969 970 // BondRefunded marks a DEX account bond as refunded by the client wallet. 971 func (db *BoltDB) BondRefunded(host string, assetID uint32, bondCoinID []byte) error { 972 return db.setBondFlag(host, assetID, bondCoinID, refundedKey) 973 } 974 975 // UpdateOrder saves the order information in the database. Any existing order 976 // info for the same order ID will be overwritten without indication. 977 func (db *BoltDB) UpdateOrder(m *dexdb.MetaOrder) error { 978 ord, md := m.Order, m.MetaData 979 if md.Status == order.OrderStatusUnknown { 980 return fmt.Errorf("cannot set order %s status to unknown", ord.ID()) 981 } 982 if md.Host == "" { 983 return fmt.Errorf("empty DEX not allowed") 984 } 985 if len(md.Proof.DEXSig) == 0 { 986 return fmt.Errorf("cannot save order without DEX signature") 987 } 988 return db.ordersUpdate(func(ob, archivedOB *bbolt.Bucket) error { 989 oid := ord.ID() 990 991 // Create or move an order bucket based on order status. Active 992 // orders go in the activeOrdersBucket. Inactive orders go in the 993 // archivedOrdersBucket. 994 bkt := ob 995 whichBkt := "active" 996 inactive := !md.Status.IsActive() 997 if inactive { 998 // No order means that it was never added to the active 999 // orders bucket, or UpdateOrder was already called on 1000 // this order after it became inactive. 1001 if ob.Bucket(oid[:]) != nil { 1002 // This order now belongs in the archived orders 1003 // bucket. Delete it from active orders. 1004 if err := ob.DeleteBucket(oid[:]); err != nil { 1005 return fmt.Errorf("archived order bucket delete error: %w", err) 1006 } 1007 } 1008 bkt = archivedOB 1009 whichBkt = "archived" 1010 } 1011 oBkt, err := bkt.CreateBucketIfNotExists(oid[:]) 1012 if err != nil { 1013 return fmt.Errorf("%s bucket error: %w", whichBkt, err) 1014 } 1015 1016 err = newBucketPutter(oBkt). 1017 put(baseKey, uint32Bytes(ord.Base())). 1018 put(quoteKey, uint32Bytes(ord.Quote())). 1019 put(dexKey, []byte(md.Host)). 1020 put(typeKey, []byte{byte(ord.Type())}). 1021 put(orderKey, order.EncodeOrder(ord)). 1022 put(epochDurKey, uint64Bytes(md.EpochDur)). 1023 put(fromVersionKey, uint32Bytes(md.FromVersion)). 1024 put(toVersionKey, uint32Bytes(md.ToVersion)). 1025 put(fromSwapConfKey, uint32Bytes(md.FromSwapConf)). 1026 put(toSwapConfKey, uint32Bytes(md.ToSwapConf)). 1027 put(redeemMaxFeeRateKey, uint64Bytes(md.RedeemMaxFeeRate)). 1028 put(maxFeeRateKey, uint64Bytes(md.MaxFeeRate)). 1029 err() 1030 1031 if err != nil { 1032 return err 1033 } 1034 1035 return updateOrderMetaData(oBkt, md) 1036 }) 1037 } 1038 1039 // ActiveOrders retrieves all orders which appear to be in an active state, 1040 // which is either in the epoch queue or in the order book. 1041 func (db *BoltDB) ActiveOrders() ([]*dexdb.MetaOrder, error) { 1042 return db.filteredOrders(func(_ *bbolt.Bucket) bool { 1043 return true 1044 }, false) 1045 } 1046 1047 // AccountOrders retrieves all orders associated with the specified DEX. n = 0 1048 // applies no limit on number of orders returned. since = 0 is equivalent to 1049 // disabling the time filter, since no orders were created before 1970. 1050 func (db *BoltDB) AccountOrders(dex string, n int, since uint64) ([]*dexdb.MetaOrder, error) { 1051 dexB := []byte(dex) 1052 if n == 0 && since == 0 { 1053 return db.filteredOrders(func(oBkt *bbolt.Bucket) bool { 1054 return bEqual(dexB, oBkt.Get(dexKey)) 1055 }, true) 1056 } 1057 sinceB := uint64Bytes(since) 1058 return db.newestOrders(n, func(_ []byte, oBkt *bbolt.Bucket) bool { 1059 timeB := oBkt.Get(updateTimeKey) 1060 return bEqual(dexB, oBkt.Get(dexKey)) && bytes.Compare(timeB, sinceB) >= 0 1061 }, true) 1062 } 1063 1064 // MarketOrders retrieves all orders for the specified DEX and market. n = 0 1065 // applies no limit on number of orders returned. since = 0 is equivalent to 1066 // disabling the time filter, since no orders were created before 1970. 1067 func (db *BoltDB) MarketOrders(dex string, base, quote uint32, n int, since uint64) ([]*dexdb.MetaOrder, error) { 1068 dexB := []byte(dex) 1069 baseB := uint32Bytes(base) 1070 quoteB := uint32Bytes(quote) 1071 if n == 0 && since == 0 { 1072 return db.marketOrdersAll(dexB, baseB, quoteB) 1073 } 1074 return db.marketOrdersSince(dexB, baseB, quoteB, n, since) 1075 } 1076 1077 // ActiveDEXOrders retrieves all orders for the specified DEX. 1078 func (db *BoltDB) ActiveDEXOrders(dex string) ([]*dexdb.MetaOrder, error) { 1079 dexB := []byte(dex) 1080 return db.filteredOrders(func(oBkt *bbolt.Bucket) bool { 1081 return bEqual(dexB, oBkt.Get(dexKey)) 1082 }, false) 1083 } 1084 1085 // marketOrdersAll retrieves all orders for the specified DEX and market. 1086 func (db *BoltDB) marketOrdersAll(dexB, baseB, quoteB []byte) ([]*dexdb.MetaOrder, error) { 1087 return db.filteredOrders(func(oBkt *bbolt.Bucket) bool { 1088 return bEqual(dexB, oBkt.Get(dexKey)) && bEqual(baseB, oBkt.Get(baseKey)) && 1089 bEqual(quoteB, oBkt.Get(quoteKey)) 1090 }, true) 1091 } 1092 1093 // marketOrdersSince grabs market orders with optional filters for maximum count 1094 // and age. The sort order is always newest first. If n is 0, there is no limit 1095 // to the number of orders returned. If using with both n = 0, and since = 0, 1096 // use marketOrdersAll instead. 1097 func (db *BoltDB) marketOrdersSince(dexB, baseB, quoteB []byte, n int, since uint64) ([]*dexdb.MetaOrder, error) { 1098 sinceB := uint64Bytes(since) 1099 return db.newestOrders(n, func(_ []byte, oBkt *bbolt.Bucket) bool { 1100 timeB := oBkt.Get(updateTimeKey) 1101 return bEqual(dexB, oBkt.Get(dexKey)) && bEqual(baseB, oBkt.Get(baseKey)) && 1102 bEqual(quoteB, oBkt.Get(quoteKey)) && bytes.Compare(timeB, sinceB) >= 0 1103 }, true) 1104 } 1105 1106 // newestOrders returns the n newest orders, filtered with a supplied filter 1107 // function. Each order's bucket is provided to the filter, and a boolean true 1108 // return value indicates the order should is eligible to be decoded and 1109 // returned. 1110 func (db *BoltDB) newestOrders(n int, filter func([]byte, *bbolt.Bucket) bool, includeArchived bool) ([]*dexdb.MetaOrder, error) { 1111 orders := make([]*dexdb.MetaOrder, 0, n) 1112 return orders, db.ordersView(func(ob, archivedOB *bbolt.Bucket) error { 1113 buckets := []*bbolt.Bucket{ob} 1114 if includeArchived { 1115 buckets = append(buckets, archivedOB) 1116 } 1117 trios := newestBuckets(buckets, n, updateTimeKey, filter) 1118 for _, trio := range trios { 1119 o, err := decodeOrderBucket(trio.k, trio.b) 1120 if err != nil { 1121 return err 1122 } 1123 orders = append(orders, o) 1124 } 1125 return nil 1126 }) 1127 } 1128 1129 // filteredOrders gets all orders that pass the provided filter function. Each 1130 // order's bucket is provided to the filter, and a boolean true return value 1131 // indicates the order should be decoded and returned. 1132 func (db *BoltDB) filteredOrders(filter func(*bbolt.Bucket) bool, includeArchived bool) ([]*dexdb.MetaOrder, error) { 1133 var orders []*dexdb.MetaOrder 1134 return orders, db.ordersView(func(ob, archivedOB *bbolt.Bucket) error { 1135 buckets := []*bbolt.Bucket{ob} 1136 if includeArchived { 1137 buckets = append(buckets, archivedOB) 1138 } 1139 for _, master := range buckets { 1140 err := master.ForEach(func(oid, _ []byte) error { 1141 oBkt := master.Bucket(oid) 1142 if oBkt == nil { 1143 return fmt.Errorf("order %x bucket is not a bucket", oid) 1144 } 1145 if filter(oBkt) { 1146 o, err := decodeOrderBucket(oid, oBkt) 1147 if err != nil { 1148 return err 1149 } 1150 orders = append(orders, o) 1151 } 1152 return nil 1153 }) 1154 if err != nil { 1155 return err 1156 } 1157 } 1158 return nil 1159 }) 1160 } 1161 1162 // Order fetches a MetaOrder by order ID. 1163 func (db *BoltDB) Order(oid order.OrderID) (mord *dexdb.MetaOrder, err error) { 1164 oidB := oid[:] 1165 err = db.ordersView(func(ob, archivedOB *bbolt.Bucket) error { 1166 oBkt := ob.Bucket(oidB) 1167 // If the order is not in the active bucket, check the archived 1168 // orders bucket. 1169 if oBkt == nil { 1170 oBkt = archivedOB.Bucket(oidB) 1171 } 1172 if oBkt == nil { 1173 return fmt.Errorf("order %s not found", oid) 1174 } 1175 var err error 1176 mord, err = decodeOrderBucket(oidB, oBkt) 1177 return err 1178 }) 1179 return mord, err 1180 } 1181 1182 // filterSet is a set of bucket filtering functions. Each function takes a 1183 // bucket key and the associated bucket, and should return true if the bucket 1184 // passes the filter. 1185 type filterSet []func(oidB []byte, oBkt *bbolt.Bucket) bool 1186 1187 // check runs the bucket through all filters, and will return false if the 1188 // bucket fails to pass any one filter. 1189 func (fs filterSet) check(oidB []byte, oBkt *bbolt.Bucket) bool { 1190 for _, f := range fs { 1191 if !f(oidB, oBkt) { 1192 return false 1193 } 1194 } 1195 return true 1196 } 1197 1198 // Orders fetches a slice of orders, sorted by descending time, and filtered 1199 // with the provided OrderFilter. Orders does not return cancel orders. 1200 func (db *BoltDB) Orders(orderFilter *dexdb.OrderFilter) (ords []*dexdb.MetaOrder, err error) { 1201 // Default filter is just to exclude cancel orders. 1202 filters := filterSet{ 1203 func(oidB []byte, oBkt *bbolt.Bucket) bool { 1204 oTypeB := oBkt.Get(typeKey) 1205 if len(oTypeB) != 1 { 1206 db.log.Error("encountered order type encoded with wrong number of bytes = %d for order %x", len(oTypeB), oidB) 1207 return false 1208 } 1209 oType := order.OrderType(oTypeB[0]) 1210 return oType != order.CancelOrderType 1211 }, 1212 } 1213 1214 if len(orderFilter.Hosts) > 0 { 1215 hosts := make(map[string]bool, len(orderFilter.Hosts)) 1216 for _, host := range orderFilter.Hosts { 1217 hosts[host] = true 1218 } 1219 filters = append(filters, func(_ []byte, oBkt *bbolt.Bucket) bool { 1220 return hosts[string(oBkt.Get(dexKey))] 1221 }) 1222 } 1223 1224 if len(orderFilter.Assets) > 0 { 1225 assetIDs := make(map[uint32]bool, len(orderFilter.Assets)) 1226 for _, assetID := range orderFilter.Assets { 1227 assetIDs[assetID] = true 1228 } 1229 filters = append(filters, func(_ []byte, oBkt *bbolt.Bucket) bool { 1230 return assetIDs[intCoder.Uint32(oBkt.Get(baseKey))] || assetIDs[intCoder.Uint32(oBkt.Get(quoteKey))] 1231 }) 1232 } 1233 1234 includeArchived := true 1235 if len(orderFilter.Statuses) > 0 { 1236 filters = append(filters, func(_ []byte, oBkt *bbolt.Bucket) bool { 1237 status := order.OrderStatus(intCoder.Uint16(oBkt.Get(statusKey))) 1238 for _, acceptable := range orderFilter.Statuses { 1239 if status == acceptable { 1240 return true 1241 } 1242 } 1243 return false 1244 }) 1245 includeArchived = false 1246 for _, status := range orderFilter.Statuses { 1247 if !status.IsActive() { 1248 includeArchived = true 1249 break 1250 } 1251 } 1252 } 1253 1254 if orderFilter.Market != nil { 1255 filters = append(filters, func(_ []byte, oBkt *bbolt.Bucket) bool { 1256 baseID, quoteID := intCoder.Uint32(oBkt.Get(baseKey)), intCoder.Uint32(oBkt.Get(quoteKey)) 1257 return orderFilter.Market.Base == baseID && orderFilter.Market.Quote == quoteID 1258 }) 1259 } 1260 1261 if !orderFilter.Offset.IsZero() { 1262 offsetOID := orderFilter.Offset 1263 var stampB []byte 1264 err := db.ordersView(func(ob, archivedOB *bbolt.Bucket) error { 1265 offsetBucket := ob.Bucket(offsetOID[:]) 1266 // If the order is not in the active bucket, check the 1267 // archived orders bucket. 1268 if offsetBucket == nil { 1269 offsetBucket = archivedOB.Bucket(offsetOID[:]) 1270 } 1271 if offsetBucket == nil { 1272 return fmt.Errorf("order %s not found", offsetOID) 1273 } 1274 stampB = getCopy(offsetBucket, updateTimeKey) 1275 return nil 1276 }) 1277 if err != nil { 1278 return nil, err 1279 } 1280 1281 filters = append(filters, func(oidB []byte, oBkt *bbolt.Bucket) bool { 1282 comp := bytes.Compare(oBkt.Get(updateTimeKey), stampB) 1283 return comp < 0 || (comp == 0 && bytes.Compare(offsetOID[:], oidB) < 0) 1284 }) 1285 } 1286 1287 return db.newestOrders(orderFilter.N, filters.check, includeArchived) 1288 } 1289 1290 // decodeOrderBucket decodes the order's *bbolt.Bucket into a *MetaOrder. 1291 func decodeOrderBucket(oid []byte, oBkt *bbolt.Bucket) (*dexdb.MetaOrder, error) { 1292 orderB := getCopy(oBkt, orderKey) 1293 if orderB == nil { 1294 return nil, fmt.Errorf("nil order bytes for order %x", oid) 1295 } 1296 ord, err := order.DecodeOrder(orderB) 1297 if err != nil { 1298 return nil, fmt.Errorf("error decoding order %x: %w", oid, err) 1299 } 1300 proofB := getCopy(oBkt, proofKey) 1301 if proofB == nil { 1302 return nil, fmt.Errorf("nil proof for order %x", oid) 1303 } 1304 proof, err := dexdb.DecodeOrderProof(proofB) 1305 if err != nil { 1306 return nil, fmt.Errorf("error decoding order proof for %x: %w", oid, err) 1307 } 1308 1309 var redemptionReserves uint64 1310 redemptionReservesB := oBkt.Get(redemptionReservesKey) 1311 if len(redemptionReservesB) == 8 { 1312 redemptionReserves = intCoder.Uint64(redemptionReservesB) 1313 } 1314 1315 var refundReserves uint64 1316 refundReservesB := oBkt.Get(refundReservesKey) 1317 if len(refundReservesB) == 8 { 1318 refundReserves = intCoder.Uint64(refundReservesB) 1319 } 1320 1321 var linkedID order.OrderID 1322 copy(linkedID[:], oBkt.Get(linkedKey)) 1323 1324 // Old cancel orders may not have a maxFeeRate set since the v2 upgrade 1325 // doesn't set it for cancel orders. 1326 var maxFeeRate uint64 1327 if maxFeeRateB := oBkt.Get(maxFeeRateKey); len(maxFeeRateB) == 8 { 1328 maxFeeRate = intCoder.Uint64(maxFeeRateB) 1329 } else if ord.Type() != order.CancelOrderType { 1330 // Cancel orders should use zero, but trades need a non-zero value. 1331 maxFeeRate = ^uint64(0) // should not happen for trade orders after v2 upgrade 1332 } 1333 1334 var redeemMaxFeeRate uint64 1335 if redeemMaxFeeRateB := oBkt.Get(redeemMaxFeeRateKey); len(redeemMaxFeeRateB) == 8 { 1336 redeemMaxFeeRate = intCoder.Uint64(redeemMaxFeeRateB) 1337 } 1338 1339 var fromVersion, toVersion uint32 1340 fromVersionB, toVersionB := oBkt.Get(fromVersionKey), oBkt.Get(toVersionKey) 1341 if len(fromVersionB) == 4 { 1342 fromVersion = intCoder.Uint32(fromVersionB) 1343 } 1344 if len(toVersionB) == 4 { 1345 toVersion = intCoder.Uint32(toVersionB) 1346 } 1347 1348 var fromSwapConf, toSwapConf uint32 1349 fromSwapConfB, toSwapConfB := oBkt.Get(fromSwapConfKey), oBkt.Get(toSwapConfKey) 1350 if len(fromSwapConfB) == 4 { 1351 fromSwapConf = intCoder.Uint32(fromSwapConfB) 1352 } 1353 if len(toSwapConfB) == 4 { 1354 toSwapConf = intCoder.Uint32(toSwapConfB) 1355 } 1356 1357 var epochDur uint64 1358 if epochDurB := oBkt.Get(epochDurKey); len(epochDurB) == 8 { 1359 epochDur = intCoder.Uint64(epochDurB) 1360 } 1361 1362 optionsB := oBkt.Get(optionsKey) 1363 options, err := config.Parse(optionsB) 1364 if err != nil { 1365 return nil, fmt.Errorf("unable to decode order options") 1366 } 1367 1368 var accelerationCoinIDs []order.CoinID 1369 accelerationsB := getCopy(oBkt, accelerationsKey) 1370 if len(accelerationsB) > 0 { 1371 _, coinIDs, err := encode.DecodeBlob(accelerationsB) 1372 if err != nil { 1373 return nil, fmt.Errorf("unable to decode accelerations") 1374 } 1375 for _, coinID := range coinIDs { 1376 accelerationCoinIDs = append(accelerationCoinIDs, order.CoinID(coinID)) 1377 } 1378 } 1379 1380 var fundingFeesPaid uint64 1381 if fundingFeesB := oBkt.Get(fundingFeesKey); len(fundingFeesB) == 8 { 1382 fundingFeesPaid = intCoder.Uint64(fundingFeesB) 1383 } 1384 1385 return &dexdb.MetaOrder{ 1386 MetaData: &dexdb.OrderMetaData{ 1387 Proof: *proof, 1388 Status: order.OrderStatus(intCoder.Uint16(oBkt.Get(statusKey))), 1389 Host: string(getCopy(oBkt, dexKey)), 1390 ChangeCoin: getCopy(oBkt, changeKey), 1391 LinkedOrder: linkedID, 1392 SwapFeesPaid: intCoder.Uint64(oBkt.Get(swapFeesKey)), 1393 EpochDur: epochDur, 1394 MaxFeeRate: maxFeeRate, 1395 RedeemMaxFeeRate: redeemMaxFeeRate, 1396 RedemptionFeesPaid: intCoder.Uint64(oBkt.Get(redemptionFeesKey)), 1397 FromSwapConf: fromSwapConf, 1398 ToSwapConf: toSwapConf, 1399 FromVersion: fromVersion, 1400 ToVersion: toVersion, 1401 Options: options, 1402 RedemptionReserves: redemptionReserves, 1403 RefundReserves: refundReserves, 1404 AccelerationCoins: accelerationCoinIDs, 1405 FundingFeesPaid: fundingFeesPaid, 1406 }, 1407 Order: ord, 1408 }, nil 1409 } 1410 1411 // updateOrderBucket expects an oid to exist in either the orders or archived 1412 // order buckets. If status is not active, it first checks active orders. If 1413 // found it moves the order to the archived bucket and returns that order 1414 // bucket. If status is less than or equal to booked, it expects the order 1415 // to already be in active orders and returns that bucket. 1416 func updateOrderBucket(ob, archivedOB *bbolt.Bucket, oid order.OrderID, status order.OrderStatus) (*bbolt.Bucket, error) { 1417 if status == order.OrderStatusUnknown { 1418 return nil, fmt.Errorf("cannot set order %s status to unknown", oid) 1419 } 1420 inactive := !status.IsActive() 1421 if inactive { 1422 if bkt := ob.Bucket(oid[:]); bkt != nil { 1423 // It's in the active bucket. Move it to the archived bucket. 1424 oBkt, err := archivedOB.CreateBucket(oid[:]) 1425 if err != nil { 1426 return nil, fmt.Errorf("unable to create archived order bucket: %v", err) 1427 } 1428 // Assume the order bucket contains only values, no 1429 // sub-buckets 1430 if err := bkt.ForEach(func(k, v []byte) error { 1431 return oBkt.Put(k, v) 1432 }); err != nil { 1433 return nil, fmt.Errorf("unable to copy active order bucket: %v", err) 1434 } 1435 if err := ob.DeleteBucket(oid[:]); err != nil { 1436 return nil, fmt.Errorf("unable to delete active order bucket: %v", err) 1437 } 1438 return oBkt, nil 1439 } 1440 // It's not in the active bucket, check archived. 1441 oBkt := archivedOB.Bucket(oid[:]) 1442 if oBkt == nil { 1443 return nil, fmt.Errorf("archived order %s not found", oid) 1444 } 1445 return oBkt, nil 1446 } 1447 // Active status should be in the active bucket. 1448 oBkt := ob.Bucket(oid[:]) 1449 if oBkt == nil { 1450 return nil, fmt.Errorf("active order %s not found", oid) 1451 } 1452 return oBkt, nil 1453 } 1454 1455 // UpdateOrderMetaData updates the order metadata, not including the Host. 1456 func (db *BoltDB) UpdateOrderMetaData(oid order.OrderID, md *dexdb.OrderMetaData) error { 1457 return db.ordersUpdate(func(ob, archivedOB *bbolt.Bucket) error { 1458 oBkt, err := updateOrderBucket(ob, archivedOB, oid, md.Status) 1459 if err != nil { 1460 return fmt.Errorf("UpdateOrderMetaData: %w", err) 1461 } 1462 1463 return updateOrderMetaData(oBkt, md) 1464 }) 1465 } 1466 1467 func updateOrderMetaData(bkt *bbolt.Bucket, md *dexdb.OrderMetaData) error { 1468 var linkedB []byte 1469 if !md.LinkedOrder.IsZero() { 1470 linkedB = md.LinkedOrder[:] 1471 } 1472 1473 var accelerationsB encode.BuildyBytes 1474 if len(md.AccelerationCoins) > 0 { 1475 accelerationsB = encode.BuildyBytes{0} 1476 for _, acceleration := range md.AccelerationCoins { 1477 accelerationsB = accelerationsB.AddData(acceleration) 1478 } 1479 } 1480 1481 return newBucketPutter(bkt). 1482 put(statusKey, uint16Bytes(uint16(md.Status))). 1483 put(updateTimeKey, uint64Bytes(timeNow())). 1484 put(proofKey, md.Proof.Encode()). 1485 put(changeKey, md.ChangeCoin). 1486 put(linkedKey, linkedB). 1487 put(swapFeesKey, uint64Bytes(md.SwapFeesPaid)). 1488 put(redemptionFeesKey, uint64Bytes(md.RedemptionFeesPaid)). 1489 put(optionsKey, config.Data(md.Options)). 1490 put(redemptionReservesKey, uint64Bytes(md.RedemptionReserves)). 1491 put(refundReservesKey, uint64Bytes(md.RefundReserves)). 1492 put(accelerationsKey, accelerationsB). 1493 put(fundingFeesKey, uint64Bytes(md.FundingFeesPaid)). 1494 err() 1495 } 1496 1497 // UpdateOrderStatus sets the order status for an order. 1498 func (db *BoltDB) UpdateOrderStatus(oid order.OrderID, status order.OrderStatus) error { 1499 return db.ordersUpdate(func(ob, archivedOB *bbolt.Bucket) error { 1500 oBkt, err := updateOrderBucket(ob, archivedOB, oid, status) 1501 if err != nil { 1502 return fmt.Errorf("UpdateOrderStatus: %w", err) 1503 } 1504 return oBkt.Put(statusKey, uint16Bytes(uint16(status))) 1505 }) 1506 } 1507 1508 // LinkOrder sets the linked order. 1509 func (db *BoltDB) LinkOrder(oid, linkedID order.OrderID) error { 1510 return db.ordersUpdate(func(ob, archivedOB *bbolt.Bucket) error { 1511 oBkt := ob.Bucket(oid[:]) 1512 // If the order is not in the active bucket, check the archived 1513 // orders bucket. 1514 if oBkt == nil { 1515 oBkt = archivedOB.Bucket(oid[:]) 1516 } 1517 if oBkt == nil { 1518 return fmt.Errorf("LinkOrder - order %s not found", oid) 1519 } 1520 linkedB := linkedID[:] 1521 if linkedID.IsZero() { 1522 linkedB = nil 1523 } 1524 return oBkt.Put(linkedKey, linkedB) 1525 }) 1526 } 1527 1528 // ordersView is a convenience function for reading from the order buckets. 1529 // Orders are spread over two buckets to make searching active orders faster. 1530 // Any reads of the order buckets should be done in the same transaction, as 1531 // orders may move from active to archived at any time. 1532 func (db *BoltDB) ordersView(f func(ob, archivedOB *bbolt.Bucket) error) error { 1533 return db.View(func(tx *bbolt.Tx) error { 1534 ob := tx.Bucket(activeOrdersBucket) 1535 if ob == nil { 1536 return fmt.Errorf("failed to open %s bucket", string(activeOrdersBucket)) 1537 } 1538 archivedOB := tx.Bucket(archivedOrdersBucket) 1539 if archivedOB == nil { 1540 return fmt.Errorf("failed to open %s bucket", string(archivedOrdersBucket)) 1541 } 1542 return f(ob, archivedOB) 1543 }) 1544 } 1545 1546 // ordersUpdate is a convenience function for updating the order buckets. 1547 // Orders are spread over two buckets to make searching active orders faster. 1548 // Any writes of the order buckets should be done in the same transaction to 1549 // ensure that reads can be kept concurrent. 1550 func (db *BoltDB) ordersUpdate(f func(ob, archivedOB *bbolt.Bucket) error) error { 1551 return db.Update(func(tx *bbolt.Tx) error { 1552 ob := tx.Bucket(activeOrdersBucket) 1553 if ob == nil { 1554 return fmt.Errorf("failed to open %s bucket", string(activeOrdersBucket)) 1555 } 1556 archivedOB := tx.Bucket(archivedOrdersBucket) 1557 if archivedOB == nil { 1558 return fmt.Errorf("failed to open %s bucket", string(archivedOrdersBucket)) 1559 } 1560 return f(ob, archivedOB) 1561 }) 1562 } 1563 1564 // matchBucket gets a match's bucket in either the active or archived matches 1565 // bucket. It will be created if it is not in either matches bucket. If an 1566 // inactive match is found in the active bucket, it is moved to the archived 1567 // matches bucket. 1568 func matchBucket(mb, archivedMB *bbolt.Bucket, metaID []byte, active bool) (*bbolt.Bucket, error) { 1569 if active { 1570 // Active match would be in active bucket if it was previously inserted. 1571 return mb.CreateBucketIfNotExists(metaID) // might exist already 1572 } 1573 1574 // Archived match may currently be in either active or archived bucket. 1575 mBkt := mb.Bucket(metaID) // try the active bucket first 1576 if mBkt == nil { 1577 // It's not in the active bucket, check/create in archived. 1578 return archivedMB.CreateBucketIfNotExists(metaID) // might exist already 1579 } 1580 1581 // It's in the active bucket, but no longer active. Move it to the archived 1582 // bucket. 1583 newBkt, err := archivedMB.CreateBucketIfNotExists(metaID) // should not exist in both buckets though! 1584 if err != nil { 1585 return nil, fmt.Errorf("unable to create archived match bucket: %w", err) 1586 } 1587 // Assume the order bucket contains only values, no sub-buckets. 1588 if err := mBkt.ForEach(func(k, v []byte) error { 1589 return newBkt.Put(k, v) 1590 }); err != nil { 1591 return nil, fmt.Errorf("unable to copy active matches bucket: %w", err) 1592 } 1593 if err := mb.DeleteBucket(metaID); err != nil { 1594 return nil, fmt.Errorf("unable to delete active match bucket: %w", err) 1595 } 1596 return newBkt, nil 1597 } 1598 1599 // UpdateMatch updates the match information in the database. Any existing 1600 // entry for the same match ID will be overwritten without indication. 1601 func (db *BoltDB) UpdateMatch(m *dexdb.MetaMatch) error { 1602 match, md := m.UserMatch, m.MetaData 1603 if md.Quote == md.Base { 1604 return fmt.Errorf("quote and base asset cannot be the same") 1605 } 1606 if md.DEX == "" { 1607 return fmt.Errorf("empty DEX not allowed") 1608 } 1609 return db.matchesUpdate(func(mb, archivedMB *bbolt.Bucket) error { 1610 metaID := m.MatchOrderUniqueID() 1611 active := dexdb.MatchIsActive(m.UserMatch, &m.MetaData.Proof) 1612 mBkt, err := matchBucket(mb, archivedMB, metaID, active) 1613 if err != nil { 1614 return err 1615 } 1616 1617 return newBucketPutter(mBkt). 1618 put(baseKey, uint32Bytes(md.Base)). 1619 put(quoteKey, uint32Bytes(md.Quote)). 1620 put(statusKey, []byte{byte(match.Status)}). 1621 put(dexKey, []byte(md.DEX)). 1622 put(updateTimeKey, uint64Bytes(timeNow())). 1623 put(proofKey, md.Proof.Encode()). 1624 put(orderIDKey, match.OrderID[:]). 1625 put(matchIDKey, match.MatchID[:]). 1626 put(matchKey, order.EncodeMatch(match)). 1627 put(stampKey, uint64Bytes(md.Stamp)). 1628 err() 1629 }) 1630 } 1631 1632 // ActiveMatches retrieves the matches that are in an active state, which is 1633 // any match that is still active. 1634 func (db *BoltDB) ActiveMatches() ([]*dexdb.MetaMatch, error) { 1635 return db.filteredMatches( 1636 func(mBkt *bbolt.Bucket) bool { 1637 return true // all matches in the bucket 1638 }, 1639 true, // don't bother with cancel matches that are never active 1640 false, // exclude archived matches 1641 ) 1642 } 1643 1644 // DEXOrdersWithActiveMatches retrieves order IDs for any order that has active 1645 // matches, regardless of whether the order itself is in an active state. 1646 func (db *BoltDB) DEXOrdersWithActiveMatches(dex string) ([]order.OrderID, error) { 1647 dexB := []byte(dex) 1648 // For each match for this DEX, pick the active ones. 1649 idMap := make(map[order.OrderID]bool) 1650 err := db.matchesView(func(ob, _ *bbolt.Bucket) error { // only the active matches bucket is used 1651 return ob.ForEach(func(k, _ []byte) error { 1652 mBkt := ob.Bucket(k) 1653 if mBkt == nil { 1654 return fmt.Errorf("match %x bucket is not a bucket", k) 1655 } 1656 if !bytes.Equal(dexB, mBkt.Get(dexKey)) { 1657 return nil 1658 } 1659 1660 oidB := mBkt.Get(orderIDKey) 1661 var oid order.OrderID 1662 copy(oid[:], oidB) 1663 idMap[oid] = true 1664 return nil 1665 }) 1666 }) 1667 if err != nil { 1668 return nil, err 1669 } 1670 ids := make([]order.OrderID, 0, len(idMap)) 1671 for id := range idMap { 1672 ids = append(ids, id) 1673 } 1674 return ids, nil 1675 1676 } 1677 1678 // MatchesForOrder retrieves the matches for the specified order ID. 1679 func (db *BoltDB) MatchesForOrder(oid order.OrderID, excludeCancels bool) ([]*dexdb.MetaMatch, error) { 1680 oidB := oid[:] 1681 return db.filteredMatches(func(mBkt *bbolt.Bucket) bool { 1682 oid := mBkt.Get(orderIDKey) 1683 return bytes.Equal(oid, oidB) 1684 }, excludeCancels, true) // include archived matches 1685 } 1686 1687 // filteredMatches gets all matches that pass the provided filter function. Each 1688 // match's bucket is provided to the filter, and a boolean true return value 1689 // indicates the match should be decoded and returned. Matches with cancel 1690 // orders may be excluded, a separate option so the filter function does not 1691 // need to load and decode the matchKey value. 1692 func (db *BoltDB) filteredMatches(filter func(*bbolt.Bucket) bool, excludeCancels, includeArchived bool) ([]*dexdb.MetaMatch, error) { 1693 var matches []*dexdb.MetaMatch 1694 return matches, db.matchesView(func(mb, archivedMB *bbolt.Bucket) error { 1695 buckets := []*bbolt.Bucket{mb} 1696 if includeArchived { 1697 buckets = append(buckets, archivedMB) 1698 } 1699 for _, master := range buckets { 1700 err := master.ForEach(func(k, _ []byte) error { 1701 mBkt := master.Bucket(k) 1702 if mBkt == nil { 1703 return fmt.Errorf("match %x bucket is not a bucket", k) 1704 } 1705 if !filter(mBkt) { 1706 return nil 1707 } 1708 match, err := loadMatchBucket(mBkt, excludeCancels) 1709 if err != nil { 1710 return fmt.Errorf("loading match %x bucket: %w", k, err) 1711 } 1712 if match != nil { 1713 matches = append(matches, match) 1714 } 1715 return nil 1716 }) 1717 if err != nil { 1718 return err 1719 } 1720 } 1721 return nil 1722 }) 1723 } 1724 1725 func loadMatchBucket(mBkt *bbolt.Bucket, excludeCancels bool) (*dexdb.MetaMatch, error) { 1726 var proof *dexdb.MatchProof 1727 matchB := getCopy(mBkt, matchKey) 1728 if matchB == nil { 1729 return nil, fmt.Errorf("nil match bytes") 1730 } 1731 match, matchVer, err := order.DecodeMatch(matchB) 1732 if err != nil { 1733 return nil, fmt.Errorf("error decoding match: %w", err) 1734 } 1735 if matchVer == 0 && match.Status == order.MatchComplete { 1736 // When v0 matches were written, there was no MatchConfirmed, so we will 1737 // "upgrade" this match on the fly to agree with MatchIsActive. 1738 match.Status = order.MatchConfirmed 1739 } 1740 // A cancel match for a maker (trade) order has an empty address. 1741 if excludeCancels && match.Address == "" { 1742 return nil, nil 1743 } 1744 proofB := getCopy(mBkt, proofKey) 1745 if len(proofB) == 0 { 1746 return nil, fmt.Errorf("empty proof") 1747 } 1748 proof, _, err = dexdb.DecodeMatchProof(proofB) 1749 if err != nil { 1750 return nil, fmt.Errorf("error decoding proof: %w", err) 1751 } 1752 // A cancel match for a taker (the cancel) order is complete with no 1753 // InitSig. Unfortunately, the trade Address was historically set. 1754 if excludeCancels && (len(proof.Auth.InitSig) == 0 && match.Status == order.MatchComplete) { 1755 return nil, nil 1756 } 1757 return &dexdb.MetaMatch{ 1758 MetaData: &dexdb.MatchMetaData{ 1759 Proof: *proof, 1760 DEX: string(getCopy(mBkt, dexKey)), 1761 Base: intCoder.Uint32(mBkt.Get(baseKey)), 1762 Quote: intCoder.Uint32(mBkt.Get(quoteKey)), 1763 Stamp: intCoder.Uint64(mBkt.Get(stampKey)), 1764 }, 1765 UserMatch: match, 1766 }, nil 1767 } 1768 1769 // matchesView is a convenience function for reading from the match bucket. 1770 func (db *BoltDB) matchesView(f func(mb, archivedMB *bbolt.Bucket) error) error { 1771 return db.View(func(tx *bbolt.Tx) error { 1772 mb := tx.Bucket(activeMatchesBucket) 1773 if mb == nil { 1774 return fmt.Errorf("failed to open %s bucket", string(activeMatchesBucket)) 1775 } 1776 archivedMB := tx.Bucket(archivedMatchesBucket) 1777 if archivedMB == nil { 1778 return fmt.Errorf("failed to open %s bucket", string(archivedMatchesBucket)) 1779 } 1780 return f(mb, archivedMB) 1781 }) 1782 } 1783 1784 // matchesUpdate is a convenience function for updating the match bucket. 1785 func (db *BoltDB) matchesUpdate(f func(mb, archivedMB *bbolt.Bucket) error) error { 1786 return db.Update(func(tx *bbolt.Tx) error { 1787 mb := tx.Bucket(activeMatchesBucket) 1788 if mb == nil { 1789 return fmt.Errorf("failed to open %s bucket", string(activeMatchesBucket)) 1790 } 1791 archivedMB := tx.Bucket(archivedMatchesBucket) 1792 if archivedMB == nil { 1793 return fmt.Errorf("failed to open %s bucket", string(archivedMatchesBucket)) 1794 } 1795 return f(mb, archivedMB) 1796 }) 1797 } 1798 1799 // UpdateWallet adds a wallet to the database. 1800 func (db *BoltDB) UpdateWallet(wallet *dexdb.Wallet) error { 1801 if wallet.Balance == nil { 1802 return fmt.Errorf("cannot UpdateWallet with nil Balance field") 1803 } 1804 return db.walletsUpdate(func(master *bbolt.Bucket) error { 1805 wBkt, err := master.CreateBucketIfNotExists(wallet.ID()) 1806 if err != nil { 1807 return err 1808 } 1809 err = wBkt.Put(walletKey, wallet.Encode()) 1810 if err != nil { 1811 return err 1812 } 1813 return wBkt.Put(balanceKey, wallet.Balance.Encode()) 1814 }) 1815 } 1816 1817 // SetWalletPassword set the encrypted password field for the wallet. 1818 func (db *BoltDB) SetWalletPassword(wid []byte, newEncPW []byte) error { 1819 return db.walletsUpdate(func(master *bbolt.Bucket) error { 1820 wBkt := master.Bucket(wid) 1821 if wBkt == nil { 1822 return fmt.Errorf("wallet with ID is %x not known", wid) 1823 } 1824 b := getCopy(wBkt, walletKey) 1825 if b == nil { 1826 return fmt.Errorf("no wallet found in bucket") 1827 } 1828 wallet, err := dexdb.DecodeWallet(b) 1829 if err != nil { 1830 return err 1831 } 1832 wallet.EncryptedPW = make([]byte, len(newEncPW)) 1833 copy(wallet.EncryptedPW, newEncPW) 1834 // No need to populate wallet.Balance since it's not part of the 1835 // serialization stored in the walletKey sub-bucket. 1836 1837 return wBkt.Put(walletKey, wallet.Encode()) 1838 }) 1839 } 1840 1841 // UpdateBalance updates balance in the wallet bucket. 1842 func (db *BoltDB) UpdateBalance(wid []byte, bal *dexdb.Balance) error { 1843 return db.walletsUpdate(func(master *bbolt.Bucket) error { 1844 wBkt := master.Bucket(wid) 1845 if wBkt == nil { 1846 return fmt.Errorf("wallet %x bucket is not a bucket", wid) 1847 } 1848 return wBkt.Put(balanceKey, bal.Encode()) 1849 }) 1850 } 1851 1852 // UpdateWalletStatus updates a wallet's status. 1853 func (db *BoltDB) UpdateWalletStatus(wid []byte, disable bool) error { 1854 return db.walletsUpdate(func(master *bbolt.Bucket) error { 1855 wBkt := master.Bucket(wid) 1856 if wBkt == nil { 1857 return fmt.Errorf("wallet %x bucket is not a bucket", wid) 1858 } 1859 if disable { 1860 return wBkt.Put(walletDisabledKey, encode.ByteTrue) 1861 } 1862 return wBkt.Put(walletDisabledKey, encode.ByteFalse) 1863 }) 1864 } 1865 1866 // Wallets loads all wallets from the database. 1867 func (db *BoltDB) Wallets() ([]*dexdb.Wallet, error) { 1868 var wallets []*dexdb.Wallet 1869 return wallets, db.walletsView(func(master *bbolt.Bucket) error { 1870 c := master.Cursor() 1871 // key, _ := c.First() 1872 for wid, _ := c.First(); wid != nil; wid, _ = c.Next() { 1873 w, err := makeWallet(master.Bucket(wid)) 1874 if err != nil { 1875 return err 1876 } 1877 wallets = append(wallets, w) 1878 } 1879 return nil 1880 }) 1881 } 1882 1883 // Wallet loads a single wallet from the database. 1884 func (db *BoltDB) Wallet(wid []byte) (wallet *dexdb.Wallet, err error) { 1885 return wallet, db.walletsView(func(master *bbolt.Bucket) error { 1886 wallet, err = makeWallet(master.Bucket(wid)) 1887 return err 1888 }) 1889 } 1890 1891 func makeWallet(wBkt *bbolt.Bucket) (*dexdb.Wallet, error) { 1892 if wBkt == nil { 1893 return nil, fmt.Errorf("wallets bucket value not a nested bucket") 1894 } 1895 b := getCopy(wBkt, walletKey) 1896 if b == nil { 1897 return nil, fmt.Errorf("no wallet found in bucket") 1898 } 1899 1900 w, err := dexdb.DecodeWallet(b) 1901 if err != nil { 1902 return nil, fmt.Errorf("DecodeWallet error: %w", err) 1903 } 1904 1905 balB := getCopy(wBkt, balanceKey) 1906 if balB != nil { 1907 bal, err := dexdb.DecodeBalance(balB) 1908 if err != nil { 1909 return nil, fmt.Errorf("DecodeBalance error: %w", err) 1910 } 1911 w.Balance = bal 1912 } 1913 1914 statusB := wBkt.Get(walletDisabledKey) 1915 if statusB != nil { 1916 w.Disabled = bytes.Equal(statusB, encode.ByteTrue) 1917 } 1918 return w, nil 1919 } 1920 1921 // walletsView is a convenience function for reading from the wallets bucket. 1922 func (db *BoltDB) walletsView(f bucketFunc) error { 1923 return db.withBucket(walletsBucket, db.View, f) 1924 } 1925 1926 // walletUpdate is a convenience function for updating the wallets bucket. 1927 func (db *BoltDB) walletsUpdate(f bucketFunc) error { 1928 return db.withBucket(walletsBucket, db.Update, f) 1929 } 1930 1931 // SaveNotification saves the notification. 1932 func (db *BoltDB) SaveNotification(note *dexdb.Notification) error { 1933 if note.Severeness < dexdb.Success { 1934 return fmt.Errorf("storage of notification with severity %s is forbidden", note.Severeness) 1935 } 1936 return db.notesUpdate(func(master *bbolt.Bucket) error { 1937 noteB := note.Encode() 1938 k := note.ID() 1939 noteBkt, err := master.CreateBucketIfNotExists(k) 1940 if err != nil { 1941 return err 1942 } 1943 err = noteBkt.Put(stampKey, uint64Bytes(note.TimeStamp)) 1944 if err != nil { 1945 return err 1946 } 1947 err = noteBkt.Put(severityKey, []byte{byte(note.Severeness)}) 1948 if err != nil { 1949 return err 1950 } 1951 return noteBkt.Put(noteKey, noteB) 1952 }) 1953 } 1954 1955 // AckNotification sets the acknowledgement for a notification. 1956 func (db *BoltDB) AckNotification(id []byte) error { 1957 return db.notesUpdate(func(master *bbolt.Bucket) error { 1958 noteBkt := master.Bucket(id) 1959 if noteBkt == nil { 1960 return fmt.Errorf("notification not found") 1961 } 1962 return noteBkt.Put(ackKey, byteTrue) 1963 }) 1964 } 1965 1966 // NotificationsN reads out the N most recent notifications. 1967 func (db *BoltDB) NotificationsN(n int) ([]*dexdb.Notification, error) { 1968 notes := make([]*dexdb.Notification, 0, n) 1969 return notes, db.notesUpdate(func(master *bbolt.Bucket) error { 1970 trios := newestBuckets([]*bbolt.Bucket{master}, n, stampKey, nil) 1971 for _, trio := range trios { 1972 note, err := dexdb.DecodeNotification(getCopy(trio.b, noteKey)) 1973 if err != nil { 1974 return err 1975 } 1976 note.Ack = bEqual(trio.b.Get(ackKey), byteTrue) 1977 note.Id = note.ID() 1978 if !bytes.Equal(note.Id, trio.k) { 1979 // This notification was initially stored when the serialization 1980 // and thus note ID did not include the TopicID. Ignore it, and 1981 // flag it acknowledged so we don't have to hear about it again. 1982 if !note.Ack { 1983 db.log.Tracef("Ignoring stored note with bad key: %x != %x \"%s\"", 1984 []byte(note.Id), trio.k, note.String()) 1985 _ = trio.b.Put(ackKey, byteTrue) 1986 } 1987 continue 1988 } 1989 notes = append(notes, note) 1990 } 1991 return nil 1992 }) 1993 } 1994 1995 // notesView is a convenience function to read from the notifications bucket. 1996 func (db *BoltDB) notesView(f bucketFunc) error { 1997 return db.withBucket(notesBucket, db.View, f) 1998 } 1999 2000 // notesUpdate is a convenience function for updating the notifications bucket. 2001 func (db *BoltDB) notesUpdate(f bucketFunc) error { 2002 return db.withBucket(notesBucket, db.Update, f) 2003 } 2004 2005 // SavePokes saves a slice of notifications, overwriting any previously saved 2006 // slice. 2007 func (db *BoltDB) SavePokes(pokes []*dexdb.Notification) error { 2008 // Just save it as JSON. 2009 b, err := json.Marshal(pokes) 2010 if err != nil { 2011 return fmt.Errorf("JSON marshal error: %w", err) 2012 } 2013 return db.withBucket(pokesBucket, db.Update, func(bkt *bbolt.Bucket) error { 2014 return bkt.Put(pokesKey, b) 2015 }) 2016 } 2017 2018 // LoadPokes loads the slice of notifications last saved with SavePokes. The 2019 // loaded pokes are deleted from the database. 2020 func (db *BoltDB) LoadPokes() (pokes []*dexdb.Notification, _ error) { 2021 return pokes, db.withBucket(pokesBucket, db.Update, func(bkt *bbolt.Bucket) error { 2022 b := bkt.Get(pokesKey) 2023 if len(b) == 0 { // None saved 2024 return nil 2025 } 2026 if err := json.Unmarshal(b, &pokes); err != nil { 2027 return err 2028 } 2029 return bkt.Delete(pokesKey) 2030 }) 2031 } 2032 2033 // newest buckets gets the nested buckets with the hightest timestamp from the 2034 // specified master buckets. The nested bucket should have an encoded uint64 at 2035 // the timeKey. An optional filter function can be used to reject buckets. 2036 func newestBuckets(buckets []*bbolt.Bucket, n int, timeKey []byte, filter func([]byte, *bbolt.Bucket) bool) []*keyTimeTrio { 2037 idx := newTimeIndexNewest(n) 2038 for _, master := range buckets { 2039 master.ForEach(func(k, _ []byte) error { 2040 bkt := master.Bucket(k) 2041 stamp := intCoder.Uint64(bkt.Get(timeKey)) 2042 if filter == nil || filter(k, bkt) { 2043 idx.add(stamp, k, bkt) 2044 } 2045 return nil 2046 }) 2047 } 2048 return idx.trios 2049 } 2050 2051 // makeTopLevelBuckets creates a top-level bucket for each of the provided keys, 2052 // if the bucket doesn't already exist. 2053 func (db *BoltDB) makeTopLevelBuckets(buckets [][]byte) error { 2054 return db.Update(func(tx *bbolt.Tx) error { 2055 for _, bucket := range buckets { 2056 _, err := tx.CreateBucketIfNotExists(bucket) 2057 if err != nil { 2058 return err 2059 } 2060 } 2061 2062 return nil 2063 }) 2064 } 2065 2066 // withBucket is a creates a view into a (probably nested) bucket. The viewer 2067 // can be read-only (db.View), or read-write (db.Update). The provided 2068 // bucketFunc will be called with the requested bucket as its only argument. 2069 func (db *BoltDB) withBucket(bkt []byte, viewer txFunc, f bucketFunc) error { 2070 return viewer(func(tx *bbolt.Tx) error { 2071 bucket := tx.Bucket(bkt) 2072 if bucket == nil { 2073 return fmt.Errorf("failed to open %s bucket", string(bkt)) 2074 } 2075 return f(bucket) 2076 }) 2077 } 2078 2079 func ovrFlag(overwrite bool) int { 2080 if overwrite { 2081 return os.O_TRUNC 2082 } 2083 return os.O_EXCL 2084 } 2085 2086 // compact writes a compacted copy of the DB into the specified destination 2087 // file. This function should be called from BackupTo to validate the 2088 // destination file and create any needed parent folders. 2089 func (db *BoltDB) compact(dst string, overwrite bool) error { 2090 opts := bbolt.Options{ 2091 OpenFile: func(path string, _ int, mode os.FileMode) (*os.File, error) { 2092 return os.OpenFile(path, os.O_RDWR|os.O_CREATE|ovrFlag(overwrite), mode) 2093 }, 2094 } 2095 newDB, err := bbolt.Open(dst, 0600, &opts) 2096 if err != nil { 2097 return fmt.Errorf("unable to compact database: %w", err) 2098 } 2099 2100 const txMaxSize = 1 << 19 // 512 KiB 2101 err = bbolt.Compact(newDB, db.DB, txMaxSize) 2102 if err != nil { 2103 _ = newDB.Close() 2104 return fmt.Errorf("unable to compact database: %w", err) 2105 } 2106 return newDB.Close() 2107 } 2108 2109 // backup writes a direct copy of the DB into the specified destination file. 2110 // This function should be called from BackupTo to validate the destination file 2111 // and create any needed parent folders. 2112 func (db *BoltDB) backup(dst string, overwrite bool) error { 2113 // Just copy. This is like tx.CopyFile but with the overwrite flag set 2114 // as specified. 2115 f, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|ovrFlag(overwrite), 0600) 2116 if err != nil { 2117 return err 2118 } 2119 defer f.Close() 2120 2121 err = db.View(func(tx *bbolt.Tx) error { 2122 _, err = tx.WriteTo(f) 2123 return err 2124 }) 2125 if err != nil { 2126 return err 2127 } 2128 return f.Sync() 2129 } 2130 2131 // BackupTo makes a copy of the database to the specified file, optionally 2132 // overwriting and compacting the DB. 2133 func (db *BoltDB) BackupTo(dst string, overwrite, compact bool) error { 2134 // If relative path, use current db path. 2135 var dir string 2136 if !filepath.IsAbs(dst) { 2137 dir = filepath.Dir(db.Path()) 2138 dst = filepath.Join(dir, dst) 2139 } 2140 dst = filepath.Clean(dst) 2141 dir = filepath.Dir(dst) 2142 if dst == filepath.Clean(db.Path()) { 2143 return errors.New("destination is the active DB") 2144 } 2145 2146 // Make the parent folder if it does not exists. 2147 if _, err := os.Stat(dir); errors.Is(err, fs.ErrNotExist) { 2148 err = os.MkdirAll(dir, 0700) 2149 if err != nil { 2150 return fmt.Errorf("unable to create backup directory: %w", err) 2151 } 2152 } 2153 2154 if compact { 2155 return db.compact(dst, overwrite) 2156 } 2157 2158 return db.backup(dst, overwrite) 2159 } 2160 2161 // Backup makes a copy of the database in the "backup" folder, overwriting any 2162 // existing backup. 2163 func (db *BoltDB) Backup() error { 2164 dir, file := filepath.Split(db.Path()) 2165 return db.BackupTo(filepath.Join(dir, backupDir, file), true, false) 2166 } 2167 2168 // bucketPutter enables chained calls to (*bbolt.Bucket).Put with error 2169 // deferment. 2170 type bucketPutter struct { 2171 bucket *bbolt.Bucket 2172 putErr error 2173 } 2174 2175 // newBucketPutter is a constructor for a bucketPutter. 2176 func newBucketPutter(bkt *bbolt.Bucket) *bucketPutter { 2177 return &bucketPutter{bucket: bkt} 2178 } 2179 2180 // put calls Put on the underlying bucket. If an error has been encountered in a 2181 // previous call to push, nothing is done. put enables the *bucketPutter to 2182 // enable chaining. 2183 func (bp *bucketPutter) put(k, v []byte) *bucketPutter { 2184 if bp.putErr != nil { 2185 return bp 2186 } 2187 bp.putErr = bp.bucket.Put(k, v) 2188 return bp 2189 } 2190 2191 // Return any push error encountered. 2192 func (bp *bucketPutter) err() error { 2193 return bp.putErr 2194 } 2195 2196 // keyTimeTrio is used to build an on-the-fly time-sorted index. 2197 type keyTimeTrio struct { 2198 k []byte 2199 t uint64 2200 b *bbolt.Bucket 2201 } 2202 2203 // timeIndexNewest is a struct used to build an index of sorted keyTimeTrios. 2204 // The index can have a maximum capacity. If the capacity is set to zero, the 2205 // index size is unlimited. 2206 type timeIndexNewest struct { 2207 trios []*keyTimeTrio 2208 cap int 2209 } 2210 2211 // Create a new *timeIndexNewest, with the specified capacity. 2212 func newTimeIndexNewest(n int) *timeIndexNewest { 2213 return &timeIndexNewest{ 2214 trios: make([]*keyTimeTrio, 0, n), 2215 cap: n, 2216 } 2217 } 2218 2219 // Conditionally add a time-key trio to the index. The trio will only be added 2220 // if the timeIndexNewest is under capacity and the time t is larger than the 2221 // oldest trio's time. 2222 func (idx *timeIndexNewest) add(t uint64, k []byte, b *bbolt.Bucket) { 2223 count := len(idx.trios) 2224 if idx.cap == 0 || count < idx.cap { 2225 idx.trios = append(idx.trios, &keyTimeTrio{ 2226 // Need to make a copy, and []byte(k) upsets the linter. 2227 k: append([]byte(nil), k...), 2228 t: t, 2229 b: b, 2230 }) 2231 } else { 2232 // non-zero length, at capacity. 2233 if t <= idx.trios[count-1].t { 2234 // Too old. Discard. 2235 return 2236 } 2237 idx.trios[count-1] = &keyTimeTrio{ 2238 k: append([]byte(nil), k...), 2239 t: t, 2240 b: b, 2241 } 2242 } 2243 sort.Slice(idx.trios, func(i, j int) bool { 2244 // newest (highest t) first, or by lexicographically by key if the times 2245 // are equal. 2246 t1, t2 := idx.trios[i].t, idx.trios[j].t 2247 return t1 > t2 || (t1 == t2 && bytes.Compare(idx.trios[i].k, idx.trios[j].k) == 1) 2248 }) 2249 } 2250 2251 // DeleteInactiveOrders deletes orders that are no longer needed for normal 2252 // operations. Optionally accepts a time to delete orders with a later time 2253 // stamp. Accepts an optional function to perform on deleted orders. 2254 func (db *BoltDB) DeleteInactiveOrders(ctx context.Context, olderThan *time.Time, 2255 perOrderFn func(ords *dexdb.MetaOrder) error) (int, error) { 2256 const batchSize = 1000 2257 var olderThanB []byte 2258 if olderThan != nil && !olderThan.IsZero() { 2259 olderThanB = uint64Bytes(uint64(olderThan.UnixMilli())) 2260 } else { 2261 olderThanB = uint64Bytes(timeNow()) 2262 } 2263 2264 activeMatchOrders := make(map[order.OrderID]struct{}) 2265 if err := db.View(func(tx *bbolt.Tx) error { 2266 // Some archived orders may still be needed for active matches. 2267 // Put those order id's in a map to prevent deletion. 2268 amb := tx.Bucket(activeMatchesBucket) 2269 if err := amb.ForEach(func(k, _ []byte) error { 2270 mBkt := amb.Bucket(k) 2271 if mBkt == nil { 2272 return fmt.Errorf("match %x bucket is not a bucket", k) 2273 } 2274 oidB := mBkt.Get(orderIDKey) 2275 var oid order.OrderID 2276 copy(oid[:], oidB) 2277 activeMatchOrders[oid] = struct{}{} 2278 return nil 2279 }); err != nil { 2280 return fmt.Errorf("unable to get active matches: %v", err) 2281 } 2282 return nil 2283 }); err != nil { 2284 return 0, err 2285 } 2286 2287 // Get the keys of every archived order. 2288 var keys [][]byte 2289 if err := db.View(func(tx *bbolt.Tx) error { 2290 archivedOB := tx.Bucket(archivedOrdersBucket) 2291 if archivedOB == nil { 2292 return fmt.Errorf("failed to open %s bucket", string(archivedOrdersBucket)) 2293 } 2294 archivedOB.ForEach(func(k, _ []byte) error { 2295 keys = append(keys, bytes.Clone(k)) 2296 return nil 2297 }) 2298 return nil 2299 }); err != nil { 2300 return 0, fmt.Errorf("unable to get archived order keys: %v", err) 2301 } 2302 2303 nDeletedOrders := 0 2304 start := time.Now() 2305 // Check orders in batches to prevent any single db transaction from 2306 // becoming too large. 2307 for i := 0; i < len(keys); i += batchSize { 2308 if err := ctx.Err(); err != nil { 2309 return 0, err 2310 } 2311 2312 // Check if this is the last batch. 2313 end := i + batchSize 2314 if end > len(keys) { 2315 end = len(keys) 2316 } 2317 2318 nDeletedBatch := 0 2319 err := db.Update(func(tx *bbolt.Tx) error { 2320 archivedOB := tx.Bucket(archivedOrdersBucket) 2321 if archivedOB == nil { 2322 return fmt.Errorf("failed to open %s bucket", string(archivedOrdersBucket)) 2323 } 2324 for j := i; j < end; j++ { 2325 key := keys[j] 2326 oBkt := archivedOB.Bucket(key) 2327 2328 var oid order.OrderID 2329 copy(oid[:], key) 2330 2331 // Don't delete this order if it is still active. 2332 if order.OrderStatus(intCoder.Uint16(oBkt.Get(statusKey))).IsActive() { 2333 db.log.Warnf("active order %v found in inactive bucket", oid) 2334 continue 2335 } 2336 2337 // Don't delete this order if it still has active matches. 2338 if _, has := activeMatchOrders[oid]; has { 2339 continue 2340 } 2341 2342 // Don't delete this order if it is too new. 2343 timeB := oBkt.Get(updateTimeKey) 2344 if bytes.Compare(timeB, olderThanB) > 0 { 2345 continue 2346 } 2347 2348 // Proceed with deletion. 2349 o, err := decodeOrderBucket(key, oBkt) 2350 if err != nil { 2351 return fmt.Errorf("failed to decode order bucket: %v", err) 2352 } 2353 if err := archivedOB.DeleteBucket(key); err != nil { 2354 return fmt.Errorf("failed to delete order bucket: %v", err) 2355 } 2356 if perOrderFn != nil { 2357 if err := perOrderFn(o); err != nil { 2358 return fmt.Errorf("problem performing batch function: %v", err) 2359 } 2360 } 2361 nDeletedBatch++ 2362 } 2363 nDeletedOrders += nDeletedBatch 2364 return nil 2365 }) 2366 if err != nil { 2367 if perOrderFn != nil && nDeletedBatch != 0 { 2368 db.log.Warnf("%d orders reported as deleted have been rolled back due to error.", nDeletedBatch) 2369 } 2370 return 0, fmt.Errorf("unable to delete orders: %v", err) 2371 } 2372 } 2373 2374 db.log.Infof("Deleted %d archived orders from the database in %v", 2375 nDeletedOrders, time.Since(start)) 2376 2377 return nDeletedOrders, nil 2378 } 2379 2380 // orderSide Returns wether the order was for buying or selling the asset. 2381 func orderSide(tx *bbolt.Tx, oid order.OrderID) (sell bool, err error) { 2382 oidB := oid[:] 2383 ob := tx.Bucket(activeOrdersBucket) 2384 if ob == nil { 2385 return false, fmt.Errorf("failed to open %s bucket", string(activeOrdersBucket)) 2386 } 2387 oBkt := ob.Bucket(oidB) 2388 // If the order is not in the active bucket, check the archived bucket. 2389 if oBkt == nil { 2390 archivedOB := tx.Bucket(archivedOrdersBucket) 2391 if archivedOB == nil { 2392 return false, fmt.Errorf("failed to open %s bucket", string(archivedOrdersBucket)) 2393 } 2394 oBkt = archivedOB.Bucket(oidB) 2395 } 2396 if oBkt == nil { 2397 return false, fmt.Errorf("order %s not found", oid) 2398 } 2399 orderB := getCopy(oBkt, orderKey) 2400 if orderB == nil { 2401 return false, fmt.Errorf("nil order bytes for order %x", oid) 2402 } 2403 ord, err := order.DecodeOrder(orderB) 2404 if err != nil { 2405 return false, fmt.Errorf("error decoding order %x: %w", oid, err) 2406 } 2407 // Cancel orders have no side. 2408 if ord.Type() == order.CancelOrderType { 2409 return false, nil 2410 } 2411 sell = ord.Trade().Sell 2412 return 2413 } 2414 2415 // DeleteInactiveMatches deletes matches that are no longer needed for normal 2416 // operations. Optionally accepts a time to delete matches with a later time 2417 // stamp. Accepts an optional function to perform on deleted matches. 2418 func (db *BoltDB) DeleteInactiveMatches(ctx context.Context, olderThan *time.Time, 2419 perMatchFn func(mtch *dexdb.MetaMatch, isSell bool) error) (int, error) { 2420 const batchSize = 1000 2421 var olderThanB []byte 2422 if olderThan != nil && !olderThan.IsZero() { 2423 olderThanB = uint64Bytes(uint64(olderThan.UnixMilli())) 2424 } else { 2425 olderThanB = uint64Bytes(timeNow()) 2426 } 2427 2428 activeOrders := make(map[order.OrderID]struct{}) 2429 if err := db.View(func(tx *bbolt.Tx) error { 2430 // Some archived matches still have active orders. Put those 2431 // order id's in a map to prevent deletion just in case they 2432 // are needed again. 2433 aob := tx.Bucket(activeOrdersBucket) 2434 if err := aob.ForEach(func(k, _ []byte) error { 2435 var oid order.OrderID 2436 copy(oid[:], k) 2437 activeOrders[oid] = struct{}{} 2438 return nil 2439 }); err != nil { 2440 return fmt.Errorf("unable to get active orders: %v", err) 2441 } 2442 return nil 2443 }); err != nil { 2444 return 0, err 2445 } 2446 2447 // Get the keys of every archived match. 2448 var keys [][]byte 2449 if err := db.View(func(tx *bbolt.Tx) error { 2450 archivedMB := tx.Bucket(archivedMatchesBucket) 2451 if archivedMB == nil { 2452 return fmt.Errorf("failed to open %s bucket", string(archivedMatchesBucket)) 2453 } 2454 archivedMB.ForEach(func(k, _ []byte) error { 2455 keys = append(keys, bytes.Clone(k)) 2456 return nil 2457 }) 2458 return nil 2459 }); err != nil { 2460 return 0, fmt.Errorf("unable to get archived match keys: %v", err) 2461 } 2462 2463 nDeletedMatches := 0 2464 start := time.Now() 2465 // Check matches in batches to prevent any single db transaction from 2466 // becoming too large. 2467 for i := 0; i < len(keys); i += batchSize { 2468 if err := ctx.Err(); err != nil { 2469 return 0, err 2470 } 2471 2472 // Check if this is the last batch. 2473 end := i + batchSize 2474 if end > len(keys) { 2475 end = len(keys) 2476 } 2477 2478 nDeletedBatch := 0 2479 if err := db.Update(func(tx *bbolt.Tx) error { 2480 archivedMB := tx.Bucket(archivedMatchesBucket) 2481 if archivedMB == nil { 2482 return fmt.Errorf("failed to open %s bucket", string(archivedMatchesBucket)) 2483 } 2484 for j := i; j < end; j++ { 2485 key := keys[j] 2486 mBkt := archivedMB.Bucket(key) 2487 2488 oidB := mBkt.Get(orderIDKey) 2489 var oid order.OrderID 2490 copy(oid[:], oidB) 2491 2492 // Don't delete this match if it still has active orders. 2493 if _, has := activeOrders[oid]; has { 2494 continue 2495 } 2496 2497 // Don't delete this match if it is too new. 2498 timeB := mBkt.Get(stampKey) 2499 if bytes.Compare(timeB, olderThanB) > 0 { 2500 continue 2501 } 2502 2503 // Proceed with deletion. 2504 m, err := loadMatchBucket(mBkt, false) 2505 if err != nil { 2506 return fmt.Errorf("failed to load match bucket: %v", err) 2507 } 2508 if err := archivedMB.DeleteBucket(key); err != nil { 2509 return fmt.Errorf("failed to delete match bucket: %v", err) 2510 } 2511 if perMatchFn != nil { 2512 isSell, err := orderSide(tx, m.OrderID) 2513 if err != nil { 2514 return fmt.Errorf("problem getting order side for order %v: %v", m.OrderID, err) 2515 } 2516 if err := perMatchFn(m, isSell); err != nil { 2517 return fmt.Errorf("problem performing batch function: %v", err) 2518 } 2519 } 2520 nDeletedBatch++ 2521 } 2522 nDeletedMatches += nDeletedBatch 2523 2524 return nil 2525 }); err != nil { 2526 if perMatchFn != nil && nDeletedBatch != 0 { 2527 db.log.Warnf("%d matches reported as deleted have been rolled back due to error.", nDeletedBatch) 2528 } 2529 return 0, fmt.Errorf("unable to delete matches: %v", err) 2530 } 2531 } 2532 2533 db.log.Infof("Deleted %d archived matches from the database in %v", 2534 nDeletedMatches, time.Since(start)) 2535 2536 return nDeletedMatches, nil 2537 } 2538 2539 // SaveDisabledRateSources updates disabled fiat rate sources. 2540 func (db *BoltDB) SaveDisabledRateSources(disabledSources []string) error { 2541 return db.Update(func(tx *bbolt.Tx) error { 2542 bkt := tx.Bucket(appBucket) 2543 if bkt == nil { 2544 return fmt.Errorf("failed to open %s bucket", string(appBucket)) 2545 } 2546 return bkt.Put(disabledRateSourceKey, []byte(strings.Join(disabledSources, ","))) 2547 }) 2548 } 2549 2550 // DisabledRateSources retrieves a map of disabled fiat rate sources. 2551 func (db *BoltDB) DisabledRateSources() (disabledSources []string, err error) { 2552 return disabledSources, db.View(func(tx *bbolt.Tx) error { 2553 bkt := tx.Bucket(appBucket) 2554 if bkt == nil { 2555 return fmt.Errorf("no %s bucket", string(appBucket)) 2556 } 2557 2558 disabledString := string(bkt.Get(disabledRateSourceKey)) 2559 if disabledString == "" { 2560 return nil 2561 } 2562 2563 disabled := strings.Split(disabledString, ",") 2564 disabledSources = make([]string, 0, len(disabled)) 2565 for _, token := range disabled { 2566 if token != "" { 2567 disabledSources = append(disabledSources, token) 2568 } 2569 } 2570 return nil 2571 }) 2572 } 2573 2574 // SetLanguage stores the language. 2575 func (db *BoltDB) SetLanguage(lang string) error { 2576 return db.Update(func(dbTx *bbolt.Tx) error { 2577 bkt := dbTx.Bucket(appBucket) 2578 if bkt == nil { 2579 return fmt.Errorf("app bucket not found") 2580 } 2581 return bkt.Put(langKey, []byte(lang)) 2582 }) 2583 } 2584 2585 // Language retrieves the language stored with SetLanguage. If no language 2586 // has been stored, an empty string is returned without an error. 2587 func (db *BoltDB) Language() (lang string, _ error) { 2588 return lang, db.View(func(dbTx *bbolt.Tx) error { 2589 bkt := dbTx.Bucket(appBucket) 2590 if bkt != nil { 2591 lang = string(bkt.Get(langKey)) 2592 } 2593 return nil 2594 }) 2595 } 2596 2597 // timeNow is the current unix timestamp in milliseconds. 2598 func timeNow() uint64 { 2599 return uint64(time.Now().UnixMilli()) 2600 } 2601 2602 // A couple of common bbolt functions. 2603 type bucketFunc func(*bbolt.Bucket) error 2604 type txFunc func(func(*bbolt.Tx) error) error