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

Sort accounts internally by their account number. #57

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Paymetheus.Bitcoin/Paymetheus.Bitcoin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<Compile Include="Transaction.cs" />
<Compile Include="Util\DateTimeOffsetExtras.cs" />
<Compile Include="Util\LittleEndian.cs" />
<Compile Include="Util\TupleValue.cs" />
<Compile Include="Util\ValueArray.cs" />
<Compile Include="Wallet\Account.cs" />
<Compile Include="Wallet\AccountProperties.cs" />
Expand Down
46 changes: 46 additions & 0 deletions Paymetheus.Bitcoin/Util/TupleValue.cs
Original file line number Diff line number Diff line change
@@ -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<T1, T2>
{
public TupleValue(T1 item1, T2 item2)
{
Item1 = item1;
Item2 = item2;
}

public T1 Item1 { get; }
public T2 Item2 { get; }
}

public struct TupleValue<T1, T2, T3>
{
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; }
}

/// <summary>
/// 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.
/// </summary>
public static class TupleValue
{
public static TupleValue<T1, T2> Create<T1, T2>(T1 item1, T2 item2) =>
new TupleValue<T1, T2>(item1, item2);

public static TupleValue<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 item2, T3 item3) =>
new TupleValue<T1, T2, T3>(item1, item2, item3);
}
}
76 changes: 57 additions & 19 deletions Paymetheus.Bitcoin/Wallet/Wallet.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -27,20 +28,26 @@ public sealed class Wallet
/// </summary>
public const int MinRecentTransactions = 10;

public Wallet(BlockChainIdentity activeChain, TransactionSet txSet, Dictionary<Account, AccountProperties> accounts, BlockIdentity chainTip)
private const uint ImportedAccountNumber = 2147483647; // 2**31 - 1

public Wallet(BlockChainIdentity activeChain, TransactionSet txSet, List<AccountProperties> 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;
Expand All @@ -49,7 +56,8 @@ public Wallet(BlockChainIdentity activeChain, TransactionSet txSet, Dictionary<A
}

private int _transactionCount;
private readonly Dictionary<Account, AccountProperties> _accounts;
private readonly List<AccountProperties> _bip0032Accounts;
private readonly AccountProperties _importedAccount;

public BlockChainIdentity ActiveChain { get; }
public TransactionSet RecentTransactions { get; }
Expand All @@ -64,7 +72,7 @@ private void AddTransactionToTotals(WalletTransaction tx, Dictionary<Account, Ac
{
TotalBalance -= input.Amount;

var accountProperties = _accounts[input.PreviousAccount];
var accountProperties = LookupAccountProperties(input.PreviousAccount);
accountProperties.TotalBalance -= input.Amount;
if (isCoinbase)
accountProperties.ImmatureCoinbaseReward -= input.Amount;
Expand All @@ -75,7 +83,7 @@ private void AddTransactionToTotals(WalletTransaction tx, Dictionary<Account, Ac
{
TotalBalance += output.Amount;

var accountProperties = _accounts[output.Account];
var accountProperties = LookupAccountProperties(output.Account);
accountProperties.TotalBalance += output.Amount;
if (isCoinbase)
accountProperties.ImmatureCoinbaseReward += output.Amount;
Expand All @@ -93,7 +101,7 @@ private void RemoveTransactionFromTotals(WalletTransaction tx, Dictionary<Accoun
{
TotalBalance += input.Amount;

var accountProperties = _accounts[input.PreviousAccount];
var accountProperties = LookupAccountProperties(input.PreviousAccount);
accountProperties.TotalBalance += input.Amount;
if (isCoinbase)
accountProperties.ImmatureCoinbaseReward += input.Amount;
Expand All @@ -104,7 +112,7 @@ private void RemoveTransactionFromTotals(WalletTransaction tx, Dictionary<Accoun
{
TotalBalance -= output.Amount;

var accountProperties = _accounts[output.Account];
var accountProperties = LookupAccountProperties(output.Account);
accountProperties.TotalBalance -= output.Amount;
if (isCoinbase)
accountProperties.ImmatureCoinbaseReward -= output.Amount;
Expand Down Expand Up @@ -239,10 +247,27 @@ public void ApplyTransactionChanges(WalletChanges changes)
public void UpdateAccountProperties(Account account, string name, uint externalKeyCount, uint internalKeyCount, uint importedKeyCount)
{
AccountProperties props;
if (!_accounts.TryGetValue(account, out props))

if (account.AccountNumber == ImportedAccountNumber)
{
props = new AccountProperties();
_accounts[account] = props;
props = _importedAccount;
}
else
{
var accountNumber = checked((int)account.AccountNumber);
if (accountNumber < _bip0032Accounts.Count)
{
props = _bip0032Accounts[accountNumber];
}
else if (accountNumber == _bip0032Accounts.Count)
{
props = new AccountProperties();
_bip0032Accounts.Add(props);
}
else
{
throw new Exception($"Account {accountNumber} is not the next BIP0032 account.");
}
}

props.AccountName = name;
Expand All @@ -262,7 +287,7 @@ public void UpdateAccountProperties(Account account, string name, uint externalK

public Amount CalculateSpendableBalance(Account account, int minConf)
{
var balance = _accounts[account].ZeroConfSpendableBalance;
var balance = LookupAccountProperties(account).ZeroConfSpendableBalance;

if (minConf == 0)
{
Expand Down Expand Up @@ -304,7 +329,7 @@ public string OutputDestination(WalletTransaction.Output output)
if (controlledOutput.Change)
return "Change";
else
return _accounts[controlledOutput.Account].AccountName;
return LookupAccountProperties(controlledOutput.Account).AccountName;
}
else
{
Expand All @@ -317,10 +342,23 @@ public string OutputDestination(WalletTransaction.Output output)
}
}

public AccountProperties LookupAccountProperties(Account account) => _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<KeyValuePair<Account, AccountProperties>> EnumrateAccounts() => _accounts;
public IEnumerable<TupleValue<Account, AccountProperties>> EnumerateAccounts()
{
return _bip0032Accounts.Select((p, i) => TupleValue.Create(new Account((uint)i), p))
.Concat(new[] { TupleValue.Create(new Account(ImportedAccountNumber), _importedAccount) });
}
}
}
28 changes: 15 additions & 13 deletions Paymetheus.Rpc/WalletClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,20 +411,22 @@ public async Task<Tuple<Wallet, Task>> Synchronize(EventHandler<Wallet.ChangesPr
}
}

var accounts = rpcAccounts.Accounts.ToDictionary(
a => 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<AccountsResponse.Types.Account, AccountProperties> 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 () =>
Expand Down
27 changes: 8 additions & 19 deletions Paymetheus/RecentAccountViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
10 changes: 4 additions & 6 deletions Paymetheus/ShellViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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));
Expand Down Expand Up @@ -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
{
Expand Down