From 2d5bfd235d02591ade76d8e046b6ccb200712dd3 Mon Sep 17 00:00:00 2001 From: "Andres G. Aragoneses" Date: Tue, 13 Feb 2024 15:36:04 +0800 Subject: [PATCH] Backend: migrate ReadOnly accounts to NativeSegwit Their public addresses would then start showing their native segwit address (that starts with "bc1") instead of the nested segwit one. This way they don't need to re-scan the QR code from the coldstorage device. In case you're wondering if there's any risk that this causes irrecoverable funds in case you use this with a coldstorage device that you haven't upgraded yet to NativeSegwit: this could not happen since the JSON format is different, and then deserialization would fail, advising the user to upgrade. --- src/GWallet.Backend/Account.fs | 53 ++++++++----------- src/GWallet.Backend/Config.fs | 7 +++ src/GWallet.Backend/Ether/EtherAccount.fs | 14 +++++ .../UtxoCoin/UtxoCoinAccount.fs | 46 +++++++++++++++- 4 files changed, 89 insertions(+), 31 deletions(-) diff --git a/src/GWallet.Backend/Account.fs b/src/GWallet.Backend/Account.fs index ddc2255fd..7ca084de6 100644 --- a/src/GWallet.Backend/Account.fs +++ b/src/GWallet.Backend/Account.fs @@ -97,14 +97,20 @@ module Account = failwith "OK, all accounts and cache is clear, you can disable this code block again" #endif - seq { - for currency in allCurrencies do - let activeKinds = [AccountKind.ReadOnly; AccountKind.Normal] - for kind in activeKinds do - for accountFile in Config.GetAccountFiles [currency] kind do - yield GetAccountFromFile accountFile currency kind - } + let allAccounts = + seq { + for currency in allCurrencies do + let activeKinds = [AccountKind.ReadOnly; AccountKind.Normal] + for kind in activeKinds do + for accountFile in Config.GetAccountFiles [currency] kind do + yield GetAccountFromFile accountFile currency kind + } + +#if NATIVE_SEGWIT + UtxoCoin.Account.MigrateReadOnlyAccountsToNativeSegWit allAccounts +#endif + allAccounts let GetNormalAccountsPairingInfoForWatchWallet(): Option = let allCurrencies = Currency.GetAll() @@ -512,30 +518,17 @@ module Account = let json = SerializeSignedTransaction trans false File.WriteAllText(filePath, json) - let CreateReadOnlyAccounts (watchWalletInfo: WatchWalletInfo): Async = async { - for etherCurrency in Currency.GetAll().Where(fun currency -> currency.IsEtherBased()) do - do! ValidateAddress etherCurrency watchWalletInfo.EtherPublicAddress - let conceptAccountForReadOnlyAccount = { - Currency = etherCurrency - FileRepresentation = { Name = watchWalletInfo.EtherPublicAddress; Content = fun _ -> String.Empty } - ExtractPublicAddressFromConfigFileFunc = (fun file -> file.Name) - } - Config.AddAccount conceptAccountForReadOnlyAccount AccountKind.ReadOnly - |> ignore - - for utxoCurrency in Currency.GetAll().Where(fun currency -> currency.IsUtxo()) do - let address = - UtxoCoin.Account.GetPublicAddressFromPublicKey utxoCurrency - (NBitcoin.PubKey(watchWalletInfo.UtxoCoinPublicKey)) - do! ValidateAddress utxoCurrency address - let conceptAccountForReadOnlyAccount = { - Currency = utxoCurrency - FileRepresentation = { Name = address; Content = fun _ -> watchWalletInfo.UtxoCoinPublicKey } - ExtractPublicAddressFromConfigFileFunc = (fun file -> file.Name) + let CreateReadOnlyAccounts (watchWalletInfo: WatchWalletInfo): Async = + let ethJob = Ether.Account.CreateReadOnlyAccounts watchWalletInfo.EtherPublicAddress + let utxoJob = + async { + UtxoCoin.Account.CreateReadOnlyAccounts watchWalletInfo.UtxoCoinPublicKey } - Config.AddAccount conceptAccountForReadOnlyAccount AccountKind.ReadOnly - |> ignore - } + async { + do! + Async.Parallel [ethJob; utxoJob] + |> Async.Ignore + } let Remove (account: ReadOnlyAccount) = Config.RemoveReadOnlyAccount account diff --git a/src/GWallet.Backend/Config.fs b/src/GWallet.Backend/Config.fs index 17c213bd9..59a4bdd22 100644 --- a/src/GWallet.Backend/Config.fs +++ b/src/GWallet.Backend/Config.fs @@ -170,3 +170,10 @@ module Config = let RemoveReadOnlyAccount (account: ReadOnlyAccount): unit = RemoveAccount account + + [] + let private NoInitMsg = "This function never really existed, but it's here to warn you that yes, configuration needs to be initialized (e.g. for migrations) but it's done in Account.GetAllActiveAccounts() so that it's done by all frontends seamlessly (not explicitly)" + + [] + let Init() = + failwith NoInitMsg diff --git a/src/GWallet.Backend/Ether/EtherAccount.fs b/src/GWallet.Backend/Ether/EtherAccount.fs index 0daff27d7..e64496bae 100644 --- a/src/GWallet.Backend/Ether/EtherAccount.fs +++ b/src/GWallet.Backend/Ether/EtherAccount.fs @@ -6,6 +6,7 @@ open System open System.Numerics open System.Threading.Tasks +open System.Linq open Nethereum.ABI.Decoders open Nethereum.Signer @@ -154,6 +155,19 @@ module internal Account = raise (AddressWithInvalidChecksum(Some validCheckSumAddress)) } + let internal CreateReadOnlyAccounts (etherPublicAddress: string) = + async { + for etherCurrency in Currency.GetAll().Where(fun currency -> currency.IsEtherBased()) do + do! ValidateAddress etherCurrency etherPublicAddress + let conceptAccountForReadOnlyAccount = { + Currency = etherCurrency + FileRepresentation = { Name = etherPublicAddress; Content = fun _ -> String.Empty } + ExtractPublicAddressFromConfigFileFunc = (fun file -> file.Name) + } + Config.AddAccount conceptAccountForReadOnlyAccount AccountKind.ReadOnly + |> ignore + } + let private GetTransactionCount (currency: Currency) (publicAddress: string): Async = async { let! result = Ether.Server.GetTransactionCount currency publicAddress let value = result.Value diff --git a/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs b/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs index c31e496b2..09e57ea30 100644 --- a/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs +++ b/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs @@ -621,11 +621,12 @@ module Account = else addressOrUrl,None + let BITCOIN_ADDRESS_BECH32_PREFIX = "bc1" + let internal ValidateAddress (currency: Currency) (address: string) = if String.IsNullOrEmpty address then raise <| ArgumentNullException "address" - let BITCOIN_ADDRESS_BECH32_PREFIX = "bc1" let LITECOIN_ADDRESS_BECH32_PREFIX = "ltc1" let utxoCoinValidAddressPrefixes = @@ -683,6 +684,49 @@ module Account = | :? FormatException -> raise (AddressWithInvalidChecksum None) + let internal CreateReadOnlyAccounts (utxoPublicKey: string) = + for utxoCurrency in Currency.GetAll().Where(fun currency -> currency.IsUtxo()) do + let address = + GetPublicAddressFromPublicKey + utxoCurrency + (NBitcoin.PubKey utxoPublicKey) + ValidateAddress utxoCurrency address + let conceptAccountForReadOnlyAccount = { + Currency = utxoCurrency + FileRepresentation = { Name = address; Content = fun _ -> utxoPublicKey } + ExtractPublicAddressFromConfigFileFunc = (fun file -> file.Name) + } + Config.AddAccount conceptAccountForReadOnlyAccount AccountKind.ReadOnly + |> ignore + +#if NATIVE_SEGWIT + let internal MigrateReadOnlyAccountsToNativeSegWit (allAccounts: seq): unit = + let utxoAccountsToMigrate = + seq { + for utxoAccount in allAccounts.Where(fun acc -> acc.Currency.IsUtxo()) do + match utxoAccount with + | :? ReadOnlyAccount as readOnlyAccount -> + let accountFile = readOnlyAccount.AccountFile + if not (accountFile.Name.StartsWith BITCOIN_ADDRESS_BECH32_PREFIX) then + yield readOnlyAccount + | _ -> () + } + + let utxoPublicKeys = + seq { + for utxoReadOnlyAccount in utxoAccountsToMigrate do + let accountFile = utxoReadOnlyAccount.AccountFile + let utxoPublicKey = accountFile.Content() + yield utxoPublicKey + } |> Set.ofSeq + + for utxoPublicKey in utxoPublicKeys do + CreateReadOnlyAccounts utxoPublicKey + + for utxoReadOnlyAccount in utxoAccountsToMigrate do + Config.RemoveReadOnlyAccount utxoReadOnlyAccount +#endif + let GetSignedTransactionDetails<'T when 'T :> IBlockchainFeeInfo>(rawTransaction: string) (currency: Currency) : ITransactionDetails =