Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend/UtxoCoin: support native-segwit with cold storage #254

Merged
merged 5 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/GWallet.Backend.Tests/Deserialization.fs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type Deserialization() =
Assert.That(deserializedUnsignedTrans.Proposal.Amount.Currency, Is.EqualTo Currency.BTC)
Assert.That(deserializedUnsignedTrans.Proposal.DestinationAddress,
Is.EqualTo("13jxHQDxGto46QhjFiMb78dZdys9ZD8vW5"))
Assert.That(deserializedUnsignedTrans.Proposal.OriginAddress,
Assert.That(deserializedUnsignedTrans.Proposal.OriginMainAddress,
Is.EqualTo("16pKBjGGZkUXo1afyBNf5ttFvV9hauS1kR"))

let btcTxMetadata = deserializedUnsignedTrans.Metadata :?> UtxoCoin.TransactionMetadata
Expand Down Expand Up @@ -84,7 +84,7 @@ type Deserialization() =
Assert.That(deserializedUnsignedTrans.Proposal.Amount.Currency, Is.EqualTo Currency.ETC)
Assert.That(deserializedUnsignedTrans.Proposal.DestinationAddress,
Is.EqualTo("0xf3j4m0rjxdddud9403j"))
Assert.That(deserializedUnsignedTrans.Proposal.OriginAddress,
Assert.That(deserializedUnsignedTrans.Proposal.OriginMainAddress,
Is.EqualTo("0xf3j4m0rjx94sushh03j"))

let etherTxMetadata = deserializedUnsignedTrans.Metadata :?> Ether.TransactionMetadata
Expand Down Expand Up @@ -122,7 +122,7 @@ type Deserialization() =
Is.EqualTo(Currency.BTC))
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.DestinationAddress,
Is.EqualTo("13jxHQDxGto46QhjFiMb78dZdys9ZD8vW5"))
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginAddress,
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginMainAddress,
Is.EqualTo("16pKBjGGZkUXo1afyBNf5ttFvV9hauS1kR"))

let btcTxMetadata = deserializedSignedTrans.TransactionInfo.Metadata :?> UtxoCoin.TransactionMetadata
Expand Down Expand Up @@ -161,7 +161,7 @@ type Deserialization() =
Is.EqualTo(Currency.ETC))
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.DestinationAddress,
Is.EqualTo("0xf3j4m0rjxdddud9403j"))
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginAddress,
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginMainAddress,
Is.EqualTo("0xf3j4m0rjx94sushh03j"))

let etherTxMetadata = deserializedSignedTrans.TransactionInfo.Metadata :?> Ether.TransactionMetadata
Expand Down Expand Up @@ -195,7 +195,7 @@ type Deserialization() =
Assert.That(deserializedUnsignedTrans.Proposal.Amount.Currency, Is.EqualTo Currency.SAI)
Assert.That(deserializedUnsignedTrans.Proposal.DestinationAddress,
Is.EqualTo("0xDb0381B1a380d8db2724A9Ca2d33E0C6C044bE3b"))
Assert.That(deserializedUnsignedTrans.Proposal.OriginAddress,
Assert.That(deserializedUnsignedTrans.Proposal.OriginMainAddress,
Is.EqualTo("0xba766d6d13E2Cc921Bf6e896319D32502af9e37E"))

let saiTxMetadata = deserializedUnsignedTrans.Metadata :?> Ether.TransactionMetadata
Expand Down Expand Up @@ -233,7 +233,7 @@ type Deserialization() =
Is.EqualTo Currency.SAI)
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.DestinationAddress,
Is.EqualTo("0xDb0381B1a380d8db2724A9Ca2d33E0C6C044bE3b"))
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginAddress,
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginMainAddress,
Is.EqualTo("0xba766d6d13E2Cc921Bf6e896319D32502af9e37E"))

let etherTxMetadata = deserializedSignedTrans.TransactionInfo.Metadata :?> Ether.TransactionMetadata
Expand Down
6 changes: 3 additions & 3 deletions src/GWallet.Backend.Tests/MarshallingData.fs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ module MarshallingData =

let private someUnsignedEtherTransactionProposal =
{
OriginAddress = "0xf3j4m0rjx94sushh03j";
OriginMainAddress = "0xf3j4m0rjx94sushh03j";
Amount = TransferAmount(10.01m, 12.02m, Currency.ETC);
DestinationAddress = "0xf3j4m0rjxdddud9403j";
}
Expand Down Expand Up @@ -180,7 +180,7 @@ module MarshallingData =

let private someUnsignedBtcTransactionProposal =
{
OriginAddress = "16pKBjGGZkUXo1afyBNf5ttFvV9hauS1kR";
OriginMainAddress = "16pKBjGGZkUXo1afyBNf5ttFvV9hauS1kR";
Amount = TransferAmount(10.01m, 12.02m, Currency.BTC);
DestinationAddress = "13jxHQDxGto46QhjFiMb78dZdys9ZD8vW5";
}
Expand Down Expand Up @@ -264,7 +264,7 @@ module MarshallingData =
}
let private someUnsignedSaiTransactionProposal =
{
OriginAddress = "0xba766d6d13E2Cc921Bf6e896319D32502af9e37E";
OriginMainAddress = "0xba766d6d13E2Cc921Bf6e896319D32502af9e37E";
Amount = TransferAmount(1m, 7.08m, Currency.SAI)
DestinationAddress = "0xDb0381B1a380d8db2724A9Ca2d33E0C6C044bE3b";
}
Expand Down
77 changes: 43 additions & 34 deletions src/GWallet.Backend/Account.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ type UnhandledCurrencyServerException(currency: Currency,

module Account =

let private isInitialized (accounts: seq<IAccount>) = lazy(
Config.Init()

#if !NATIVE_SEGWIT
let _readonlyUtxoAccounts =
#else
let readonlyUtxoAccounts =
#endif
accounts.Where(fun acc -> acc.Currency.IsUtxo()).OfType<ReadOnlyAccount>()
#if NATIVE_SEGWIT
UtxoCoin.Account.MigrateReadOnlyAccountsToNativeSegWit readonlyUtxoAccounts
#endif
()
)

let private GetShowableBalanceAndImminentPaymentInternal (account: IAccount)
(mode: ServerSelectionMode)
(cancelSourceOption: Option<CustomCancelSource>)
Expand Down Expand Up @@ -86,8 +101,6 @@ module Account =
failwith <| SPrintF1 "Currency (%A) not supported for this API" currency

let GetAllActiveAccounts(): seq<IAccount> =
Config.PropagateEthAccountInfoToMissingTokensAccounts()

let allCurrencies = Currency.GetAll()

// uncomment this block below, manually, if when testing you need to go back to test the WelcomePage.xaml
Expand All @@ -97,14 +110,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 getAccountsOfKind (kinds: seq<AccountKind>) =
seq {
for currency in allCurrencies do
for kind in kinds do
for accountFile in Config.GetAccountFilesWithCurrency currency kind do
yield GetAccountFromFile accountFile currency kind
}

let allAccounts = getAccountsOfKind [AccountKind.Normal; AccountKind.ReadOnly]

let _checkInit: unit =
(isInitialized allAccounts).Value

allAccounts

let GetNormalAccountsPairingInfoForWatchWallet(): Option<WatchWalletInfo> =
let allCurrencies = Currency.GetAll()
Expand Down Expand Up @@ -223,7 +242,7 @@ module Account =
else
transactionProposal.Amount.ValueToSend + fee.FeeValue
Caching.Instance.StoreOutgoingTransaction
transactionProposal.OriginAddress
transactionProposal.OriginMainAddress
transactionProposal.Amount.Currency
fee.Currency
txId
Expand Down Expand Up @@ -445,7 +464,7 @@ module Account =

let transactionProposal =
{
OriginAddress = baseAccount.PublicAddress
OriginMainAddress = baseAccount.PublicAddress
Amount = amount
DestinationAddress = destination
}
Expand Down Expand Up @@ -512,30 +531,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)
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>

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)
}
Config.AddAccount conceptAccountForReadOnlyAccount AccountKind.ReadOnly
|> ignore<FileRepresentation>
}
async {
do!
Async.Parallel [ethJob; utxoJob]
|> Async.Ignore
}

let Remove (account: ReadOnlyAccount) =
Config.RemoveReadOnlyAccount account
Expand Down Expand Up @@ -790,9 +796,12 @@ module Account =
: ITransactionDetails =
let currency = signedTransaction.TransactionInfo.Proposal.Amount.Currency
if currency.IsUtxo () then
let readOnlyUtxoAccounts =
GetAllActiveAccounts().OfType<UtxoCoin.ReadOnlyUtxoAccount>()
UtxoCoin.Account.GetSignedTransactionDetails
signedTransaction.RawTransaction
currency
readOnlyUtxoAccounts
elif currency.IsEtherBased () then
Ether.Account.GetSignedTransactionDetails
signedTransaction
Expand Down
52 changes: 31 additions & 21 deletions src/GWallet.Backend/Config.fs
Original file line number Diff line number Diff line change
Expand Up @@ -127,31 +127,17 @@ module Config =
| Some dir -> dir
| None -> failwith "Unreachable, after invoking with createIfNotAlreadyExisting=true, it should return Some"

// In case a new token was added it will not have a config for an existing user
// we copy the eth configs to the new tokens config directory
let PropagateEthAccountInfoToMissingTokensAccounts() =
for accountKind in (AccountKind.All()) do
let ethConfigDir = GetConfigDir Currency.ETH accountKind
for token in Currency.GetAll() do
if token.IsEthToken() then
let maybeTokenConfigDir = GetConfigDirInternal token accountKind false
match maybeTokenConfigDir with
| Some _ ->
// already removed token account before
()
| None ->
// now create it if it wasn't there before
let tokenConfigDir = GetConfigDir token accountKind
for ethAccountFilePath in Directory.GetFiles ethConfigDir.FullName do
let newPath = ethAccountFilePath.Replace(ethConfigDir.FullName, tokenConfigDir.FullName)
if not (File.Exists newPath) then
File.Copy(ethAccountFilePath, newPath)
let GetAccountFilesWithCurrency (currency: Currency) (accountKind: AccountKind): seq<FileRepresentation> =
seq {
for filePath in Directory.GetFiles (GetConfigDir currency accountKind).FullName do
yield FileRepresentation.FromFile (FileInfo filePath)
}

let GetAccountFiles (currencies: seq<Currency>) (accountKind: AccountKind): seq<FileRepresentation> =
seq {
for currency in currencies do
for filePath in Directory.GetFiles (GetConfigDir currency accountKind).FullName do
yield FileRepresentation.FromFile (FileInfo(filePath))
for accountFile in GetAccountFilesWithCurrency currency accountKind do
yield accountFile
}

let private GetFile (currency: Currency) (account: BaseAccount): FileInfo =
Expand Down Expand Up @@ -187,3 +173,27 @@ module Config =

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

/// NOTE: the real initialization happens in Account.fs , see isInitialized
let internal Init() =
// In case a new token was added it will not have a config for an existing user
// we copy the eth configs to the new tokens config directory
let propagateEthAccountInfoToMissingTokensAccounts() =
for accountKind in (AccountKind.All()) do
let ethConfigDir = GetConfigDir Currency.ETH accountKind
for token in Currency.GetAll() do
if token.IsEthToken() then
let maybeTokenConfigDir = GetConfigDirInternal token accountKind false
match maybeTokenConfigDir with
| Some _ ->
// already removed token account before
()
| None ->
// now create it if it wasn't there before
let tokenConfigDir = GetConfigDir token accountKind
for ethAccountFilePath in Directory.GetFiles ethConfigDir.FullName do
let newPath = ethAccountFilePath.Replace(ethConfigDir.FullName, tokenConfigDir.FullName)
if not (File.Exists newPath) then
File.Copy(ethAccountFilePath, newPath)

propagateEthAccountInfoToMissingTokensAccounts()
16 changes: 15 additions & 1 deletion 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 Expand Up @@ -487,7 +501,7 @@ module internal Account =

let txDetails =
{
OriginAddress = signer.GetSenderAddress signedTransaction.RawTransaction
OriginMainAddress = signer.GetSenderAddress signedTransaction.RawTransaction
Amount = UnitConversion.Convert.FromWei (IntTypeDecoder().DecodeBigInteger tx.Value)
Currency = getTransactionCurrency tx
DestinationAddress = destAddress
Expand Down
10 changes: 4 additions & 6 deletions src/GWallet.Backend/Marshalling.fs
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,12 @@ module Marshalling =
JsonConvert.DeserializeObject<MarshallingWrapper<'T>>(json, settings)
with
| ex ->
let versionJsonTag = "\"Version\":\""
let versionJsonTag = "\"Version\":"
if (json.Contains(versionJsonTag)) then
let jsonSinceVersion = json.Substring(json.IndexOf(versionJsonTag) + versionJsonTag.Length)
let endVersionIndex = jsonSinceVersion.IndexOf("\"")
let version = jsonSinceVersion.Substring(0, endVersionIndex)
if (version <> currentVersion) then
let wrapper = ExtractWrapper json
if wrapper.Version <> currentVersion then
let msg = SPrintF2 "Incompatible marshalling version found (%s vs. current %s) while trying to deserialize JSON"
version currentVersion
wrapper.Version currentVersion
raise <| VersionMismatchDuringDeserializationException(msg, ex)

let targetTypeName = typeof<'T>.FullName
Expand Down
20 changes: 15 additions & 5 deletions src/GWallet.Backend/Transaction.fs
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
namespace GWallet.Backend

open Newtonsoft.Json

type ITransactionDetails =
abstract member OriginAddress: string
abstract member OriginMainAddress: string
abstract member Amount: decimal
abstract member Currency: Currency
abstract member DestinationAddress: string

type internal SignedTransactionDetails =
{
OriginAddress: string
#if !NATIVE_SEGWIT
[<JsonProperty(PropertyName = "OriginAddress")>]
#endif
OriginMainAddress: string

Amount: decimal
Currency: Currency
DestinationAddress: string
}
interface ITransactionDetails with
member self.OriginAddress = self.OriginAddress
member self.OriginMainAddress = self.OriginMainAddress
member self.Amount = self.Amount
member self.Currency = self.Currency
member self.DestinationAddress = self.DestinationAddress

type UnsignedTransactionProposal =
{
OriginAddress: string;
#if !NATIVE_SEGWIT
[<JsonProperty(PropertyName = "OriginAddress")>]
#endif
OriginMainAddress: string

Amount: TransferAmount;
DestinationAddress: string;
}
interface ITransactionDetails with
member self.OriginAddress = self.OriginAddress
member self.OriginMainAddress = self.OriginMainAddress
member self.Amount = self.Amount.ValueToSend
member self.Currency = self.Amount.Currency
member self.DestinationAddress = self.DestinationAddress
Expand Down
Loading
Loading