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; }
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}"); } }