gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/wallet/persist.go (about) 1 package wallet 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 9 "gitlab.com/NebulousLabs/errors" 10 "gitlab.com/NebulousLabs/fastrand" 11 "gitlab.com/SiaPrime/SiaPrime/crypto" 12 "gitlab.com/SiaPrime/SiaPrime/encoding" 13 "gitlab.com/SiaPrime/SiaPrime/modules" 14 "gitlab.com/SiaPrime/SiaPrime/persist" 15 "gitlab.com/SiaPrime/SiaPrime/types" 16 17 bolt "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 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 w.mu.Lock() 153 defer w.mu.Unlock() 154 var err error 155 if w.dbRollback { 156 // rollback txn if necessry. 157 err = errors.New("database unable to sync - rollback requested") 158 err = errors.Compose(err, w.dbTx.Rollback()) 159 } else { 160 // else commit the transaction. 161 err = w.dbTx.Commit() 162 } 163 if err != nil { 164 w.log.Severe("ERROR: failed to apply database update:", err) 165 return errors.AddContext(err, "unable to commit dbTx in syncDB") 166 } 167 return w.db.Close() 168 }) 169 if err != nil { 170 return err 171 } 172 173 // spawn a goroutine to commit the db transaction at regular intervals 174 go w.threadedDBUpdate() 175 return nil 176 } 177 178 // createBackup copies the wallet database to dst. 179 func (w *Wallet) createBackup(dst io.Writer) error { 180 _, err := w.dbTx.WriteTo(dst) 181 return err 182 } 183 184 // CreateBackup creates a backup file at the desired filepath. 185 func (w *Wallet) CreateBackup(backupFilepath string) error { 186 if err := w.tg.Add(); err != nil { 187 return err 188 } 189 defer w.tg.Done() 190 w.mu.Lock() 191 defer w.mu.Unlock() 192 f, err := os.Create(backupFilepath) 193 if err != nil { 194 return err 195 } 196 defer f.Close() 197 return w.createBackup(f) 198 } 199 200 // compat112Persist is the structure of the wallet.json file used in v1.1.2 201 type compat112Persist struct { 202 UID uniqueID 203 EncryptionVerification crypto.Ciphertext 204 PrimarySeedFile seedFile 205 PrimarySeedProgress uint64 206 AuxiliarySeedFiles []seedFile 207 UnseededKeys []spendableKeyFile 208 } 209 210 // compat112Meta is the metadata of the wallet.json file used in v1.1.2 211 var compat112Meta = persist.Metadata{ 212 Header: "Wallet Settings", 213 Version: "0.4.0", 214 } 215 216 // convertPersistFrom112To120 converts an old (pre-v1.2.0) wallet.json file to 217 // a wallet.db database. 218 func (w *Wallet) convertPersistFrom112To120(dbFilename, compatFilename string) error { 219 var data compat112Persist 220 err := persist.LoadJSON(compat112Meta, &data, compatFilename) 221 if err != nil { 222 return err 223 } 224 225 w.db, err = persist.OpenDatabase(dbMetadata, dbFilename) 226 if err != nil { 227 return err 228 } 229 // initialize the database 230 err = w.db.Update(func(tx *bolt.Tx) error { 231 for _, b := range dbBuckets { 232 _, err := tx.CreateBucket(b) 233 if err != nil { 234 return fmt.Errorf("could not create bucket %v: %v", string(b), err) 235 } 236 } 237 // set UID, verification, seeds, and seed progress 238 tx.Bucket(bucketWallet).Put(keyUID, data.UID[:]) 239 tx.Bucket(bucketWallet).Put(keyEncryptionVerification, data.EncryptionVerification) 240 tx.Bucket(bucketWallet).Put(keyPrimarySeedFile, encoding.Marshal(data.PrimarySeedFile)) 241 tx.Bucket(bucketWallet).Put(keyAuxiliarySeedFiles, encoding.Marshal(data.AuxiliarySeedFiles)) 242 tx.Bucket(bucketWallet).Put(keySpendableKeyFiles, encoding.Marshal(data.UnseededKeys)) 243 // old wallets had a "preload depth" of 25 244 dbPutPrimarySeedProgress(tx, data.PrimarySeedProgress+25) 245 246 // set consensus height and CCID to zero so that a full rescan is 247 // triggered 248 dbPutConsensusHeight(tx, 0) 249 dbPutConsensusChangeID(tx, modules.ConsensusChangeBeginning) 250 return nil 251 }) 252 w.encrypted = true 253 return err 254 } 255 256 /* 257 // LoadBackup loads a backup file from the provided filepath. The backup file 258 // primary seed is loaded as an auxiliary seed. 259 func (w *Wallet) LoadBackup(masterKey, backupMasterKey crypto.TwofishKey, backupFilepath string) error { 260 if err := w.tg.Add(); err != nil { 261 return err 262 } 263 defer w.tg.Done() 264 265 lockID := w.mu.Lock() 266 defer w.mu.Unlock(lockID) 267 268 // Load all of the seed files, check for duplicates, re-encrypt them (but 269 // keep the UID), and add them to the walletPersist object) 270 var backupPersist walletPersist 271 err := persist.LoadFile(settingsMetadata, &backupPersist, backupFilepath) 272 if err != nil { 273 return err 274 } 275 backupSeeds := append(backupPersist.AuxiliarySeedFiles, backupPersist.PrimarySeedFile) 276 TODO: more 277 } 278 */