github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/wallet/database.go (about) 1 package wallet 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "reflect" 7 "time" 8 9 "github.com/Synthesix/Sia/encoding" 10 "github.com/Synthesix/Sia/modules" 11 "github.com/Synthesix/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 w.dbTx.Rollback() 106 return errors.New("database unable to sync - rollback requested") 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 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 dbPutTransactionIndex(tx *bolt.Tx, txid types.TransactionID, key []byte) error { 299 return dbPut(tx.Bucket(bucketProcessedTxnIndex), txid, key) 300 } 301 302 func dbGetTransactionIndex(tx *bolt.Tx, txid types.TransactionID) (key []byte, err error) { 303 key = make([]byte, 8) 304 err = dbGet(tx.Bucket(bucketProcessedTxnIndex), txid, &key) 305 return 306 } 307 308 // initProcessedTxnIndex initializes the bucketProcessedTxnIndex with the 309 // elements from bucketProcessedTransactions 310 func initProcessedTxnIndex(tx *bolt.Tx) error { 311 it := dbProcessedTransactionsIterator(tx) 312 indexBytes := make([]byte, 8) 313 for it.next() { 314 index, pt := it.key(), it.value() 315 binary.BigEndian.PutUint64(indexBytes, index) 316 if err := dbPutTransactionIndex(tx, pt.TransactionID, indexBytes); err != nil { 317 return err 318 } 319 } 320 return nil 321 } 322 323 func dbAppendProcessedTransaction(tx *bolt.Tx, pt modules.ProcessedTransaction) error { 324 b := tx.Bucket(bucketProcessedTransactions) 325 key, err := b.NextSequence() 326 if err != nil { 327 return errors.AddContext(err, "failed to get next sequence from bucket") 328 } 329 // big-endian is used so that the keys are properly sorted 330 keyBytes := make([]byte, 8) 331 binary.BigEndian.PutUint64(keyBytes, key) 332 if err = b.Put(keyBytes, encoding.Marshal(pt)); err != nil { 333 return errors.AddContext(err, "failed to store processed txn in database") 334 } 335 336 // add used index to bucketProcessedTxnIndex 337 if err = dbPutTransactionIndex(tx, pt.TransactionID, keyBytes); err != nil { 338 return errors.AddContext(err, "failed to store txn index in database") 339 } 340 341 // also add this txid to the bucketAddrTransactions 342 if err = dbAddProcessedTransactionAddrs(tx, pt, key); err != nil { 343 return errors.AddContext(err, "failed to add processed transaction to addresses in database") 344 } 345 return nil 346 } 347 348 func dbGetLastProcessedTransaction(tx *bolt.Tx) (pt modules.ProcessedTransaction, err error) { 349 _, val := tx.Bucket(bucketProcessedTransactions).Cursor().Last() 350 err = decodeProcessedTransaction(val, &pt) 351 return 352 } 353 354 func dbDeleteLastProcessedTransaction(tx *bolt.Tx) error { 355 // delete the last entry in the bucket. Note that we don't need to 356 // decrement the sequence integer; we only care that the next integer is 357 // larger than the previous one. 358 b := tx.Bucket(bucketProcessedTransactions) 359 key, _ := b.Cursor().Last() 360 return b.Delete(key) 361 } 362 363 func dbGetProcessedTransaction(tx *bolt.Tx, index uint64) (pt modules.ProcessedTransaction, err error) { 364 // big-endian is used so that the keys are properly sorted 365 indexBytes := make([]byte, 8) 366 binary.BigEndian.PutUint64(indexBytes, index) 367 val := tx.Bucket(bucketProcessedTransactions).Get(indexBytes) 368 err = decodeProcessedTransaction(val, &pt) 369 return 370 } 371 372 // A processedTransactionsIter iterates through the ProcessedTransactions bucket. 373 type processedTransactionsIter struct { 374 c *bolt.Cursor 375 seq uint64 376 pt modules.ProcessedTransaction 377 } 378 379 // next decodes the next ProcessedTransaction, returning false if the end of 380 // the bucket has been reached. 381 func (it *processedTransactionsIter) next() bool { 382 var seqBytes, ptBytes []byte 383 if it.pt.TransactionID == (types.TransactionID{}) { 384 // this is the first time next has been called, so cursor is not 385 // initialized yet 386 seqBytes, ptBytes = it.c.First() 387 } else { 388 seqBytes, ptBytes = it.c.Next() 389 } 390 if seqBytes == nil { 391 return false 392 } 393 it.seq = binary.BigEndian.Uint64(seqBytes) 394 return decodeProcessedTransaction(ptBytes, &it.pt) == nil 395 } 396 397 // key returns the key for the most recently decoded ProcessedTransaction. 398 func (it *processedTransactionsIter) key() uint64 { 399 return it.seq 400 } 401 402 // value returns the most recently decoded ProcessedTransaction. 403 func (it *processedTransactionsIter) value() modules.ProcessedTransaction { 404 return it.pt 405 } 406 407 // dbProcessedTransactionsIterator creates a new processedTransactionsIter. 408 func dbProcessedTransactionsIterator(tx *bolt.Tx) *processedTransactionsIter { 409 return &processedTransactionsIter{ 410 c: tx.Bucket(bucketProcessedTransactions).Cursor(), 411 } 412 } 413 414 // dbGetWalletUID returns the UID assigned to the wallet's primary seed. 415 func dbGetWalletUID(tx *bolt.Tx) (uid uniqueID) { 416 copy(uid[:], tx.Bucket(bucketWallet).Get(keyUID)) 417 return 418 } 419 420 // dbGetPrimarySeedProgress returns the number of keys generated from the 421 // primary seed. 422 func dbGetPrimarySeedProgress(tx *bolt.Tx) (progress uint64, err error) { 423 err = encoding.Unmarshal(tx.Bucket(bucketWallet).Get(keyPrimarySeedProgress), &progress) 424 return 425 } 426 427 // dbPutPrimarySeedProgress sets the primary seed progress counter. 428 func dbPutPrimarySeedProgress(tx *bolt.Tx, progress uint64) error { 429 return tx.Bucket(bucketWallet).Put(keyPrimarySeedProgress, encoding.Marshal(progress)) 430 } 431 432 // dbGetConsensusChangeID returns the ID of the last ConsensusChange processed by the wallet. 433 func dbGetConsensusChangeID(tx *bolt.Tx) (cc modules.ConsensusChangeID) { 434 copy(cc[:], tx.Bucket(bucketWallet).Get(keyConsensusChange)) 435 return 436 } 437 438 // dbPutConsensusChangeID stores the ID of the last ConsensusChange processed by the wallet. 439 func dbPutConsensusChangeID(tx *bolt.Tx, cc modules.ConsensusChangeID) error { 440 return tx.Bucket(bucketWallet).Put(keyConsensusChange, cc[:]) 441 } 442 443 // dbGetConsensusHeight returns the height that the wallet has scanned to. 444 func dbGetConsensusHeight(tx *bolt.Tx) (height types.BlockHeight, err error) { 445 err = encoding.Unmarshal(tx.Bucket(bucketWallet).Get(keyConsensusHeight), &height) 446 return 447 } 448 449 // dbPutConsensusHeight stores the height that the wallet has scanned to. 450 func dbPutConsensusHeight(tx *bolt.Tx, height types.BlockHeight) error { 451 return tx.Bucket(bucketWallet).Put(keyConsensusHeight, encoding.Marshal(height)) 452 } 453 454 // dbGetSiafundPool returns the value of the siafund pool. 455 func dbGetSiafundPool(tx *bolt.Tx) (pool types.Currency, err error) { 456 err = encoding.Unmarshal(tx.Bucket(bucketWallet).Get(keySiafundPool), &pool) 457 return 458 } 459 460 // dbPutSiafundPool stores the value of the siafund pool. 461 func dbPutSiafundPool(tx *bolt.Tx, pool types.Currency) error { 462 return tx.Bucket(bucketWallet).Put(keySiafundPool, encoding.Marshal(pool)) 463 } 464 465 // COMPATv121: these types were stored in the db in v1.2.2 and earlier. 466 type ( 467 v121ProcessedInput struct { 468 FundType types.Specifier 469 WalletAddress bool 470 RelatedAddress types.UnlockHash 471 Value types.Currency 472 } 473 474 v121ProcessedOutput struct { 475 FundType types.Specifier 476 MaturityHeight types.BlockHeight 477 WalletAddress bool 478 RelatedAddress types.UnlockHash 479 Value types.Currency 480 } 481 482 v121ProcessedTransaction struct { 483 Transaction types.Transaction 484 TransactionID types.TransactionID 485 ConfirmationHeight types.BlockHeight 486 ConfirmationTimestamp types.Timestamp 487 Inputs []v121ProcessedInput 488 Outputs []v121ProcessedOutput 489 } 490 ) 491 492 func convertProcessedTransaction(oldpt v121ProcessedTransaction) (pt modules.ProcessedTransaction) { 493 pt.Transaction = oldpt.Transaction 494 pt.TransactionID = oldpt.TransactionID 495 pt.ConfirmationHeight = oldpt.ConfirmationHeight 496 pt.ConfirmationTimestamp = oldpt.ConfirmationTimestamp 497 pt.Inputs = make([]modules.ProcessedInput, len(oldpt.Inputs)) 498 for i, in := range oldpt.Inputs { 499 pt.Inputs[i] = modules.ProcessedInput{ 500 FundType: in.FundType, 501 WalletAddress: in.WalletAddress, 502 RelatedAddress: in.RelatedAddress, 503 Value: in.Value, 504 } 505 } 506 pt.Outputs = make([]modules.ProcessedOutput, len(oldpt.Outputs)) 507 for i, out := range oldpt.Outputs { 508 pt.Outputs[i] = modules.ProcessedOutput{ 509 FundType: out.FundType, 510 MaturityHeight: out.MaturityHeight, 511 WalletAddress: out.WalletAddress, 512 RelatedAddress: out.RelatedAddress, 513 Value: out.Value, 514 } 515 } 516 return 517 }