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  }