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