Skip to content

Commit 0e210fe

Browse files
authored
Merge PR #254 from nblockchain/wip/nativeSegwitWithColdStorage
Backend/UtxoCoin: support native-segwit with cold storage.
2 parents 47066f7 + 008da60 commit 0e210fe

File tree

11 files changed

+255
-145
lines changed

11 files changed

+255
-145
lines changed

src/GWallet.Backend.Tests/Deserialization.fs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ type Deserialization() =
5555
Assert.That(deserializedUnsignedTrans.Proposal.Amount.Currency, Is.EqualTo Currency.BTC)
5656
Assert.That(deserializedUnsignedTrans.Proposal.DestinationAddress,
5757
Is.EqualTo("13jxHQDxGto46QhjFiMb78dZdys9ZD8vW5"))
58-
Assert.That(deserializedUnsignedTrans.Proposal.OriginAddress,
58+
Assert.That(deserializedUnsignedTrans.Proposal.OriginMainAddress,
5959
Is.EqualTo("16pKBjGGZkUXo1afyBNf5ttFvV9hauS1kR"))
6060

6161
let btcTxMetadata = deserializedUnsignedTrans.Metadata :?> UtxoCoin.TransactionMetadata
@@ -84,7 +84,7 @@ type Deserialization() =
8484
Assert.That(deserializedUnsignedTrans.Proposal.Amount.Currency, Is.EqualTo Currency.ETC)
8585
Assert.That(deserializedUnsignedTrans.Proposal.DestinationAddress,
8686
Is.EqualTo("0xf3j4m0rjxdddud9403j"))
87-
Assert.That(deserializedUnsignedTrans.Proposal.OriginAddress,
87+
Assert.That(deserializedUnsignedTrans.Proposal.OriginMainAddress,
8888
Is.EqualTo("0xf3j4m0rjx94sushh03j"))
8989

9090
let etherTxMetadata = deserializedUnsignedTrans.Metadata :?> Ether.TransactionMetadata
@@ -122,7 +122,7 @@ type Deserialization() =
122122
Is.EqualTo(Currency.BTC))
123123
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.DestinationAddress,
124124
Is.EqualTo("13jxHQDxGto46QhjFiMb78dZdys9ZD8vW5"))
125-
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginAddress,
125+
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginMainAddress,
126126
Is.EqualTo("16pKBjGGZkUXo1afyBNf5ttFvV9hauS1kR"))
127127

128128
let btcTxMetadata = deserializedSignedTrans.TransactionInfo.Metadata :?> UtxoCoin.TransactionMetadata
@@ -161,7 +161,7 @@ type Deserialization() =
161161
Is.EqualTo(Currency.ETC))
162162
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.DestinationAddress,
163163
Is.EqualTo("0xf3j4m0rjxdddud9403j"))
164-
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginAddress,
164+
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginMainAddress,
165165
Is.EqualTo("0xf3j4m0rjx94sushh03j"))
166166

167167
let etherTxMetadata = deserializedSignedTrans.TransactionInfo.Metadata :?> Ether.TransactionMetadata
@@ -195,7 +195,7 @@ type Deserialization() =
195195
Assert.That(deserializedUnsignedTrans.Proposal.Amount.Currency, Is.EqualTo Currency.SAI)
196196
Assert.That(deserializedUnsignedTrans.Proposal.DestinationAddress,
197197
Is.EqualTo("0xDb0381B1a380d8db2724A9Ca2d33E0C6C044bE3b"))
198-
Assert.That(deserializedUnsignedTrans.Proposal.OriginAddress,
198+
Assert.That(deserializedUnsignedTrans.Proposal.OriginMainAddress,
199199
Is.EqualTo("0xba766d6d13E2Cc921Bf6e896319D32502af9e37E"))
200200

201201
let saiTxMetadata = deserializedUnsignedTrans.Metadata :?> Ether.TransactionMetadata
@@ -233,7 +233,7 @@ type Deserialization() =
233233
Is.EqualTo Currency.SAI)
234234
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.DestinationAddress,
235235
Is.EqualTo("0xDb0381B1a380d8db2724A9Ca2d33E0C6C044bE3b"))
236-
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginAddress,
236+
Assert.That(deserializedSignedTrans.TransactionInfo.Proposal.OriginMainAddress,
237237
Is.EqualTo("0xba766d6d13E2Cc921Bf6e896319D32502af9e37E"))
238238

239239
let etherTxMetadata = deserializedSignedTrans.TransactionInfo.Metadata :?> Ether.TransactionMetadata

src/GWallet.Backend.Tests/MarshallingData.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ module MarshallingData =
127127

128128
let private someUnsignedEtherTransactionProposal =
129129
{
130-
OriginAddress = "0xf3j4m0rjx94sushh03j";
130+
OriginMainAddress = "0xf3j4m0rjx94sushh03j";
131131
Amount = TransferAmount(10.01m, 12.02m, Currency.ETC);
132132
DestinationAddress = "0xf3j4m0rjxdddud9403j";
133133
}
@@ -180,7 +180,7 @@ module MarshallingData =
180180

181181
let private someUnsignedBtcTransactionProposal =
182182
{
183-
OriginAddress = "16pKBjGGZkUXo1afyBNf5ttFvV9hauS1kR";
183+
OriginMainAddress = "16pKBjGGZkUXo1afyBNf5ttFvV9hauS1kR";
184184
Amount = TransferAmount(10.01m, 12.02m, Currency.BTC);
185185
DestinationAddress = "13jxHQDxGto46QhjFiMb78dZdys9ZD8vW5";
186186
}
@@ -264,7 +264,7 @@ module MarshallingData =
264264
}
265265
let private someUnsignedSaiTransactionProposal =
266266
{
267-
OriginAddress = "0xba766d6d13E2Cc921Bf6e896319D32502af9e37E";
267+
OriginMainAddress = "0xba766d6d13E2Cc921Bf6e896319D32502af9e37E";
268268
Amount = TransferAmount(1m, 7.08m, Currency.SAI)
269269
DestinationAddress = "0xDb0381B1a380d8db2724A9Ca2d33E0C6C044bE3b";
270270
}

src/GWallet.Backend/Account.fs

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ type UnhandledCurrencyServerException(currency: Currency,
1717

1818
module Account =
1919

20+
let private isInitialized (accounts: seq<IAccount>) = lazy(
21+
Config.Init()
22+
23+
#if !NATIVE_SEGWIT
24+
let _readonlyUtxoAccounts =
25+
#else
26+
let readonlyUtxoAccounts =
27+
#endif
28+
accounts.Where(fun acc -> acc.Currency.IsUtxo()).OfType<ReadOnlyAccount>()
29+
#if NATIVE_SEGWIT
30+
UtxoCoin.Account.MigrateReadOnlyAccountsToNativeSegWit readonlyUtxoAccounts
31+
#endif
32+
()
33+
)
34+
2035
let private GetShowableBalanceAndImminentPaymentInternal (account: IAccount)
2136
(mode: ServerSelectionMode)
2237
(cancelSourceOption: Option<CustomCancelSource>)
@@ -86,8 +101,6 @@ module Account =
86101
failwith <| SPrintF1 "Currency (%A) not supported for this API" currency
87102

88103
let GetAllActiveAccounts(): seq<IAccount> =
89-
Config.PropagateEthAccountInfoToMissingTokensAccounts()
90-
91104
let allCurrencies = Currency.GetAll()
92105

93106
// uncomment this block below, manually, if when testing you need to go back to test the WelcomePage.xaml
@@ -97,14 +110,20 @@ module Account =
97110
failwith "OK, all accounts and cache is clear, you can disable this code block again"
98111
#endif
99112

100-
seq {
101-
for currency in allCurrencies do
102-
let activeKinds = [AccountKind.ReadOnly; AccountKind.Normal]
103-
for kind in activeKinds do
104-
for accountFile in Config.GetAccountFiles [currency] kind do
105-
yield GetAccountFromFile accountFile currency kind
106-
}
113+
let getAccountsOfKind (kinds: seq<AccountKind>) =
114+
seq {
115+
for currency in allCurrencies do
116+
for kind in kinds do
117+
for accountFile in Config.GetAccountFilesWithCurrency currency kind do
118+
yield GetAccountFromFile accountFile currency kind
119+
}
120+
121+
let allAccounts = getAccountsOfKind [AccountKind.Normal; AccountKind.ReadOnly]
122+
123+
let _checkInit: unit =
124+
(isInitialized allAccounts).Value
107125

126+
allAccounts
108127

109128
let GetNormalAccountsPairingInfoForWatchWallet(): Option<WatchWalletInfo> =
110129
let allCurrencies = Currency.GetAll()
@@ -223,7 +242,7 @@ module Account =
223242
else
224243
transactionProposal.Amount.ValueToSend + fee.FeeValue
225244
Caching.Instance.StoreOutgoingTransaction
226-
transactionProposal.OriginAddress
245+
transactionProposal.OriginMainAddress
227246
transactionProposal.Amount.Currency
228247
fee.Currency
229248
txId
@@ -445,7 +464,7 @@ module Account =
445464

446465
let transactionProposal =
447466
{
448-
OriginAddress = baseAccount.PublicAddress
467+
OriginMainAddress = baseAccount.PublicAddress
449468
Amount = amount
450469
DestinationAddress = destination
451470
}
@@ -512,30 +531,17 @@ module Account =
512531
let json = SerializeSignedTransaction trans false
513532
File.WriteAllText(filePath, json)
514533

515-
let CreateReadOnlyAccounts (watchWalletInfo: WatchWalletInfo): Async<unit> = async {
516-
for etherCurrency in Currency.GetAll().Where(fun currency -> currency.IsEtherBased()) do
517-
do! ValidateAddress etherCurrency watchWalletInfo.EtherPublicAddress
518-
let conceptAccountForReadOnlyAccount = {
519-
Currency = etherCurrency
520-
FileRepresentation = { Name = watchWalletInfo.EtherPublicAddress; Content = fun _ -> String.Empty }
521-
ExtractPublicAddressFromConfigFileFunc = (fun file -> file.Name)
534+
let CreateReadOnlyAccounts (watchWalletInfo: WatchWalletInfo): Async<unit> =
535+
let ethJob = Ether.Account.CreateReadOnlyAccounts watchWalletInfo.EtherPublicAddress
536+
let utxoJob =
537+
async {
538+
UtxoCoin.Account.CreateReadOnlyAccounts watchWalletInfo.UtxoCoinPublicKey
522539
}
523-
Config.AddAccount conceptAccountForReadOnlyAccount AccountKind.ReadOnly
524-
|> ignore<FileRepresentation>
525-
526-
for utxoCurrency in Currency.GetAll().Where(fun currency -> currency.IsUtxo()) do
527-
let address =
528-
UtxoCoin.Account.GetPublicAddressFromPublicKey utxoCurrency
529-
(NBitcoin.PubKey(watchWalletInfo.UtxoCoinPublicKey))
530-
do! ValidateAddress utxoCurrency address
531-
let conceptAccountForReadOnlyAccount = {
532-
Currency = utxoCurrency
533-
FileRepresentation = { Name = address; Content = fun _ -> watchWalletInfo.UtxoCoinPublicKey }
534-
ExtractPublicAddressFromConfigFileFunc = (fun file -> file.Name)
535-
}
536-
Config.AddAccount conceptAccountForReadOnlyAccount AccountKind.ReadOnly
537-
|> ignore<FileRepresentation>
538-
}
540+
async {
541+
do!
542+
Async.Parallel [ethJob; utxoJob]
543+
|> Async.Ignore
544+
}
539545

540546
let Remove (account: ReadOnlyAccount) =
541547
Config.RemoveReadOnlyAccount account
@@ -790,9 +796,12 @@ module Account =
790796
: ITransactionDetails =
791797
let currency = signedTransaction.TransactionInfo.Proposal.Amount.Currency
792798
if currency.IsUtxo () then
799+
let readOnlyUtxoAccounts =
800+
GetAllActiveAccounts().OfType<UtxoCoin.ReadOnlyUtxoAccount>()
793801
UtxoCoin.Account.GetSignedTransactionDetails
794802
signedTransaction.RawTransaction
795803
currency
804+
readOnlyUtxoAccounts
796805
elif currency.IsEtherBased () then
797806
Ether.Account.GetSignedTransactionDetails
798807
signedTransaction

src/GWallet.Backend/Config.fs

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -127,31 +127,17 @@ module Config =
127127
| Some dir -> dir
128128
| None -> failwith "Unreachable, after invoking with createIfNotAlreadyExisting=true, it should return Some"
129129

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

150136
let GetAccountFiles (currencies: seq<Currency>) (accountKind: AccountKind): seq<FileRepresentation> =
151137
seq {
152138
for currency in currencies do
153-
for filePath in Directory.GetFiles (GetConfigDir currency accountKind).FullName do
154-
yield FileRepresentation.FromFile (FileInfo(filePath))
139+
for accountFile in GetAccountFilesWithCurrency currency accountKind do
140+
yield accountFile
155141
}
156142

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

188174
let RemoveReadOnlyAccount (account: ReadOnlyAccount): unit =
189175
RemoveAccount account
176+
177+
/// NOTE: the real initialization happens in Account.fs , see isInitialized
178+
let internal Init() =
179+
// In case a new token was added it will not have a config for an existing user
180+
// we copy the eth configs to the new tokens config directory
181+
let propagateEthAccountInfoToMissingTokensAccounts() =
182+
for accountKind in (AccountKind.All()) do
183+
let ethConfigDir = GetConfigDir Currency.ETH accountKind
184+
for token in Currency.GetAll() do
185+
if token.IsEthToken() then
186+
let maybeTokenConfigDir = GetConfigDirInternal token accountKind false
187+
match maybeTokenConfigDir with
188+
| Some _ ->
189+
// already removed token account before
190+
()
191+
| None ->
192+
// now create it if it wasn't there before
193+
let tokenConfigDir = GetConfigDir token accountKind
194+
for ethAccountFilePath in Directory.GetFiles ethConfigDir.FullName do
195+
let newPath = ethAccountFilePath.Replace(ethConfigDir.FullName, tokenConfigDir.FullName)
196+
if not (File.Exists newPath) then
197+
File.Copy(ethAccountFilePath, newPath)
198+
199+
propagateEthAccountInfoToMissingTokensAccounts()

src/GWallet.Backend/Ether/EtherAccount.fs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
open System
77
open System.Numerics
88
open System.Threading.Tasks
9+
open System.Linq
910

1011
open Nethereum.ABI.Decoders
1112
open Nethereum.Signer
@@ -154,6 +155,19 @@ module internal Account =
154155
raise (AddressWithInvalidChecksum(Some validCheckSumAddress))
155156
}
156157

158+
let internal CreateReadOnlyAccounts (etherPublicAddress: string) =
159+
async {
160+
for etherCurrency in Currency.GetAll().Where(fun currency -> currency.IsEtherBased()) do
161+
do! ValidateAddress etherCurrency etherPublicAddress
162+
let conceptAccountForReadOnlyAccount = {
163+
Currency = etherCurrency
164+
FileRepresentation = { Name = etherPublicAddress; Content = fun _ -> String.Empty }
165+
ExtractPublicAddressFromConfigFileFunc = (fun file -> file.Name)
166+
}
167+
Config.AddAccount conceptAccountForReadOnlyAccount AccountKind.ReadOnly
168+
|> ignore<FileRepresentation>
169+
}
170+
157171
let private GetTransactionCount (currency: Currency) (publicAddress: string): Async<int64> = async {
158172
let! result = Ether.Server.GetTransactionCount currency publicAddress
159173
let value = result.Value
@@ -487,7 +501,7 @@ module internal Account =
487501

488502
let txDetails =
489503
{
490-
OriginAddress = signer.GetSenderAddress signedTransaction.RawTransaction
504+
OriginMainAddress = signer.GetSenderAddress signedTransaction.RawTransaction
491505
Amount = UnitConversion.Convert.FromWei (IntTypeDecoder().DecodeBigInteger tx.Value)
492506
Currency = getTransactionCurrency tx
493507
DestinationAddress = destAddress

src/GWallet.Backend/Marshalling.fs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,12 @@ module Marshalling =
182182
JsonConvert.DeserializeObject<MarshallingWrapper<'T>>(json, settings)
183183
with
184184
| ex ->
185-
let versionJsonTag = "\"Version\":\""
185+
let versionJsonTag = "\"Version\":"
186186
if (json.Contains(versionJsonTag)) then
187-
let jsonSinceVersion = json.Substring(json.IndexOf(versionJsonTag) + versionJsonTag.Length)
188-
let endVersionIndex = jsonSinceVersion.IndexOf("\"")
189-
let version = jsonSinceVersion.Substring(0, endVersionIndex)
190-
if (version <> currentVersion) then
187+
let wrapper = ExtractWrapper json
188+
if wrapper.Version <> currentVersion then
191189
let msg = SPrintF2 "Incompatible marshalling version found (%s vs. current %s) while trying to deserialize JSON"
192-
version currentVersion
190+
wrapper.Version currentVersion
193191
raise <| VersionMismatchDuringDeserializationException(msg, ex)
194192

195193
let targetTypeName = typeof<'T>.FullName

src/GWallet.Backend/Transaction.fs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,42 @@
11
namespace GWallet.Backend
22

3+
open Newtonsoft.Json
4+
35
type ITransactionDetails =
4-
abstract member OriginAddress: string
6+
abstract member OriginMainAddress: string
57
abstract member Amount: decimal
68
abstract member Currency: Currency
79
abstract member DestinationAddress: string
810

911
type internal SignedTransactionDetails =
1012
{
11-
OriginAddress: string
13+
#if !NATIVE_SEGWIT
14+
[<JsonProperty(PropertyName = "OriginAddress")>]
15+
#endif
16+
OriginMainAddress: string
17+
1218
Amount: decimal
1319
Currency: Currency
1420
DestinationAddress: string
1521
}
1622
interface ITransactionDetails with
17-
member self.OriginAddress = self.OriginAddress
23+
member self.OriginMainAddress = self.OriginMainAddress
1824
member self.Amount = self.Amount
1925
member self.Currency = self.Currency
2026
member self.DestinationAddress = self.DestinationAddress
2127

2228
type UnsignedTransactionProposal =
2329
{
24-
OriginAddress: string;
30+
#if !NATIVE_SEGWIT
31+
[<JsonProperty(PropertyName = "OriginAddress")>]
32+
#endif
33+
OriginMainAddress: string
34+
2535
Amount: TransferAmount;
2636
DestinationAddress: string;
2737
}
2838
interface ITransactionDetails with
29-
member self.OriginAddress = self.OriginAddress
39+
member self.OriginMainAddress = self.OriginMainAddress
3040
member self.Amount = self.Amount.ValueToSend
3141
member self.Currency = self.Amount.Currency
3242
member self.DestinationAddress = self.DestinationAddress

0 commit comments

Comments
 (0)