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