void GetStatus(StatusOptions options) { options.Query = options?.Query?.Trim() ?? String.Empty; if (!string.IsNullOrWhiteSpace(options.Query)) { bool parsed = false; try { options.CycleId = int.Parse(options.Query); parsed = true; } catch { } try { options.TxId = new uint256(options.Query).ToString(); parsed = true; } catch { } try { options.Address = BitcoinAddress.Create(options.Query, Runtime.Network).ToString(); parsed = true; } catch { } if (!parsed) { throw new FormatException(); } } if (options.CycleId != null) { CycleParameters cycle = null; try { cycle = Runtime.TumblerParameters?.CycleGenerator?.GetCycle(options.CycleId.Value); } catch { Console.WriteLine("Invalid cycle"); return; } var records = Runtime.Tracker.GetRecords(options.CycleId.Value); var currentHeight = Runtime.Services.BlockExplorerService.GetCurrentHeight(); var phases = new[] { CyclePhase.Registration, CyclePhase.ClientChannelEstablishment, CyclePhase.TumblerChannelEstablishment, CyclePhase.PaymentPhase, CyclePhase.TumblerCashoutPhase, CyclePhase.ClientCashoutPhase }; if (cycle != null) { Console.WriteLine("Phases:"); Console.WriteLine(cycle.ToString(currentHeight)); var periods = cycle.GetPeriods(); foreach (var phase in phases) { var period = periods.GetPeriod(phase); if (period.IsInPeriod(currentHeight)) { Console.WriteLine($"In phase: {phase.ToString()} ({(period.End - currentHeight)} blocks left)"); } } Console.WriteLine(); } Console.WriteLine("Records:"); foreach (var correlationGroup in records.GroupBy(r => r.Correlation)) { Console.WriteLine("========"); foreach (var group in correlationGroup.GroupBy(r => r.TransactionType).OrderBy(r => (int)r.Key)) { var builder = new StringBuilder(); builder.AppendLine(group.Key.ToString()); foreach (var data in group.OrderBy(g => g.RecordType)) { builder.Append("\t" + data.RecordType.ToString()); if (data.ScriptPubKey != null) { builder.AppendLine(" " + data.ScriptPubKey.GetDestinationAddress(Runtime.Network)); } if (data.TransactionId != null) { builder.AppendLine(" " + data.TransactionId); } } Console.WriteLine(builder.ToString()); } Console.WriteLine("========"); } } if (options.TxId != null) { var currentHeight = Runtime.Services.BlockExplorerService.GetCurrentHeight(); var txId = new uint256(options.TxId); var result = Runtime.Tracker.Search(txId); foreach (var record in result) { Console.WriteLine("Cycle " + record.Cycle); Console.WriteLine("Type " + record.TransactionType); } var knownTransaction = Runtime.Services.TrustedBroadcastService.GetKnownTransaction(txId); Transaction tx = knownTransaction?.Transaction; if (knownTransaction != null) { if (knownTransaction.BroadcastableHeight != 0) { var blockLeft = (knownTransaction.BroadcastableHeight - currentHeight); Console.Write("Planned for " + knownTransaction.BroadcastableHeight.ToString()); if (blockLeft > 0) { Console.WriteLine($" ({blockLeft} blocks left)"); } else { Console.WriteLine($" ({-blockLeft} blocks ago)"); } } } if (tx == null) { tx = Runtime.Services.BroadcastService.GetKnownTransaction(txId); } var txInfo = Runtime.Services.BlockExplorerService.GetTransaction(txId); if (tx == null) { tx = txInfo?.Transaction; } if (txInfo != null) { if (txInfo.Confirmations != 0) { Console.WriteLine(txInfo.Confirmations + " Confirmations"); } else { Console.WriteLine("Unconfirmed"); } } if (tx != null) { Console.WriteLine("Hex " + tx.ToHex()); } //TODO ask to other objects for more info } if (options.Address != null) { var address = BitcoinAddress.Create(options.Address, Runtime.TumblerParameters.Network); var result = Runtime.Tracker.Search(address.ScriptPubKey); foreach (var record in result) { Console.WriteLine("Cycle " + record.Cycle); Console.WriteLine("Type " + record.TransactionType); } if (Runtime.DestinationWallet != null) { var keyPath = Runtime.DestinationWallet.GetKeyPath(address.ScriptPubKey); if (keyPath != null) { Console.WriteLine("KeyPath: " + keyPath.ToString()); } } } }
//https://medium.com/@nicolasdorier/tumblebit-tumbler-mode-ea44e9a2a2ec#.a4wgwa86u public void CanCalculatePhase() { var parameter = new CycleParameters { Start = 100, RegistrationDuration = 10, ClientChannelEstablishmentDuration = 11, TumblerChannelEstablishmentDuration = 12, TumblerCashoutDuration = 10, ClientCashoutDuration = 13, PaymentPhaseDuration = 3, SafetyPeriodDuration = 2, }; Assert.Equal("{[..o.......]..[...........]..[............]..[[...].......][.............]..}", parameter.ToString(102)); // 0 10 12 23 25 37 39 42 49 62 Assert.True(parameter.IsInPhase(CyclePhase.Registration, 100)); Assert.True(parameter.IsInPhase(CyclePhase.Registration, 109)); Assert.False(parameter.IsInPhase(CyclePhase.Registration, 110)); Assert.True(parameter.IsInPhase(CyclePhase.ClientChannelEstablishment, 112)); Assert.True(parameter.IsInPhase(CyclePhase.TumblerChannelEstablishment, 125)); Assert.True(parameter.IsInPhase(CyclePhase.TumblerCashoutPhase, 139)); Assert.True(parameter.IsInPhase(CyclePhase.PaymentPhase, 139)); Assert.True(parameter.IsInPhase(CyclePhase.ClientCashoutPhase, 149)); Assert.Equal(149 + 2, parameter.GetClientLockTime().Height); var total = parameter.GetPeriods().Total; Assert.Equal(100, total.Start); Assert.Equal(162 + 2, total.End); Assert.Equal(162 + 2, parameter.GetTumblerLockTime().Height); var cycleGenerator = new OverlappedCycleGenerator(); cycleGenerator.FirstCycle = parameter; cycleGenerator.RegistrationOverlap = 3; Assert.Equal(100, cycleGenerator.GetRegistratingCycle(100).Start); Assert.Equal(100, cycleGenerator.GetRegistratingCycle(106).Start); Assert.Equal(107, cycleGenerator.GetRegistratingCycle(107).Start); }
void GetStatus(StatusOptions options) { options.Query = options?.Query?.Trim() ?? String.Empty; if (!string.IsNullOrWhiteSpace(options.Query)) { bool parsed = false; if (options.Query.StartsWith("now", StringComparison.Ordinal)) { var blockCount = Runtime.Services.BlockExplorerService.GetCurrentHeight(); options.CycleId = Runtime.TumblerParameters?.CycleGenerator?.GetCycles(blockCount) .OrderByDescending(o => o.Start) .Select(o => o.Start) .FirstOrDefault(); parsed = options.CycleId != 0; options.Query = options.Query.Replace("now", options.CycleId.Value.ToString()); } try { var regex = System.Text.RegularExpressions.Regex.Match(options.Query, @"^(\d+)(([+|-])(\d))?$"); if (regex.Success) { options.CycleId = int.Parse(regex.Groups[1].Value); if (regex.Groups[3].Success && regex.Groups[4].Success) { int offset = 1; if (regex.Groups[3].Value.Equals("-", StringComparison.OrdinalIgnoreCase)) { offset = -1; } offset = offset * int.Parse(regex.Groups[4].Value); options.CycleOffset = offset; } parsed = true; } } catch { } try { options.TxId = new uint256(options.Query).ToString(); parsed = true; } catch { } try { options.Address = BitcoinAddress.Create(options.Query, Runtime.Network).ToString(); parsed = true; } catch { } if (!parsed) { throw new FormatException(); } } Stats stats = new Stats(); Stats statsTotal = new Stats(); if (options.CycleId != null) { while (options.PreviousCount > 0) { CycleParameters cycle = null; try { cycle = Runtime.TumblerParameters.CycleGenerator.GetCycle(options.CycleId.Value); if (cycle == null) { throw new NullReferenceException(); //Cleanup } for (int i = 0; i < Math.Abs(options.CycleOffset); i++) { cycle = options.CycleOffset < 0 ? Runtime.TumblerParameters.CycleGenerator.GetPreviousCycle(cycle) : Runtime.TumblerParameters.CycleGenerator.GetNextCycle(cycle); } options.CycleId = cycle.Start; } catch { Console.WriteLine("Invalid cycle"); return; } var state = Services.OfType <StateMachinesExecutor>().Select(e => e.GetPaymentStateMachineState(cycle)).FirstOrDefault(); var records = Runtime.Tracker.GetRecords(options.CycleId.Value); var currentHeight = Runtime.Services.BlockExplorerService.GetCurrentHeight(); bool hasData = false; var phases = new[] { CyclePhase.Registration, CyclePhase.ClientChannelEstablishment, CyclePhase.TumblerChannelEstablishment, CyclePhase.PaymentPhase, CyclePhase.TumblerCashoutPhase, CyclePhase.ClientCashoutPhase }; Console.WriteLine("====================================="); if (cycle != null) { Console.WriteLine("CycleId: " + cycle.Start); if (state != null) { Console.WriteLine("Status: " + state.Status); hasData = true; } Console.WriteLine("Phases:"); Console.WriteLine(cycle.ToString(currentHeight)); var periods = cycle.GetPeriods(); foreach (var phase in phases) { var period = periods.GetPeriod(phase); if (period.IsInPeriod(currentHeight)) { Console.WriteLine($"In phase: {phase.ToString()} ({(period.End - currentHeight)} blocks left)"); } } Console.WriteLine(); } Console.WriteLine("Records:"); foreach (var correlationGroup in records.GroupBy(r => r.Correlation)) { stats.CorrelationGroupCount++; hasData = true; Console.WriteLine("========"); var transactions = correlationGroup.Where(o => o.RecordType == RecordType.Transaction).ToArray(); if (state == null) { var isBob = transactions.Any(o => o.TransactionType == TransactionType.TumblerEscrow); var isAlice = transactions.Any(o => o.TransactionType == TransactionType.ClientEscrow); if (isBob) { stats.BobCount++; } if (isAlice) { stats.AliceCount++; } var isUncooperative = transactions.Any(o => o.TransactionType == TransactionType.ClientFulfill) && transactions.All(o => o.TransactionType != TransactionType.ClientEscape); if (isUncooperative) { stats.UncooperativeCount++; } var isCashout = transactions.Any(o => (o.TransactionType == TransactionType.ClientEscape || o.TransactionType == TransactionType.ClientFulfill)); if (isCashout) { stats.CashoutCount++; } } else { var isUncooperative = transactions.Any(o => (o.TransactionType == TransactionType.ClientOffer || o.TransactionType == TransactionType.ClientOfferRedeem)); if (isUncooperative) { stats.UncooperativeCount++; } var isCashout = transactions.Any(o => (o.TransactionType == TransactionType.TumblerCashout)); if (isCashout) { stats.CashoutCount++; } } foreach (var group in correlationGroup.GroupBy(r => r.TransactionType).OrderBy(r => (int)r.Key)) { var builder = new StringBuilder(); builder.AppendLine(group.Key.ToString()); foreach (var data in group.OrderBy(g => g.RecordType)) { builder.Append("\t" + data.RecordType.ToString()); if (data.ScriptPubKey != null) { builder.AppendLine(" " + data.ScriptPubKey.GetDestinationAddress(Runtime.Network)); } if (data.TransactionId != null) { builder.AppendLine(" " + data.TransactionId); } } Console.WriteLine(builder.ToString()); } Console.WriteLine("========"); } if (!hasData) { Console.WriteLine("Cycle " + cycle.Start + " has no data"); } Console.WriteLine("Status for " + cycle.Start + ":"); Console.Write(stats.ToString()); Console.WriteLine("====================================="); statsTotal = statsTotal + stats; stats = new Stats(); options.PreviousCount--; try { options.CycleId = Runtime.TumblerParameters.CycleGenerator.GetPreviousCycle(cycle).Start; } catch { break; } } Console.WriteLine("Stats Total:"); Console.Write(statsTotal.ToString()); } if (options.TxId != null) { var currentHeight = Runtime.Services.BlockExplorerService.GetCurrentHeight(); var txId = new uint256(options.TxId); var result = Runtime.Tracker.Search(txId); foreach (var record in result) { Console.WriteLine("Cycle " + record.Cycle); Console.WriteLine("Type " + record.TransactionType); } var knownTransaction = Runtime.Services.TrustedBroadcastService.GetKnownTransaction(txId); Transaction tx = knownTransaction?.Transaction; if (knownTransaction != null) { if (knownTransaction.BroadcastableHeight != 0) { var blockLeft = (knownTransaction.BroadcastableHeight - currentHeight); Console.Write("Planned for " + knownTransaction.BroadcastableHeight.ToString()); if (blockLeft > 0) { Console.WriteLine($" ({blockLeft} blocks left)"); } else { Console.WriteLine($" ({-blockLeft} blocks ago)"); } } } if (tx == null) { tx = Runtime.Services.BroadcastService.GetKnownTransaction(txId); } var txInfo = Runtime.Services.BlockExplorerService.GetTransaction(txId); if (tx == null) { tx = txInfo?.Transaction; } if (txInfo != null) { if (txInfo.Confirmations != 0) { Console.WriteLine(txInfo.Confirmations + " Confirmations"); } else { Console.WriteLine("Unconfirmed"); } } if (tx != null) { Console.WriteLine("Hex " + tx.ToHex()); } //TODO ask to other objects for more info } if (options.Address != null) { var address = BitcoinAddress.Create(options.Address, Runtime.TumblerParameters.Network); var result = Runtime.Tracker.Search(address.ScriptPubKey); foreach (var record in result) { Console.WriteLine("Cycle " + record.Cycle); Console.WriteLine("Type " + record.TransactionType); } if (Runtime.DestinationWallet != null) { var keyPath = Runtime.DestinationWallet.GetKeyPath(address.ScriptPubKey); if (keyPath != null) { Console.WriteLine("KeyPath: " + keyPath.ToString()); } } } }