github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/wallet/database.go (about) 1 package wallet 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "reflect" 7 "time" 8 9 "SiaPrime/encoding" 10 "SiaPrime/modules" 11 "SiaPrime/types" 12 "gitlab.com/NebulousLabs/errors" 13 "gitlab.com/NebulousLabs/fastrand" 14 15 "gitlab.com/NebulousLabs/bolt" 16 ) 17 18 var ( 19 // bucketProcessedTransactions stores ProcessedTransactions in 20 // chronological order. Only transactions relevant to the wallet are 21 // stored. The key of this bucket is an autoincrementing integer. 22 bucketProcessedTransactions = []byte("bucketProcessedTransactions") 23 // bucketProcessedTxnIndex maps a ProcessedTransactions ID to it's 24 // autoincremented index in bucketProcessedTransactions 25 bucketProcessedTxnIndex = []byte("bucketProcessedTxnKey") 26 // bucketAddrTransactions maps an UnlockHash to the 27 // ProcessedTransactions that it appears in. 28 bucketAddrTransactions = []byte("bucketAddrTransactions") 29 // bucketSiacoinOutputs maps a SiacoinOutputID to its SiacoinOutput. Only 30 // outputs that the wallet controls are stored. The wallet uses these 31 // outputs to fund transactions. 32 bucketSiacoinOutputs = []byte("bucketSiacoinOutputs") 33 // bucketSiacoinOutputs maps a SiafundOutputID to its SiafundOutput. Only 34 // outputs that the wallet controls are stored. The wallet uses these 35 // outputs to fund transactions. 36 bucketSiafundOutputs = []byte("bucketSiafundOutputs") 37 // bucketSpentOutputs maps an OutputID to the height at which it was 38 // spent. Only outputs spent by the wallet are stored. The wallet tracks 39 // these outputs so that it can reuse them if they are not confirmed on 40 // the blockchain. 41 bucketSpentOutputs = []byte("bucketSpentOutputs") 42 // bucketUnlockConditions maps an UnlockHash to its UnlockConditions. It 43 // is used to track UnlockConditions manually stored by the user, 44 // typically with an offline wallet. 45 bucketUnlockConditions = []byte("bucketUnlockConditions") 46 // bucketWallet contains various fields needed by the wallet, such as its 47 // UID, EncryptionVerification, and PrimarySeedFile. 48 bucketWallet = []byte("bucketWallet") 49 50 dbBuckets = [][]byte{ 51 bucketProcessedTransactions, 52 bucketProcessedTxnIndex, 53 bucketAddrTransactions, 54 bucketSiacoinOutputs, 55 bucketSiafundOutputs, 56 bucketSpentOutputs, 57 bucketUnlockConditions, 58 bucketWallet, 59 } 60 61 errNoKey = errors.New("key does not exist") 62 63 // these keys are used in bucketWallet 64 keyAuxiliarySeedFiles = []byte("keyAuxiliarySeedFiles") 65 keyConsensusChange = []byte("keyConsensusChange") 66 keyConsensusHeight = []byte("keyConsensusHeight") 67 keyEncryptionVerification = []byte("keyEncryptionVerification") 68 keyPrimarySeedFile = []byte("keyPrimarySeedFile") 69 keyPrimarySeedProgress = []byte("keyPrimarySeedProgress") 70 keySiafundPool = []byte("keySiafundPool") 71 keySpendableKeyFiles = []byte("keySpendableKeyFiles") 72 keyUID = []byte("keyUID") 73 keyWatchedAddrs = []byte("keyWatchedAddrs") 74 ) 75 76 // threadedDBUpdate commits the active database transaction and starts a new 77 // transaction. 78 func (w *Wallet) threadedDBUpdate() { 79 if err := w.tg.Add(); err != nil { 80 return 81 } 82 defer w.tg.Done() 83 84 for { 85 select { 86 case <-time.After(2 * time.Minute): 87 case <-w.tg.StopChan(): 88 return 89 } 90 w.mu.Lock() 91 err := w.syncDB() 92 w.mu.Unlock() 93 if err != nil { 94 // If the database is having problems, we need to close it to 95 // protect it. This will likely cause a panic somewhere when another 96 // caller tries to access dbTx but it is nil. 97 w.log.Severe("ERROR: syncDB encountered an error. Closing database to protect wallet. wallet may crash:", err) 98 w.db.Close() 99 return 100 } 101 } 102 } 103 104 // syncDB commits the current global transaction and immediately begins a 105 // new one. It must be called with a write-lock. 106 func (w *Wallet) syncDB() error { 107 // If the rollback flag is set, it means that somewhere in the middle of an 108 // atomic update there was a failure, and that failure needs to be rolled 109 // back. An error will be returned. 110 if w.dbRollback { 111 err := errors.New("database unable to sync - rollback requested") 112 return errors.Compose(err, w.dbTx.Rollback()) 113 } 114 115 // commit the current tx 116 err := w.dbTx.Commit() 117 if err != nil { 118 w.log.Severe("ERROR: failed to apply database update:", err) 119 err = errors.Compose(err, w.dbTx.Rollback()) 120 return errors.AddContext(err, "unable to commit dbTx in syncDB") 121 } 122 // begin a new tx 123 w.dbTx, err = w.db.Begin(true) 124 if err != nil { 125 w.log.Severe("ERROR: failed to start database update:", err) 126 return errors.AddContext(err, "unable to begin new dbTx in syncDB") 127 } 128 return nil 129 } 130 131 // dbReset wipes and reinitializes a wallet database. 132 func dbReset(tx *bolt.Tx) error { 133 for _, bucket := range dbBuckets { 134 err := tx.DeleteBucket(bucket) 135 if err != nil { 136 return err 137 } 138 _, err = tx.CreateBucket(bucket) 139 if err != nil { 140 return err 141 } 142 } 143 144 // reinitialize the database with default values 145 wb := tx.Bucket(bucketWallet) 146 wb.Put(keyUID, fastrand.Bytes(len(uniqueID{}))) 147 wb.Put(keyConsensusHeight, encoding.Marshal(uint64(0))) 148 wb.Put(keyAuxiliarySeedFiles, encoding.Marshal([]seedFile{})) 149 wb.Put(keySpendableKeyFiles, encoding.Marshal([]spendableKeyFile{})) 150 wb.Put(keyWatchedAddrs, encoding.Marshal([]types.UnlockHash{})) 151 dbPutConsensusHeight(tx, 0) 152 dbPutConsensusChangeID(tx, modules.ConsensusChangeBeginning) 153 dbPutSiafundPool(tx, types.ZeroCurrency) 154 155 return nil 156 } 157 158 // dbPut is a helper function for storing a marshalled key/value pair. 159 func dbPut(b *bolt.Bucket, key, val interface{}) error { 160 return b.Put(encoding.Marshal(key), encoding.Marshal(val)) 161 } 162 163 // dbGet is a helper function for retrieving a marshalled key/value pair. val 164 // must be a pointer. 165 func dbGet(b *bolt.Bucket, key, val interface{}) error { 166 valBytes := b.Get(encoding.Marshal(key)) 167 if valBytes == nil { 168 return errNoKey 169 } 170 return encoding.Unmarshal(valBytes, val) 171 } 172 173 // dbDelete is a helper function for deleting a marshalled key/value pair. 174 func dbDelete(b *bolt.Bucket, key interface{}) error { 175 return b.Delete(encoding.Marshal(key)) 176 } 177 178 // dbForEach is a helper function for iterating over a bucket and calling fn 179 // on each entry. fn must be a function with two parameters. The key/value 180 // bytes of each bucket entry will be unmarshalled into the types of fn's 181 // parameters. 182 func dbForEach(b *bolt.Bucket, fn interface{}) error { 183 // check function type 184 fnVal, fnTyp := reflect.ValueOf(fn), reflect.TypeOf(fn) 185 if fnTyp.Kind() != reflect.Func || fnTyp.NumIn() != 2 { 186 panic("bad fn type: needed func(key, val), got " + fnTyp.String()) 187 } 188 189 return b.ForEach(func(keyBytes, valBytes []byte) error { 190 key, val := reflect.New(fnTyp.In(0)), reflect.New(fnTyp.In(1)) 191 if err := encoding.Unmarshal(keyBytes, key.Interface()); err != nil { 192 return err 193 } else if err := encoding.Unmarshal(valBytes, val.Interface()); err != nil { 194 return err 195 } 196 fnVal.Call([]reflect.Value{key.Elem(), val.Elem()}) 197 return nil 198 }) 199 } 200 201 // Type-safe wrappers around the db helpers 202 203 func dbPutSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID, output types.SiacoinOutput) error { 204 return dbPut(tx.Bucket(bucketSiacoinOutputs), id, output) 205 } 206 func dbGetSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) (output types.SiacoinOutput, err error) { 207 err = dbGet(tx.Bucket(bucketSiacoinOutputs), id, &output) 208 return 209 } 210 func dbDeleteSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) error { 211 return dbDelete(tx.Bucket(bucketSiacoinOutputs), id) 212 } 213 func dbForEachSiacoinOutput(tx *bolt.Tx, fn func(types.SiacoinOutputID, types.SiacoinOutput)) error { 214 return dbForEach(tx.Bucket(bucketSiacoinOutputs), fn) 215 } 216 217 func dbPutSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID, output types.SiafundOutput) error { 218 return dbPut(tx.Bucket(bucketSiafundOutputs), id, output) 219 } 220 func dbGetSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) (output types.SiafundOutput, err error) { 221 err = dbGet(tx.Bucket(bucketSiafundOutputs), id, &output) 222 return 223 } 224 func dbDeleteSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) error { 225 return dbDelete(tx.Bucket(bucketSiafundOutputs), id) 226 } 227 func dbForEachSiafundOutput(tx *bolt.Tx, fn func(types.SiafundOutputID, types.SiafundOutput)) error { 228 return dbForEach(tx.Bucket(bucketSiafundOutputs), fn) 229 } 230 231 func dbPutSpentOutput(tx *bolt.Tx, id types.OutputID, height types.BlockHeight) error { 232 return dbPut(tx.Bucket(bucketSpentOutputs), id, height) 233 } 234 func dbGetSpentOutput(tx *bolt.Tx, id types.OutputID) (height types.BlockHeight, err error) { 235 err = dbGet(tx.Bucket(bucketSpentOutputs), id, &height) 236 return 237 } 238 func dbDeleteSpentOutput(tx *bolt.Tx, id types.OutputID) error { 239 return dbDelete(tx.Bucket(bucketSpentOutputs), id) 240 } 241 242 func dbPutAddrTransactions(tx *bolt.Tx, addr types.UnlockHash, txns []uint64) error { 243 return dbPut(tx.Bucket(bucketAddrTransactions), addr, txns) 244 } 245 func dbGetAddrTransactions(tx *bolt.Tx, addr types.UnlockHash) (txns []uint64, err error) { 246 err = dbGet(tx.Bucket(bucketAddrTransactions), addr, &txns) 247 return 248 } 249 250 func dbPutUnlockConditions(tx *bolt.Tx, uc types.UnlockConditions) error { 251 return dbPut(tx.Bucket(bucketUnlockConditions), uc.UnlockHash(), uc) 252 } 253 func dbGetUnlockConditions(tx *bolt.Tx, addr types.UnlockHash) (uc types.UnlockConditions, err error) { 254 err = dbGet(tx.Bucket(bucketUnlockConditions), addr, &uc) 255 return 256 } 257 258 // dbAddAddrTransaction appends a single transaction index to the set of 259 // transactions associated with addr. If the index is already in the set, it is 260 // not added again. 261 func dbAddAddrTransaction(tx *bolt.Tx, addr types.UnlockHash, txn uint64) error { 262 txns, err := dbGetAddrTransactions(tx, addr) 263 if err != nil && err != errNoKey { 264 return err 265 } 266 for _, i := range txns { 267 if i == txn { 268 return nil 269 } 270 } 271 return dbPutAddrTransactions(tx, addr, append(txns, txn)) 272 } 273 274 // dbAddProcessedTransactionAddrs updates bucketAddrTransactions to associate 275 // every address in pt with txn, which is assumed to be pt's index in 276 // bucketProcessedTransactions. 277 func dbAddProcessedTransactionAddrs(tx *bolt.Tx, pt modules.ProcessedTransaction, txn uint64) error { 278 addrs := make(map[types.UnlockHash]struct{}) 279 for _, input := range pt.Inputs { 280 addrs[input.RelatedAddress] = struct{}{} 281 } 282 for _, output := range pt.Outputs { 283 // miner fees don't have an address, so skip them 284 if output.FundType == types.SpecifierMinerFee { 285 continue 286 } 287 addrs[output.RelatedAddress] = struct{}{} 288 } 289 for addr := range addrs { 290 if err := dbAddAddrTransaction(tx, addr, txn); err != nil { 291 return errors.AddContext(err, fmt.Sprintf("failed to add txn %v to address %v", 292 pt.TransactionID, addr)) 293 } 294 } 295 return nil 296 } 297 298 // bucketProcessedTransactions works a little differently: the key is 299 // meaningless, only used to order the transactions chronologically. 300 301 // decodeProcessedTransaction decodes a marshalled processedTransaction 302 func decodeProcessedTransaction(ptBytes []byte, pt *modules.ProcessedTransaction) error { 303 err := encoding.Unmarshal(ptBytes, pt) 304 if err != nil { 305 // COMPATv1.2.1: try decoding into old transaction type 306 var oldpt v121ProcessedTransaction 307 err = encoding.Unmarshal(ptBytes, &oldpt) 308 *pt = convertProcessedTransaction(oldpt) 309 } 310 return err 311 } 312 313 func dbDeleteTransactionIndex(tx *bolt.Tx, txid types.TransactionID) error { 314 return dbDelete(tx.Bucket(bucketProcessedTxnIndex), txid) 315 } 316 func dbPutTransactionIndex(tx *bolt.Tx, txid types.TransactionID, key []byte) error { 317 return dbPut(tx.Bucket(bucketProcessedTxnIndex), txid, key) 318 } 319 320 func dbGetTransactionIndex(tx *bolt.Tx, txid types.TransactionID) (key []byte, err error) { 321 key = make([]byte, 8) 322 err = dbGet(tx.Bucket(bucketProcessedTxnIndex), txid, &key) 323 return 324 } 325 326 // initProcessedTxnIndex initializes the bucketProcessedTxnIndex with the 327 // elements from bucketProcessedTransactions 328 func initProcessedTxnIndex(tx *bolt.Tx) error { 329 it := dbProcessedTransactionsIterator(tx) 330 indexBytes := make([]byte, 8) 331 for it.next() { 332 index, pt := it.key(), it.value() 333 binary.BigEndian.PutUint64(indexBytes, index) 334 if err := dbPutTransactionIndex(tx, pt.TransactionID, indexBytes); err != nil { 335 return err 336 } 337 } 338 return nil 339 } 340 341 func dbAppendProcessedTransaction(tx *bolt.Tx, pt modules.ProcessedTransaction) error { 342 b := tx.Bucket(bucketProcessedTransactions) 343 key, err := b.NextSequence() 344 if err != nil { 345 return errors.AddContext(err, "failed to get next sequence from bucket") 346 } 347 // big-endian is used so that the keys are properly sorted 348 keyBytes := make([]byte, 8) 349 binary.BigEndian.PutUint64(keyBytes, key) 350 if err = b.Put(keyBytes, encoding.Marshal(pt)); err != nil { 351 return errors.AddContext(err, "failed to store processed txn in database") 352 } 353 354 // add used index to bucketProcessedTxnIndex 355 if err = dbPutTransactionIndex(tx, pt.TransactionID, keyBytes); err != nil { 356 return errors.AddContext(err, "failed to store txn index in database") 357 } 358 359 // also add this txid to the bucketAddrTransactions 360 if err = dbAddProcessedTransactionAddrs(tx, pt, key); err != nil { 361 return errors.AddContext(err, "failed to add processed transaction to addresses in database") 362 } 363 return nil 364 } 365 366 func dbGetLastProcessedTransaction(tx *bolt.Tx) (pt modules.ProcessedTransaction, err error) { 367 seq := tx.Bucket(bucketProcessedTransactions).Sequence() 368 keyBytes := make([]byte, 8) 369 binary.BigEndian.PutUint64(keyBytes, seq) 370 val := tx.Bucket(bucketProcessedTransactions).Get(keyBytes) 371 err = decodeProcessedTransaction(val, &pt) 372 return 373 } 374 375 func dbDeleteLastProcessedTransaction(tx *bolt.Tx) error { 376 // Get the last processed txn. 377 pt, err := dbGetLastProcessedTransaction(tx) 378 if err != nil { 379 return errors.New("can't delete from empty bucket") 380 } 381 // Delete its txid from the index bucket. 382 if err := dbDeleteTransactionIndex(tx, pt.TransactionID); err != nil { 383 return errors.AddContext(err, "couldn't delete txn index") 384 } 385 // Delete the last processed txn and decrement the sequence. 386 b := tx.Bucket(bucketProcessedTransactions) 387 seq := b.Sequence() 388 keyBytes := make([]byte, 8) 389 binary.BigEndian.PutUint64(keyBytes, seq) 390 return errors.Compose(b.SetSequence(seq-1), b.Delete(keyBytes)) 391 } 392 393 func dbGetProcessedTransaction(tx *bolt.Tx, index uint64) (pt modules.ProcessedTransaction, err error) { 394 // big-endian is used so that the keys are properly sorted 395 indexBytes := make([]byte, 8) 396 binary.BigEndian.PutUint64(indexBytes, index) 397 val := tx.Bucket(bucketProcessedTransactions).Get(indexBytes) 398 err = decodeProcessedTransaction(val, &pt) 399 return 400 } 401 402 // A processedTransactionsIter iterates through the ProcessedTransactions bucket. 403 type processedTransactionsIter struct { 404 c *bolt.Cursor 405 seq uint64 406 pt modules.ProcessedTransaction 407 } 408 409 // next decodes the next ProcessedTransaction, returning false if the end of 410 // the bucket has been reached. 411 func (it *processedTransactionsIter) next() bool { 412 var seqBytes, ptBytes []byte 413 if it.pt.TransactionID == (types.TransactionID{}) { 414 // this is the first time next has been called, so cursor is not 415 // initialized yet 416 seqBytes, ptBytes = it.c.First() 417 } else { 418 seqBytes, ptBytes = it.c.Next() 419 } 420 if seqBytes == nil { 421 return false 422 } 423 it.seq = binary.BigEndian.Uint64(seqBytes) 424 return decodeProcessedTransaction(ptBytes, &it.pt) == nil 425 } 426 427 // key returns the key for the most recently decoded ProcessedTransaction. 428 func (it *processedTransactionsIter) key() uint64 { 429 return it.seq 430 } 431 432 // value returns the most recently decoded ProcessedTransaction. 433 func (it *processedTransactionsIter) value() modules.ProcessedTransaction { 434 return it.pt 435 } 436 437 // dbProcessedTransactionsIterator creates a new processedTransactionsIter. 438 func dbProcessedTransactionsIterator(tx *bolt.Tx) *processedTransactionsIter { 439 return &processedTransactionsIter{ 440 c: tx.Bucket(bucketProcessedTransactions).Cursor(), 441 } 442 } 443 444 // dbGetWalletUID returns the UID assigned to the wallet's primary seed. 445 func dbGetWalletUID(tx *bolt.Tx) (uid uniqueID) { 446 copy(uid[:], tx.Bucket(bucketWallet).Get(keyUID)) 447 return 448 } 449 450 // dbGetPrimarySeedProgress returns the number of keys generated from the 451 // primary seed. 452 func dbGetPrimarySeedProgress(tx *bolt.Tx) (progress uint64, err error) { 453 err = encoding.Unmarshal(tx.Bucket(bucketWallet).Get(keyPrimarySeedProgress), &progress) 454 return 455 } 456 457 // dbPutPrimarySeedProgress sets the primary seed progress counter. 458 func dbPutPrimarySeedProgress(tx *bolt.Tx, progress uint64) error { 459 return tx.Bucket(bucketWallet).Put(keyPrimarySeedProgress, encoding.Marshal(progress)) 460 } 461 462 // dbGetConsensusChangeID returns the ID of the last ConsensusChange processed by the wallet. 463 func dbGetConsensusChangeID(tx *bolt.Tx) (cc modules.ConsensusChangeID) { 464 copy(cc[:], tx.Bucket(bucketWallet).Get(keyConsensusChange)) 465 return 466 } 467 468 // dbPutConsensusChangeID stores the ID of the last ConsensusChange processed by the wallet. 469 func dbPutConsensusChangeID(tx *bolt.Tx, cc modules.ConsensusChangeID) error { 470 return tx.Bucket(bucketWallet).Put(keyConsensusChange, cc[:]) 471 } 472 473 // dbGetConsensusHeight returns the height that the wallet has scanned to. 474 func dbGetConsensusHeight(tx *bolt.Tx) (height types.BlockHeight, err error) { 475 err = encoding.Unmarshal(tx.Bucket(bucketWallet).Get(keyConsensusHeight), &height) 476 return 477 } 478 479 // dbPutConsensusHeight stores the height that the wallet has scanned to. 480 func dbPutConsensusHeight(tx *bolt.Tx, height types.BlockHeight) error { 481 return tx.Bucket(bucketWallet).Put(keyConsensusHeight, encoding.Marshal(height)) 482 } 483 484 // dbGetSiafundPool returns the value of the siafund pool. 485 func dbGetSiafundPool(tx *bolt.Tx) (pool types.Currency, err error) { 486 err = encoding.Unmarshal(tx.Bucket(bucketWallet).Get(keySiafundPool), &pool) 487 return 488 } 489 490 // dbPutSiafundPool stores the value of the siafund pool. 491 func dbPutSiafundPool(tx *bolt.Tx, pool types.Currency) error { 492 return tx.Bucket(bucketWallet).Put(keySiafundPool, encoding.Marshal(pool)) 493 } 494 495 // dbPutWatchedAddresses stores the set of watched addresses. 496 func dbPutWatchedAddresses(tx *bolt.Tx, addrs []types.UnlockHash) error { 497 return tx.Bucket(bucketWallet).Put(keyWatchedAddrs, encoding.Marshal(addrs)) 498 } 499 500 // COMPATv121: these types were stored in the db in v1.2.2 and earlier. 501 type ( 502 v121ProcessedInput struct { 503 FundType types.Specifier 504 WalletAddress bool 505 RelatedAddress types.UnlockHash 506 Value types.Currency 507 } 508 509 v121ProcessedOutput struct { 510 FundType types.Specifier 511 MaturityHeight types.BlockHeight 512 WalletAddress bool 513 RelatedAddress types.UnlockHash 514 Value types.Currency 515 } 516 517 v121ProcessedTransaction struct { 518 Transaction types.Transaction 519 TransactionID types.TransactionID 520 ConfirmationHeight types.BlockHeight 521 ConfirmationTimestamp types.Timestamp 522 Inputs []v121ProcessedInput 523 Outputs []v121ProcessedOutput 524 } 525 ) 526 527 func convertProcessedTransaction(oldpt v121ProcessedTransaction) (pt modules.ProcessedTransaction) { 528 pt.Transaction = oldpt.Transaction 529 pt.TransactionID = oldpt.TransactionID 530 pt.ConfirmationHeight = oldpt.ConfirmationHeight 531 pt.ConfirmationTimestamp = oldpt.ConfirmationTimestamp 532 pt.Inputs = make([]modules.ProcessedInput, len(oldpt.Inputs)) 533 for i, in := range oldpt.Inputs { 534 pt.Inputs[i] = modules.ProcessedInput{ 535 FundType: in.FundType, 536 WalletAddress: in.WalletAddress, 537 RelatedAddress: in.RelatedAddress, 538 Value: in.Value, 539 } 540 } 541 pt.Outputs = make([]modules.ProcessedOutput, len(oldpt.Outputs)) 542 for i, out := range oldpt.Outputs { 543 pt.Outputs[i] = modules.ProcessedOutput{ 544 FundType: out.FundType, 545 MaturityHeight: out.MaturityHeight, 546 WalletAddress: out.WalletAddress, 547 RelatedAddress: out.RelatedAddress, 548 Value: out.Value, 549 } 550 } 551 return 552 }