Example #1
0
        private async Task <SignedMultisigTransactionBroadcastResultItem> BroadcastFullySignedTransfersAsync(ICrossChainTransfer crossChainTransfer)
        {
            var transferItem = new SignedMultisigTransactionBroadcastResultItem()
            {
                DepositId     = crossChainTransfer.DepositTransactionId?.ToString(),
                TransactionId = crossChainTransfer.PartialTransaction.GetHash().ToString(),
            };

            TxMempoolInfo txMempoolInfo = await this.mempoolManager.InfoAsync(crossChainTransfer.PartialTransaction.GetHash()).ConfigureAwait(false);

            if (txMempoolInfo != null)
            {
                this.logger.Info("Deposit '{0}' already in the mempool.", crossChainTransfer.DepositTransactionId);
                transferItem.ItemMessage = $"Deposit '{crossChainTransfer.DepositTransactionId}' already in the mempool.";
                return(transferItem);
            }

            this.logger.Info("Broadcasting deposit '{0}', a signed multisig transaction '{1}' to the network.", crossChainTransfer.DepositTransactionId, crossChainTransfer.PartialTransaction.GetHash());

            await this.broadcasterManager.BroadcastTransactionAsync(crossChainTransfer.PartialTransaction).ConfigureAwait(false);

            // Check if transaction was added to a mempool.
            TransactionBroadcastEntry transactionBroadCastEntry = this.broadcasterManager.GetTransaction(crossChainTransfer.PartialTransaction.GetHash());

            if (transactionBroadCastEntry == null)
            {
                return(transferItem);
            }

            // If there was no mempool error, then it safe to assume the transaction was broadcasted ok or already known.
            if (transactionBroadCastEntry.MempoolError == null)
            {
                return(transferItem);
            }

            if (transactionBroadCastEntry.TransactionBroadcastState == TransactionBroadcastState.CantBroadcast && !CrossChainTransferStore.IsMempoolErrorRecoverable(transactionBroadCastEntry.MempoolError))
            {
                this.logger.Warn("Deposit '{0}' rejected: '{1}'.", crossChainTransfer.DepositTransactionId, transactionBroadCastEntry.ErrorMessage);
                this.crossChainTransferStore.RejectTransfer(crossChainTransfer);
                transferItem.ItemMessage = $"Deposit '{crossChainTransfer.DepositTransactionId}' rejected: '{transactionBroadCastEntry.ErrorMessage}'.";
                return(transferItem);
            }

            return(transferItem);
        }
Example #2
0
        public void StoreMergesSignaturesAsExpected()
        {
            var dataFolder = new DataFolder(CreateTestDir(this));

            this.Init(dataFolder);
            this.AddFunding();
            this.AppendBlocks(5);

            using (ICrossChainTransferStore crossChainTransferStore = this.CreateStore())
            {
                crossChainTransferStore.Initialize();
                crossChainTransferStore.Start();

                Assert.Equal(this.chain.Tip.HashBlock, crossChainTransferStore.TipHashAndHeight.HashBlock);
                Assert.Equal(this.chain.Tip.Height, crossChainTransferStore.TipHashAndHeight.Height);

                BitcoinAddress address = (new Key()).PubKey.Hash.GetAddress(this.network);

                Deposit deposit = new Deposit(0, new Money(160m, MoneyUnit.BTC), address.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);

                IMaturedBlockDeposits[] blockDeposits = new[] { new MaturedBlockDepositsModel(
                                                                    new MaturedBlockModel()
                    {
                        BlockHash   = 1,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                    new[] { deposit }) };

                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposits).GetAwaiter().GetResult();

                ICrossChainTransfer crossChainTransfer = crossChainTransferStore.GetAsync(new[] { deposit.Id }).GetAwaiter().GetResult().SingleOrDefault();

                Assert.NotNull(crossChainTransfer);

                Transaction transaction = crossChainTransfer.PartialTransaction;

                Assert.True(crossChainTransferStore.ValidateTransaction(transaction));

                // Create a separate instance to generate another transaction.
                Transaction transaction2;
                var         newTest     = new CrossChainTransferStoreTests();
                DataFolder  dataFolder2 = new DataFolder(CreateTestDir(this));

                newTest.federationKeys = this.federationKeys;
                newTest.SetExtendedKey(1);
                newTest.Init(dataFolder2);
                newTest.AddFunding();
                newTest.AppendBlocks(3);

                using (ICrossChainTransferStore crossChainTransferStore2 = newTest.CreateStore())
                {
                    crossChainTransferStore2.Initialize();
                    crossChainTransferStore2.Start();

                    Assert.Equal(newTest.chain.Tip.HashBlock, crossChainTransferStore2.TipHashAndHeight.HashBlock);
                    Assert.Equal(newTest.chain.Tip.Height, crossChainTransferStore2.TipHashAndHeight.Height);

                    crossChainTransferStore2.RecordLatestMatureDepositsAsync(blockDeposits).GetAwaiter().GetResult();

                    ICrossChainTransfer crossChainTransfer2 = crossChainTransferStore2.GetAsync(new[] { deposit.Id }).GetAwaiter().GetResult().SingleOrDefault();

                    Assert.NotNull(crossChainTransfer2);

                    transaction2 = crossChainTransfer2.PartialTransaction;

                    Assert.True(crossChainTransferStore2.ValidateTransaction(transaction2));
                }

                // Merges the transaction signatures.
                crossChainTransferStore.MergeTransactionSignaturesAsync(deposit.Id, new[] { transaction2 }).GetAwaiter().GetResult();

                // Test the outcome.
                crossChainTransfer = crossChainTransferStore.GetAsync(new[] { deposit.Id }).GetAwaiter().GetResult().SingleOrDefault();

                Assert.NotNull(crossChainTransfer);
                Assert.Equal(CrossChainTransferStatus.FullySigned, crossChainTransfer.Status);

                // Should be returned as signed.
                Transaction signedTransaction = crossChainTransferStore.GetTransactionsByStatusAsync(CrossChainTransferStatus.FullySigned).GetAwaiter().GetResult().Values.SingleOrDefault();
                Assert.NotNull(signedTransaction);

                // Check ths signature.
                Assert.True(crossChainTransferStore.ValidateTransaction(signedTransaction, true));
            }
        }
Example #3
0
        public async Task AttemptFederationInvalidWithdrawal()
        {
            var dataFolder = new DataFolder(CreateTestDir(this));

            this.Init(dataFolder);
            this.AddFunding();
            this.AppendBlocks(WithdrawalTransactionBuilder.MinConfirmations);

            using (ICrossChainTransferStore crossChainTransferStore = this.CreateStore())
            {
                crossChainTransferStore.Initialize();
                crossChainTransferStore.Start();

                WaitLoop(() => this.ChainIndexer.Tip.Height == crossChainTransferStore.TipHashAndHeight.Height);
                Assert.Equal(this.ChainIndexer.Tip.HashBlock, crossChainTransferStore.TipHashAndHeight.HashBlock);

                BitcoinAddress address = (new Key()).PubKey.Hash.GetAddress(this.network);

                var deposit = new Deposit(0, new Money(160m, MoneyUnit.BTC), address.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);

                MaturedBlockDepositsModel[] blockDeposits = new[] { new MaturedBlockDepositsModel(
                                                                        new MaturedBlockInfoModel()
                    {
                        BlockHash   = 1,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                        new[] { deposit }) };

                await crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposits);

                ICrossChainTransfer[] crossChainTransfers = await crossChainTransferStore.GetAsync(new[] { deposit.Id });

                ICrossChainTransfer crossChainTransfer = crossChainTransfers.SingleOrDefault();

                Assert.NotNull(crossChainTransfer);

                Transaction transaction = crossChainTransfer.PartialTransaction;

                Assert.True(crossChainTransferStore.ValidateTransaction(transaction));

                crossChainTransfers = await crossChainTransferStore.GetAsync(new[] { deposit.Id });

                ICrossChainTransfer crossChainTransfer2 = crossChainTransfers.SingleOrDefault();

                Assert.NotNull(crossChainTransfer2);

                // Modify transaction 2 to send the funds to a new address.
                BitcoinAddress bogusAddress = new Key().PubKey.Hash.GetAddress(this.network);

                Transaction transaction2 = crossChainTransfer2.PartialTransaction;

                transaction2.Outputs[1].ScriptPubKey = bogusAddress.ScriptPubKey;

                // Merges the transaction signatures.
                await crossChainTransferStore.MergeTransactionSignaturesAsync(deposit.Id, new[] { transaction2 });

                // Test the outcome.
                crossChainTransfers = await crossChainTransferStore.GetAsync(new[] { deposit.Id });

                crossChainTransfer = crossChainTransfers.SingleOrDefault();

                Assert.NotNull(crossChainTransfer);

                // Expect signing to fail.
                Assert.NotEqual(CrossChainTransferStatus.FullySigned, crossChainTransfer.Status);

                // Should return null.
                Dictionary <uint256, Transaction> signedTransactions = await crossChainTransferStore.GetTransactionsByStatusAsync(CrossChainTransferStatus.FullySigned);

                Transaction signedTransaction = signedTransactions.Values.SingleOrDefault();

                Assert.Null(signedTransaction);
            }
        }
        private string CollectStats()
        {
            StringBuilder benchLog = new StringBuilder();

            benchLog.AppendLine();
            benchLog.AppendLine("====== Federation Wallet ======");

            (Money ConfirmedAmount, Money UnConfirmedAmount)balances = this.federationWalletManager.GetWallet().GetSpendableAmount();
            bool isFederationActive = this.federationWalletManager.IsFederationActive();

            benchLog.AppendLine("Federation Wallet: ".PadRight(LoggingConfiguration.ColumnLength)
                                + " Confirmed balance: " + balances.ConfirmedAmount.ToString().PadRight(LoggingConfiguration.ColumnLength)
                                + " Unconfirmed balance: " + balances.UnConfirmedAmount.ToString().PadRight(LoggingConfiguration.ColumnLength)
                                + " Federation Status: " + (isFederationActive ? "Active" : "Inactive"));
            benchLog.AppendLine();

            var apiSettings = (ApiSettings)this.fullNode.Services.ServiceProvider.GetService(typeof(ApiSettings));

            if (!isFederationActive)
            {
                var warning =
                    "=============================================".PadLeft(10, '=')
                    + Environment.NewLine
                    + Environment.NewLine + "Federation node not enabled. You will not be able to sign transactions until you enable it."
                    + Environment.NewLine + $"If not done previously, please import your private key using "
                    + Environment.NewLine + $"{apiSettings.ApiUri}/api/FederationWallet/{FederationWalletRouteEndPoint.ImportKey}"
                    + Environment.NewLine + $"Then enable the wallet using "
                    + Environment.NewLine + $"{apiSettings.ApiUri}/api/FederationWallet/{FederationWalletRouteEndPoint.EnableFederation}"
                    + Environment.NewLine
                    + Environment.NewLine + $"============================================".PadLeft(10, '=')
                    + Environment.NewLine;
                benchLog.AppendLine(warning);
            }

            // Display recent withdrawals (if any).
            IWithdrawal[] withdrawals = this.federationWalletManager.GetWithdrawals().Take(5).ToArray();
            if (withdrawals.Length > 0)
            {
                benchLog.AppendLine("-- Recent Withdrawals --");
                ICrossChainTransfer[] transfers = this.crossChainTransferStore.GetAsync(withdrawals.Select(w => w.DepositId).ToArray()).GetAwaiter().GetResult().ToArray();
                for (int i = 0; i < withdrawals.Length; i++)
                {
                    ICrossChainTransfer transfer   = transfers[i];
                    IWithdrawal         withdrawal = withdrawals[i];
                    string line = withdrawal.GetInfo() + " Status=" + transfer?.Status;
                    switch (transfer?.Status)
                    {
                    case CrossChainTransferStatus.FullySigned:
                        if (this.mempoolManager.InfoAsync(withdrawal.Id).GetAwaiter().GetResult() != null)
                        {
                            line += "+InMempool";
                        }
                        break;

                    case CrossChainTransferStatus.Partial:
                        line += " (" + transfer.GetSignatureCount(this.network) + "/" + this.federationGatewaySettings.MultiSigM + ")";
                        break;
                    }

                    benchLog.AppendLine(line);
                }
                benchLog.AppendLine();
            }

            benchLog.AppendLine("====== NodeStore ======");
            this.AddBenchmarkLine(benchLog, new (string, int)[] {
Example #5
0
        public void StoringDepositsAfterRewindIsPrecededByClearingInvalidTransientsAndSettingNextMatureDepositHeightCorrectly()
        {
            var dataFolder = new DataFolder(TestBase.CreateTestDir(this));

            this.Init(dataFolder);

            // Creates two consecutive blocks of funding transactions with 100 coins each.
            (Transaction fundingTransaction1, ChainedHeader fundingBlock1) = AddFundingTransaction(new Money[] { Money.COIN * 100 });
            (Transaction fundingTransaction2, ChainedHeader fundingBlock2) = AddFundingTransaction(new Money[] { Money.COIN * 100 });

            this.AppendBlocks(WithdrawalTransactionBuilder.MinConfirmations);

            MultiSigAddress multiSigAddress = this.wallet.MultiSigAddress;

            using (ICrossChainTransferStore crossChainTransferStore = this.CreateStore())
            {
                crossChainTransferStore.Initialize();
                crossChainTransferStore.Start();

                TestBase.WaitLoopMessage(() => (this.ChainIndexer.Tip.Height == crossChainTransferStore.TipHashAndHeight.Height, $"ChainIndexer.Height:{this.ChainIndexer.Tip.Height} Store.TipHashHeight:{crossChainTransferStore.TipHashAndHeight.Height}"));
                Assert.Equal(this.ChainIndexer.Tip.HashBlock, crossChainTransferStore.TipHashAndHeight.HashBlock);

                BitcoinAddress address1 = (new Key()).PubKey.Hash.GetAddress(this.network);
                BitcoinAddress address2 = (new Key()).PubKey.Hash.GetAddress(this.network);

                // First deposit.
                var deposit1 = new Deposit(1, new Money(100m, MoneyUnit.BTC), address1.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);

                MaturedBlockDepositsModel[] blockDeposit1 = new[] { new MaturedBlockDepositsModel(
                                                                        new MaturedBlockInfoModel()
                    {
                        BlockHash   = 1,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                        new[] { deposit1 }) };

                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposit1).GetAwaiter().GetResult();

                ICrossChainTransfer transfer1 = crossChainTransferStore.GetAsync(new[] { deposit1.Id }).GetAwaiter().GetResult().FirstOrDefault();
                Assert.Equal(CrossChainTransferStatus.Partial, transfer1?.Status);

                // Second deposit.
                var deposit2 = new Deposit(2, new Money(100m, MoneyUnit.BTC), address2.ToString(), crossChainTransferStore.NextMatureDepositHeight, 2);

                MaturedBlockDepositsModel[] blockDeposit2 = new[] { new MaturedBlockDepositsModel(
                                                                        new MaturedBlockInfoModel()
                    {
                        BlockHash   = 2,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                        new[] { deposit2 }) };

                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposit2).GetAwaiter().GetResult();

                ICrossChainTransfer transfer2 = crossChainTransferStore.GetAsync(new[] { deposit2.Id }).GetAwaiter().GetResult().FirstOrDefault();
                Assert.Equal(CrossChainTransferStatus.Partial, transfer2?.Status);

                // Both partial transactions have been created. Now rewind the wallet.
                this.ChainIndexer.SetTip(fundingBlock1);
                this.federationWalletSyncManager.ProcessBlock(fundingBlock1.Block);

                TestBase.WaitLoopMessage(() => (this.ChainIndexer.Tip.Height == this.federationWalletSyncManager.WalletTip.Height, $"ChainIndexer.Height:{this.ChainIndexer.Tip.Height} SyncManager.TipHashHeight:{this.federationWalletSyncManager.WalletTip.Height}"));

                // Synchronize the store using a dummy get.
                crossChainTransferStore.GetAsync(new uint256[] { }).GetAwaiter().GetResult();

                // See if the NextMatureDepositHeight was rewound for the replay of deposit 2.
                Assert.Equal(deposit2.BlockNumber, crossChainTransferStore.NextMatureDepositHeight);

                // That's great. Now let's redo deposit 2 which had its funding wiped out.
                (fundingTransaction2, fundingBlock2) = AddFundingTransaction(new Money[] { Money.COIN * 100 });

                // Ensure that the new funds are mature.
                this.AppendBlocks(WithdrawalTransactionBuilder.MinConfirmations);

                // Recreate the second deposit.
                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposit2).GetAwaiter().GetResult();

                // Check that its status is partial.
                transfer2 = crossChainTransferStore.GetAsync(new[] { deposit2.Id }).GetAwaiter().GetResult().FirstOrDefault();
                Assert.Equal(CrossChainTransferStatus.Partial, transfer2?.Status);

                Assert.Equal(2, this.wallet.MultiSigAddress.Transactions.Count);
            }
        }
Example #6
0
        public void StoreMergesSignaturesAsExpected()
        {
            var dataFolder = new DataFolder(TestBase.CreateTestDir(this));

            this.Init(dataFolder);
            this.AddFunding();
            this.AppendBlocks(WithdrawalTransactionBuilder.MinConfirmations);

            using (ICrossChainTransferStore crossChainTransferStore = this.CreateStore())
            {
                crossChainTransferStore.Initialize();
                crossChainTransferStore.Start();

                TestBase.WaitLoopMessage(() => (this.ChainIndexer.Tip.Height == crossChainTransferStore.TipHashAndHeight.Height, $"ChainIndexer.Height:{this.ChainIndexer.Tip.Height} Store.TipHashHeight:{crossChainTransferStore.TipHashAndHeight.Height}"));
                Assert.Equal(this.ChainIndexer.Tip.HashBlock, crossChainTransferStore.TipHashAndHeight.HashBlock);

                BitcoinAddress address = (new Key()).PubKey.Hash.GetAddress(this.network);

                var deposit = new Deposit(0, new Money(160m, MoneyUnit.BTC), address.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);

                MaturedBlockDepositsModel[] blockDeposits = new[] { new MaturedBlockDepositsModel(
                                                                        new MaturedBlockInfoModel()
                    {
                        BlockHash   = 1,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                        new[] { deposit }) };

                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposits).GetAwaiter().GetResult();

                ICrossChainTransfer crossChainTransfer = crossChainTransferStore.GetAsync(new[] { deposit.Id }).GetAwaiter().GetResult().SingleOrDefault();

                Assert.NotNull(crossChainTransfer);

                Transaction transaction = crossChainTransfer.PartialTransaction;

                Assert.True(crossChainTransferStore.ValidateTransaction(transaction));

                // Create a separate instance to generate another transaction.
                Transaction transaction2;
                var         newTest     = new CrossChainTransferStoreTests(this.network);
                var         dataFolder2 = new DataFolder(TestBase.CreateTestDir(this));

                newTest.federationKeys = this.federationKeys;
                newTest.SetExtendedKey(1);
                newTest.Init(dataFolder2);

                // Clone chain
                for (int i = 1; i <= this.ChainIndexer.Height; i++)
                {
                    ChainedHeader header = this.ChainIndexer.GetHeader(i);
                    Block         block  = this.blockDict[header.HashBlock];
                    newTest.AppendBlock(block);
                }

                using (ICrossChainTransferStore crossChainTransferStore2 = newTest.CreateStore())
                {
                    crossChainTransferStore2.Initialize();
                    crossChainTransferStore2.Start();

                    Assert.Equal(newTest.ChainIndexer.Tip.HashBlock, crossChainTransferStore2.TipHashAndHeight.HashBlock);
                    Assert.Equal(newTest.ChainIndexer.Tip.Height, crossChainTransferStore2.TipHashAndHeight.Height);

                    crossChainTransferStore2.RecordLatestMatureDepositsAsync(blockDeposits).GetAwaiter().GetResult();

                    ICrossChainTransfer crossChainTransfer2 = crossChainTransferStore2.GetAsync(new[] { deposit.Id }).GetAwaiter().GetResult().SingleOrDefault();

                    Assert.NotNull(crossChainTransfer2);

                    transaction2 = crossChainTransfer2.PartialTransaction;

                    Assert.True(crossChainTransferStore2.ValidateTransaction(transaction2));
                }

                // Merges the transaction signatures.
                crossChainTransferStore.MergeTransactionSignaturesAsync(deposit.Id, new[] { transaction2 }).GetAwaiter().GetResult();

                // Test the outcome.
                crossChainTransfer = crossChainTransferStore.GetAsync(new[] { deposit.Id }).GetAwaiter().GetResult().SingleOrDefault();

                Assert.NotNull(crossChainTransfer);
                Assert.Equal(CrossChainTransferStatus.FullySigned, crossChainTransfer.Status);

                // Should be returned as signed.
                Transaction signedTransaction = crossChainTransferStore.GetTransfersByStatus(new[] { CrossChainTransferStatus.FullySigned }).Select(x => x.PartialTransaction).SingleOrDefault();

                Assert.NotNull(signedTransaction);

                // Check ths signature.
                Assert.True(crossChainTransferStore.ValidateTransaction(signedTransaction, true));
            }
        }