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  }