github.com/ZuluSpl0it/Sia@v1.3.7/modules/wallet/persist.go (about)

     1  package wallet
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/NebulousLabs/Sia/crypto"
    10  	"github.com/NebulousLabs/Sia/encoding"
    11  	"github.com/NebulousLabs/Sia/modules"
    12  	"github.com/NebulousLabs/Sia/persist"
    13  	"github.com/NebulousLabs/Sia/types"
    14  	"github.com/NebulousLabs/errors"
    15  	"github.com/NebulousLabs/fastrand"
    16  
    17  	"github.com/coreos/bbolt"
    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  
    78  		// build the bucketAddrTransactions bucket if necessary
    79  		if buildAddrTxns {
    80  			it := dbProcessedTransactionsIterator(tx)
    81  			for it.next() {
    82  				index, pt := it.key(), it.value()
    83  				if err := dbAddProcessedTransactionAddrs(tx, pt, index); err != nil {
    84  					return err
    85  				}
    86  			}
    87  		}
    88  
    89  		// check whether wallet is encrypted
    90  		w.encrypted = tx.Bucket(bucketWallet).Get(keyEncryptionVerification) != nil
    91  		return nil
    92  	})
    93  	return err
    94  }
    95  
    96  // initPersist loads all of the wallet's persistence files into memory,
    97  // creating them if they do not exist.
    98  func (w *Wallet) initPersist() error {
    99  	// Create a directory for the wallet without overwriting an existing
   100  	// directory.
   101  	err := os.MkdirAll(w.persistDir, 0700)
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	// Start logging.
   107  	w.log, err = persist.NewFileLogger(filepath.Join(w.persistDir, logFile))
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	// Open the database.
   113  	dbFilename := filepath.Join(w.persistDir, dbFile)
   114  	compatFilename := filepath.Join(w.persistDir, compatFile)
   115  	_, dbErr := os.Stat(dbFilename)
   116  	_, compatErr := os.Stat(compatFilename)
   117  	if dbErr != nil && compatErr == nil {
   118  		// database does not exist, but old persist does; convert it
   119  		err = w.convertPersistFrom112To120(dbFilename, compatFilename)
   120  	} else {
   121  		// either database exists or neither exists; open/create the database
   122  		err = w.openDB(filepath.Join(w.persistDir, dbFile))
   123  	}
   124  	if err != nil {
   125  		return err
   126  	}
   127  	err = w.tg.AfterStop(func() error {
   128  		var err error
   129  		if w.dbRollback {
   130  			// rollback txn if necessry.
   131  			err = errors.New("database unable to sync - rollback requested")
   132  			err = errors.Compose(err, w.dbTx.Rollback())
   133  		} else {
   134  			// else commit the transaction.
   135  			err = w.dbTx.Commit()
   136  		}
   137  		if err != nil {
   138  			w.log.Severe("ERROR: failed to apply database update:", err)
   139  			return errors.AddContext(err, "unable to commit dbTx in syncDB")
   140  		}
   141  		return w.db.Close()
   142  	})
   143  	if err != nil {
   144  		return err
   145  	}
   146  	go w.threadedDBUpdate()
   147  	return nil
   148  }
   149  
   150  // createBackup copies the wallet database to dst.
   151  func (w *Wallet) createBackup(dst io.Writer) error {
   152  	_, err := w.dbTx.WriteTo(dst)
   153  	return err
   154  }
   155  
   156  // CreateBackup creates a backup file at the desired filepath.
   157  func (w *Wallet) CreateBackup(backupFilepath string) error {
   158  	if err := w.tg.Add(); err != nil {
   159  		return err
   160  	}
   161  	defer w.tg.Done()
   162  	w.mu.Lock()
   163  	defer w.mu.Unlock()
   164  	f, err := os.Create(backupFilepath)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	defer f.Close()
   169  	return w.createBackup(f)
   170  }
   171  
   172  // compat112Persist is the structure of the wallet.json file used in v1.1.2
   173  type compat112Persist struct {
   174  	UID                    uniqueID
   175  	EncryptionVerification crypto.Ciphertext
   176  	PrimarySeedFile        seedFile
   177  	PrimarySeedProgress    uint64
   178  	AuxiliarySeedFiles     []seedFile
   179  	UnseededKeys           []spendableKeyFile
   180  }
   181  
   182  // compat112Meta is the metadata of the wallet.json file used in v1.1.2
   183  var compat112Meta = persist.Metadata{
   184  	Header:  "Wallet Settings",
   185  	Version: "0.4.0",
   186  }
   187  
   188  // convertPersistFrom112To120 converts an old (pre-v1.2.0) wallet.json file to
   189  // a wallet.db database.
   190  func (w *Wallet) convertPersistFrom112To120(dbFilename, compatFilename string) error {
   191  	var data compat112Persist
   192  	err := persist.LoadJSON(compat112Meta, &data, compatFilename)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	w.db, err = persist.OpenDatabase(dbMetadata, dbFilename)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	// initialize the database
   202  	err = w.db.Update(func(tx *bolt.Tx) error {
   203  		for _, b := range dbBuckets {
   204  			_, err := tx.CreateBucket(b)
   205  			if err != nil {
   206  				return fmt.Errorf("could not create bucket %v: %v", string(b), err)
   207  			}
   208  		}
   209  		// set UID, verification, seeds, and seed progress
   210  		tx.Bucket(bucketWallet).Put(keyUID, data.UID[:])
   211  		tx.Bucket(bucketWallet).Put(keyEncryptionVerification, data.EncryptionVerification)
   212  		tx.Bucket(bucketWallet).Put(keyPrimarySeedFile, encoding.Marshal(data.PrimarySeedFile))
   213  		tx.Bucket(bucketWallet).Put(keyAuxiliarySeedFiles, encoding.Marshal(data.AuxiliarySeedFiles))
   214  		tx.Bucket(bucketWallet).Put(keySpendableKeyFiles, encoding.Marshal(data.UnseededKeys))
   215  		// old wallets had a "preload depth" of 25
   216  		dbPutPrimarySeedProgress(tx, data.PrimarySeedProgress+25)
   217  
   218  		// set consensus height and CCID to zero so that a full rescan is
   219  		// triggered
   220  		dbPutConsensusHeight(tx, 0)
   221  		dbPutConsensusChangeID(tx, modules.ConsensusChangeBeginning)
   222  		return nil
   223  	})
   224  	w.encrypted = true
   225  	return err
   226  }
   227  
   228  /*
   229  // LoadBackup loads a backup file from the provided filepath. The backup file
   230  // primary seed is loaded as an auxiliary seed.
   231  func (w *Wallet) LoadBackup(masterKey, backupMasterKey crypto.TwofishKey, backupFilepath string) error {
   232  	if err := w.tg.Add(); err != nil {
   233  		return err
   234  	}
   235  	defer w.tg.Done()
   236  
   237  	lockID := w.mu.Lock()
   238  	defer w.mu.Unlock(lockID)
   239  
   240  	// Load all of the seed files, check for duplicates, re-encrypt them (but
   241  	// keep the UID), and add them to the walletPersist object)
   242  	var backupPersist walletPersist
   243  	err := persist.LoadFile(settingsMetadata, &backupPersist, backupFilepath)
   244  	if err != nil {
   245  		return err
   246  	}
   247  	backupSeeds := append(backupPersist.AuxiliarySeedFiles, backupPersist.PrimarySeedFile)
   248  	TODO: more
   249  }
   250  */