public CyclePeriod GetPeriod(CyclePhase phase) { switch (phase) { case CyclePhase.Registration: return(Registration); case CyclePhase.ClientChannelEstablishment: return(ClientChannelEstablishment); case CyclePhase.TumblerChannelEstablishment: return(TumblerChannelEstablishment); case CyclePhase.TumblerCashoutPhase: return(TumblerCashout); case CyclePhase.PaymentPhase: return(Payment); case CyclePhase.ClientCashoutPhase: return(ClientCashout); default: throw new NotSupportedException(); } }
private void CheckPhase(CyclePhase expectedPhase, int height, int cycleId) { CycleParameters cycle = GetCycle(cycleId); if (!cycle.IsInPhase(expectedPhase, height)) { throw BadRequest("invalid-phase").AsException(); } }
private void MineTo(CoreNode node, CycleParameters cycle, CyclePhase phase, bool end = false, int offset = 0) { var height = node.CreateRPCClient().GetBlockCount(); var periodStart = end ? cycle.GetPeriods().GetPeriod(phase).End : cycle.GetPeriods().GetPeriod(phase).Start; var blocksToFind = periodStart - height + offset; if (blocksToFind <= 0) { return; } node.FindBlock(blocksToFind); }
public CyclePhase CreateNewTestCyclePhase(Cycle cycle, string bfName) { try { CyclePhase CyclePhase = zephyrEntRepository.CreateCyclePhase(new CyclePhase() { name = bfName, phaseStartDate = cycle.cycleStartDate, phaseEndDate = cycle.cycleEndDate, freeForm = true, releaseId = projectId }, cycle.id); return(CyclePhase); } catch (Exception ex) { Reporter.ToLog(eLogLevel.ERROR, "Failed to create Zephyr Ent. new cycle phase " + GingerDicser.GetTermResValue(eTermResKey.BusinessFlow), ex); return(null); } }
public CycleProgressInfo(CyclePeriod period, int height, int blocksLeft, int start, PaymentStateMachineStatus status, CyclePhase phase, bool isSafetyPeriod, string asciiArt) { this.Period = period; this.Height = height; this.BlocksLeft = blocksLeft; this.Start = start; this.StatusEnum = status; this.PhaseEnum = phase; this.IsSafetyPeriod = isSafetyPeriod; this.AsciiArt = asciiArt; this.CheckForFailedState(); }
public void Update() { int height = Services.BlockExplorerService.GetCurrentHeight(); CycleParameters cycle; CyclePhase phase; if (ClientChannelNegotiation == null) { cycle = Parameters.CycleGenerator.GetRegistratingCycle(height); phase = CyclePhase.Registration; } else { cycle = ClientChannelNegotiation.GetCycle(); var phases = new CyclePhase[] { CyclePhase.Registration, CyclePhase.ClientChannelEstablishment, CyclePhase.TumblerChannelEstablishment, CyclePhase.PaymentPhase, CyclePhase.TumblerCashoutPhase, CyclePhase.ClientCashoutPhase }; if (!phases.Any(p => cycle.IsInPhase(p, height))) { return; } phase = phases.First(p => cycle.IsInPhase(p, height)); } Logs.Client.LogInformation("[[[Updating cycle " + cycle.Start + "]]]"); Logs.Client.LogInformation("Phase " + Enum.GetName(typeof(CyclePhase), phase) + ", ending in " + (cycle.GetPeriods().GetPeriod(phase).End - height) + " blocks"); TumblerClient bob = null, alice = null; try { var correlation = SolverClientSession == null ? 0 : GetCorrelation(SolverClientSession.EscrowedCoin); FeeRate feeRate = null; switch (phase) { case CyclePhase.Registration: if (ClientChannelNegotiation == null) { bob = Runtime.CreateTumblerClient(cycle.Start, Identity.Bob); //Client asks for voucher var voucherResponse = bob.AskUnsignedVoucher(); //Client ensures he is in the same cycle as the tumbler (would fail if one tumbler or client's chain isn't sync) var tumblerCycle = Parameters.CycleGenerator.GetCycle(voucherResponse.CycleStart); Assert(tumblerCycle.Start == cycle.Start, "invalid-phase"); //Saving the voucher for later StartCycle = cycle.Start; ClientChannelNegotiation = new ClientChannelNegotiation(Parameters, cycle.Start); ClientChannelNegotiation.ReceiveUnsignedVoucher(voucherResponse); Logs.Client.LogInformation("Registered"); } break; case CyclePhase.ClientChannelEstablishment: if (ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingTumblerClientTransactionKey) { alice = Runtime.CreateTumblerClient(cycle.Start, Identity.Alice); var key = alice.RequestTumblerEscrowKey(); ClientChannelNegotiation.ReceiveTumblerEscrowKey(key.PubKey, key.KeyIndex); //Client create the escrow var escrowTxOut = ClientChannelNegotiation.BuildClientEscrowTxOut(); feeRate = GetFeeRate(); Transaction clientEscrowTx = null; try { clientEscrowTx = Services.WalletService.FundTransaction(escrowTxOut, feeRate); } catch (NotEnoughFundsException ex) { Logs.Client.LogInformation($"Not enough funds in the wallet to tumble. Missing about {ex.Missing}. Denomination is {Parameters.Denomination}."); break; } var redeemDestination = Services.WalletService.GenerateAddress().ScriptPubKey; SolverClientSession = ClientChannelNegotiation.SetClientSignedTransaction(clientEscrowTx, redeemDestination); correlation = GetCorrelation(SolverClientSession.EscrowedCoin); Tracker.AddressCreated(cycle.Start, TransactionType.ClientEscrow, escrowTxOut.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.ClientEscrow, clientEscrowTx.GetHash(), correlation); Services.BlockExplorerService.Track(escrowTxOut.ScriptPubKey); var redeemTx = SolverClientSession.CreateRedeemTransaction(feeRate); Tracker.AddressCreated(cycle.Start, TransactionType.ClientRedeem, redeemDestination, correlation); //redeemTx does not be to be recorded to the tracker, this is TrustedBroadcastService job Services.BroadcastService.Broadcast(clientEscrowTx); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.ClientRedeem, correlation, redeemTx); Logs.Client.LogInformation("Client channel broadcasted"); } else if (ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingSolvedVoucher) { alice = Runtime.CreateTumblerClient(cycle.Start, Identity.Alice); TransactionInformation clientTx = GetTransactionInformation(SolverClientSession.EscrowedCoin, true); var state = ClientChannelNegotiation.GetInternalState(); if (clientTx != null && clientTx.Confirmations >= cycle.SafetyPeriodDuration) { Logs.Client.LogInformation($"Client escrow reached {cycle.SafetyPeriodDuration} confirmations"); //Client asks the public key of the Tumbler and sends its own var voucher = alice.SignVoucher(new SignVoucherRequest { MerkleProof = clientTx.MerkleProof, Transaction = clientTx.Transaction, KeyReference = state.TumblerEscrowKeyReference, UnsignedVoucher = state.BlindedVoucher, Cycle = cycle.Start, ClientEscrowKey = state.ClientEscrowKey.PubKey }); ClientChannelNegotiation.CheckVoucherSolution(voucher); Logs.Client.LogInformation($"Tumbler escrow voucher obtained"); } } break; case CyclePhase.TumblerChannelEstablishment: if (ClientChannelNegotiation != null && ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingGenerateTumblerTransactionKey) { bob = Runtime.CreateTumblerClient(cycle.Start, Identity.Bob); //Client asks the Tumbler to make a channel var bobEscrowInformation = ClientChannelNegotiation.GetOpenChannelRequest(); var tumblerInformation = bob.OpenChannel(bobEscrowInformation); PromiseClientSession = ClientChannelNegotiation.ReceiveTumblerEscrowedCoin(tumblerInformation); Logs.Client.LogInformation("Tumbler escrow broadcasted"); //Tell to the block explorer we need to track that address (for checking if it is confirmed in payment phase) Services.BlockExplorerService.Track(PromiseClientSession.EscrowedCoin.ScriptPubKey); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, PromiseClientSession.EscrowedCoin.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, PromiseClientSession.EscrowedCoin.Outpoint.Hash, correlation); //Channel is done, now need to run the promise protocol to get valid puzzle var cashoutDestination = DestinationWallet.GetNewDestination(); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerCashout, cashoutDestination, correlation); feeRate = GetFeeRate(); var sigReq = PromiseClientSession.CreateSignatureRequest(cashoutDestination, feeRate); var commiments = bob.SignHashes(PromiseClientSession.Id, sigReq); var revelation = PromiseClientSession.Reveal(commiments); var proof = bob.CheckRevelation(PromiseClientSession.Id, revelation); var puzzle = PromiseClientSession.CheckCommitmentProof(proof); SolverClientSession.AcceptPuzzle(puzzle); Logs.Client.LogInformation("Tumbler escrow puzzle obtained"); } break; case CyclePhase.PaymentPhase: if (PromiseClientSession != null) { TransactionInformation tumblerTx = GetTransactionInformation(PromiseClientSession.EscrowedCoin, false); //Ensure the tumbler coin is confirmed before paying anything if (tumblerTx != null || tumblerTx.Confirmations >= cycle.SafetyPeriodDuration) { Logs.Client.LogInformation($"Client escrow reached {cycle.SafetyPeriodDuration} confirmations"); if (SolverClientSession.Status == SolverClientStates.WaitingGeneratePuzzles) { feeRate = GetFeeRate(); alice = Runtime.CreateTumblerClient(cycle.Start, Identity.Alice); var puzzles = SolverClientSession.GeneratePuzzles(); var commmitments = alice.SolvePuzzles(SolverClientSession.Id, puzzles); var revelation2 = SolverClientSession.Reveal(commmitments); var solutionKeys = alice.CheckRevelation(SolverClientSession.Id, revelation2); var blindFactors = SolverClientSession.GetBlindFactors(solutionKeys); var offerInformation = alice.CheckBlindFactors(SolverClientSession.Id, blindFactors); var offerSignature = SolverClientSession.SignOffer(offerInformation); var offerRedeem = SolverClientSession.CreateOfferRedeemTransaction(feeRate); //May need to find solution in the fulfillment transaction Services.BlockExplorerService.Track(offerRedeem.PreviousScriptPubKey); Tracker.AddressCreated(cycle.Start, TransactionType.ClientOfferRedeem, SolverClientSession.GetInternalState().RedeemDestination, correlation); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.ClientOfferRedeem, correlation, offerRedeem); try { solutionKeys = alice.FulfillOffer(SolverClientSession.Id, offerSignature); SolverClientSession.CheckSolutions(solutionKeys); var tumblingSolution = SolverClientSession.GetSolution(); var transaction = PromiseClientSession.GetSignedTransaction(tumblingSolution); Logs.Client.LogInformation("Got puzzle solution cooperatively from the tumbler"); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.TumblerCashout, correlation, new TrustedBroadcastRequest() { BroadcastAt = cycle.GetPeriods().ClientCashout.Start, Transaction = transaction }); if (Cooperative) { var signature = SolverClientSession.SignEscape(); alice.GiveEscapeKey(SolverClientSession.Id, signature); Logs.Client.LogInformation("Gave escape signature to the tumbler"); } } catch (Exception ex) { Logs.Client.LogWarning("The tumbler did not gave puzzle solution cooperatively"); Logs.Client.LogWarning(ex.ToString()); } } } } break; case CyclePhase.ClientCashoutPhase: if (SolverClientSession != null) { //If the tumbler is uncooperative, he published solutions on the blockchain if (SolverClientSession.Status == SolverClientStates.WaitingPuzzleSolutions) { var transactions = Services.BlockExplorerService.GetTransactions(SolverClientSession.GetInternalState().OfferCoin.ScriptPubKey, false); if (transactions.Length != 0) { SolverClientSession.CheckSolutions(transactions.Select(t => t.Transaction).ToArray()); Logs.Client.LogInformation("Puzzle solution recovered from tumbler's fulfill transaction"); var tumblingSolution = SolverClientSession.GetSolution(); var transaction = PromiseClientSession.GetSignedTransaction(tumblingSolution); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerCashout, transaction.GetHash(), correlation); Services.BroadcastService.Broadcast(transaction); } } } break; } } finally { if (alice != null && bob != null) { throw new InvalidOperationException("Bob and Alice have been both initialized, please report the bug to NTumbleBit developers"); } if (alice != null) { alice.Dispose(); } if (bob != null) { bob.Dispose(); } } }
public void Update() { int height = Services.BlockExplorerService.GetCurrentHeight(); CycleParameters cycle; CyclePhase phase; if (ClientChannelNegotiation == null) { cycle = Parameters.CycleGenerator.GetRegistratingCycle(height); phase = CyclePhase.Registration; } else { cycle = ClientChannelNegotiation.GetCycle(); var phases = new CyclePhase[] { CyclePhase.Registration, CyclePhase.ClientChannelEstablishment, CyclePhase.TumblerChannelEstablishment, CyclePhase.PaymentPhase, CyclePhase.TumblerCashoutPhase, CyclePhase.ClientCashoutPhase }; if (!phases.Any(p => cycle.IsInPhase(p, height))) { return; } phase = phases.First(p => cycle.IsInPhase(p, height)); } Logs.Client.LogInformation(Environment.NewLine); var period = cycle.GetPeriods().GetPeriod(phase); var blocksLeft = period.End - height; Logs.Client.LogInformation($"Cycle {cycle.Start} ({Status})"); Logs.Client.LogInformation($"{cycle.ToString(height)} in phase {phase} ({blocksLeft} more blocks)"); var previousState = Status; TumblerClient bob = null, alice = null; try { var correlation = SolverClientSession == null ? CorrelationId.Zero : new CorrelationId(SolverClientSession.Id); FeeRate feeRate = null; switch (phase) { case CyclePhase.Registration: if (ClientChannelNegotiation == null) { bob = Runtime.CreateTumblerClient(cycle.Start, Identity.Bob); //Client asks for voucher var voucherResponse = bob.AskUnsignedVoucher(); //Client ensures he is in the same cycle as the tumbler (would fail if one tumbler or client's chain isn't sync) var tumblerCycle = Parameters.CycleGenerator.GetCycle(voucherResponse.CycleStart); Assert(tumblerCycle.Start == cycle.Start, "invalid-phase"); //Saving the voucher for later StartCycle = cycle.Start; ClientChannelNegotiation = new ClientChannelNegotiation(Parameters, cycle.Start); ClientChannelNegotiation.ReceiveUnsignedVoucher(voucherResponse); Status = PaymentStateMachineStatus.Registered; } break; case CyclePhase.ClientChannelEstablishment: if (ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingTumblerClientTransactionKey) { alice = Runtime.CreateTumblerClient(cycle.Start, Identity.Alice); var key = alice.RequestTumblerEscrowKey(); ClientChannelNegotiation.ReceiveTumblerEscrowKey(key.PubKey, key.KeyIndex); //Client create the escrow var escrowTxOut = ClientChannelNegotiation.BuildClientEscrowTxOut(); feeRate = GetFeeRate(); Transaction clientEscrowTx = null; try { clientEscrowTx = Services.WalletService.FundTransactionAsync(escrowTxOut, feeRate).GetAwaiter().GetResult(); } catch (NotEnoughFundsException ex) { Logs.Client.LogInformation($"Not enough funds in the wallet to tumble. Missing about {ex.Missing}. Denomination is {Parameters.Denomination}."); break; } var redeemDestination = Services.WalletService.GenerateAddress().ScriptPubKey; var channelId = new uint160(RandomUtils.GetBytes(20)); SolverClientSession = ClientChannelNegotiation.SetClientSignedTransaction(channelId, clientEscrowTx, redeemDestination); correlation = new CorrelationId(SolverClientSession.Id); Tracker.AddressCreated(cycle.Start, TransactionType.ClientEscrow, escrowTxOut.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.ClientEscrow, clientEscrowTx.GetHash(), correlation); Services.BlockExplorerService.TrackAsync(escrowTxOut.ScriptPubKey).GetAwaiter().GetResult(); var redeemTx = SolverClientSession.CreateRedeemTransaction(feeRate); Tracker.AddressCreated(cycle.Start, TransactionType.ClientRedeem, redeemDestination, correlation); //redeemTx does not be to be recorded to the tracker, this is TrustedBroadcastService job Services.BroadcastService.BroadcastAsync(clientEscrowTx).GetAwaiter().GetResult(); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.ClientRedeem, correlation, redeemTx); Status = PaymentStateMachineStatus.ClientChannelBroadcasted; } else if (ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingSolvedVoucher) { alice = Runtime.CreateTumblerClient(cycle.Start, Identity.Alice); TransactionInformation clientTx = GetTransactionInformation(SolverClientSession.EscrowedCoin, true); var state = ClientChannelNegotiation.GetInternalState(); if (clientTx != null && clientTx.Confirmations >= cycle.SafetyPeriodDuration) { Logs.Client.LogInformation($"Client escrow reached {cycle.SafetyPeriodDuration} confirmations"); //Client asks the public key of the Tumbler and sends its own var voucher = alice.SignVoucher(new SignVoucherRequest { MerkleProof = clientTx.MerkleProof, Transaction = clientTx.Transaction, KeyReference = state.TumblerEscrowKeyReference, UnsignedVoucher = state.BlindedVoucher, Cycle = cycle.Start, ClientEscrowKey = state.ClientEscrowKey.PubKey, ChannelId = SolverClientSession.Id }); ClientChannelNegotiation.CheckVoucherSolution(voucher); Status = PaymentStateMachineStatus.TumblerVoucherObtained; } } break; case CyclePhase.TumblerChannelEstablishment: bob = Runtime.CreateTumblerClient(cycle.Start, Identity.Bob); if (Status == PaymentStateMachineStatus.TumblerVoucherObtained) { Logs.Client.LogInformation("Begin ask to open the channel..."); //Client asks the Tumbler to make a channel var bobEscrowInformation = ClientChannelNegotiation.GetOpenChannelRequest(); uint160 channelId = null; try { channelId = bob.BeginOpenChannel(bobEscrowInformation); } catch (Exception ex) { if (ex.Message.Contains("tumbler-insufficient-funds")) { Logs.Client.LogWarning("The tumbler server has not enough funds and can't open a channel for now"); break; } throw; } ClientChannelNegotiation.SetChannelId(channelId); Status = PaymentStateMachineStatus.TumblerChannelCreating; } else if (Status == PaymentStateMachineStatus.TumblerChannelCreating) { var tumblerEscrow = bob.EndOpenChannel(cycle.Start, ClientChannelNegotiation.GetInternalState().ChannelId); if (tumblerEscrow == null) { Logs.Client.LogInformation("Tumbler escrow still creating..."); break; } PromiseClientSession = ClientChannelNegotiation.ReceiveTumblerEscrowedCoin(tumblerEscrow); Logs.Client.LogInformation("Tumbler escrow broadcasted"); //Tell to the block explorer we need to track that address (for checking if it is confirmed in payment phase) Services.BlockExplorerService.TrackAsync(PromiseClientSession.EscrowedCoin.ScriptPubKey).GetAwaiter().GetResult(); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, PromiseClientSession.EscrowedCoin.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, PromiseClientSession.EscrowedCoin.Outpoint.Hash, correlation); //Channel is done, now need to run the promise protocol to get valid puzzle var cashoutDestination = DestinationWallet.GetNewDestination(); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerCashout, cashoutDestination, correlation); feeRate = GetFeeRate(); var sigReq = PromiseClientSession.CreateSignatureRequest(cashoutDestination, feeRate); var commitments = bob.SignHashes(PromiseClientSession.Id, sigReq); var revelation = PromiseClientSession.Reveal(commitments); var proof = bob.CheckRevelation(PromiseClientSession.Id, revelation); var puzzle = PromiseClientSession.CheckCommitmentProof(proof); SolverClientSession.AcceptPuzzle(puzzle[0]); Status = PaymentStateMachineStatus.TumblerChannelBroadcasted; } else if (Status == PaymentStateMachineStatus.TumblerChannelBroadcasted) { CheckTumblerChannelConfirmed(cycle); } break; case CyclePhase.PaymentPhase: //Could have confirmed during safe period //Only check for the first block when period start, //else Tumbler can know deanonymize you based on the timing of first Alice request if the transaction was not confirmed previously if (Status == PaymentStateMachineStatus.TumblerChannelBroadcasted && height == period.Start) { CheckTumblerChannelConfirmed(cycle); } if (PromiseClientSession != null && Status == PaymentStateMachineStatus.TumblerChannelConfirmed) { TransactionInformation tumblerTx = GetTransactionInformation(PromiseClientSession.EscrowedCoin, false); //Ensure the tumbler coin is confirmed before paying anything if (tumblerTx != null && tumblerTx.Confirmations >= cycle.SafetyPeriodDuration) { if (SolverClientSession.Status == SolverClientStates.WaitingGeneratePuzzles) { feeRate = GetFeeRate(); alice = Runtime.CreateTumblerClient(cycle.Start, Identity.Alice); var puzzles = SolverClientSession.GeneratePuzzles(); var commmitments = alice.SolvePuzzles(SolverClientSession.Id, puzzles); var revelation2 = SolverClientSession.Reveal(commmitments); var solutionKeys = alice.CheckRevelation(SolverClientSession.Id, revelation2); var blindFactors = SolverClientSession.GetBlindFactors(solutionKeys); var offerInformation = alice.CheckBlindFactors(SolverClientSession.Id, blindFactors); var offerSignature = SolverClientSession.SignOffer(offerInformation); var offerRedeem = SolverClientSession.CreateOfferRedeemTransaction(feeRate); //May need to find solution in the fulfillment transaction Services.BlockExplorerService.TrackAsync(offerRedeem.PreviousScriptPubKey).GetAwaiter().GetResult(); Tracker.AddressCreated(cycle.Start, TransactionType.ClientOfferRedeem, SolverClientSession.GetInternalState().RedeemDestination, correlation); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.ClientOfferRedeem, correlation, offerRedeem); try { solutionKeys = alice.FulfillOffer(SolverClientSession.Id, offerSignature); SolverClientSession.CheckSolutions(solutionKeys); var tumblingSolution = SolverClientSession.GetSolution(); // TODO: Figure out which puzzle is passed here. var transaction = PromiseClientSession.GetSignedTransaction(tumblingSolution, 0); Logs.Client.LogInformation("Got puzzle solution cooperatively from the tumbler"); Status = PaymentStateMachineStatus.PuzzleSolutionObtained; Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.TumblerCashout, correlation, new TrustedBroadcastRequest() { BroadcastAt = cycle.GetPeriods().ClientCashout.Start, Transaction = transaction }); if (Cooperative) { var signature = SolverClientSession.SignEscape(); // No need to await for it, it is a just nice for the tumbler (we don't want the underlying socks connection cut before the escape key is sent) alice.GiveEscapeKeyAsync(SolverClientSession.Id, signature).GetAwaiter().GetResult(); Logs.Client.LogInformation("Gave escape signature to the tumbler"); } } catch (Exception ex) { Status = PaymentStateMachineStatus.UncooperativeTumbler; Logs.Client.LogWarning("The tumbler did not gave puzzle solution cooperatively"); Logs.Client.LogWarning(ex.ToString()); } } } } break; case CyclePhase.ClientCashoutPhase: if (SolverClientSession != null) { //If the tumbler is uncooperative, he published solutions on the blockchain if (SolverClientSession.Status == SolverClientStates.WaitingPuzzleSolutions) { var transactions = Services.BlockExplorerService.GetTransactions(SolverClientSession.GetInternalState().OfferCoin.ScriptPubKey, false); if (transactions.Count != 0) { SolverClientSession.CheckSolutions(transactions.Select(t => t.Transaction).ToArray()); Logs.Client.LogInformation("Puzzle solution recovered from tumbler's fulfill transaction"); Status = PaymentStateMachineStatus.PuzzleSolutionObtained; var tumblingSolution = SolverClientSession.GetSolution(); // TODO: Figure out which puzzle is passed here. var transaction = PromiseClientSession.GetSignedTransaction(tumblingSolution, 0); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerCashout, transaction.GetHash(), correlation); Services.BroadcastService.BroadcastAsync(transaction).GetAwaiter().GetResult(); } } } break; } } finally { if (previousState != Status) { Logs.Client.LogInformation($"Status changed {previousState} => {Status}"); } if (alice != null && bob != null) { throw new InvalidOperationException("Bob and Alice have been both initialized, please report the bug to NTumbleBit developers"); } if (alice != null) { alice.Dispose(); } if (bob != null) { bob.Dispose(); } } }
public CycleProgressInfo Update() { int height = Services.BlockExplorerService.GetCurrentHeight(); CycleParameters cycle; CyclePhase phase; CyclePeriod period; bool isSafetyPeriod = false; if (ClientChannelNegotiation == null) { cycle = Parameters.CycleGenerator.GetRegisteringCycle(height); phase = CyclePhase.Registration; period = cycle.GetPeriods().GetPeriod(phase); } else { cycle = ClientChannelNegotiation.GetCycle(); var phases = new CyclePhase[] { CyclePhase.Registration, CyclePhase.ClientChannelEstablishment, CyclePhase.TumblerChannelEstablishment, CyclePhase.PaymentPhase, CyclePhase.TumblerCashoutPhase, CyclePhase.ClientCashoutPhase }; if (cycle.IsComplete(height)) { return(null); } //If we are not in any phase we are in the SaftyPeriod. if (!phases.Any(p => cycle.IsInPhase(p, height))) { //Find last CyclePhase phase = CyclePhase.Registration; for (int i = height - 1; i >= cycle.Start; i--) { if (phases.Any(p => cycle.IsInPhase(p, i))) { phase = phases.First(p => cycle.IsInPhase(p, i)); break; } } period = cycle.GetPeriods().GetPeriod(phase); period.End += cycle.SafetyPeriodDuration; isSafetyPeriod = true; } else { phase = phases.First(p => cycle.IsInPhase(p, height)); period = cycle.GetPeriods().GetPeriod(phase); isSafetyPeriod = false; } } var blocksLeft = period.End - height; Logs.Client.LogInformation($"Cycle {cycle.Start} ({Status})"); Logs.Client.LogInformation($"{cycle.ToString(height)} in phase {phase} ({blocksLeft} more blocks)"); var previousState = Status; var progressInfo = new CycleProgressInfo(period, height, blocksLeft, cycle.Start, Status, phase, isSafetyPeriod, $"{cycle.ToString(height)} in phase {phase} ({blocksLeft} more blocks)"); TumblerClient bob = null, alice = null; try { var correlation = SolverClientSession == null ? CorrelationId.Zero : new CorrelationId(SolverClientSession.Id); FeeRate feeRate = null; switch (phase) { case CyclePhase.Registration: if (Status == PaymentStateMachineStatus.New) { bob = Runtime.CreateTumblerClient(cycle.Start, Runtime.TumblerProtocol, Identity.Bob); //Client asks for voucher var voucherResponse = bob.AskUnsignedVoucher(); NeedSave = true; //Client ensures he is in the same cycle as the tumbler (would fail if one tumbler or client's chain isn't sync) var tumblerCycle = Parameters.CycleGenerator.GetCycle(voucherResponse.CycleStart); Assert(tumblerCycle.Start == cycle.Start, "invalid-phase"); //Saving the voucher for later StartCycle = cycle.Start; ClientChannelNegotiation = new ClientChannelNegotiation(Parameters, cycle.Start); ClientChannelNegotiation.ReceiveUnsignedVoucher(voucherResponse); Status = PaymentStateMachineStatus.Registered; } break; case CyclePhase.ClientChannelEstablishment: if (Status == PaymentStateMachineStatus.Registered) { alice = Runtime.CreateTumblerClient(cycle.Start, Runtime.TumblerProtocol, Identity.Alice); var key = alice.RequestTumblerEscrowKey(); ClientChannelNegotiation.ReceiveTumblerEscrowKey(key.PubKey, key.KeyIndex); //Client create the escrow var escrowTxOut = ClientChannelNegotiation.BuildClientEscrowTxOut(); Logs.Client.LogDebug($"Alice ClientChannelEstablishment escrowTxOut.ScriptPubKey: {escrowTxOut.ScriptPubKey.ToHex()}, value: {escrowTxOut.Value})"); feeRate = GetFeeRate(); Logs.Client.LogDebug($"Alice ClientChannelEstablishment feeRate.FeePerK: {feeRate.FeePerK})"); Transaction clientEscrowTx = null; try { clientEscrowTx = Services.WalletService.FundTransactionAsync(escrowTxOut, feeRate).GetAwaiter().GetResult(); } catch (NotEnoughFundsException ex) { Logs.Client.LogInformation($"Not enough funds in the wallet to tumble. Missing about {ex.Missing}. Denomination is {Parameters.Denomination}."); break; } Logs.Client.LogDebug($"Alice ClientChannelEstablishment clientEscrowTx: {clientEscrowTx})"); NeedSave = true; var redeemDestination = Services.WalletService.GenerateAddressAsync().GetAwaiter().GetResult().ScriptPubKey; var channelId = new uint160(RandomUtils.GetBytes(20)); SolverClientSession = ClientChannelNegotiation.SetClientSignedTransaction(channelId, clientEscrowTx, redeemDestination); correlation = new CorrelationId(SolverClientSession.Id); Tracker.AddressCreated(cycle.Start, TransactionType.ClientEscrow, escrowTxOut.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.ClientEscrow, clientEscrowTx.GetHash(), correlation); Services.BlockExplorerService.TrackAsync(escrowTxOut.ScriptPubKey).GetAwaiter().GetResult(); var redeemTx = SolverClientSession.CreateRedeemTransaction(feeRate); Logs.Client.LogDebug($"Alice ClientChannelEstablishment redeemTx.Transaction: {redeemTx.Transaction}, redeemTx.BroadcastableHeight: {redeemTx.BroadcastableHeight})"); Tracker.AddressCreated(cycle.Start, TransactionType.ClientRedeem, redeemDestination, correlation); //redeemTx does not be to be recorded to the tracker, this is TrustedBroadcastService job Services.BroadcastService.BroadcastAsync(clientEscrowTx).GetAwaiter().GetResult(); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.ClientRedeem, correlation, redeemTx); Status = PaymentStateMachineStatus.ClientChannelBroadcasted; } else if (Status == PaymentStateMachineStatus.ClientChannelBroadcasted) { alice = Runtime.CreateTumblerClient(cycle.Start, Runtime.TumblerProtocol, Identity.Alice); TransactionInformation clientTx = GetTransactionInformation(SolverClientSession.EscrowedCoin, true); var state = ClientChannelNegotiation.GetInternalState(); if (clientTx != null && clientTx.Confirmations >= cycle.SafetyPeriodDuration) { Logs.Client.LogInformation($"Client escrow reached {cycle.SafetyPeriodDuration} confirmations"); //Client asks the public key of the Tumbler and sends its own alice.BeginSignVoucher(new SignVoucherRequest { MerkleProof = clientTx.MerkleProof, Transaction = clientTx.Transaction, KeyReference = state.TumblerEscrowKeyReference, UnsignedVoucher = state.BlindedVoucher, Cycle = cycle.Start, ClientEscrowKey = state.ClientEscrowKey.PubKey, ChannelId = SolverClientSession.Id }); NeedSave = true; Status = PaymentStateMachineStatus.TumblerVoucherSigning; } } else if (Status == PaymentStateMachineStatus.TumblerVoucherSigning) { alice = Runtime.CreateTumblerClient(cycle.Start, Runtime.TumblerProtocol, Identity.Alice); var voucher = alice.EndSignVoucher(SolverClientSession.Id); if (voucher != null) { ClientChannelNegotiation.CheckVoucherSolution(voucher); NeedSave = true; Status = PaymentStateMachineStatus.TumblerVoucherObtained; } } break; case CyclePhase.TumblerChannelEstablishment: if (Status == PaymentStateMachineStatus.TumblerVoucherObtained) { bob = Runtime.CreateTumblerClient(cycle.Start, Runtime.TumblerProtocol, Identity.Bob); Logs.Client.LogInformation("Begin ask to open the channel..."); //Client asks the Tumbler to make a channel var bobEscrowInformation = ClientChannelNegotiation.GetOpenChannelRequest(); uint160 channelId = null; try { channelId = bob.BeginOpenChannel(bobEscrowInformation); NeedSave = true; } catch (Exception ex) { if (ex.Message.Contains("tumbler-insufficient-funds")) { Logs.Client.LogWarning("The tumbler server has not enough funds and can't open a channel for now"); break; } throw; } ClientChannelNegotiation.SetChannelId(channelId); Status = PaymentStateMachineStatus.TumblerChannelCreating; } else if (Status == PaymentStateMachineStatus.TumblerChannelCreating) { bob = Runtime.CreateTumblerClient(cycle.Start, Runtime.TumblerProtocol, Identity.Bob); var tumblerEscrow = bob.EndOpenChannel(cycle.Start, ClientChannelNegotiation.GetInternalState().ChannelId); if (tumblerEscrow == null) { Logs.Client.LogInformation("Tumbler escrow still creating..."); break; } NeedSave = true; if (tumblerEscrow.OutputIndex >= tumblerEscrow.Transaction.Outputs.Count) { Logs.Client.LogError("Tumbler escrow output out-of-bound"); Status = PaymentStateMachineStatus.Wasted; break; } var txOut = tumblerEscrow.Transaction.Outputs[tumblerEscrow.OutputIndex]; var outpoint = new OutPoint(tumblerEscrow.Transaction.GetHash(), tumblerEscrow.OutputIndex); var escrowCoin = new Coin(outpoint, txOut).ToScriptCoin(ClientChannelNegotiation.GetTumblerEscrowParameters(tumblerEscrow.EscrowInitiatorKey).ToScript()); Logs.Client.LogDebug($"tumblerEscrow.Transaction: {tumblerEscrow.Transaction}"); Logs.Client.LogDebug($"TumblerEscrow hex: {tumblerEscrow.Transaction.ToHex()}"); PromiseClientSession = ClientChannelNegotiation.ReceiveTumblerEscrowedCoin(escrowCoin); Logs.Client.LogInformation("Tumbler expected escrowed coin received"); //Tell to the block explorer we need to track that address (for checking if it is confirmed in payment phase) Services.BlockExplorerService.TrackAsync(PromiseClientSession.EscrowedCoin.ScriptPubKey).GetAwaiter().GetResult(); Services.BlockExplorerService.TrackPrunedTransactionAsync(tumblerEscrow.Transaction, tumblerEscrow.MerkleProof).GetAwaiter().GetResult(); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, PromiseClientSession.EscrowedCoin.ScriptPubKey, correlation); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, PromiseClientSession.EscrowedCoin.Outpoint.Hash, correlation); Services.BroadcastService.BroadcastAsync(tumblerEscrow.Transaction).GetAwaiter().GetResult(); //Channel is done, now need to run the promise protocol to get valid puzzle var cashoutDestination = DestinationWallet.GetNewDestination(); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerCashout, cashoutDestination, correlation); feeRate = GetFeeRate(); var sigReq = PromiseClientSession.CreateSignatureRequest(cashoutDestination, feeRate); var commitments = bob.SignHashes(PromiseClientSession.Id, sigReq); var revelation = PromiseClientSession.Reveal(commitments); var proof = bob.CheckRevelation(PromiseClientSession.Id, revelation); var puzzle = PromiseClientSession.CheckCommitmentProof(proof); SolverClientSession.AcceptPuzzle(puzzle); Status = PaymentStateMachineStatus.TumblerChannelCreated; } else if (Status == PaymentStateMachineStatus.TumblerChannelCreated) { CheckTumblerChannelSecured(cycle); } break; case CyclePhase.PaymentPhase: //Could have confirmed during safe period //Only check for the first block when period start, //else Tumbler can know deanonymize you based on the timing of first Alice request if the transaction was not confirmed previously if (Status == PaymentStateMachineStatus.TumblerChannelCreated && height == period.Start) { CheckTumblerChannelSecured(cycle); } //No "else if" intended if (Status == PaymentStateMachineStatus.TumblerChannelSecured) { alice = Runtime.CreateTumblerClient(cycle.Start, Runtime.TumblerProtocol, Identity.Alice); Logs.Client.LogDebug("Starting the puzzle solver protocol..."); var puzzles = SolverClientSession.GeneratePuzzles(); alice.BeginSolvePuzzles(SolverClientSession.Id, puzzles); NeedSave = true; Status = PaymentStateMachineStatus.ProcessingPayment; } else if (Status == PaymentStateMachineStatus.ProcessingPayment) { feeRate = GetFeeRate(); alice = Runtime.CreateTumblerClient(cycle.Start, Runtime.TumblerProtocol, Identity.Alice); var commitments = alice.EndSolvePuzzles(SolverClientSession.Id); NeedSave = true; if (commitments == null) { Logs.Client.LogDebug("Still solving puzzles..."); break; } bool isRegtest = Runtime.Network.Name == Network.RegTest.Name; int connectionCount = Services.BlockExplorerService.GetConnectionCount(); if (connectionCount < 4 && !isRegtest) { Logs.Client.LogWarning("There are too few bitcoin peers connected; payment will not be processed"); break; } var revelation2 = SolverClientSession.Reveal(commitments); var solutionKeys = alice.CheckRevelation(SolverClientSession.Id, revelation2); var blindFactors = SolverClientSession.GetBlindFactors(solutionKeys); var offerInformation = alice.CheckBlindFactors(SolverClientSession.Id, blindFactors); var offerSignature = SolverClientSession.SignOffer(offerInformation); var offerRedeem = SolverClientSession.CreateOfferRedeemTransaction(feeRate); Logs.Client.LogDebug("Puzzle solver protocol ended..."); Logs.Client.LogDebug($"offerRedeem.Transaction: {offerRedeem.Transaction} at height {offerRedeem.BroadcastableHeight}"); //May need to find solution in the fulfillment transaction Services.BlockExplorerService.TrackAsync(offerRedeem.PreviousScriptPubKey).GetAwaiter().GetResult(); Tracker.AddressCreated(cycle.Start, TransactionType.ClientOfferRedeem, SolverClientSession.GetInternalState().RedeemDestination, correlation); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.ClientOfferRedeem, correlation, offerRedeem); try { solutionKeys = alice.FulfillOffer(SolverClientSession.Id, offerSignature); SolverClientSession.CheckSolutions(solutionKeys); var tumblingSolution = SolverClientSession.GetSolution(); var transaction = PromiseClientSession.GetSignedTransaction(tumblingSolution); Logs.Client.LogDebug("Got puzzle solution cooperatively from the tumbler"); Logs.Client.LogDebug($"TumblerCashOut 1: {transaction}"); Logs.Client.LogDebug($"TumblerCashOut hex 1: {transaction.ToHex()}"); Status = PaymentStateMachineStatus.PuzzleSolutionObtained; Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.TumblerCashout, correlation, new TrustedBroadcastRequest() { BroadcastAt = cycle.GetPeriods().ClientCashout.Start, Transaction = transaction }); if (Cooperative) { try { // No need to await for it, it is a just nice for the tumbler (we don't want the underlying socks connection cut before the escape key is sent) var signature = SolverClientSession.SignEscape(); alice.GiveEscapeKeyAsync(SolverClientSession.Id, signature).GetAwaiter().GetResult(); } catch (Exception ex) { Logs.Client.LogDebug(new EventId(), ex, "Exception while giving the escape key"); } Logs.Client.LogInformation("Gave escape signature to the tumbler"); } } catch (Exception ex) { Status = PaymentStateMachineStatus.UncooperativeTumbler; Logs.Client.LogWarning("The tumbler did not gave puzzle solution cooperatively"); Logs.Client.LogWarning(ex.ToString()); } } break; case CyclePhase.ClientCashoutPhase: //If the tumbler is uncooperative, he published solutions on the blockchain if (Status == PaymentStateMachineStatus.UncooperativeTumbler) { var transactions = Services.BlockExplorerService.GetTransactionsAsync(SolverClientSession.GetInternalState().OfferCoin.ScriptPubKey, false).GetAwaiter().GetResult(); if (transactions.Count != 0) { SolverClientSession.CheckSolutions(transactions.Select(t => t.Transaction).ToArray()); Logs.Client.LogInformation("Puzzle solution recovered from tumbler's fulfill transaction"); NeedSave = true; Status = PaymentStateMachineStatus.PuzzleSolutionObtained; var tumblingSolution = SolverClientSession.GetSolution(); var transaction = PromiseClientSession.GetSignedTransaction(tumblingSolution); Logs.Client.LogDebug($"TumblerCashOut 2: {transaction}"); Logs.Client.LogDebug($"TumblerCashOut hex 2: {transaction.ToHex()}"); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerCashout, transaction.GetHash(), correlation); Services.BroadcastService.BroadcastAsync(transaction).GetAwaiter().GetResult(); } } break; } } catch (InvalidStateException ex) { Logs.Client.LogDebug(new EventId(), ex, "Client side Invalid State, the payment is wasted"); Status = PaymentStateMachineStatus.Wasted; } catch (Exception ex) when(ex.Message.IndexOf("invalid-state", StringComparison.OrdinalIgnoreCase) >= 0) { Logs.Client.LogDebug(new EventId(), ex, "Tumbler side Invalid State, the payment is wasted"); Status = PaymentStateMachineStatus.Wasted; } finally { if (previousState != Status) { Logs.Client.LogInformation($"Status changed {previousState} => {Status}"); } if (alice != null && bob != null) { throw new InvalidOperationException("Bob and Alice have been both initialized, please report the bug to NTumbleBit developers"); } if (alice != null) { alice.Dispose(); } if (bob != null) { bob.Dispose(); } } progressInfo.ShouldStayConnected = ShouldStayConnected(); return(progressInfo); }
private SolverServerSession GetSolverServerSession(int cycleId, uint160 channelId, CyclePhase expectedPhase) { if (channelId == null) { throw new ArgumentNullException(nameof(channelId)); } var height = Services.BlockExplorerService.GetCurrentHeight(); var session = Repository.GetSolverServerSession(cycleId, channelId); if (session == null) { throw NotFound("channel-not-found").AsException(); } CheckPhase(expectedPhase, height, cycleId); return(session); }
private PromiseServerSession GetPromiseServerSession(int cycleId, string channelId, CyclePhase expectedPhase) { var height = Services.BlockExplorerService.GetCurrentHeight(); var session = Repository.GetPromiseServerSession(cycleId, channelId); if (session == null) { throw NotFound("channel-not-found").AsException(); } CheckPhase(expectedPhase, height, cycleId); return(session); }
public bool IsInPhase(CyclePhase phase, int blockHeight) { return(GetPeriod(phase).IsInPeriod(blockHeight)); }
public bool IsInPhase(CyclePhase phase, int blockHeight) { var periods = GetPeriods(); return(periods.IsInPhase(phase, blockHeight)); }
public void Update(ILogger logger) { logger = logger ?? new NullLogger(); int height = Services.BlockExplorerService.GetCurrentHeight(); CycleParameters cycle; CyclePhase phase; if (ClientChannelNegotiation == null) { cycle = Parameters.CycleGenerator.GetRegistratingCycle(height); phase = CyclePhase.Registration; } else { cycle = ClientChannelNegotiation.GetCycle(); var phases = new CyclePhase[] { CyclePhase.Registration, CyclePhase.ClientChannelEstablishment, CyclePhase.TumblerChannelEstablishment, CyclePhase.PaymentPhase, CyclePhase.TumblerCashoutPhase, CyclePhase.ClientCashoutPhase }; if (!phases.Any(p => cycle.IsInPhase(p, height))) { return; } phase = phases.First(p => cycle.IsInPhase(p, height)); } logger.LogInformation("Cycle " + cycle.Start + " in phase " + Enum.GetName(typeof(CyclePhase), phase) + ", ending in " + (cycle.GetPeriods().GetPeriod(phase).End - height) + " blocks"); FeeRate feeRate = null; switch (phase) { case CyclePhase.Registration: if (ClientChannelNegotiation == null) { //Client asks for voucher var voucherResponse = BobClient.AskUnsignedVoucher(); //Client ensures he is in the same cycle as the tumbler (would fail if one tumbler or client's chain isn't sync) var tumblerCycle = Parameters.CycleGenerator.GetCycle(voucherResponse.CycleStart); Assert(tumblerCycle.Start == cycle.Start, "invalid-phase"); //Saving the voucher for later StartCycle = cycle.Start; ClientChannelNegotiation = new ClientChannelNegotiation(Parameters, cycle.Start); ClientChannelNegotiation.ReceiveUnsignedVoucher(voucherResponse); logger.LogInformation("Registration Complete"); } break; case CyclePhase.ClientChannelEstablishment: if (ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingTumblerClientTransactionKey) { var key = AliceClient.RequestTumblerEscrowKey(cycle.Start); ClientChannelNegotiation.ReceiveTumblerEscrowKey(key.PubKey, key.KeyIndex); //Client create the escrow var txout = ClientChannelNegotiation.BuildClientEscrowTxOut(); feeRate = GetFeeRate(); var clientEscrowTx = Services.WalletService.FundTransaction(txout, feeRate); if (clientEscrowTx == null) { logger.LogInformation("Not enough funds in the wallet to tumble"); break; } SolverClientSession = ClientChannelNegotiation.SetClientSignedTransaction(clientEscrowTx); var redeem = SolverClientSession.CreateRedeemTransaction(feeRate, Services.WalletService.GenerateAddress($"Cycle {cycle.Start} Client Redeem").ScriptPubKey); var escrowLabel = $"Cycle {cycle.Start} Client Escrow"; Services.BlockExplorerService.Track(escrowLabel, redeem.PreviousScriptPubKey); Services.BroadcastService.Broadcast(escrowLabel, clientEscrowTx); Services.TrustedBroadcastService.Broadcast($"Cycle {cycle.Start} Client Redeem (locked until {redeem.Transaction.LockTime})", redeem); logger.LogInformation("Client escrow broadcasted " + clientEscrowTx.GetHash()); logger.LogInformation("Client escrow redeem " + redeem.Transaction.GetHash() + " will be broadcast later if tumbler unresponsive"); } else if (ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingSolvedVoucher) { TransactionInformation clientTx = GetTransactionInformation(SolverClientSession.EscrowedCoin, true); var state = ClientChannelNegotiation.GetInternalState(); if (clientTx != null && clientTx.Confirmations >= cycle.SafetyPeriodDuration) { //Client asks the public key of the Tumbler and sends its own var aliceEscrowInformation = ClientChannelNegotiation.GenerateClientTransactionKeys(); var voucher = AliceClient.SignVoucher(new Models.SignVoucherRequest { MerkleProof = clientTx.MerkleProof, Transaction = clientTx.Transaction, KeyReference = state.TumblerEscrowKeyReference, ClientEscrowInformation = aliceEscrowInformation, TumblerEscrowPubKey = state.ClientEscrowInformation.OtherEscrowKey }); ClientChannelNegotiation.CheckVoucherSolution(voucher); logger.LogInformation("Voucher solution obtained"); } } break; case CyclePhase.TumblerChannelEstablishment: if (ClientChannelNegotiation != null && ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingGenerateTumblerTransactionKey) { //Client asks the Tumbler to make a channel var bobEscrowInformation = ClientChannelNegotiation.GetOpenChannelRequest(); var tumblerInformation = BobClient.OpenChannel(bobEscrowInformation); PromiseClientSession = ClientChannelNegotiation.ReceiveTumblerEscrowedCoin(tumblerInformation); //Tell to the block explorer we need to track that address (for checking if it is confirmed in payment phase) var escrowTumblerLabel = $"Cycle {cycle.Start} Tumbler Escrow"; Services.BlockExplorerService.Track(escrowTumblerLabel, PromiseClientSession.EscrowedCoin.ScriptPubKey); //Channel is done, now need to run the promise protocol to get valid puzzle var cashoutDestination = DestinationWallet.GetNewDestination(); feeRate = GetFeeRate(); var sigReq = PromiseClientSession.CreateSignatureRequest(cashoutDestination, feeRate); var commiments = BobClient.SignHashes(cycle.Start, PromiseClientSession.Id, sigReq); var revelation = PromiseClientSession.Reveal(commiments); var proof = BobClient.CheckRevelation(cycle.Start, PromiseClientSession.Id, revelation); var puzzle = PromiseClientSession.CheckCommitmentProof(proof); SolverClientSession.AcceptPuzzle(puzzle); logger.LogInformation("Tumbler escrow broadcasted " + PromiseClientSession.EscrowedCoin.Outpoint.Hash); } break; case CyclePhase.PaymentPhase: if (PromiseClientSession != null) { TransactionInformation tumblerTx = GetTransactionInformation(PromiseClientSession.EscrowedCoin, false); //Ensure the tumbler coin is confirmed before paying anything if (tumblerTx == null || tumblerTx.Confirmations < cycle.SafetyPeriodDuration) { if (tumblerTx != null) { logger.LogInformation("Tumbler escrow " + tumblerTx.Transaction.GetHash() + " expecting " + cycle.SafetyPeriodDuration + " current is " + tumblerTx.Confirmations); } else { logger.LogInformation("Tumbler escrow not found"); } return; } if (SolverClientSession.Status == SolverClientStates.WaitingGeneratePuzzles) { logger.LogInformation("Tumbler escrow confirmed " + tumblerTx.Transaction.GetHash()); feeRate = GetFeeRate(); var puzzles = SolverClientSession.GeneratePuzzles(); var commmitments = AliceClient.SolvePuzzles(cycle.Start, SolverClientSession.Id, puzzles); var revelation2 = SolverClientSession.Reveal(commmitments); var solutionKeys = AliceClient.CheckRevelation(cycle.Start, SolverClientSession.Id, revelation2); var blindFactors = SolverClientSession.GetBlindFactors(solutionKeys); var offerInformation = AliceClient.CheckBlindFactors(cycle.Start, SolverClientSession.Id, blindFactors); var offerSignature = SolverClientSession.SignOffer(offerInformation); var offerRedeem = SolverClientSession.CreateOfferRedeemTransaction(feeRate, Services.WalletService.GenerateAddress($"Cycle {cycle.Start} Tumbler Redeem").ScriptPubKey); //May need to find solution in the fulfillment transaction var offerLabel = $"Cycle {cycle.Start} Client Offer Redeem (locked until {offerRedeem.Transaction.LockTime})"; Services.BlockExplorerService.Track(offerLabel, offerRedeem.PreviousScriptPubKey); Services.TrustedBroadcastService.Broadcast(offerLabel, offerRedeem); logger.LogInformation("Offer redeem " + offerRedeem.Transaction.GetHash() + " locked until " + offerRedeem.Transaction.LockTime.Height); try { solutionKeys = AliceClient.FulfillOffer(cycle.Start, SolverClientSession.Id, offerSignature); SolverClientSession.CheckSolutions(solutionKeys); logger.LogInformation("Solution recovered from cooperative tumbler"); } catch { logger.LogWarning("Uncooperative tumbler detected, keep connection open."); } logger.LogInformation("Payment completed"); } } break; case CyclePhase.ClientCashoutPhase: if (SolverClientSession != null) { //If the tumbler is uncooperative, he published solutions on the blockchain if (SolverClientSession.Status == SolverClientStates.WaitingPuzzleSolutions) { var transactions = Services.BlockExplorerService.GetTransactions(SolverClientSession.GetOfferScriptPubKey(), false); SolverClientSession.CheckSolutions(transactions.Select(t => t.Transaction).ToArray()); logger.LogInformation("Solution recovered from blockchain transaction"); } if (SolverClientSession.Status == SolverClientStates.Completed) { var tumblingSolution = SolverClientSession.GetSolution(); var transaction = PromiseClientSession.GetSignedTransaction(tumblingSolution); Services.BroadcastService.Broadcast($"Cycle {cycle.Start} Client Cashout", transaction); logger.LogInformation("Client Cashout completed " + transaction.GetHash()); } } break; } }
public async Task Update(Session session) { int height = this.chain.Tip.Height; CycleParameters cycle; CyclePhase phase; if (session.ClientChannelNegotiation == null) { cycle = this.TumblerParameters.CycleGenerator.GetRegistratingCycle(height); phase = CyclePhase.Registration; } else { cycle = session.ClientChannelNegotiation.GetCycle(); var phases = new CyclePhase[] { CyclePhase.Registration, CyclePhase.ClientChannelEstablishment, CyclePhase.TumblerChannelEstablishment, CyclePhase.PaymentPhase, CyclePhase.TumblerCashoutPhase, CyclePhase.ClientCashoutPhase }; if (!phases.Any(p => cycle.IsInPhase(p, height))) { return; } phase = phases.First(p => cycle.IsInPhase(p, height)); } logger.LogInformation("Cycle " + cycle.Start + " in phase " + Enum.GetName(typeof(CyclePhase), phase) + ", ending in " + (cycle.GetPeriods().GetPeriod(phase).End - height) + " blocks"); var correlation = session.SolverClientSession == null ? 0 : GetCorrelation(session.SolverClientSession.EscrowedCoin.ScriptPubKey); FeeRate feeRate = null; switch (phase) { case CyclePhase.Registration: if (session.ClientChannelNegotiation == null) { //Client asks for voucher var voucherResponse = await this.BobClient.AskUnsignedVoucherAsync(); //Client ensures he is in the same cycle as the tumbler (would fail if one tumbler or client's chain isn't sync) var tumblerCycle = this.TumblerParameters.CycleGenerator.GetCycle(voucherResponse.CycleStart); Assert(tumblerCycle.Start == cycle.Start, "invalid-phase"); //Saving the voucher for later session.StartCycle = cycle.Start; session.ClientChannelNegotiation = new ClientChannelNegotiation(this.TumblerParameters, cycle.Start); session.ClientChannelNegotiation.ReceiveUnsignedVoucher(voucherResponse); logger.LogInformation("Registration Complete"); } break; case CyclePhase.ClientChannelEstablishment: if (session.ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingTumblerClientTransactionKey) { var key = await this.AliceClient.RequestTumblerEscrowKeyAsync(cycle.Start); session.ClientChannelNegotiation.ReceiveTumblerEscrowKey(key.PubKey, key.KeyIndex); //Client create the escrow var escrowTxOut = session.ClientChannelNegotiation.BuildClientEscrowTxOut(); feeRate = GetFeeRate(); Transaction clientEscrowTx = null; try { clientEscrowTx = services.FundTransaction(escrowTxOut, feeRate); } catch (NotEnoughFundsException ex) { logger.LogInformation($"Not enough funds in the wallet to tumble. Missing about {ex.Missing}. Denomination is {this.TumblerParameters.Denomination}."); break; } session.SolverClientSession = session.ClientChannelNegotiation.SetClientSignedTransaction(clientEscrowTx); correlation = GetCorrelation(session.SolverClientSession.EscrowedCoin.ScriptPubKey); // Tracker.AddressCreated(cycle.Start, TransactionType.ClientEscrow, escrowTxOut.ScriptPubKey, correlation); // Tracker.TransactionCreated(cycle.Start, TransactionType.ClientEscrow, clientEscrowTx.GetHash(), correlation); services.Track(escrowTxOut.ScriptPubKey); var redeemDestination = this.OriginWallet.GetAccountsByCoinType(this.coinType).First().GetFirstUnusedReceivingAddress().ScriptPubKey; // Services.WalletService.GenerateAddress().ScriptPubKey; var redeemTx = session.SolverClientSession.CreateRedeemTransaction(feeRate, redeemDestination); //Tracker.AddressCreated(cycle.Start, TransactionType.ClientRedeem, redeemDestination, correlation); //redeemTx does not be to be recorded to the tracker, this is TrustedBroadcastService job services.Broadcast(clientEscrowTx); services.TrustedBroadcast(cycle.Start, TransactionType.ClientRedeem, correlation, redeemTx); logger.LogInformation("Client escrow broadcasted " + clientEscrowTx.GetHash()); logger.LogInformation("Client escrow redeem " + redeemTx.Transaction.GetHash() + " will be broadcast later if tumbler unresponsive"); } else if (session.ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingSolvedVoucher) { TransactionInformation clientTx = GetTransactionInformation(session.SolverClientSession.EscrowedCoin, true); var state = session.ClientChannelNegotiation.GetInternalState(); if (clientTx != null && clientTx.Confirmations >= cycle.SafetyPeriodDuration) { //Client asks the public key of the Tumbler and sends its own var aliceEscrowInformation = session.ClientChannelNegotiation.GenerateClientTransactionKeys(); var voucher = await this.AliceClient.SignVoucherAsync(new Models.SignVoucherRequest { MerkleProof = clientTx.MerkleProof, Transaction = clientTx.Transaction, KeyReference = state.TumblerEscrowKeyReference, ClientEscrowInformation = aliceEscrowInformation, TumblerEscrowPubKey = state.ClientEscrowInformation.OtherEscrowKey }); session.ClientChannelNegotiation.CheckVoucherSolution(voucher); logger.LogInformation("Voucher solution obtained"); } } break; case CyclePhase.TumblerChannelEstablishment: if (session.ClientChannelNegotiation != null && session.ClientChannelNegotiation.Status == TumblerClientSessionStates.WaitingGenerateTumblerTransactionKey) { //Client asks the Tumbler to make a channel var bobEscrowInformation = session.ClientChannelNegotiation.GetOpenChannelRequest(); var tumblerInformation = await this.BobClient.OpenChannelAsync(bobEscrowInformation); session.PromiseClientSession = session.ClientChannelNegotiation.ReceiveTumblerEscrowedCoin(tumblerInformation); //Tell to the block explorer we need to track that address (for checking if it is confirmed in payment phase) services.Track(session.PromiseClientSession.EscrowedCoin.ScriptPubKey); //Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, PromiseClientSession.EscrowedCoin.ScriptPubKey, correlation); //Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, PromiseClientSession.EscrowedCoin.Outpoint.Hash, correlation); //Channel is done, now need to run the promise protocol to get valid puzzle var cashoutDestination = this.DestinationWallet.GetAccountsByCoinType(CoinType.Bitcoin).First().GetFirstUnusedReceivingAddress().ScriptPubKey; //Tracker.AddressCreated(cycle.Start, TransactionType.TumblerCashout, cashoutDestination, correlation); feeRate = GetFeeRate(); var sigReq = session.PromiseClientSession.CreateSignatureRequest(cashoutDestination, feeRate); var commiments = await this.BobClient.SignHashesAsync(cycle.Start, session.PromiseClientSession.Id, sigReq); var revelation = session.PromiseClientSession.Reveal(commiments); var proof = await this.BobClient.CheckRevelationAsync(cycle.Start, session.PromiseClientSession.Id, revelation); var puzzle = session.PromiseClientSession.CheckCommitmentProof(proof); session.SolverClientSession.AcceptPuzzle(puzzle); logger.LogInformation("Tumbler escrow broadcasted " + session.PromiseClientSession.EscrowedCoin.Outpoint.Hash); } break; case CyclePhase.PaymentPhase: if (session.PromiseClientSession != null) { TransactionInformation tumblerTx = GetTransactionInformation(session.PromiseClientSession.EscrowedCoin, false); //Ensure the tumbler coin is confirmed before paying anything if (tumblerTx == null || tumblerTx.Confirmations < cycle.SafetyPeriodDuration) { if (tumblerTx != null) { logger.LogInformation("Tumbler escrow " + tumblerTx.Transaction.GetHash() + " expecting " + cycle.SafetyPeriodDuration + " current is " + tumblerTx.Confirmations); } else { logger.LogInformation("Tumbler escrow not found"); } return; } if (session.SolverClientSession.Status == SolverClientStates.WaitingGeneratePuzzles) { logger.LogInformation("Tumbler escrow confirmed " + tumblerTx.Transaction.GetHash()); feeRate = GetFeeRate(); var puzzles = session.SolverClientSession.GeneratePuzzles(); var commmitments = await this.AliceClient.SolvePuzzlesAsync(cycle.Start, session.SolverClientSession.Id, puzzles); var revelation2 = session.SolverClientSession.Reveal(commmitments); var solutionKeys = await this.AliceClient.CheckRevelationAsync(cycle.Start, session.SolverClientSession.Id, revelation2); var blindFactors = session.SolverClientSession.GetBlindFactors(solutionKeys); var offerInformation = await this.AliceClient.CheckBlindFactorsAsync(cycle.Start, session.SolverClientSession.Id, blindFactors); var offerSignature = session.SolverClientSession.SignOffer(offerInformation); var offerRedeemAddress = this.OriginWallet.GetAccountsByCoinType(this.coinType).First().GetFirstUnusedReceivingAddress(); // Services.WalletService.GenerateAddress($"Cycle {cycle.Start} Tumbler Redeem").ScriptPubKey); var offerRedeem = session.SolverClientSession.CreateOfferRedeemTransaction(feeRate, offerRedeemAddress.ScriptPubKey); //May need to find solution in the fulfillment transaction services.Track(offerRedeem.PreviousScriptPubKey); //Tracker.AddressCreated(cycle.Start, TransactionType.ClientOfferRedeem, offerRedeemAddress.ScriptPubKey, correlation); services.TrustedBroadcast(cycle.Start, TransactionType.ClientOfferRedeem, correlation, offerRedeem); logger.LogInformation("Offer redeem " + offerRedeem.Transaction.GetHash() + " locked until " + offerRedeem.Transaction.LockTime.Height); try { solutionKeys = await this.AliceClient.FulfillOfferAsync(cycle.Start, session.SolverClientSession.Id, offerSignature); session.SolverClientSession.CheckSolutions(solutionKeys); var tumblingSolution = session.SolverClientSession.GetSolution(); var transaction = session.PromiseClientSession.GetSignedTransaction(tumblingSolution); services.TrustedBroadcast(cycle.Start, TransactionType.TumblerCashout, correlation, new TrustedBroadcastRequest() { BroadcastAt = cycle.GetPeriods().ClientCashout.Start, Transaction = transaction }); if (!NonCooperative) { var signature = session.SolverClientSession.SignEscape(); await this.AliceClient.GiveEscapeKeyAsync(cycle.Start, session.SolverClientSession.Id, signature); } logger.LogInformation("Solution recovered from cooperative tumbler"); } catch (Exception ex) { logger.LogWarning("Uncooperative tumbler detected, keep connection open."); logger.LogWarning(ex.ToString()); } logger.LogInformation("Payment completed"); } } break; case CyclePhase.ClientCashoutPhase: if (session.SolverClientSession != null) { //If the tumbler is uncooperative, he published solutions on the blockchain if (session.SolverClientSession.Status == SolverClientStates.WaitingPuzzleSolutions) { var transactions = services.GetTransactions(session.SolverClientSession.GetOfferScriptPubKey(), false); if (transactions.Length == 0) { logger.LogInformation("Solution of puzzle not on the blockchain"); } else { session.SolverClientSession.CheckSolutions(transactions.Select(t => t.Transaction).ToArray()); logger.LogInformation("Solution recovered from blockchain transaction"); var tumblingSolution = session.SolverClientSession.GetSolution(); var transaction = session.PromiseClientSession.GetSignedTransaction(tumblingSolution); // Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerCashout, transaction.GetHash(), correlation); services.Broadcast(transaction); logger.LogInformation("Client Cashout completed " + transaction.GetHash()); } } } break; } }
private bool ExportBusinessFlowToTestPlanning(BusinessFlow businessFlow, List <BaseResponseItem> matchingTS, string testLabUploadPath, Dictionary <string, string> testSetFieldsFields, Dictionary <string, string> testInstanceFields, ref string res) { Cycle cycle = null; CyclePhase cyclePhase = null; long folderId; long tcrCatalogTreeId; long testerId = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).GetCurrentUser(); bool isUpdate = false; // Update testset if (matchingTS != null && matchingTS.Count > 0) { testLabUploadPath = businessFlow.ExternalID; moduleParentId = businessFlow.ExternalID2; bfEntityType = matchingTS[0].TryGetItem("type").ToString(); if (bfEntityType.Equals(EntityFolderType.Phase.ToString())) { folderCycleId = moduleParentId; } isUpdate = true; } if (String.IsNullOrEmpty(testLabUploadPath)) { cycle = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).CreateNewTestCycle(); cyclePhase = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).CreateNewTestCyclePhase(cycle, businessFlow.Name); folderId = cyclePhase.id; tcrCatalogTreeId = cyclePhase.tcrCatalogTreeId; } else if (bfEntityType.Equals(EntityFolderType.Cycle.ToString()) || bfEntityType.Equals(EntityFolderType.CyclePhase.ToString())) { cycle = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).GetZephyrEntCycleById(Convert.ToInt32(testLabUploadPath)); cyclePhase = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).CreateNewTestCyclePhase(cycle, businessFlow.Name); folderId = cyclePhase.id; tcrCatalogTreeId = cyclePhase.tcrCatalogTreeId; } else if (bfEntityType.Equals(EntityFolderType.Phase.ToString())) { List <BaseResponseItem> phase = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).GetZephyrEntPhaseById(Convert.ToInt32(folderCycleId)); BaseResponseItem item = phase.FirstOrDefault(md => md.id.ToString().Equals(testLabUploadPath)); if (isUpdate) { ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).UpdateTestPlanningFolder(Convert.ToInt64(folderCycleId), Convert.ToInt64(item.TryGetItem("id")), businessFlow); return(true); } dynamic treeNode = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).CreateNewTestPlanningFolder(Convert.ToInt64(folderCycleId), Convert.ToInt64(item.TryGetItem("id")) , businessFlow.Name, String.IsNullOrEmpty(businessFlow.Description) ? businessFlow.Name + " description" : businessFlow.Description); folderId = Convert.ToInt64(folderCycleId); tcrCatalogTreeId = treeNode.id; } else { List <BaseResponseItem> module = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).GetTreeByCretiria(EntityFolderType.Module.ToString(), Convert.ToInt32(ALMCore.DefaultAlmConfig.ALMProjectKey), 180, Convert.ToInt32(moduleParentId)); BaseResponseItem item = module.FirstOrDefault(md => md.id.ToString().Equals(testLabUploadPath)); if (isUpdate) { ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).UpdateTestPlanningFolder(Convert.ToInt64(folderCycleId), Convert.ToInt64(item.TryGetItem("id")), businessFlow); return(true); } dynamic treeNode = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).CreateNewTestPlanningFolder(Convert.ToInt64(folderCycleId), Convert.ToInt64(item.TryGetItem("id")) , businessFlow.Name, String.IsNullOrEmpty(businessFlow.Description) ? businessFlow.Name + " description" : businessFlow.Description); folderId = Convert.ToInt64(folderCycleId); tcrCatalogTreeId = treeNode.id; } ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).AssigningTestCasesToCyclePhase(tcsRepositoryList.Select(z => z.id).ToList(), folderId, tcrCatalogTreeId); List <TestCaseResource> tcsPlanningList = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).GetTestCasesByAssignmentTree((int)tcrCatalogTreeId); List <Execution> assignsList = ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).AssigningTestCasesToTesterForExecution( tcsPlanningList.Select(z => z.tct.id).ToList(), folderId, testerId, tcrCatalogTreeId); ((ZephyrEntCore)ALMIntegration.Instance.AlmCore).ExecuteTestCases(assignsList, testerId, businessFlow.ActivitiesGroups); businessFlow.ExternalID = tcrCatalogTreeId.ToString(); businessFlow.ExternalID2 = folderId.ToString(); return(true); }