github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/wallet/persist.go (about)

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