diff --git a/Paymetheus.Bitcoin/Paymetheus.Bitcoin.csproj b/Paymetheus.Bitcoin/Paymetheus.Bitcoin.csproj index 10d12aa..0cee54a 100644 --- a/Paymetheus.Bitcoin/Paymetheus.Bitcoin.csproj +++ b/Paymetheus.Bitcoin/Paymetheus.Bitcoin.csproj @@ -72,6 +72,7 @@ + diff --git a/Paymetheus.Bitcoin/Util/TupleValue.cs b/Paymetheus.Bitcoin/Util/TupleValue.cs new file mode 100644 index 0000000..c1fa243 --- /dev/null +++ b/Paymetheus.Bitcoin/Util/TupleValue.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2016 The btcsuite developers +// Licensed under the ISC license. See LICENSE file in the project root for full license information. + +namespace Paymetheus.Bitcoin.Util +{ + public struct TupleValue + { + public TupleValue(T1 item1, T2 item2) + { + Item1 = item1; + Item2 = item2; + } + + public T1 Item1 { get; } + public T2 Item2 { get; } + } + + public struct TupleValue + { + public TupleValue(T1 item1, T2 item2, T3 item3) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + } + + public T1 Item1 { get; } + public T2 Item2 { get; } + public T3 Item3 { get; } + } + + /// + /// TupleValue is an efficient alternative for the System.Tuple types. TupleValue is a + /// struct (value type) while Tuple is a class (reference type). A similar TupleValue struct + /// and tuple syntax sugar is planned for C# 7, which would eliminate the need for these + /// types if added. + /// + public static class TupleValue + { + public static TupleValue Create(T1 item1, T2 item2) => + new TupleValue(item1, item2); + + public static TupleValue Create(T1 item1, T2 item2, T3 item3) => + new TupleValue(item1, item2, item3); + } +} diff --git a/Paymetheus.Bitcoin/Wallet/Wallet.cs b/Paymetheus.Bitcoin/Wallet/Wallet.cs index b9c6969..258eb50 100644 --- a/Paymetheus.Bitcoin/Wallet/Wallet.cs +++ b/Paymetheus.Bitcoin/Wallet/Wallet.cs @@ -1,6 +1,7 @@ // Copyright (c) 2016 The btcsuite developers // Licensed under the ISC license. See LICENSE file in the project root for full license information. +using Paymetheus.Bitcoin.Util; using System; using System.Collections.Generic; using System.Linq; @@ -27,20 +28,26 @@ public sealed class Wallet /// public const int MinRecentTransactions = 10; - public Wallet(BlockChainIdentity activeChain, TransactionSet txSet, Dictionary accounts, BlockIdentity chainTip) + private const uint ImportedAccountNumber = 2147483647; // 2**31 - 1 + + public Wallet(BlockChainIdentity activeChain, TransactionSet txSet, List bip0032Accounts, + AccountProperties importedAccount, BlockIdentity chainTip) { if (activeChain == null) throw new ArgumentNullException(nameof(activeChain)); - if (accounts == null) - throw new ArgumentNullException(nameof(accounts)); + if (bip0032Accounts == null) + throw new ArgumentNullException(nameof(bip0032Accounts)); + if (importedAccount == null) + throw new ArgumentNullException(nameof(importedAccount)); if (chainTip == null) throw new ArgumentNullException(nameof(chainTip)); - var totalBalance = accounts.Aggregate((Amount)0, (acc, kvp) => acc + kvp.Value.TotalBalance); - _transactionCount = txSet.MinedTransactions.Aggregate(0, (acc, b) => acc + b.Transactions.Count) + txSet.UnminedTransactions.Count; - _accounts = accounts; + _bip0032Accounts = bip0032Accounts; + _importedAccount = importedAccount; + + var totalBalance = EnumerateAccounts().Aggregate((Amount)0, (acc, a) => acc + a.Item2.TotalBalance); ActiveChain = activeChain; RecentTransactions = txSet; @@ -49,7 +56,8 @@ public Wallet(BlockChainIdentity activeChain, TransactionSet txSet, Dictionary _accounts; + private readonly List _bip0032Accounts; + private readonly AccountProperties _importedAccount; public BlockChainIdentity ActiveChain { get; } public TransactionSet RecentTransactions { get; } @@ -64,7 +72,7 @@ private void AddTransactionToTotals(WalletTransaction tx, Dictionary _accounts[account]; + public AccountProperties LookupAccountProperties(Account account) + { + if (account.AccountNumber == ImportedAccountNumber) + { + return _importedAccount; + } + + var accountIndex = checked((int)account.AccountNumber); + return _bip0032Accounts[accountIndex]; + } - public string AccountName(Account account) => _accounts[account].AccountName; + public string AccountName(Account account) => LookupAccountProperties(account).AccountName; - public IEnumerable> EnumrateAccounts() => _accounts; + public IEnumerable> EnumerateAccounts() + { + return _bip0032Accounts.Select((p, i) => TupleValue.Create(new Account((uint)i), p)) + .Concat(new[] { TupleValue.Create(new Account(ImportedAccountNumber), _importedAccount) }); + } } } diff --git a/Paymetheus.Rpc/WalletClient.cs b/Paymetheus.Rpc/WalletClient.cs index dd89832..6ece92a 100644 --- a/Paymetheus.Rpc/WalletClient.cs +++ b/Paymetheus.Rpc/WalletClient.cs @@ -411,20 +411,22 @@ public async Task> Synchronize(EventHandler new Account(a.AccountNumber), - a => new AccountProperties - { - AccountName = a.AccountName, - TotalBalance = a.TotalBalance, - // TODO: uncomment when added to protospec and implemented by wallet. - //ImmatureCoinbaseReward = a.ImmatureBalance, - ExternalKeyCount = a.ExternalKeyCount, - InternalKeyCount = a.InternalKeyCount, - ImportedKeyCount = a.ImportedKeyCount, - }); + Func createProperties = a => new AccountProperties + { + AccountName = a.AccountName, + TotalBalance = a.TotalBalance, + // TODO: uncomment when added to protospec and implemented by wallet. + //ImmatureCoinbaseReward = a.ImmatureBalance, + ExternalKeyCount = a.ExternalKeyCount, + InternalKeyCount = a.InternalKeyCount, + ImportedKeyCount = a.ImportedKeyCount, + }; + // This assumes that all but the last account listed in the RPC response are + // BIP0032 accounts, with the same account number as their List index. + var bip0032Accounts = rpcAccounts.Accounts.Take(rpcAccounts.Accounts.Count - 1).Select(createProperties).ToList(); + var importedAccount = createProperties(rpcAccounts.Accounts.Last()); var chainTip = new BlockIdentity(lastAccountBlockHash, lastAccountBlockHeight); - var wallet = new Wallet(activeBlockChain, txSet, accounts, chainTip); + var wallet = new Wallet(activeBlockChain, txSet, bip0032Accounts, importedAccount, chainTip); wallet.ChangesProcessed += walletEventHandler; var syncTask = Task.Run(async () => diff --git a/Paymetheus/RecentAccountViewModel.cs b/Paymetheus/RecentAccountViewModel.cs index dbe613f..91dc5c3 100644 --- a/Paymetheus/RecentAccountViewModel.cs +++ b/Paymetheus/RecentAccountViewModel.cs @@ -18,30 +18,19 @@ public RecentAccountViewModel(ViewModelBase parent, Account account, AccountProp throw new ArgumentNullException(nameof(properties)); Account = account; - AccountName = properties.AccountName; - Balance = properties.TotalBalance; + _accountProperties = properties; } + private readonly AccountProperties _accountProperties; + public Account Account { get; } - private string _accountName; - public string AccountName - { - get { return _accountName; } - set { if (_accountName != value) { _accountName = value; RaisePropertyChanged(); } } - } + public string AccountName => _accountProperties.AccountName; + public string BalanceString => Denomination.Bitcoin.FormatAmount(_accountProperties.TotalBalance); - private Amount _balance; - public Amount Balance + public void NotifyPropertiesChanged() { - get { return _balance; } - set - { - _balance = value; - RaisePropertyChanged(); - RaisePropertyChanged(nameof(BalanceString)); - } + RaisePropertyChanged(nameof(AccountName)); + RaisePropertyChanged(nameof(BalanceString)); } - - public string BalanceString => Denomination.Bitcoin.FormatAmount(Balance); } } diff --git a/Paymetheus/ShellViewModel.cs b/Paymetheus/ShellViewModel.cs index 21e7937..3ff7d13 100644 --- a/Paymetheus/ShellViewModel.cs +++ b/Paymetheus/ShellViewModel.cs @@ -120,8 +120,8 @@ private void OnSyncedWallet() .Select(x => new TransactionViewModel(_wallet, x.Value, BlockIdentity.Unmined)) .Concat(txSet.MinedTransactions.ReverseList().SelectMany(b => b.Transactions.Select(tx => new TransactionViewModel(_wallet, tx, b.Identity)))) .Take(10); - var recentAccounts = _wallet.EnumrateAccounts() - .Select(a => new RecentAccountViewModel(this, a.Key, a.Value)); + var recentAccounts = _wallet.EnumerateAccounts() + .Select(a => new RecentAccountViewModel(this, a.Item1, a.Item2)); Application.Current.Dispatcher.Invoke(() => { foreach (var tx in recentTx) @@ -139,8 +139,7 @@ private void NotifyRecalculatedBalances() { foreach (var recentAccount in RecentAccounts) { - var currentState = _wallet.LookupAccountProperties(recentAccount.Account); - recentAccount.Balance = currentState.ZeroConfSpendableBalance; + recentAccount.NotifyPropertiesChanged(); } RaisePropertyChanged(nameof(TotalBalance)); @@ -175,8 +174,7 @@ private void _wallet_ChangesProcessed(object sender, Wallet.ChangesProcessedEven var recentAccountVM = RecentAccounts.FirstOrDefault(vm => vm.Account == account); if (recentAccountVM != null) { - recentAccountVM.AccountName = state.AccountName; - recentAccountVM.Balance = state.TotalBalance; + recentAccountVM.NotifyPropertiesChanged(); } else {