github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/wallet/persist.go (about)

     1  package wallet
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"SiaPrime/crypto"
    10  	"SiaPrime/encoding"
    11  	"SiaPrime/modules"
    12  	"SiaPrime/persist"
    13  	"SiaPrime/types"
    14  	"gitlab.com/NebulousLabs/errors"
    15  	"gitlab.com/NebulousLabs/fastrand"
    16  
    17  	"gitlab.com/NebulousLabs/bolt"
    18  )
    19  
    20  const (
    21  	compatFile = modules.WalletDir + ".json"
    22  	dbFile     = modules.WalletDir + ".db"
    23  	logFile    = modules.WalletDir + ".log"
    24  )
    25  
    26  var (
    27  	dbMetadata = persist.Metadata{
    28  		Header:  "Wallet Database",
    29  		Version: "1.1.0",
    30  	}
    31  )
    32  
    33  // spendableKeyFile stores an encrypted spendable key on disk.
    34  type spendableKeyFile struct {
    35  	UID                    uniqueID
    36  	EncryptionVerification crypto.Ciphertext
    37  	SpendableKey           crypto.Ciphertext
    38  }
    39  
    40  // openDB loads the set database and populates it with the necessary buckets.
    41  func (w *Wallet) openDB(filename string) (err error) {
    42  	w.db, err = persist.OpenDatabase(dbMetadata, filename)
    43  	if err != nil {
    44  		return err
    45  	}
    46  	// initialize the database
    47  	err = w.db.Update(func(tx *bolt.Tx) error {
    48  		// check whether we need to init bucketAddrTransactions
    49  		buildAddrTxns := tx.Bucket(bucketAddrTransactions) == nil
    50  		// ensure that all buckets exist
    51  		for _, b := range dbBuckets {
    52  			_, err := tx.CreateBucketIfNotExists(b)
    53  			if err != nil {
    54  				return fmt.Errorf("could not create bucket %v: %v", string(b), err)
    55  			}
    56  		}
    57  		// if the wallet does not have a UID, create one
    58  		if tx.Bucket(bucketWallet).Get(keyUID) == nil {
    59  			uid := make([]byte, len(uniqueID{}))
    60  			fastrand.Read(uid[:])
    61  			tx.Bucket(bucketWallet).Put(keyUID, uid)
    62  		}
    63  		// if fields in bucketWallet are nil, set them to zero to prevent unmarshal errors
    64  		wb := tx.Bucket(bucketWallet)
    65  		if wb.Get(keyConsensusHeight) == nil {
    66  			wb.Put(keyConsensusHeight, encoding.Marshal(uint64(0)))
    67  		}
    68  		if wb.Get(keyAuxiliarySeedFiles) == nil {
    69  			wb.Put(keyAuxiliarySeedFiles, encoding.Marshal([]seedFile{}))
    70  		}
    71  		if wb.Get(keySpendableKeyFiles) == nil {
    72  			wb.Put(keySpendableKeyFiles, encoding.Marshal([]spendableKeyFile{}))
    73  		}
    74  		if wb.Get(keySiafundPool) == nil {
    75  			wb.Put(keySiafundPool, encoding.Marshal(types.ZeroCurrency))
    76  		}
    77  		if wb.Get(keyWatchedAddrs) == nil {
    78  			wb.Put(keyWatchedAddrs, encoding.Marshal([]types.UnlockHash{}))
    79  		}
    80  
    81  		// build the bucketAddrTransactions bucket if necessary
    82  		if buildAddrTxns {
    83  			it := dbProcessedTransactionsIterator(tx)
    84  			for it.next() {
    85  				index, pt := it.key(), it.value()
    86  				if err := dbAddProcessedTransactionAddrs(tx, pt, index); err != nil {
    87  					return err
    88  				}
    89  			}
    90  		}
    91  
    92  		// check whether wallet is encrypted
    93  		w.encrypted = tx.Bucket(bucketWallet).Get(keyEncryptionVerification) != nil
    94  		return nil
    95  	})
    96  	return err
    97  }
    98  
    99  // initPersist loads all of the wallet's persistence files into memory,
   100  // creating them if they do not exist.
   101  func (w *Wallet) initPersist() error {
   102  	// Create a directory for the wallet without overwriting an existing
   103  	// directory.
   104  	err := os.MkdirAll(w.persistDir, 0700)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	// Start logging.
   110  	w.log, err = persist.NewFileLogger(filepath.Join(w.persistDir, logFile))
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	// Open the database.
   116  	dbFilename := filepath.Join(w.persistDir, dbFile)
   117  	compatFilename := filepath.Join(w.persistDir, compatFile)
   118  	_, dbErr := os.Stat(dbFilename)
   119  	_, compatErr := os.Stat(compatFilename)
   120  	if dbErr != nil && compatErr == nil {
   121  		// database does not exist, but old persist does; convert it
   122  		err = w.convertPersistFrom112To120(dbFilename, compatFilename)
   123  	} else {
   124  		// either database exists or neither exists; open/create the database
   125  		err = w.openDB(filepath.Join(w.persistDir, dbFile))
   126  	}
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	// begin the initial transaction
   132  	w.dbTx, err = w.db.Begin(true)
   133  	if err != nil {
   134  		w.log.Critical("ERROR: failed to start database update:", err)
   135  	}
   136  
   137  	// COMPATv131 we need to create the bucketProcessedTxnIndex if it doesn't exist
   138  	if w.dbTx.Bucket(bucketProcessedTransactions).Stats().KeyN > 0 &&
   139  		w.dbTx.Bucket(bucketProcessedTxnIndex).Stats().KeyN == 0 {
   140  		err = initProcessedTxnIndex(w.dbTx)
   141  		if err != nil {
   142  			return err
   143  		}
   144  		// Save changes to disk
   145  		if err = w.syncDB(); err != nil {
   146  			return err
   147  		}
   148  	}
   149  
   150  	// ensure that the final db transaction is committed when the wallet closes
   151  	err = w.tg.AfterStop(func() error {
   152  		var err error
   153  		if w.dbRollback {
   154  			// rollback txn if necessry.
   155  			err = errors.New("database unable to sync - rollback requested")
   156  			err = errors.Compose(err, w.dbTx.Rollback())
   157  		} else {
   158  			// else commit the transaction.
   159  			err = w.dbTx.Commit()
   160  		}
   161  		if err != nil {
   162  			w.log.Severe("ERROR: failed to apply database update:", err)
   163  			return errors.AddContext(err, "unable to commit dbTx in syncDB")
   164  		}
   165  		return w.db.Close()
   166  	})
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	// spawn a goroutine to commit the db transaction at regular intervals
   172  	go w.threadedDBUpdate()
   173  	return nil
   174  }
   175  
   176  // createBackup copies the wallet database to dst.
   177  func (w *Wallet) createBackup(dst io.Writer) error {
   178  	_, err := w.dbTx.WriteTo(dst)
   179  	return err
   180  }
   181  
   182  // CreateBackup creates a backup file at the desired filepath.
   183  func (w *Wallet) CreateBackup(backupFilepath string) error {
   184  	if err := w.tg.Add(); err != nil {
   185  		return err
   186  	}
   187  	defer w.tg.Done()
   188  	w.mu.Lock()
   189  	defer w.mu.Unlock()
   190  	f, err := os.Create(backupFilepath)
   191  	if err != nil {
   192  		return err
   193  	}
   194  	defer f.Close()
   195  	return w.createBackup(f)
   196  }
   197  
   198  // compat112Persist is the structure of the wallet.json file used in v1.1.2
   199  type compat112Persist struct {
   200  	UID                    uniqueID
   201  	EncryptionVerification crypto.Ciphertext
   202  	PrimarySeedFile        seedFile
   203  	PrimarySeedProgress    uint64
   204  	AuxiliarySeedFiles     []seedFile
   205  	UnseededKeys           []spendableKeyFile
   206  }
   207  
   208  // compat112Meta is the metadata of the wallet.json file used in v1.1.2
   209  var compat112Meta = persist.Metadata{
   210  	Header:  "Wallet Settings",
   211  	Version: "0.4.0",
   212  }
   213  
   214  // convertPersistFrom112To120 converts an old (pre-v1.2.0) wallet.json file to
   215  // a wallet.db database.
   216  func (w *Wallet) convertPersistFrom112To120(dbFilename, compatFilename string) error {
   217  	var data compat112Persist
   218  	err := persist.LoadJSON(compat112Meta, &data, compatFilename)
   219  	if err != nil {
   220  		return err
   221  	}
   222  
   223  	w.db, err = persist.OpenDatabase(dbMetadata, dbFilename)
   224  	if err != nil {
   225  		return err
   226  	}
   227  	// initialize the database
   228  	err = w.db.Update(func(tx *bolt.Tx) error {
   229  		for _, b := range dbBuckets {
   230  			_, err := tx.CreateBucket(b)
   231  			if err != nil {
   232  				return fmt.Errorf("could not create bucket %v: %v", string(b), err)
   233  			}
   234  		}
   235  		// set UID, verification, seeds, and seed progress
   236  		tx.Bucket(bucketWallet).Put(keyUID, data.UID[:])
   237  		tx.Bucket(bucketWallet).Put(keyEncryptionVerification, data.EncryptionVerification)
   238  		tx.Bucket(bucketWallet).Put(keyPrimarySeedFile, encoding.Marshal(data.PrimarySeedFile))
   239  		tx.Bucket(bucketWallet).Put(keyAuxiliarySeedFiles, encoding.Marshal(data.AuxiliarySeedFiles))
   240  		tx.Bucket(bucketWallet).Put(keySpendableKeyFiles, encoding.Marshal(data.UnseededKeys))
   241  		// old wallets had a "preload depth" of 25
   242  		dbPutPrimarySeedProgress(tx, data.PrimarySeedProgress+25)
   243  
   244  		// set consensus height and CCID to zero so that a full rescan is
   245  		// triggered
   246  		dbPutConsensusHeight(tx, 0)
   247  		dbPutConsensusChangeID(tx, modules.ConsensusChangeBeginning)
   248  		return nil
   249  	})
   250  	w.encrypted = true
   251  	return err
   252  }
   253  
   254  /*
   255  // LoadBackup loads a backup file from the provided filepath. The backup file
   256  // primary seed is loaded as an auxiliary seed.
   257  func (w *Wallet) LoadBackup(masterKey, backupMasterKey crypto.TwofishKey, backupFilepath string) error {
   258  	if err := w.tg.Add(); err != nil {
   259  		return err
   260  	}
   261  	defer w.tg.Done()
   262  
   263  	lockID := w.mu.Lock()
   264  	defer w.mu.Unlock(lockID)
   265  
   266  	// Load all of the seed files, check for duplicates, re-encrypt them (but
   267  	// keep the UID), and add them to the walletPersist object)
   268  	var backupPersist walletPersist
   269  	err := persist.LoadFile(settingsMetadata, &backupPersist, backupFilepath)
   270  	if err != nil {
   271  		return err
   272  	}
   273  	backupSeeds := append(backupPersist.AuxiliarySeedFiles, backupPersist.PrimarySeedFile)
   274  	TODO: more
   275  }
   276  */