This repository was archived by the owner on Aug 18, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 631
/
Copy pathMigration.hs
183 lines (164 loc) · 7.94 KB
/
Migration.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
{-# LANGUAGE LambdaCase #-}
module Cardano.Wallet.Kernel.Migration
( migrateLegacyDataLayer
, sanityCheckSpendingPassword
) where
import Universum
import Data.Acid.Advanced (update')
import Data.Text (pack)
import Data.Time (defaultTimeLocale, formatTime, getCurrentTime,
iso8601DateFormat)
import Pos.Crypto.Signing (checkPassMatches)
import System.Directory (doesDirectoryExist, makeAbsolute, renamePath)
import Formatting ((%))
import qualified Formatting as F
import Pos.Core.NetworkMagic (makeNetworkMagic)
import Pos.Crypto (EncryptedSecretKey)
import Pos.Util.Wlog (Severity (..))
import qualified Cardano.Wallet.Kernel as Kernel
import Cardano.Wallet.Kernel.DB.AcidState (UpdateHdRootPassword (..))
import qualified Cardano.Wallet.Kernel.DB.HdWallet as HD
import Cardano.Wallet.Kernel.DB.InDb (InDb (..))
import qualified Cardano.Wallet.Kernel.DB.Read as Kernel
import qualified Cardano.Wallet.Kernel.Internal as Kernel
import Cardano.Wallet.Kernel.Keystore as Keystore
import qualified Cardano.Wallet.Kernel.Read as Kernel
import Cardano.Wallet.Kernel.Restore (restoreWallet)
import Cardano.Wallet.Kernel.Util.Core (getCurrentTimestamp)
{-------------------------------------------------------------------------------
Pure helper functions for migration.
-------------------------------------------------------------------------------}
--- | Tries to check the existence of the DB at 'FilePath'. It first tries to
-- check for the input 'FilePath' as-it-is. If this fails, it tries to check
-- for absolute path.
-- This is sometimes necessary if the DB path is given with paths like
-- '../wallet-db'. Apparently `doesDirectoryExist` for such a relative path
-- was returning 'False', after making the path absolute.
resolveDbPath :: FilePath -> IO (Maybe FilePath)
resolveDbPath fp = do
exists <- doesDirectoryExist fp
case exists of
True -> return (Just fp)
False -> do
absPath <- makeAbsolute fp
absExist <- doesDirectoryExist absPath
return $ if absExist then Just absPath else Nothing
--- | Move the legacy database into a backup directory.
moveLegacyDB :: FilePath -> IO FilePath
moveLegacyDB filepath = do
now <- getCurrentTime
let backupPath = filepath
<> "-backup-"
<> formatTime defaultTimeLocale (iso8601DateFormat (Just "%H_%M_%S")) now
renamePath filepath backupPath
return backupPath
-- | Migrates all wallets present in the Keystore by restoring each wallet from
-- its encrypted secret key. Since the spending password is not available, we
-- cannot create a default address for new wallets. This has the effect that
-- the user won't be able to rely on transacting with a default address
-- while restoration is in progress.
--
-- When @forced@ is False we are lenient in logging any error and continuing
-- rather than crashing the node. The rationale is that if
-- we leave the node running, we would give the user a chance
-- to submit a bug report from the Daedalus interface.
--
-- However when @forced@ is True the migration is a all-or-nothing.
-- If anything fails the node crashes.
migrateLegacyDataLayer :: Kernel.PassiveWallet
-> FilePath
-> Bool
-> IO ()
migrateLegacyDataLayer pw unresolvedDbPath forced = do
logMsg Info "Starting wallet(s) migration"
resolved <- resolveDbPath unresolvedDbPath
case resolved of
Nothing -> -- We assume no migration is needed and we move along
case forced of
True -> do
logMsg Error $ "Migration failed! Legacy DB was not found at " <> pack unresolvedDbPath <> ", but migration is forced"
exitFailure
False -> do
logMsg Info $ "No legacy DB at " <> pack unresolvedDbPath <> ", migration is not needed."
Just legacyDbPath -> do
wKeys <- Keystore.getKeys (pw ^. Kernel.walletKeystore)
mapM_ (restore pw forced) wKeys
-- asynchronous restoration still runs at this point.
logMsg Info $ "Migration succeeded, restoration of migrated wallets in progress..."
-- Now we can move the directory
backupPath <- moveLegacyDB legacyDbPath
-- asynchronous restoration still runs at this point.
logMsg Info $ "acid state migration succeeded. Old db backup can be found at " <> pack backupPath
where
logMsg = pw ^. Kernel.walletLogMessage
restore :: Kernel.PassiveWallet
-> Bool
-> EncryptedSecretKey
-> IO ()
restore pw forced esk = do
let logMsg = pw ^. Kernel.walletLogMessage
nm = makeNetworkMagic (pw ^. Kernel.walletProtocolMagic)
rootId = HD.eskToHdRootId nm esk
let -- DEFAULTS for wallet restoration
hasSpendingPassword = isNothing $ checkPassMatches mempty esk
defaultAddress = Nothing
defaultWalletName = HD.WalletName "<Migrated Wallet>"
defaultAssuranceLevel = HD.AssuranceLevelStrict
res <- restoreWallet pw
hasSpendingPassword
defaultAddress
defaultWalletName
defaultAssuranceLevel
esk
case res of
Right (restoredRoot, balance) -> do
let msg = "Migrating " % F.build
% " with balance " % F.build
logMsg Info (F.sformat msg restoredRoot balance)
Left err -> do
let errMsg = "Couldn't migrate " % F.build
% " due to : " % F.build % "."
msg = F.sformat errMsg rootId err
case forced of
False -> logMsg Error msg
True -> do
logMsg Error ("Migration failed! " <> msg <> " You are advised to delete the newly created db and try again.")
exitFailure
-- | Verify that the spending password metadata are correctly set. We mistakenly
-- forgot to port a fix from RCD-47 done on 2.0.x onto 3.0.0 and, for a few
-- users, have migrated / restored their wallet with a wrong spending password
-- metadata (arbitrarily set to `False`), making their wallet completely
-- unusable.
--
-- This checks makes sure that the 'hasSpendingPassword' metadata correctly
-- reflects the wallet condition. To be run on each start-up, unfortunately.
sanityCheckSpendingPassword
:: Kernel.PassiveWallet
-> IO ()
sanityCheckSpendingPassword pw = do
let nm = makeNetworkMagic (pw ^. Kernel.walletProtocolMagic)
wKeys <- Keystore.getKeys (pw ^. Kernel.walletKeystore)
db <- Kernel.getWalletSnapshot pw
lastUpdateNow <- InDb <$> getCurrentTimestamp
forM_ wKeys $ \esk -> do
let hasSpendingPassword = case checkPassMatches mempty esk of
Nothing -> HD.HasSpendingPassword lastUpdateNow
Just _ -> HD.NoSpendingPassword
let rootId = HD.eskToHdRootId nm esk
whenDiscrepancy db rootId hasSpendingPassword restoreTruth >>= \case
Left (HD.UnknownHdRoot _) ->
logMsg Error "Failed to update spending password status, HDRoot is gone?"
Right _ ->
return ()
where
logMsg = pw ^. Kernel.walletLogMessage
whenDiscrepancy db rootId hasSpendingPassword action = do
case (hasSpendingPassword, Kernel.lookupHdRootId db rootId) of
(_, Left e) ->
return $ Left e
(HD.HasSpendingPassword _, Right root) | root ^. HD.hdRootHasPassword == HD.NoSpendingPassword ->
action rootId hasSpendingPassword
_ -> -- Avoid making a DB update when there's no need
return $ Right ()
restoreTruth rootId hasSpendingPassword =
void <$> update' (pw ^. Kernel.wallets) (UpdateHdRootPassword rootId hasSpendingPassword)