Beispiel #1
0
        public void RegisterServices(
            BitcoinStore bitcoinStore,
            WasabiSynchronizer syncer,
            NodesGroup nodes,
            ServiceConfiguration serviceConfiguration,
            IFeeProvider feeProvider,
            CoreNode coreNode = null)
        {
            if (State != WalletState.Uninitialized)
            {
                throw new InvalidOperationException($"{nameof(State)} must be {WalletState.Uninitialized}. Current state: {State}.");
            }

            BitcoinStore         = Guard.NotNull(nameof(bitcoinStore), bitcoinStore);
            Nodes                = Guard.NotNull(nameof(nodes), nodes);
            Synchronizer         = Guard.NotNull(nameof(syncer), syncer);
            ServiceConfiguration = Guard.NotNull(nameof(serviceConfiguration), serviceConfiguration);
            FeeProvider          = Guard.NotNull(nameof(feeProvider), feeProvider);
            CoreNode             = coreNode;

            ChaumianClient = new CoinJoinClient(Synchronizer, Network, KeyManager);

            TransactionProcessor = new TransactionProcessor(BitcoinStore.TransactionStore, KeyManager, ServiceConfiguration.DustThreshold, ServiceConfiguration.PrivacyLevelStrong);
            Coins = TransactionProcessor.Coins;

            TransactionProcessor.WalletRelevantTransactionProcessed += TransactionProcessor_WalletRelevantTransactionProcessedAsync;
            ChaumianClient.OnDequeue += ChaumianClient_OnDequeue;

            BitcoinStore.IndexStore.NewFilter += IndexDownloader_NewFilterAsync;
            BitcoinStore.IndexStore.Reorged   += IndexDownloader_ReorgedAsync;
            BitcoinStore.MempoolService.TransactionReceived += Mempool_TransactionReceived;

            State = WalletState.Initialized;
        }
Beispiel #2
0
    public async Task StartParticipatingAsync(CancellationToken cancellationToken)
    {
        ThrowIfDisposed();
        var apiClient = new WabiSabiHttpApiClient(HttpClientFactory.NewHttpClientWithDefaultCircuit());

        using var roundStateUpdater = new RoundStateUpdater(TimeSpan.FromSeconds(3), apiClient);
        await roundStateUpdater.StartAsync(cancellationToken).ConfigureAwait(false);

        var coinJoinClient = new CoinJoinClient(
            HttpClientFactory,
            Wallet,
            Wallet,
            roundStateUpdater,
            consolidationMode: true);

        // Run the coinjoin client task.
        var walletHdPubKey = new HdPubKey(Wallet.PubKey, KeyPath.Parse("m/84'/0/0/0/0"), SmartLabel.Empty, KeyState.Clean);

        walletHdPubKey.SetAnonymitySet(1);         // bug if not settled

        if (SplitTransaction is null)
        {
            throw new InvalidOperationException($"{nameof(GenerateCoinsAsync)} has to be called first.");
        }

        var smartCoins = SplitTransaction.Transaction.Outputs.AsIndexedOutputs()
                         .Select(x => new SmartCoin(SplitTransaction, x.N, walletHdPubKey))
                         .ToList();

        // Run the coinjoin client task.
        await coinJoinClient.StartCoinJoinAsync(smartCoins, cancellationToken).ConfigureAwait(false);

        await roundStateUpdater.StopAsync(cancellationToken).ConfigureAwait(false);
    }
        private async Task <TransactionProcessor> CreateTransactionProcessorAsync([CallerMemberName] string callerName = "")
        {
            var datadir = EnvironmentHelpers.GetDataDir(Path.Combine("WalletWasabi", "Bench"));
            var dir     = Path.Combine(datadir, callerName, "TransactionStore");

            Console.WriteLine(dir);
            await IoHelpers.DeleteRecursivelyWithMagicDustAsync(dir);

            // Create the services.
            // 1. Create connection service.
            var nodes                = new NodesGroup(Network.Main);
            var bitcoinStore         = new BitcoinStore();
            var serviceConfiguration = new ServiceConfiguration(2, 2, 21, 50, new IPEndPoint(IPAddress.Parse("127.0.0.1"), 45678), Money.Coins(0.0001m));

            // 2. Create wasabi synchronizer service.
            var synchronizer = new WasabiSynchronizer(Network.Main, bitcoinStore, () => new Uri("http://localhost:35474"), null);

            synchronizer.Start(requestInterval: TimeSpan.FromDays(1), TimeSpan.FromDays(1), 1000);

            // 3. Create key manager service.
            var keyManager = KeyManager.CreateNew(out _, "password");

            // 4. Create chaumian coinjoin client.
            var chaumianClient = new CoinJoinClient(synchronizer, Network.Main, keyManager);

            // 5. Create wallet service.
            await bitcoinStore.InitializeAsync(dir, Network.Main);

            var workDir      = Path.Combine(datadir, EnvironmentHelpers.GetMethodName());
            var feeProviders = new FeeProviders(new[] { synchronizer });

            var wallet = new WalletService(bitcoinStore, keyManager, synchronizer, nodes, workDir, serviceConfiguration, feeProviders);

            return(wallet.TransactionProcessor);
        }
Beispiel #4
0
        public WalletService(
            BitcoinStore bitcoinStore,
            KeyManager keyManager,
            WasabiSynchronizer syncer,
            CoinJoinClient chaumianClient,
            NodesGroup nodes,
            string workFolderDir,
            ServiceConfiguration serviceConfiguration,
            IFeeProvider feeProvider,
            CoreNode coreNode = null)
        {
            BitcoinStore         = Guard.NotNull(nameof(bitcoinStore), bitcoinStore);
            KeyManager           = Guard.NotNull(nameof(keyManager), keyManager);
            Nodes                = Guard.NotNull(nameof(nodes), nodes);
            Synchronizer         = Guard.NotNull(nameof(syncer), syncer);
            ChaumianClient       = Guard.NotNull(nameof(chaumianClient), chaumianClient);
            ServiceConfiguration = Guard.NotNull(nameof(serviceConfiguration), serviceConfiguration);
            FeeProvider          = Guard.NotNull(nameof(feeProvider), feeProvider);
            CoreNode             = coreNode;

            HandleFiltersLock = new AsyncLock();

            BlocksFolderPath = Path.Combine(workFolderDir, "Blocks", Network.ToString());
            RuntimeParams.SetDataDir(workFolderDir);

            BlockFolderLock = new AsyncLock();

            KeyManager.AssertCleanKeysIndexed();
            KeyManager.AssertLockedInternalKeysIndexed(14);

            TransactionProcessor = new TransactionProcessor(BitcoinStore.TransactionStore, KeyManager, ServiceConfiguration.DustThreshold, ServiceConfiguration.PrivacyLevelStrong);
            Coins = TransactionProcessor.Coins;

            TransactionProcessor.WalletRelevantTransactionProcessed += TransactionProcessor_WalletRelevantTransactionProcessedAsync;

            if (Directory.Exists(BlocksFolderPath))
            {
                if (Synchronizer.Network == Network.RegTest)
                {
                    Directory.Delete(BlocksFolderPath, true);
                    Directory.CreateDirectory(BlocksFolderPath);
                }
            }
            else
            {
                Directory.CreateDirectory(BlocksFolderPath);
            }

            var walletName = "UnnamedWallet";

            if (!string.IsNullOrWhiteSpace(KeyManager.FilePath))
            {
                walletName = Path.GetFileNameWithoutExtension(KeyManager.FilePath);
            }

            BitcoinStore.IndexStore.NewFilter += IndexDownloader_NewFilterAsync;
            BitcoinStore.IndexStore.Reorged   += IndexDownloader_ReorgedAsync;
            BitcoinStore.MempoolService.TransactionReceived += Mempool_TransactionReceived;
        }
Beispiel #5
0
        public Wallet(
            Network network,
            BitcoinStore bitcoinStore,
            KeyManager keyManager,
            WasabiSynchronizer syncer,
            NodesGroup nodes,
            string workFolderDir,
            ServiceConfiguration serviceConfiguration,
            IFeeProvider feeProvider,
            CoreNode coreNode = null)
        {
            Network              = Guard.NotNull(nameof(network), network);
            BitcoinStore         = Guard.NotNull(nameof(bitcoinStore), bitcoinStore);
            KeyManager           = Guard.NotNull(nameof(keyManager), keyManager);
            Nodes                = Guard.NotNull(nameof(nodes), nodes);
            Synchronizer         = Guard.NotNull(nameof(syncer), syncer);
            ServiceConfiguration = Guard.NotNull(nameof(serviceConfiguration), serviceConfiguration);
            FeeProvider          = Guard.NotNull(nameof(feeProvider), feeProvider);
            CoreNode             = coreNode;

            ChaumianClient    = new CoinJoinClient(Synchronizer, Network, keyManager);
            HandleFiltersLock = new AsyncLock();

            BlocksFolderPath = Path.Combine(workFolderDir, "Blocks", Network.ToString());
            RuntimeParams.SetDataDir(workFolderDir);

            BlockFolderLock = new AsyncLock();

            KeyManager.AssertCleanKeysIndexed();
            KeyManager.AssertLockedInternalKeysIndexed(14);

            TransactionProcessor = new TransactionProcessor(BitcoinStore.TransactionStore, KeyManager, ServiceConfiguration.DustThreshold, ServiceConfiguration.PrivacyLevelStrong);
            Coins = TransactionProcessor.Coins;

            TransactionProcessor.WalletRelevantTransactionProcessed += TransactionProcessor_WalletRelevantTransactionProcessedAsync;
            ChaumianClient.OnDequeue += ChaumianClient_OnDequeue;

            if (Directory.Exists(BlocksFolderPath))
            {
                if (Network == Network.RegTest)
                {
                    Directory.Delete(BlocksFolderPath, true);
                    Directory.CreateDirectory(BlocksFolderPath);
                }
            }
            else
            {
                Directory.CreateDirectory(BlocksFolderPath);
            }

            BitcoinStore.IndexStore.NewFilter += IndexDownloader_NewFilterAsync;
            BitcoinStore.IndexStore.Reorged   += IndexDownloader_ReorgedAsync;
            BitcoinStore.MempoolService.TransactionReceived += Mempool_TransactionReceived;

            State = WalletState.Initialized;
        }
Beispiel #6
0
    public CoinJoinTracker CreateAndStart(Wallet wallet, IEnumerable <SmartCoin> coinCandidates)
    {
        var coinJoinClient = new CoinJoinClient(
            HttpClientFactory,
            new KeyChain(wallet.KeyManager, wallet.Kitchen),
            new InternalDestinationProvider(wallet.KeyManager),
            RoundStatusUpdater,
            wallet.KeyManager.MinAnonScoreTarget,
            doNotRegisterInLastMinuteTimeLimit: TimeSpan.FromMinutes(1));

        return(new CoinJoinTracker(wallet, coinJoinClient, coinCandidates, CancellationToken));
    }
 public CoinJoinTracker(
     Wallet wallet,
     CoinJoinClient coinJoinClient,
     IEnumerable <SmartCoin> coinCandidates,
     CancellationToken cancellationToken)
 {
     Wallet                  = wallet;
     CoinJoinClient          = coinJoinClient;
     CoinCandidates          = coinCandidates;
     CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
     CoinJoinTask            = coinJoinClient.StartCoinJoinAsync(coinCandidates, CancellationTokenSource.Token);
 }
    public void SelectNothingFromEmptySetOfCoins()
    {
        // This test is to make sure no coins are selected when there are no coins.
        var coins = CoinJoinClient.SelectCoinsForRound(
            coins: Enumerable.Empty <SmartCoin>(),
            CreateMultipartyTransactionParameters(),
            consolidationMode: false,
            anonScoreTarget: 10,
            ConfigureRng(5));

        Assert.Empty(coins);
    }
Beispiel #9
0
    public CoinJoinTracker CreateAndStart(Wallet wallet, IEnumerable <SmartCoin> coinCandidates, bool restartAutomatically, bool overridePlebStop)
    {
        var coinJoinClient = new CoinJoinClient(
            HttpClientFactory,
            new KeyChain(wallet.KeyManager, wallet.Kitchen),
            new InternalDestinationProvider(wallet.KeyManager),
            RoundStatusUpdater,
            wallet.KeyManager.AnonScoreTarget,
            feeRateMedianTimeFrame: TimeSpan.FromHours(wallet.KeyManager.FeeRateMedianTimeFrameHours),
            doNotRegisterInLastMinuteTimeLimit: TimeSpan.FromMinutes(1));

        return(new CoinJoinTracker(wallet, coinJoinClient, coinCandidates, restartAutomatically, overridePlebStop, CancellationToken));
    }
Beispiel #10
0
        public async Task InitializeWalletServiceAsync(KeyManager keyManager)
        {
            using (_cancelWalletServiceInitialization = new CancellationTokenSource())
            {
                var token = _cancelWalletServiceInitialization.Token;
                while (!Initialized)
                {
                    await Task.Delay(100, token);
                }

                if (Config.UseTor)
                {
                    ChaumianClient = new CoinJoinClient(Synchronizer, Network, keyManager, () => Config.GetCurrentBackendUri(), Config.TorSocks5EndPoint);
                }
                else
                {
                    ChaumianClient = new CoinJoinClient(Synchronizer, Network, keyManager, Config.GetFallbackBackendUri(), null);
                }

                try
                {
                    keyManager.CorrectBlockHeights(BitcoinStore.HashChain); // Block heights are wrong sometimes. It's a hack. We have to retroactively fix existing wallets, but also we have to figure out where we ruin the block heights.
                }
                catch (Exception ex)                                        // Whatever this is not critical, but let's log it.
                {
                    Logger.LogWarning(ex);
                }

                WalletService = new WalletService(BitcoinStore, keyManager, Synchronizer, ChaumianClient, Nodes, DataDir, Config.ServiceConfiguration, FeeProviders, BitcoinCoreNode?.RpcClient);

                ChaumianClient.Start();
                Logger.LogInfo("Start Chaumian CoinJoin service...");

                Logger.LogInfo($"Starting {nameof(WalletService)}...");
                await WalletService.InitializeAsync(token);

                Logger.LogInfo($"{nameof(WalletService)} started.");

                token.ThrowIfCancellationRequested();
                WalletService.TransactionProcessor.CoinReceived += CoinReceived;

                TransactionBroadcaster.AddWalletService(WalletService);
            }
            _cancelWalletServiceInitialization = null;             // Must make it null explicitly, because dispose won't make it null.
        }
Beispiel #11
0
    public CoinJoinTracker(
        Wallet wallet,
        CoinJoinClient coinJoinClient,
        IEnumerable <SmartCoin> coinCandidates,
        bool stopWhenAllMixed,
        bool overridePlebStop,
        CancellationToken cancellationToken)
    {
        Wallet         = wallet;
        CoinJoinClient = coinJoinClient;
        CoinJoinClient.CoinJoinClientProgress += CoinJoinClient_CoinJoinClientProgress;

        CoinCandidates          = coinCandidates;
        StopWhenAllMixed        = stopWhenAllMixed;
        OverridePlebStop        = overridePlebStop;
        CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        CoinJoinTask            = coinJoinClient.StartCoinJoinAsync(coinCandidates, CancellationTokenSource.Token);
    }
    public void SelectNonPrivateCoinFromOneCoinSetOfCoins()
    {
        // This test is to make sure that we select the only non-private coin when it is the only coin in the wallet.
        const int AnonymitySet      = 10;
        var       km                = KeyManager.CreateNew(out _, "", Network.Main);
        var       coinsToSelectFrom = Enumerable
                                      .Empty <SmartCoin>()
                                      .Prepend(BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: AnonymitySet - 1))
                                      .ToList();

        var coins = CoinJoinClient.SelectCoinsForRound(
            coins: coinsToSelectFrom,
            CreateMultipartyTransactionParameters(),
            consolidationMode: false,
            anonScoreTarget: AnonymitySet,
            ConfigureRng(1));

        Assert.Single(coins);
    }
    public void SelectNothingFromFullyPrivateSetOfCoins()
    {
        // This test is to make sure no coins are selected when all coins are private.
        const int AnonymitySet      = 10;
        var       km                = KeyManager.CreateNew(out _, "", Network.Main);
        var       coinsToSelectFrom = Enumerable
                                      .Range(0, 10)
                                      .Select(i => BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: AnonymitySet + 1))
                                      .ToList();

        var coins = CoinJoinClient.SelectCoinsForRound(
            coins: coinsToSelectFrom,
            CreateMultipartyTransactionParameters(),
            consolidationMode: false,
            anonScoreTarget: AnonymitySet,
            ConfigureRng(5));

        Assert.Empty(coins);
    }
    public void TwoNonPrivateCoinInSetOfCoins()
    {
        const int MinAnonimitySet = 10;
        var       km = KeyManager.CreateNew(out _, "", Network.Main);
        var       coinsToSelectFrom = Enumerable
                                      .Empty <SmartCoin>()
                                      .Prepend(BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: MinAnonimitySet - 1))
                                      .Prepend(BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: MinAnonimitySet - 1))
                                      .ToList();

        var coins = CoinJoinClient.SelectCoinsForRound(
            coins: coinsToSelectFrom,
            CreateMultipartyTransactionParameters(),
            consolidationMode: false,
            minAnonScoreTarget: MinAnonimitySet,
            ConfigureRng(1));

        Assert.Single(coins);
    }
    public void SelectTwoNonPrivateCoinsFromTwoCoinsSetOfCoinsConsolidationMode()
    {
        // This test is to make sure that we select more than one non-private coin.
        const int AnonymitySet      = 10;
        var       km                = KeyManager.CreateNew(out _, "", Network.Main);
        var       coinsToSelectFrom = Enumerable
                                      .Empty <SmartCoin>()
                                      .Prepend(BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: AnonymitySet - 1))
                                      .Prepend(BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: AnonymitySet - 1))
                                      .ToList();

        var coins = CoinJoinClient.SelectCoinsForRound(
            coins: coinsToSelectFrom,
            CreateMultipartyTransactionParameters(),
            consolidationMode: true,
            anonScoreTarget: AnonymitySet,
            ConfigureRng(1));

        Assert.Equal(2, coins.Count);
    }
    public void OnlyOneNonPrivateCoinInBigSetOfCoinsConsolidationMode()
    {
        const int MinAnonimitySet = 10;
        var       km = KeyManager.CreateNew(out _, "", Network.Main);
        SmartCoin smallerAnonCoin   = BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: MinAnonimitySet - 1);
        var       coinsToSelectFrom = Enumerable
                                      .Range(0, 10)
                                      .Select(i => BitcoinFactory.CreateSmartCoin(BitcoinFactory.CreateHdPubKey(km), Money.Coins(1m), 0, anonymitySet: MinAnonimitySet + 1))
                                      .Prepend(smallerAnonCoin)
                                      .ToList();

        var coins = CoinJoinClient.SelectCoinsForRound(
            coins: coinsToSelectFrom,
            CreateMultipartyTransactionParameters(),
            consolidationMode: true,
            minAnonScoreTarget: MinAnonimitySet,
            ConfigureRng(5));

        Assert.Contains(smallerAnonCoin, coins);
        Assert.Equal(10, coins.Count);
    }
        public async Task SoloCoinJoinTest()
        {
            const int InputCount = 2;

            // At the end of the test a coinjoin transaction has to be created and broadcasted.
            var transactionCompleted = new TaskCompletionSource <Transaction>();

            // Total test timeout.
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(120));
            cts.Token.Register(() => transactionCompleted.TrySetCanceled(), useSynchronizationContext: false);

            // Create a key manager and use it to create two fake coins.
            var keyManager = KeyManager.CreateNew(out var _, password: "");

            keyManager.AssertCleanKeysIndexed();
            var coins = keyManager.GetKeys()
                        .Take(InputCount)
                        .Select(x => new Coin(
                                    BitcoinFactory.CreateOutPoint(),
                                    new TxOut(Money.Coins(1), x.P2wpkhScript)))
                        .ToArray();

            var httpClient = _apiApplicationFactory.WithWebHostBuilder(builder =>
            {
                builder.ConfigureServices(services =>
                {
                    var rpc = BitcoinFactory.GetMockMinimalRpc();

                    // Make the coordinator to believe that those two coins are real and
                    // that they exist in the blockchain with many confirmations.
                    rpc.OnGetTxOutAsync = (txId, idx, _) => new()
                    {
                        Confirmations    = 101,
                        IsCoinBase       = false,
                        ScriptPubKeyType = "witness_v0_keyhash",
                        TxOut            = coins.Single(x => x.Outpoint.Hash == txId && x.Outpoint.N == idx).TxOut
                    };

                    // Make the coordinator believe that the transaction is being
                    // broadcasted using the RPC interface. Once we receive this tx
                    // (the `SendRawTransationAsync` was invoked) we stop waiting
                    // and finish the waiting tasks to finish the test successfully.
                    rpc.OnSendRawTransactionAsync = (tx) =>
                    {
                        transactionCompleted.SetResult(tx);
                        return(tx.GetHash());
                    };

                    // Instruct the coodinator DI container to use these two scoped
                    // services to build everything (wabisabi controller, arena, etc)
                    services.AddScoped <IRPCClient>(s => rpc);
                    services.AddScoped <WabiSabiConfig>(s => new WabiSabiConfig {
                        MaxInputCountByRound = InputCount
                    });
                });
            }).CreateClient();

            // Create the coinjoin client
            var apiClient = _apiApplicationFactory.CreateWabiSabiHttpApiClient(httpClient);

            using var roundStateUpdater = new RoundStateUpdater(TimeSpan.FromSeconds(1), apiClient);
            await roundStateUpdater.StartAsync(CancellationToken.None);

            var kitchen = new Kitchen();

            kitchen.Cook("");

            var coinJoinClient = new CoinJoinClient(apiClient, coins, kitchen, keyManager, roundStateUpdater);

            // Run the coinjoin client task.
            await coinJoinClient.StartCoinJoinAsync(cts.Token);

            var boadcastedTx = await transactionCompleted.Task.ConfigureAwait(false);             // wait for the transaction to be broadcasted.

            Assert.NotNull(boadcastedTx);

            await roundStateUpdater.StopAsync(CancellationToken.None);
        }