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