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); }
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)); } }
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)[] {
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); } }
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)); } }