Skip to content

Commit

Permalink
Backend: migrate ReadOnly accounts to NativeSegwit
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
knocte committed Feb 17, 2024
1 parent e186182 commit 2d5bfd2
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 31 deletions.
53 changes: 23 additions & 30 deletions src/GWallet.Backend/Account.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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<WatchWalletInfo> =
let allCurrencies = Currency.GetAll()
Expand Down Expand Up @@ -512,30 +518,17 @@ module Account =
let json = SerializeSignedTransaction trans false
File.WriteAllText(filePath, json)

let CreateReadOnlyAccounts (watchWalletInfo: WatchWalletInfo): Async<unit> = 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<FileRepresentation>

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<unit> =
let ethJob = Ether.Account.CreateReadOnlyAccounts watchWalletInfo.EtherPublicAddress
let utxoJob =
async {
UtxoCoin.Account.CreateReadOnlyAccounts watchWalletInfo.UtxoCoinPublicKey
}
Config.AddAccount conceptAccountForReadOnlyAccount AccountKind.ReadOnly
|> ignore<FileRepresentation>
}
async {
do!
Async.Parallel [ethJob; utxoJob]
|> Async.Ignore
}

let Remove (account: ReadOnlyAccount) =
Config.RemoveReadOnlyAccount account
Expand Down
7 changes: 7 additions & 0 deletions src/GWallet.Backend/Config.fs
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,10 @@ module Config =

let RemoveReadOnlyAccount (account: ReadOnlyAccount): unit =
RemoveAccount account

[<Literal>]
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)"

[<Obsolete "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
14 changes: 14 additions & 0 deletions src/GWallet.Backend/Ether/EtherAccount.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
open System
open System.Numerics
open System.Threading.Tasks
open System.Linq

open Nethereum.ABI.Decoders
open Nethereum.Signer
Expand Down Expand Up @@ -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<FileRepresentation>
}

let private GetTransactionCount (currency: Currency) (publicAddress: string): Async<int64> = async {
let! result = Ether.Server.GetTransactionCount currency publicAddress
let value = result.Value
Expand Down
46 changes: 45 additions & 1 deletion src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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<FileRepresentation>

#if NATIVE_SEGWIT
let internal MigrateReadOnlyAccountsToNativeSegWit (allAccounts: seq<IAccount>): 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 =
Expand Down

0 comments on commit 2d5bfd2

Please sign in to comment.