public async Task AliceTimesoutAsync() { // Alice times out when its deadline is reached. WabiSabiConfig cfg = new(); var round = WabiSabiFactory.CreateRound(cfg); var km = ServiceFactory.CreateKeyManager(""); var key = BitcoinFactory.CreateHdPubKey(km); var smartCoin = BitcoinFactory.CreateSmartCoin(key, 10m); var rpc = WabiSabiFactory.CreatePreconfiguredRpcClient(smartCoin.Coin); using Arena arena = await ArenaBuilder.From(cfg).With(rpc).CreateAndStartAsync(round); var arenaClient = WabiSabiFactory.CreateArenaClient(arena); using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), arena); await roundStateUpdater.StartAsync(CancellationToken.None); // Register Alices. var keyChain = new KeyChain(km, new Kitchen("")); using CancellationTokenSource cancellationTokenSource = new(); var task = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, smartCoin, keyChain, roundStateUpdater, cancellationTokenSource.Token); while (round.Alices.Count == 0) { await Task.Delay(10); } var alice = Assert.Single(round.Alices); alice.Deadline = DateTimeOffset.UtcNow - TimeSpan.FromMilliseconds(1); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Empty(round.Alices); cancellationTokenSource.Cancel(); await Assert.ThrowsAsync <OperationCanceledException>(async() => await task); await roundStateUpdater.StopAsync(CancellationToken.None); await arena.StopAsync(CancellationToken.None); }
CreateRoundWithTwoConfirmedConnectionsAsync(Arena arena, Key key1, SmartCoin coin1, Key key2, SmartCoin coin2) { // Create the round. await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); var arenaClient = WabiSabiFactory.CreateArenaClient(arena); var round = Assert.Single(arena.Rounds); round.MaxVsizeAllocationPerAlice = 11 + 31 + MultipartyTransactionParameters.SharedOverhead; using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), arena); await roundStateUpdater.StartAsync(CancellationToken.None); var identificationKey = new Key(); var task1 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin1, key1.GetBitcoinSecret(round.Network), identificationKey, roundStateUpdater, CancellationToken.None); var task2 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin2, key2.GetBitcoinSecret(round.Network), identificationKey, roundStateUpdater, CancellationToken.None); while (Phase.ConnectionConfirmation != round.Phase) { await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); } await Task.WhenAll(task1, task2); var aliceClient1 = task1.Result; var aliceClient2 = task2.Result; await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.OutputRegistration, round.Phase); await roundStateUpdater.StopAsync(CancellationToken.None); return(round, arenaClient, new[] { aliceClient1, aliceClient2 }); }
CreateRoundWithTwoConfirmedConnectionsAsync(Arena arena, IKeyChain keyChain, SmartCoin coin1, SmartCoin coin2) { // Get the round. await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); var arenaClient = WabiSabiFactory.CreateArenaClient(arena); var round = Assert.Single(arena.Rounds); // Refresh the Arena States because of vsize manipulation. await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), arena); await roundStateUpdater.StartAsync(CancellationToken.None); var task1 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin1, keyChain, roundStateUpdater, CancellationToken.None); var task2 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin2, keyChain, roundStateUpdater, CancellationToken.None); while (Phase.ConnectionConfirmation != round.Phase) { await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); } await Task.WhenAll(task1, task2); var aliceClient1 = await task1; var aliceClient2 = await task2; await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.OutputRegistration, round.Phase); await roundStateUpdater.StopAsync(CancellationToken.None); return(round, arenaClient, new[] { aliceClient1, aliceClient2 }); }
public async Task RegisterOutputTestAsync() { var config = new WabiSabiConfig { MaxInputCountByRound = 1 }; var round = WabiSabiFactory.CreateRound(config); var km = ServiceFactory.CreateKeyManager(""); var key = BitcoinFactory.CreateHdPubKey(km); SmartCoin coin1 = BitcoinFactory.CreateSmartCoin(key, Money.Coins(2m)); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin); using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, mockRpc, round); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object); var insecureRandom = new InsecureRandom(); var wabiSabiApi = new WabiSabiController(coordinator); var roundState = RoundState.FromRound(round); var aliceArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); var bobArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); Assert.Equal(Phase.InputRegistration, round.Phase); var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main); using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), wabiSabiApi); await roundStateUpdater.StartAsync(CancellationToken.None); var task = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), aliceArenaClient, coin1, bitcoinSecret, roundStateUpdater, CancellationToken.None); do { await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); }while (round.Phase != Phase.ConnectionConfirmation); var aliceClient = await task; await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.OutputRegistration, round.Phase); using var destinationKey = new Key(); var destination = destinationKey.PubKey.WitHash.ScriptPubKey; var bobClient = new BobClient(round.Id, bobArenaClient); await bobClient.RegisterOutputAsync( destination, aliceClient.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber), aliceClient.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); var bob = Assert.Single(round.Bobs); Assert.Equal(destination, bob.Script); var credentialAmountSum = aliceClient.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber).Sum(x => x.Value); Assert.Equal(credentialAmountSum, bob.CredentialAmount); }
public async Task RegisterOutputTestAsync() { var config = new WabiSabiConfig { MaxInputCountByRound = 1 }; var round = WabiSabiFactory.CreateRound(config); var km = ServiceFactory.CreateKeyManager(""); var key = BitcoinFactory.CreateHdPubKey(km); SmartCoin coin1 = BitcoinFactory.CreateSmartCoin(key, Money.Coins(2m)); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin); using Arena arena = await ArenaBuilder.From(config).With(mockRpc).CreateAndStartAsync(round); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); using var memoryCache = new MemoryCache(new MemoryCacheOptions()); var idempotencyRequestCache = new IdempotencyRequestCache(memoryCache); using CoinJoinFeeRateStatStore coinJoinFeeRateStatStore = new(config, arena.Rpc); var wabiSabiApi = new WabiSabiController(idempotencyRequestCache, arena, coinJoinFeeRateStatStore); InsecureRandom insecureRandom = InsecureRandom.Instance; var roundState = RoundState.FromRound(round); var aliceArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); var bobArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); Assert.Equal(Phase.InputRegistration, round.Phase); using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), wabiSabiApi); await roundStateUpdater.StartAsync(CancellationToken.None); var keyChain = new KeyChain(km, new Kitchen("")); var task = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), aliceArenaClient, coin1, keyChain, roundStateUpdater, CancellationToken.None); do { await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); }while (round.Phase != Phase.ConnectionConfirmation); var aliceClient = await task; await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.OutputRegistration, round.Phase); using var destinationKey = new Key(); var destination = destinationKey.PubKey.WitHash.ScriptPubKey; var bobClient = new BobClient(round.Id, bobArenaClient); await bobClient.RegisterOutputAsync( destination, aliceClient.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber), aliceClient.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); var bob = Assert.Single(round.Bobs); Assert.Equal(destination, bob.Script); var credentialAmountSum = aliceClient.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber).Sum(x => x.Value); Assert.Equal(credentialAmountSum, bob.CredentialAmount); }
private async Task <ImmutableArray <(AliceClient AliceClient, PersonCircuit PersonCircuit)> > CreateRegisterAndConfirmCoinsAsync(IEnumerable <SmartCoin> smartCoins, RoundState roundState, CancellationToken cancellationToken) { int eventInvokedAlready = 0; async Task <(AliceClient?AliceClient, PersonCircuit?PersonCircuit)> RegisterInputAsync(SmartCoin coin, CancellationToken cancellationToken) { PersonCircuit?personCircuit = null; try { personCircuit = HttpClientFactory.NewHttpClientWithPersonCircuit(out Tor.Http.IHttpClient httpClient); // Alice client requests are inherently linkable to each other, so the circuit can be reused var arenaRequestHandler = new WabiSabiHttpApiClient(httpClient); var aliceArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(SecureRandom), roundState.CreateVsizeCredentialClient(SecureRandom), arenaRequestHandler); var aliceClient = await AliceClient.CreateRegisterAndConfirmInputAsync(roundState, aliceArenaClient, coin, KeyChain, RoundStatusUpdater, cancellationToken).ConfigureAwait(false); // Right after the first real-cred confirmation happened we entered into critical phase. if (Interlocked.Exchange(ref eventInvokedAlready, 1) == 0) { CoinJoinClientProgress.SafeInvoke(this, new EnteringCriticalPhase()); } return(aliceClient, personCircuit); } catch (WabiSabiProtocolException) { personCircuit?.Dispose(); return(null, null); } catch (Exception) { personCircuit?.Dispose(); throw; } } // Gets the list of scheduled dates/time in the remaining available time frame when each alice has to be registered. var remainingTimeForRegistration = roundState.InputRegistrationEnd - DateTimeOffset.UtcNow; roundState.LogDebug($"Inputs({smartCoins.Count()}) registration started - it will end in: {remainingTimeForRegistration:hh\\:mm\\:ss}."); var scheduledDates = GetScheduledDates(smartCoins.Count(), roundState.InputRegistrationEnd); // Creates scheduled tasks (tasks that wait until the specified date/time and then perform the real registration) var aliceClients = smartCoins.Zip( scheduledDates, async(coin, date) => { var delay = date - DateTimeOffset.UtcNow; if (delay > TimeSpan.Zero) { await Task.Delay(delay, cancellationToken).ConfigureAwait(false); } return(await RegisterInputAsync(coin, cancellationToken).ConfigureAwait(false)); }) .ToImmutableArray(); await Task.WhenAll(aliceClients).ConfigureAwait(false); return(aliceClients .Select(x => x.Result) .Where(r => r.AliceClient is not null && r.PersonCircuit is not null) .Select(r => (r.AliceClient !, r.PersonCircuit !)) .ToImmutableArray()); }