/// <summary> /// /// </summary> /// <param name="account">if null then default safe, if doesn't contain, then exception</param> /// <returns></returns> public IEnumerable <SafeHistoryRecord> GetSafeHistory(SafeAccount account = null) { AssertAccount(account); var safeHistory = new HashSet <SafeHistoryRecord>(); var transactions = GetAllChainAndMemPoolTransactionsBySafeAccount(account); var scriptPubKeys = GetTrackedScriptPubKeysBySafeAccount(account); foreach (SmartTransaction transaction in transactions) { SafeHistoryRecord record = new SafeHistoryRecord(); record.TransactionId = transaction.GetHash(); record.BlockHeight = transaction.Height; // todo: the mempool could note when it seen the transaction the first time record.TimeStamp = !transaction.Confirmed ? DateTimeOffset.UtcNow : HeaderChain.GetBlock(transaction.Height).Header.BlockTime; record.Amount = Money.Zero; //for now // how much came to our scriptpubkeys foreach (var output in transaction.Transaction.Outputs) { if (scriptPubKeys.Contains(output.ScriptPubKey)) { record.Amount += output.Value; } } foreach (var input in transaction.Transaction.Inputs) { // do we have the input? SmartTransaction inputTransaction = transactions.FirstOrDefault(x => x.GetHash() == input.PrevOut.Hash); if (default(SmartTransaction) != inputTransaction) { // if yes then deduct from amount (bitcoin output cannot be partially spent) var prevOutput = inputTransaction.Transaction.Outputs[input.PrevOut.N]; if (scriptPubKeys.Contains(prevOutput.ScriptPubKey)) { record.Amount -= prevOutput.Value; } } // if no then whatever } safeHistory.Add(record); } return(safeHistory.ToList().OrderBy(x => x.TimeStamp)); }
public void RealHistoryTest() { // load wallet Network network = Network.TestNet; string path = Path.Combine(Helpers.CommittedWalletsFolderPath, $"HiddenWallet.json"); const string password = ""; // I change it because I am using a very old wallet to test Safe.EarliestPossibleCreationTime = DateTimeOffset.ParseExact("2016-12-18", "yyyy-MM-dd", CultureInfo.InvariantCulture); Safe safe = Safe.Load(password, path); Assert.Equal(network, safe.Network); Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}"); // create walletjob WalletJob walletJob = new WalletJob(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe) { MaxCleanAddressCount = 79 }; // note some event WalletJob.ConnectedNodeCountChanged += WalletJob_ConnectedNodeCountChanged; _syncedOnce = false; walletJob.StateChanged += WalletJob_StateChanged; // start syncing var cts = new CancellationTokenSource(); var walletJobTask = walletJob.StartAsync(cts.Token); Task reportTask = Helpers.ReportAsync(cts.Token, walletJob); try { // wait until fully synced while (!_syncedOnce) { Task.Delay(1000).Wait(); } Helpers.ReportFullHistory(walletJob); // 0. Query all operations, grouped our used safe addresses int MinUnusedKeyNum = 37; Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses = Helpers.QueryOperationsPerSafeAddressesAsync(new QBitNinjaClient(safe.Network), safe, MinUnusedKeyNum).Result; Dictionary <uint256, List <BalanceOperation> > operationsPerTransactions = QBitNinjaJutsus.GetOperationsPerTransactions(operationsPerAddresses); // 3. Create history records from the transactions // History records is arbitrary data we want to show to the user var txHistoryRecords = new List <Tuple <DateTimeOffset, Money, int, uint256> >(); foreach (var elem in operationsPerTransactions) { var amount = Money.Zero; foreach (var op in elem.Value) { amount += op.Amount; } var firstOp = elem.Value.First(); txHistoryRecords .Add(new Tuple <DateTimeOffset, Money, int, uint256>( firstOp.FirstSeen, amount, firstOp.Confirmations, elem.Key)); } // 4. Order the records by confirmations and time (Simply time does not work, because of a QBitNinja issue) var qBitHistoryRecords = txHistoryRecords .OrderByDescending(x => x.Item3) // Confirmations .ThenBy(x => x.Item1); // FirstSeen var fullSpvHistoryRecords = walletJob.GetSafeHistory(); // This won't be equal QBit doesn't show us this transaction: 2017.01.04. 16:24:49 0.00000000 True 77b10ff78aab2e41764a05794c4c464922c73f0c23356190429833ce68fd7be9 // Assert.Equal(qBitHistoryRecords.Count(), fullSpvHistoryRecords.Count()); HashSet <SafeHistoryRecord> qBitFoundItToo = new HashSet <SafeHistoryRecord>(); // Assert all record found by qbit also found by spv and they are identical foreach (var record in qBitHistoryRecords) { // Item2 is the Amount SafeHistoryRecord found = fullSpvHistoryRecords.FirstOrDefault(x => x.TransactionId == record.Item4); Assert.True(found != default(SafeHistoryRecord)); Assert.True(found.TimeStamp.Equals(record.Item1)); Assert.True(found.Confirmed.Equals(record.Item3 > 0)); Assert.True(found.Amount.Equals(record.Item2)); qBitFoundItToo.Add(found); } foreach (var record in fullSpvHistoryRecords) { if (!qBitFoundItToo.Contains(record)) { Assert.True(null == qBitHistoryRecords.FirstOrDefault(x => x.Item4 == record.TransactionId)); Debug.WriteLine($@"QBitNinja failed to find, but SPV found it: {record.TimeStamp.DateTime} {record.Amount} {record.Confirmed} {record.TransactionId}"); } } } finally { cts.Cancel(); Task.WhenAll(reportTask, walletJobTask).Wait(); WalletJob.ConnectedNodeCountChanged -= WalletJob_ConnectedNodeCountChanged; walletJob.StateChanged -= WalletJob_StateChanged; } }