private void ProcessEvent(WalletDepositEventEntry eventEntry)
        {
            var oldBalance = GetCurrentlyCachedBalance(eventEntry.DepositWalletPublicKey).Result;
            var newBalance = oldBalance + eventEntry.DepositQty;

            if (newBalance != eventEntry.NewSourcePublicKeyBalance)
            {
                throw new Exception(
                          $"{GetType().Name} deposit event processing detected fatal event inconsistency of wallet values, new balance event value of {eventEntry.NewSourcePublicKeyBalance} should be {newBalance}");
            }

            _knownPublicKeyBalances[eventEntry.DepositWalletPublicKey] = eventEntry.NewSourcePublicKeyBalance;
        }
Exemplo n.º 2
0
        public void ProcessEvent(WalletDepositEventEntry eventEntry)
        {
            _userService.ModifyBalance(
                eventEntry.User,
                eventEntry.AccountId,
                eventEntry.CoinSymbol,
                eventEntry.DepositQty
                ).Wait();

            if (eventEntry.DepositQty >= 0)
            {
                return;
            }

            // Double-spending attack protection
            var(balance, reservedBalance) =
                _userService.GetBalanceAndReservedBalance(eventEntry.User, eventEntry.AccountId,
                                                          eventEntry.CoinSymbol);
            if (balance < reservedBalance)
            {
                CloseUserOrders(eventEntry.User, eventEntry.AccountId, eventEntry.CoinSymbol, eventEntry.EntryTime);
            }
        }
        protected override async Task ListenForBlockchainEvents()
        {
            // Copy the collection as it's modified during iteration
            var keysIteration = new List <string>(_knownPublicKeyBalances.Keys);

            // Listen for deposits
            foreach (var publicKey in keysIteration)
            {
                decimal balance;
                try
                {
                    try
                    {
                        balance = GetBalance(publicKey).Result - _lockedPublicKeyBalances[publicKey];
                    }
                    catch (AggregateException e)
                    {
                        foreach (var ex in e.InnerExceptions)
                        {
                            _logger.LogWarning(ex.GetType().Name + ": " + ex.Message);
                        }

                        throw;
                    }
                }
                catch (Exception)
                {
                    _logger.LogWarning(
                        $"Could not receive blockchain balance of {ThisCoinSymbol} public key {publicKey}, " +
                        $"service is probably offline, skipping the wallet and waiting a few seconds");
                    Task.Delay(2000).Wait();
                    continue;
                }

                var oldBalance = GetCurrentlyCachedBalance(publicKey).Result;
                if (balance == oldBalance)
                {
                    continue;
                }

                // Generate event does not change, so we won't request it multiple times
                var generateEvent = _eventHistoryService.FindWalletGenerateByPublicKey(publicKey);

                if (balance < oldBalance)
                {
                    _logger.LogError(
                        "Detected a negative value deposit event. This is really unexpected, check your implementation stability. Invoking a value-driven deposit revocation!");
                    await new WalletCommandProcessor(
                        _versionControl, _eventHistoryService, _walletOperationService, _logger)
                    .RetryPersist(lastAttempt =>
                    {
                        IList <EventEntry> result = null;
                        _versionControl.ExecuteUsingFixedVersion(currentVersionNumber =>
                        {
                            var eventVersionNumber = currentVersionNumber + 1;
                            try
                            {
                                balance = GetBalance(publicKey).Result - _lockedPublicKeyBalances[publicKey];
                            }
                            catch (Exception)
                            {
                                _logger.LogError(
                                    $"Could not receive blockchain balance of {ThisCoinSymbol} public key {publicKey}, " +
                                    $"service is probably offline, waiting a few seconds");
                                Task.Delay(5000).Wait();
                                return;
                            }

                            // Example: balance is 30 minus locked 10. It was previously 40.
                            // 20 + returned 10 - 40  = negativeDeposit of -10.
                            // It's below zero, so we invoke a negative deposit.
                            // New balance of 30 still includes the locked withdrawal of 10.
                            var negativeDeposit = balance
                                                  + _lockedPublicKeyBalances[publicKey]
                                                  - GetCurrentlyCachedBalance(publicKey).Result;
                            result = negativeDeposit < 0
                                    ? new List <EventEntry>
                            {
                                new WalletDepositEventEntry
                                {
                                    VersionNumber          = eventVersionNumber,
                                    DepositWalletPublicKey = publicKey,
                                    User                      = generateEvent.User,
                                    AccountId                 = generateEvent.AccountId,
                                    CoinSymbol                = ThisCoinSymbol,
                                    LastWalletPublicKey       = null,
                                    DepositQty                = negativeDeposit,
                                    NewSourcePublicKeyBalance = balance + _lockedPublicKeyBalances[publicKey]
                                }
                            }
                                    : null;
                        });
                        if (result == null)
                        {
                            _logger.LogInformation(
                                "Negative value deposit event has resolved itself, canceling the value-driven deposit revocation!");
                        }

                        return(result, persistedEvents =>
                        {
                            _logger.LogInformation(
                                "Successfully persisted negative value deposit event");
                        });
                    });
                    continue;
                }

                var retry = false;
                do
                {
                    long lastTriedVersion = 0;
                    _versionControl.ExecuteUsingFixedVersion(currentVersion =>
                    {
                        if (retry && currentVersion == lastTriedVersion)
                        {
                            _logger.LogInformation(
                                $"Blockchain deposit @ version number {currentVersion + 1} waiting for new events' integration...");
                            Task.Delay(1000).Wait();
                            return;
                        }

                        // We have acquired a lock, so the deposit event may have been already processed
                        try
                        {
                            balance = GetBalance(publicKey).Result - _lockedPublicKeyBalances[publicKey];
                        }
                        catch (Exception)
                        {
                            _logger.LogError(
                                $"Could not receive blockchain balance of {ThisCoinSymbol} public key {publicKey}, " +
                                $"service is probably offline, waiting a few seconds");
                            Task.Delay(5000).Wait();
                            retry = true;
                            return;
                        }

                        lastTriedVersion = currentVersion;

                        oldBalance = GetCurrentlyCachedBalance(publicKey).Result;
                        if (balance <= oldBalance)
                        {
                            _logger.LogInformation(
                                "Blockchain deposit canceled, it was already processed by a newer event");
                            retry = false;
                            return;
                        }

                        _logger.LogInformation(
                            $"{(retry ? "Retrying" : "Trying")} a detected {ThisCoinSymbol} blockchain deposit event @ public key {publicKey}, balance {oldBalance} => {balance}, event persistence @ version number {currentVersion + 1}");

                        var deposit = new WalletDepositEventEntry
                        {
                            User       = generateEvent.User,
                            AccountId  = generateEvent.AccountId,
                            CoinSymbol = ThisCoinSymbol,
                            DepositQty = balance - oldBalance,
                            NewSourcePublicKeyBalance = balance,
                            LastWalletPublicKey       = _walletOperationService.GetLastPublicKey(publicKey),
                            DepositWalletPublicKey    = publicKey,
                            VersionNumber             = currentVersion + 1
                        };
                        IList <EventEntry> persist = new List <EventEntry>
                        {
                            deposit
                        };
                        retry = null == _eventHistoryService.Persist(persist, currentVersion).Result;
                    });
                }while (retry);

                _logger.LogInformation(
                    $"{ThisCoinSymbol} blockchain deposit event successfully persisted @ public key {publicKey}, balance {oldBalance} => {balance}");
            }
        }