public async Task RemovedFromMempoolEventAsync(RemovedFromMempoolEvent e) { if (e.Message.Reason == RemovedFromMempoolMessage.Reasons.CollisionInBlockTx) { var removedTxId = new uint256(e.Message.TxId).ToBytes(); var txWithDSCheck = (await txRepository.GetTxsForDSCheckAsync(new[] { removedTxId }, false)).ToArray(); if (txWithDSCheck.Any()) { // Try to insert the block into DB. If block is already present in DB nothing will be done await blockParser.NewBlockDiscoveredAsync(new NewBlockDiscoveredEvent() { CreationDate = clock.UtcNow(), BlockHash = e.Message.BlockHash }); foreach (var tx in txWithDSCheck) { await txRepository.InsertBlockDoubleSpendAsync( tx.TxInternalId, new uint256(e.Message.BlockHash).ToBytes(), new uint256(e.Message.CollidedWith.TxId).ToBytes(), HelperTools.HexStringToByteArray(e.Message.CollidedWith.Hex)); var notificationEvent = new NewNotificationEvent() { CreationDate = clock.UtcNow(), NotificationType = CallbackReason.DoubleSpend, TransactionId = tx.TxExternalIdBytes }; eventBus.Publish(notificationEvent); } } } }
/// <summary> /// Store the notification event into the queue to be picked up by instant notification processor or a background job /// if the queue is already full (determined by `MaxInstantNotificationsQueueSize` setting) /// </summary> public async Task EnqueueNotificationAsync(NewNotificationEvent notificationEvent) { try { // Prepare notification data from DB before writing it to queue NotificationData notificationData = null; notificationData = notificationEvent.NotificationType switch { CallbackReason.DoubleSpend => await txRepository.GetTxToSendBlockDSNotificationAsync(notificationEvent.TransactionId), CallbackReason.DoubleSpendAttempt => notificationEvent.NotificationData, CallbackReason.MerkleProof => await txRepository.GetTxToSendMerkleProofNotificationAsync(notificationEvent.TransactionId), _ => throw new InvalidOperationException($"Invalid notification type {notificationEvent.NotificationType}"), }; notificationData.NotificationType = notificationEvent.NotificationType; notificationData.CreatedAt = clock.UtcNow(); Uri uri = new(notificationData.CallbackUrl); var host = uri.Host.ToLower(); if (!notificationScheduler.Add(notificationData, host)) { // Error count is set to 0, because there was no attempt to send it out yet, we just mark it for non-instant notification await txRepository.SetNotificationErrorAsync(notificationEvent.TransactionId, notificationEvent.NotificationType, "Queue is full or too many notifications for slow hosts.", 0); return; } } catch (Exception ex) { logger.LogError($"Error while trying to enqueue notification '{notificationEvent.NotificationType}' for {new uint256(notificationEvent.TransactionId)}. Error: {ex.GetBaseException().Message}"); // Error count is set to 0, because there was no attempt to send it out yet, we just mark it for non-instant notification await txRepository.SetNotificationErrorAsync(notificationEvent.TransactionId, notificationEvent.NotificationType, ex.GetBaseException().Message, 0); } }
public async Task InvalidTxDetectedAsync(InvalidTxDetectedEvent e) { if (e.Message.RejectionCode == InvalidTxRejectionCodes.TxMempoolConflict || e.Message.RejectionCode == InvalidTxRejectionCodes.TxDoubleSpendDetected) { if (e.Message.CollidedWith != null && e.Message.CollidedWith.Length > 0) { var collisionTxList = e.Message.CollidedWith.Select(t => new uint256(t.TxId).ToBytes()); var txsWithDSCheck = (await txRepository.GetTxsForDSCheckAsync(collisionTxList, true)).ToArray(); if (txsWithDSCheck.Any()) { var dsTxId = new uint256(e.Message.TxId).ToBytes(); var dsTxPayload = string.IsNullOrEmpty(e.Message.Hex) ? Array.Empty <byte>() : HelperTools.HexStringToByteArray(e.Message.Hex); foreach (var tx in txsWithDSCheck) { var inserted = await txRepository.InsertMempoolDoubleSpendAsync( tx.TxInternalId, dsTxId, dsTxPayload); if (inserted == 0) { return; } var notificationData = new NotificationData { TxExternalId = tx.TxExternalIdBytes, DoubleSpendTxId = dsTxId, CallbackUrl = tx.CallbackUrl, CallbackEncryption = tx.CallbackEncryption, CallbackToken = tx.CallbackToken, TxInternalId = tx.TxInternalId, BlockHeight = -1, BlockInternalId = -1, BlockHash = null }; var notificationEvent = new NewNotificationEvent() { CreationDate = clock.UtcNow(), NotificationType = CallbackReason.DoubleSpendAttempt, TransactionId = tx.TxExternalIdBytes, NotificationData = notificationData }; eventBus.Publish(notificationEvent); } } } } }
private async Task InsertTxBlockLinkAsync(NBitcoin.Block block, long blockInternalId) { var txsToCheck = await txRepository.GetTxsNotInCurrentBlockChainAsync(blockInternalId); var txIdsFromBlock = new HashSet <uint256>(block.Transactions.Select(x => x.GetHash())); // Generate a list of transactions that are present in the last block and are also present in our database without a link to existing block var transactionsForMerkleProofCheck = txsToCheck.Where(x => txIdsFromBlock.Contains(x.TxExternalId)).ToArray(); await txRepository.InsertTxBlockAsync(transactionsForMerkleProofCheck.Select(x => x.TxInternalId).ToList(), blockInternalId); foreach (var transaction in transactionsForMerkleProofCheck) { var notificationEvent = new NewNotificationEvent() { CreationDate = clock.UtcNow(), NotificationType = CallbackReason.MerkleProof, TransactionId = transaction.TxExternalIdBytes }; eventBus.Publish(notificationEvent); } }
private async Task TransactionsDSCheckAsync(NBitcoin.Block block, long blockInternalId) { // Inputs are flattened along with transactionId so they can be checked for double spends. var allTransactionInputs = block.Transactions.SelectMany(x => x.Inputs.AsIndexedInputs(), (tx, txIn) => new { TxId = tx.GetHash().ToBytes(), TxInput = txIn }).Select(x => new TxWithInput { TxExternalId = x.TxId, PrevTxId = x.TxInput.PrevOut.Hash.ToBytes(), Prev_N = x.TxInput.PrevOut.N }); // Insert raw data and let the database queries find double spends await txRepository.CheckAndInsertBlockDoubleSpendAsync(allTransactionInputs, appSettings.DeltaBlockHeightForDoubleSpendCheck, blockInternalId); // If any new double spend records were generated we need to update them with transaction payload // and trigger notification events var dsTxIds = await txRepository.GetDSTxWithoutPayloadAsync(); foreach (var(dsTxId, TxId) in dsTxIds) { var payload = block.Transactions.Single(x => x.GetHash() == new uint256(dsTxId)).ToBytes(); await txRepository.UpdateDsTxPayloadAsync(dsTxId, payload); var notificationEvent = new NewNotificationEvent() { CreationDate = clock.UtcNow(), NotificationType = CallbackReason.DoubleSpend, TransactionId = TxId }; eventBus.Publish(notificationEvent); } await txRepository.SetBlockParsedForDoubleSpendDateAsync(blockInternalId); }
private async Task ProcessNotificationAsync(NewNotificationEvent e) { await notificationsHandler.EnqueueNotificationAsync(e); }
protected void PublishNotification(IPushNotification notification) { NewNotificationEvent?.Invoke(this, new NewNotificationEventArgs(notification)); }