Example #1
0
    public async Task <CoinJoinResult> StartRoundAsync(IEnumerable <SmartCoin> smartCoins, RoundState roundState, CancellationToken cancellationToken)
    {
        var roundId = roundState.Id;

        ImmutableArray <(AliceClient AliceClient, PersonCircuit PersonCircuit)> registeredAliceClientAndCircuits = ImmutableArray <(AliceClient, PersonCircuit)> .Empty;

        // Because of the nature of the protocol, the input registration and the connection confirmation phases are done subsequently thus they have a merged timeout.
        var timeUntilOutputReg = (roundState.InputRegistrationEnd - DateTimeOffset.UtcNow) + roundState.CoinjoinState.Parameters.ConnectionConfirmationTimeout;

        try
        {
            try
            {
                using CancellationTokenSource timeUntilOutputRegCts = new(timeUntilOutputReg + ExtraPhaseTimeoutMargin);
                using CancellationTokenSource linkedCts             = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeUntilOutputRegCts.Token);

                registeredAliceClientAndCircuits = await ProceedWithInputRegAndConfirmAsync(smartCoins, roundState, linkedCts.Token).ConfigureAwait(false);
            }
            catch (UnexpectedRoundPhaseException ex)
            {
                roundState = ex.RoundState;
                var message = ex.RoundState.EndRoundState switch
                {
                    EndRoundState.AbortedNotEnoughAlices => $"Not enough participants in the round to continue. Waiting for a new round.",
                    _ => $"Registration phase ended by the coordinator: '{ex.Message}' code: '{ex.RoundState.EndRoundState}'."
                };

                roundState.LogInfo(message);
                return(new CoinJoinResult(false));
            }

            if (!registeredAliceClientAndCircuits.Any())
            {
                roundState.LogInfo("There are no available Alices to participate with.");
                return(new CoinJoinResult(false));
            }

            roundState.LogDebug($"Successfully registered {registeredAliceClientAndCircuits.Length} inputs.");

            var registeredAliceClients = registeredAliceClientAndCircuits.Select(x => x.AliceClient).ToImmutableArray();

            var outputTxOuts = await ProceedWithOutputRegistrationPhaseAsync(roundId, registeredAliceClients, cancellationToken).ConfigureAwait(false);

            var(unsignedCoinJoin, aliceClientsThatSigned) = await ProceedWithSigningStateAsync(roundId, registeredAliceClients, outputTxOuts, cancellationToken).ConfigureAwait(false);

            roundState = await RoundStatusUpdater.CreateRoundAwaiter(s => s.Id == roundId && s.Phase == Phase.Ended, cancellationToken).ConfigureAwait(false);

            var msg = roundState.EndRoundState switch
            {
                EndRoundState.TransactionBroadcasted => $"Broadcasted. Coinjoin TxId: ({unsignedCoinJoin.GetHash()})",
                EndRoundState.TransactionBroadcastFailed => $"Failed to broadcast. Coinjoin TxId: ({unsignedCoinJoin.GetHash()})",
                EndRoundState.AbortedWithError => "Round abnormally finished.",
                EndRoundState.AbortedNotEnoughAlices => "Aborted. Not enough participants.",
                EndRoundState.AbortedNotEnoughAlicesSigned => "Aborted. Not enough participants signed the coinjoin transaction.",
                EndRoundState.NotAllAlicesSign => "Aborted. Some Alices didn't sign. Go to blame round.",
                EndRoundState.None => "Unknown.",
                _ => throw new ArgumentOutOfRangeException()
            };
            roundState.LogInfo(msg);

            LogCoinJoinSummary(registeredAliceClients, outputTxOuts, unsignedCoinJoin, roundState);

            return(new CoinJoinResult(
                       GoForBlameRound: roundState.EndRoundState == EndRoundState.NotAllAlicesSign,
                       SuccessfulBroadcast: roundState.EndRoundState == EndRoundState.TransactionBroadcasted,
                       RegisteredCoins: aliceClientsThatSigned.Select(a => a.SmartCoin).ToImmutableList(),
                       RegisteredOutputs: outputTxOuts.Select(o => o.ScriptPubKey).ToImmutableList()));
        }
        finally
        {
            foreach (var coins in smartCoins)
            {
                coins.CoinJoinInProgress = false;
            }

            foreach (var aliceClientAndCircuit in registeredAliceClientAndCircuits)
            {
                aliceClientAndCircuit.AliceClient.Finish();
                aliceClientAndCircuit.PersonCircuit.Dispose();
            }
            CoinJoinClientProgress.SafeInvoke(this, new LeavingCriticalPhase());
            CoinJoinClientProgress.SafeInvoke(this, new RoundEnded(roundState));
        }
    }