decred.org/dcrdex@v1.0.5/server/db/driver/pg/accounts.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 pg 5 6 import ( 7 "database/sql" 8 "errors" 9 "fmt" 10 "time" 11 12 "decred.org/dcrdex/server/account" 13 "decred.org/dcrdex/server/db" 14 "decred.org/dcrdex/server/db/driver/pg/internal" 15 "github.com/decred/dcrd/dcrutil/v4" // TODO: consider a move to "crypto/sha256" instead of dcrutil.Hash160 16 ) 17 18 // Account retrieves the account pubkey, active bonds, and if the account has a 19 // legacy registration fee address and transaction recorded. If the account does 20 // not exist or there is in an error retrieving any data, a nil *account.Account 21 // is returned. 22 func (a *Archiver) Account(aid account.AccountID, bondExpiry time.Time) (acct *account.Account, bonds []*db.Bond) { 23 acct, err := getAccount(a.db, a.tables.accounts, aid) 24 switch { 25 case errors.Is(err, sql.ErrNoRows): 26 return nil, nil 27 case err == nil: 28 default: 29 log.Errorf("getAccount error: %v", err) 30 return nil, nil 31 } 32 33 bonds, err = getBondsForAccount(a.db, a.tables.bonds, aid, bondExpiry.Unix()) 34 switch { 35 case errors.Is(err, sql.ErrNoRows): 36 bonds = nil 37 case err == nil: 38 default: 39 log.Errorf("getBondsForAccount error: %v", err) 40 return nil, nil 41 } 42 43 return acct, bonds 44 } 45 46 // AccountInfo returns data for an account. 47 func (a *Archiver) AccountInfo(aid account.AccountID) (*db.Account, error) { 48 // bondExpiry time.Time and bonds return needed? 49 stmt := fmt.Sprintf(internal.SelectAccountInfo, a.tables.accounts) 50 acct := new(db.Account) 51 if err := a.db.QueryRow(stmt, aid).Scan(&acct.AccountID, &acct.Pubkey); err != nil { 52 if errors.Is(err, sql.ErrNoRows) { 53 err = db.ArchiveError{Code: db.ErrAccountUnknown} 54 } 55 return nil, err 56 } 57 return acct, nil 58 } 59 60 // CreateAccountWithBond creates a new account with a fidelity bond. 61 func (a *Archiver) CreateAccountWithBond(acct *account.Account, bond *db.Bond) error { 62 dbTx, err := a.db.BeginTx(a.ctx, nil) 63 if err != nil { 64 return err 65 } 66 defer func() { 67 if err == nil || errors.Is(err, sql.ErrTxDone) { 68 return 69 } 70 if errR := dbTx.Rollback(); errR != nil { 71 log.Errorf("Rollback failed: %v", errR) 72 } 73 }() 74 75 err = createAccountForBond(dbTx, a.tables.accounts, acct) 76 if err != nil { 77 return err 78 } 79 err = addBond(dbTx, a.tables.bonds, acct.ID, bond) 80 if err != nil { 81 return err 82 } 83 84 err = dbTx.Commit() // for the defer 85 return err 86 } 87 88 // AddBond stores a new Bond for an existing account. 89 func (a *Archiver) AddBond(aid account.AccountID, bond *db.Bond) error { 90 return addBond(a.db, a.tables.bonds, aid, bond) 91 } 92 93 func (a *Archiver) DeleteBond(assetID uint32, coinID []byte) error { 94 return deleteBond(a.db, a.tables.bonds, assetID, coinID) 95 } 96 97 func (a *Archiver) FetchPrepaidBond(coinID []byte) (strength uint32, lockTime int64, err error) { 98 stmt := fmt.Sprintf(internal.SelectPrepaidBond, prepaidBondsTableName) 99 err = a.db.QueryRow(stmt, coinID).Scan(&strength, &lockTime) 100 return 101 } 102 103 func (a *Archiver) DeletePrepaidBond(coinID []byte) (err error) { 104 stmt := fmt.Sprintf(internal.DeletePrepaidBond, prepaidBondsTableName) 105 _, err = a.db.ExecContext(a.ctx, stmt, coinID) 106 return 107 } 108 109 func (a *Archiver) StorePrepaidBonds(coinIDs [][]byte, strength uint32, lockTime int64) error { 110 stmt := fmt.Sprintf(internal.InsertPrepaidBond, prepaidBondsTableName) 111 for i := range coinIDs { 112 if _, err := a.db.ExecContext(a.ctx, stmt, coinIDs[i], strength, lockTime); err != nil { 113 return err 114 } 115 } 116 return nil 117 } 118 119 // KeyIndex returns the current child index for the an xpub. If it is not 120 // known, this creates a new entry with index zero. 121 func (a *Archiver) KeyIndex(xpub string) (uint32, error) { 122 keyHash := dcrutil.Hash160([]byte(xpub)) 123 124 var child uint32 125 stmt := fmt.Sprintf(internal.CurrentKeyIndex, feeKeysTableName) 126 err := a.db.QueryRow(stmt, keyHash).Scan(&child) 127 switch { 128 case errors.Is(err, sql.ErrNoRows): // continue to create new entry 129 case err == nil: 130 return child, nil 131 default: 132 return 0, err 133 } 134 135 log.Debugf("Inserting key entry for xpub %.40s..., hash160 = %x", xpub, keyHash) 136 stmt = fmt.Sprintf(internal.InsertKeyIfMissing, feeKeysTableName) 137 err = a.db.QueryRow(stmt, keyHash).Scan(&child) 138 if err != nil { 139 return 0, err 140 } 141 return child, nil 142 } 143 144 // SetKeyIndex records the child index for an xpub. An error is returned 145 // unless exactly 1 row is updated or created. 146 func (a *Archiver) SetKeyIndex(idx uint32, xpub string) error { 147 keyHash := dcrutil.Hash160([]byte(xpub)) 148 log.Debugf("Recording new index %d for xpub %.40s... (%x)", idx, xpub, keyHash) 149 stmt := fmt.Sprintf(internal.UpsertKeyIndex, feeKeysTableName) 150 res, err := a.db.Exec(stmt, idx, keyHash) 151 if err != nil { 152 return err 153 } 154 N, err := res.RowsAffected() 155 if err != nil { 156 return err 157 } 158 if N != 1 { 159 return fmt.Errorf("updated %d rows, expected 1", N) 160 } 161 return nil 162 } 163 164 // createAccountTables creates the accounts and fee_keys tables. 165 func createAccountTables(db sqlQueryExecutor) error { 166 for _, c := range createAccountTableStatements { 167 created, err := createTable(db, publicSchema, c.name) 168 if err != nil { 169 return err 170 } 171 if created { 172 log.Tracef("Table %s created", c.name) 173 } 174 } 175 176 for _, c := range createBondIndexesStatements { 177 err := createIndexStmt(db, c.stmt, c.idxName, bondsTableName) 178 if err != nil { 179 return err 180 } 181 } 182 183 return nil 184 } 185 186 // getAccount gets retrieves the account details, including the pubkey, a flag 187 // indicating if the account was created with a legacy fee address (not a 188 // fidelity bond), and a flag indicating if that legacy fee was paid. 189 func getAccount(dbe sqlQueryer, tableName string, aid account.AccountID) (acct *account.Account, err error) { 190 var pubkey []byte 191 stmt := fmt.Sprintf(internal.SelectAccount, tableName) 192 err = dbe.QueryRow(stmt, aid).Scan(&pubkey) 193 if err != nil { 194 return 195 } 196 acct, err = account.NewAccountFromPubKey(pubkey) 197 if err != nil { 198 return 199 } 200 return 201 } 202 203 // createAccountForBond creates an entry for the account in the accounts table. 204 func createAccountForBond(dbe sqlExecutor, tableName string, acct *account.Account) error { 205 stmt := fmt.Sprintf(internal.CreateAccountForBond, tableName) 206 _, err := dbe.Exec(stmt, acct.ID, acct.PubKey.SerializeCompressed()) 207 return err 208 } 209 210 func addBond(dbe sqlExecutor, tableName string, aid account.AccountID, bond *db.Bond) error { 211 stmt := fmt.Sprintf(internal.AddBond, tableName) 212 _, err := dbe.Exec(stmt, bond.Version, bond.CoinID, bond.AssetID, aid, 213 bond.Amount, bond.Strength, bond.LockTime) 214 return err 215 } 216 217 func deleteBond(dbe sqlExecutor, tableName string, assetID uint32, coinID []byte) error { 218 stmt := fmt.Sprintf(internal.DeleteBond, tableName) 219 _, err := dbe.Exec(stmt, coinID, assetID) 220 return err 221 } 222 223 func getBondsForAccount(dbe sqlQueryer, tableName string, acct account.AccountID, bondExpiryTime int64) ([]*db.Bond, error) { 224 stmt := fmt.Sprintf(internal.SelectActiveBondsForUser, tableName) 225 rows, err := dbe.Query(stmt, acct, bondExpiryTime) 226 if err != nil { 227 return nil, err 228 } 229 defer rows.Close() 230 231 var bonds []*db.Bond 232 for rows.Next() { 233 var bond db.Bond 234 err = rows.Scan(&bond.Version, &bond.CoinID, &bond.AssetID, 235 &bond.Amount, &bond.Strength, &bond.LockTime) 236 if err != nil { 237 return nil, err 238 } 239 bonds = append(bonds, &bond) 240 } 241 if err = rows.Err(); err != nil { 242 return nil, err 243 } 244 return bonds, nil 245 }