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);
                    }
                }
            }
        }
Example #2
0
        /// <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));
 }