Пример #1
0
        private async void StartupWizard_WalletOpened(object sender, EventArgs args)
        {
            try
            {
                var syncingWallet = await App.Current.WalletRpcClient.Synchronize(_wallet_ChangesProcessed);
                _wallet = syncingWallet.Item1;
                OnSyncedWallet();

                var syncTask = syncingWallet.Item2;
                await syncTask;
            }
            catch (ConnectTimeoutException)
            {
                MessageBox.Show("Unable to connect to wallet");
            }
            catch (Exception ex)
            {
                var ae = ex as AggregateException;
                if (ae != null)
                {
                    Exception inner;
                    if (ae.TryUnwrap(out inner))
                        ex = inner;
                }

                HandleSyncFault(ex);
            }
            finally
            {
                if (_wallet != null)
                    _wallet.ChangesProcessed -= _wallet_ChangesProcessed;
                StartupWizardVisible = true;
            }
        }
Пример #2
0
        public TransactionViewModel(Wallet wallet, WalletTransaction transaction, BlockIdentity transactionLocation)
        {
            _transaction = transaction;
            _location = transactionLocation;

            var groupedOutputs = _transaction.NonChangeOutputs.Select(o =>
            {
                var destination = wallet.OutputDestination(o);
                return new GroupedOutput(o.Amount, destination);
            }).Aggregate(new List<GroupedOutput>(), (items, next) =>
            {
                var item = items.Find(a => a.Destination == next.Destination);
                if (item == null)
                    items.Add(next);
                else
                    item.Amount += next.Amount;

                return items;
            });

            Confirmations = BlockChain.Confirmations(wallet.ChainTip.Height, transactionLocation);
            Inputs = _transaction.Inputs.Select(input => new Input(-input.Amount, wallet.AccountName(input.PreviousAccount))).ToArray();
            Outputs = _transaction.Outputs.Select(output => new Output(output.Amount, wallet.OutputDestination(output))).ToArray();
            GroupedOutputs = groupedOutputs;
        }
Пример #3
0
        public AccountViewModel(ShellViewModel shell, Wallet wallet, Account account)
            : base(shell)
        {
            _wallet = wallet;
            _account = account;
            _accountState = _wallet.LookupAccountState(_account);
            UpdateAccountStateProperties(1, _accountState); // TODO: Don't hardcode confs

            RenameAccount = new DelegateCommand(RenameAcountAction);
            ImportKey = new DelegateCommand(ImportKeyAction);

            PopulateTransactionHistory();

            FetchUnspentOutputs = new DelegateCommand(FetchUnspentOutputsAction);

            AddPendingOutput = new ButtonCommand("Add output", AddPendingOutputAction);
            RemovePendingOutput = new DelegateCommand<PendingOutput>(RemovePendingOutputAction);
            FinishCreateTransaction = new ButtonCommand("Send transaction", FinishCreateTransactionAction);
            AddPendingOutputAction();
            RecalculateCreateTransaction();

            RequestAddress = new ButtonCommand("Create single-use address", RequestAddressAction);
        }
Пример #4
0
        /// <summary>
        /// Begins synchronization of the client with the remote wallet process.
        /// A delegate must be passed to be connected to the wallet's ChangesProcessed event to avoid
        /// a race where additional notifications are processed in the sync task before the caller
        /// can connect the event.  The caller is responsible for disconnecting the delegate from the
        /// event handler when finished.
        /// </summary>
        /// <param name="walletEventHandler">Event handler for changes to wallet as new transactions are processed.</param>
        /// <returns>The synced Wallet and the Task that is keeping the wallet in sync.</returns>
        public async Task<Tuple<Wallet, Task>> Synchronize(EventHandler<Wallet.ChangesProcessedEventArgs> walletEventHandler)
        {
            if (walletEventHandler == null)
                throw new ArgumentNullException(nameof(walletEventHandler));

            TransactionNotifications notifications;
            Task notificationsTask;

            // TODO: Initialization requests need timeouts.

            // Loop until synchronization did not race on a reorg.
            while (true)
            {
                // Begin receiving notifications for new and removed wallet transactions before
                // old transactions are downloaded.  Any received notifications are saved to
                // a buffer and are processed after GetAllTransactionsAsync is awaited.
                notifications = new TransactionNotifications(_channel, _tokenSource.Token);
                notificationsTask = notifications.ListenAndBuffer();

                var networkTask = NetworkAsync();

                // Concurrently download transaction history and account properties.
                var txSetTask = GetTransactionsAsync(Wallet.MinRecentTransactions, Wallet.NumRecentBlocks);
                var accountsTask = AccountsAsync();

                var networkResp = await networkTask;
                var activeBlockChain = BlockChainIdentity.FromNetworkBits(networkResp.ActiveNetwork);

                var txSet = await txSetTask;
                var rpcAccounts = await accountsTask;

                var lastAccountBlockHeight = rpcAccounts.CurrentBlockHeight;
                var lastAccountBlockHash = new Sha256Hash(rpcAccounts.CurrentBlockHash.ToByteArray());
                var lastTxBlock = txSet.MinedTransactions.LastOrDefault();
                if (lastTxBlock != null)
                {
                    var lastTxBlockHeight = lastTxBlock.Height;
                    var lastTxBlockHash = lastTxBlock.Hash;
                    if (lastTxBlockHeight > lastAccountBlockHeight ||
                        (lastTxBlockHeight == lastAccountBlockHeight && !lastTxBlockHash.Equals(lastAccountBlockHash)))
                    {
                        _tokenSource.Cancel();
                        continue;
                    }
                }

                // Read all received notifications thus far and determine if synchronization raced
                // on a chain reorganize.  Try again if so.
                IList<WalletChanges> transactionNotifications;
                if (notifications.Buffer.TryReceiveAll(out transactionNotifications))
                {
                    if (transactionNotifications.Any(r => r.DetachedBlocks.Count != 0))
                    {
                        _tokenSource.Cancel();
                        continue;
                    }

                    // Skip all attached block notifications that are in blocks lower than the
                    // block accounts notification.  If blocks exist at or past that height,
                    // the first's hash should equal that from the accounts notification.
                    //
                    // This ensures that both notifications contain data that is valid at this
                    // block.
                    var remainingNotifications = transactionNotifications
                        .SelectMany(r => r.AttachedBlocks)
                        .SkipWhile(b => b.Height < lastAccountBlockHeight)
                        .ToList();
                    if (remainingNotifications.Count != 0)
                    {
                        if (!remainingNotifications[0].Hash.Equals(lastAccountBlockHash))
                        {
                            _tokenSource.Cancel();
                            continue;
                        }
                    }

                    // TODO: Merge remaining notifications with the transaction set.
                    // For now, be lazy and start the whole sync over.
                    if (remainingNotifications.Count > 1)
                    {
                        _tokenSource.Cancel();
                        continue;
                    }
                }

                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,
                    });
                var chainTip = new BlockIdentity(lastAccountBlockHash, lastAccountBlockHeight);
                var wallet = new Wallet(activeBlockChain, txSet, accounts, chainTip);
                wallet.ChangesProcessed += walletEventHandler;

                var syncTask = Task.Run(async () =>
                {
                    var client = WalletService.NewClient(_channel);
                    var accountsStream = client.AccountNotifications(new AccountNotificationsRequest(), cancellationToken: _tokenSource.Token);
                    var accountChangesTask = accountsStream.ResponseStream.MoveNext();
                    var txChangesTask = notifications.Buffer.OutputAvailableAsync();
                    while (true)
                    {
                        var completedTask = await Task.WhenAny(accountChangesTask, txChangesTask);
                        if (!await completedTask)
                        {
                            break;
                        }
                        if (completedTask == accountChangesTask)
                        {
                            var accountProperties = accountsStream.ResponseStream.Current;
                            var account = new Account(accountProperties.AccountNumber);
                            wallet.UpdateAccountProperties(account, accountProperties.AccountName,
                                accountProperties.ExternalKeyCount, accountProperties.InternalKeyCount,
                                accountProperties.ImportedKeyCount);
                            accountChangesTask = accountsStream.ResponseStream.MoveNext();
                        }
                        else if (completedTask == txChangesTask)
                        {
                            var changes = notifications.Buffer.Receive();
                            wallet.ApplyTransactionChanges(changes);
                        }
                    }

                    await notificationsTask;
                });

                return Tuple.Create(wallet, syncTask);
            }
        }
Пример #5
0
        private void _wallet_ChangesProcessed(object sender, Wallet.ChangesProcessedEventArgs e)
        {
            var currentHeight = e.NewChainTip?.Height ?? SyncedBlockHeight;

            var movedTxViewModels = _recentActivityViewModel.RecentTransactions
                .Where(txvm => e.MovedTransactions.ContainsKey(txvm.TxHash))
                .Select(txvm => Tuple.Create(txvm, e.MovedTransactions[txvm.TxHash]));

            var newTxViewModels = e.AddedTransactions.Select(tx => new TransactionViewModel(_wallet, tx.Item1, tx.Item2)).ToList();

            foreach (var kvp in e.ModifiedAccountProperties)
            {
                var account = kvp.Key;
                var state = kvp.Value;
                var recentAccountVM = RecentAccounts.FirstOrDefault(vm => vm.Account == account);
                if (recentAccountVM != null)
                {
                    recentAccountVM.AccountName = state.AccountName;
                    recentAccountVM.Balance = state.TotalBalance;
                }
                else
                {
                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        RecentAccounts.Add(new RecentAccountViewModel(this, account, state));
                    });
                }
            }

            RaisePropertyChanged(nameof(TotalBalance));

            if (VisibleContent is AccountViewModel)
            {
                var accountViewModel = (AccountViewModel)VisibleContent;
                AccountProperties accountProperties;
                if (e.ModifiedAccountProperties.TryGetValue(accountViewModel.Account, out accountProperties))
                {
                    accountViewModel.UpdateAccountProperties(1, accountProperties);
                }
            }

            foreach (var movedTx in movedTxViewModels)
            {
                var txvm = movedTx.Item1;
                var location = movedTx.Item2;

                txvm.Location = location;
                txvm.Confirmations = BlockChain.Confirmations(currentHeight, location);
            }

            Application.Current.Dispatcher.Invoke(() =>
            {
                foreach (var txvm in newTxViewModels)
                {
                    _recentActivityViewModel.RecentTransactions.Insert(0, txvm);
                }
            });

            if (e.NewChainTip != null)
            {
                SyncedBlockHeight = ((BlockIdentity)(e.NewChainTip)).Height;
            }
        }