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 */