public async Task SuccessFromPreviousCoinJoinAsync()
    {
        WabiSabiConfig cfg   = new();
        var            round = WabiSabiFactory.CreateRound(cfg);

        using Key key = new();
        var coin             = WabiSabiFactory.CreateCoin(key);
        var rpc              = WabiSabiFactory.CreatePreconfiguredRpcClient(coin);
        var coinJoinIdsStore = new InMemoryCoinJoinIdStore();

        coinJoinIdsStore.Add(coin.Outpoint.Hash);
        using Arena arena = await ArenaBuilder.From(cfg).With(rpc).With(coinJoinIdsStore).CreateAndStartAsync(round);

        var minAliceDeadline = DateTimeOffset.UtcNow + cfg.ConnectionConfirmationTimeout * 0.9;
        var arenaClient      = WabiSabiFactory.CreateArenaClient(arena);
        var ownershipProof   = WabiSabiFactory.CreateOwnershipProof(key, round.Id);

        var(resp, _) = await arenaClient.RegisterInputAsync(round.Id, coin.Outpoint, ownershipProof, CancellationToken.None);

        AssertSingleAliceSuccessfullyRegistered(round, minAliceDeadline, resp);

        var myAlice = Assert.Single(round.Alices);

        Assert.True(myAlice.IsPayingZeroCoordinationFee);

        await arena.StopAsync(CancellationToken.None);
    }
Esempio n. 2
0
    public WabiSabiCoordinator(CoordinatorParameters parameters, IRPCClient rpc)
    {
        Parameters = parameters;

        Warden        = new(parameters.UtxoWardenPeriod, parameters.PrisonFilePath, Config);
        ConfigWatcher = new(parameters.ConfigChangeMonitoringPeriod, Config, () => Logger.LogInfo("WabiSabi configuration has changed."));

        CoinJoinTransactionArchiver transactionArchiver = new(Path.Combine(parameters.CoordinatorDataDir, "CoinJoinTransactions"));

        CoinJoinFeeRateStatStore = CoinJoinFeeRateStatStore.LoadFromFile(parameters.CoinJoinFeeRateStatStoreFilePath, Config, rpc);
        IoHelpers.EnsureContainingDirectoryExists(Parameters.CoinJoinFeeRateStatStoreFilePath);
        CoinJoinFeeRateStatStore.NewStat += FeeRateStatStore_NewStat;

        var inMemoryCoinJoinIdStore = InMemoryCoinJoinIdStore.LoadFromFile(parameters.CoinJoinIdStoreFilePath);

        var coinJoinScriptStore = CoinJoinScriptStore.LoadFromFile(parameters.CoinJoinScriptStoreFilePath);

        IoHelpers.EnsureContainingDirectoryExists(Parameters.CoinJoinScriptStoreFilePath);

        Arena = new(
            parameters.RoundProgressSteppingPeriod,
            rpc.Network,
            Config,
            rpc,
            Warden.Prison,
            inMemoryCoinJoinIdStore,
            transactionArchiver,
            coinJoinScriptStore);

        IoHelpers.EnsureContainingDirectoryExists(Parameters.CoinJoinIdStoreFilePath);
        Arena.CoinJoinBroadcast += Arena_CoinJoinBroadcast;
    }
Esempio n. 3
0
    public static InMemoryCoinJoinIdStore LoadFromFile(string filePath)
    {
        var lines = File.Exists(filePath)
                        ? File.ReadAllLines(filePath).Select(x => uint256.Parse(x))
                        : Enumerable.Empty <uint256>();

        var store = new InMemoryCoinJoinIdStore(lines);

        return(store);
    }
Esempio n. 4
0
 public Arena(
     TimeSpan period,
     Network network,
     WabiSabiConfig config,
     IRPCClient rpc,
     Prison prison,
     InMemoryCoinJoinIdStore inMemoryCoinJoinIdStore,
     CoinJoinTransactionArchiver?archiver = null) : base(period)
 {
     Network                 = network;
     Config                  = config;
     Rpc                     = rpc;
     Prison                  = prison;
     TransactionArchiver     = archiver;
     Random                  = new SecureRandom();
     InMemoryCoinJoinIdStore = inMemoryCoinJoinIdStore;
 }
Esempio n. 5
0
    private async Task <InputRegistrationResponse> RegisterInputCoreAsync(InputRegistrationRequest request, CancellationToken cancellationToken)
    {
        var coin = await OutpointToCoinAsync(request, cancellationToken).ConfigureAwait(false);

        using (await AsyncLock.LockAsync(cancellationToken).ConfigureAwait(false))
        {
            var round = GetRound(request.RoundId);

            var registeredCoins = Rounds.Where(x => !(x.Phase == Phase.Ended && !x.WasTransactionBroadcast))
                                  .SelectMany(r => r.Alices.Select(a => a.Coin));

            if (registeredCoins.Any(x => x.Outpoint == coin.Outpoint))
            {
                throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.AliceAlreadyRegistered);
            }

            if (round.IsInputRegistrationEnded(Config.MaxInputCountByRound))
            {
                throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
            }

            if (round is BlameRound blameRound && !blameRound.BlameWhitelist.Contains(coin.Outpoint))
            {
                throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted);
            }

            // Compute but don't commit updated coinjoin to round state, it will
            // be re-calculated on input confirmation. This is computed in here
            // for validation purposes.
            _ = round.Assert <ConstructionState>().AddInput(coin);

            var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Id);
            if (!OwnershipProof.VerifyCoinJoinInputProof(request.OwnershipProof, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData))
            {
                throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongOwnershipProof);
            }

            // Generate a new GUID with the secure random source, to be sure
            // that it is not guessable (Guid.NewGuid() documentation does
            // not say anything about GUID version or randomness source,
            // only that the probability of duplicates is very low).
            var id = new Guid(Random.GetBytes(16));

            var isPayingZeroCoordinationFee = InMemoryCoinJoinIdStore.Contains(coin.Outpoint.Hash);

            if (!isPayingZeroCoordinationFee)
            {
                // If the coin comes from a tx that all of the tx inputs are coming from a CJ (1 hop - no pay).
                Transaction tx = await Rpc.GetRawTransactionAsync(coin.Outpoint.Hash, true, cancellationToken).ConfigureAwait(false);

                if (tx.Inputs.All(input => InMemoryCoinJoinIdStore.Contains(input.PrevOut.Hash)))
                {
                    isPayingZeroCoordinationFee = true;
                }
            }

            var alice = new Alice(coin, request.OwnershipProof, round, id, isPayingZeroCoordinationFee);

            if (alice.CalculateRemainingAmountCredentials(round.FeeRate, round.CoordinationFeeRate) <= Money.Zero)
            {
                throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.UneconomicalInput);
            }

            if (alice.TotalInputAmount < round.MinAmountCredentialValue)
            {
                throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds);
            }
            if (alice.TotalInputAmount > round.MaxAmountCredentialValue)
            {
                throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds);
            }

            if (alice.TotalInputVsize > round.MaxVsizeAllocationPerAlice)
            {
                throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchVsize);
            }

            var amountCredentialTask = round.AmountCredentialIssuer.HandleRequestAsync(request.ZeroAmountCredentialRequests, cancellationToken);
            var vsizeCredentialTask  = round.VsizeCredentialIssuer.HandleRequestAsync(request.ZeroVsizeCredentialRequests, cancellationToken);

            if (round.RemainingInputVsizeAllocation < round.MaxVsizeAllocationPerAlice)
            {
                throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.VsizeQuotaExceeded);
            }

            var commitAmountCredentialResponse = await amountCredentialTask.ConfigureAwait(false);

            var commitVsizeCredentialResponse = await vsizeCredentialTask.ConfigureAwait(false);

            alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeFrame.Duration);
            round.Alices.Add(alice);

            return(new(alice.Id,
                       commitAmountCredentialResponse,
                       commitVsizeCredentialResponse,
                       alice.IsPayingZeroCoordinationFee));
        }
    }