private async Task EndGameRoundAsync(INetworkBlockHeader blockHeader, GameRound gameRound, CancellationToken cancellationToken) { await using (IObjectLock <GameRoundId>?gameRoundLock = await this._gameRoundLockManager.TakeLockAsync(gameRound.GameRoundId)) { if (gameRoundLock == null) { // something else has the game round locked this._logger.LogInformation($"{gameRound.Network.Name}: could not get lock for {gameRound.GameRoundId}"); return; } try { INetworkSigningAccount signingAccount = this._ethereumAccountManager.GetAccount(new NetworkAccount(network: gameRound.Network, address: gameRound.CreatedByAccount)); this._logger.LogInformation($"{gameRound.Network.Name}: End using game round: {gameRound.GameRoundId}"); await this._gameManager.EndGameAsync(account : signingAccount, gameRoundId : gameRound.GameRoundId, networkBlockHeader : blockHeader, cancellationToken : cancellationToken); } catch (Exception exception) { this._logger.LogError(new EventId(exception.HResult), exception: exception, $"{gameRound.Network.Name}: Failed to end game {gameRound.GameRoundId}: {exception.Message}"); } } }
private async Task <(EthereumAmount sourceAccountBalance, EthereumAmount recipientEthBalance)> GetNativeCurrencyBalancesAsync( INetworkAccount recipient, ContractAddress faucetContractAddress, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken) { IReadOnlyDictionary <EthereumAddress, EthereumAmount> accountBalances = await this._ethereumAccountBalanceSource.GetAccountBalancesAsync( networkAccounts : new EthereumAddress[] { faucetContractAddress, recipient.Address }, networkBlockHeader : networkBlockHeader, cancellationToken : cancellationToken); // get the SOURCE account's balance if (!accountBalances.TryGetValue(key: faucetContractAddress, out EthereumAmount? sourceAccountBalance)) { this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve {recipient.Network.NativeCurrency} balance for {faucetContractAddress}"); throw new InsufficientTokenException(); } if (!accountBalances.TryGetValue(key: recipient.Address, out EthereumAmount? recipientEthBalance)) { this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve {recipient.Network.NativeCurrency} balance for {recipient.Address}"); throw new InsufficientTokenException(); } return(sourceAccountBalance, recipientEthBalance); }
private Task FixGameAsync(INetworkBlockHeader blockHeader, GameRound game, CancellationToken cancellationToken) { if (!this._contractInfo.Addresses.TryGetValue(key: blockHeader.Network, out ContractAddress? gameManagerContractAddress)) { this._logger.LogWarning($"{blockHeader.Network.Name}: {game.GameRoundId} - No game contract."); return(Task.CompletedTask); } if (gameManagerContractAddress != game.GameManagerContract) { this._logger.LogWarning($"{blockHeader.Network.Name}: {game.GameRoundId} - Unknown contract address - using {game.GameManagerContract}, Current: {gameManagerContractAddress}"); return(Task.CompletedTask); } switch (game.Status) { case GameRoundStatus.PENDING: return(this.FixPendingGameAsync(blockHeader: blockHeader, game: game, cancellationToken: cancellationToken)); case GameRoundStatus.BETTING_STOPPING: return(this.FixBettingStoppingGameAsync(blockHeader: blockHeader, game: game, cancellationToken: cancellationToken)); case GameRoundStatus.COMPLETING: return(this.FixCompletingGameAsync(blockHeader: blockHeader, game: game, cancellationToken: cancellationToken)); default: this._logger.LogWarning($"{blockHeader.Network.Name}: {game.GameRoundId} - in unexpected state: {game.Status.GetName()}"); return(Task.CompletedTask); } }
/// <inheritdoc /> protected override async Task <bool> ProcessEventUnderLockAsync(GameRound gameRound, StartGameRoundEventOutput eventData, TransactionHash transactionHash, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken) { if (gameRound.Status != GameRoundStatus.PENDING) { // Don't care what status it is in - if its not pending then this event isn't relevant return(true); } // n.b. can't use the network time as blocks are not regular DateTime now = this._dateTimeSource.UtcNow(); GameRound newRoundState = new(gameRoundId : gameRound.GameRoundId, network : gameRound.Network, gameManagerContract : gameRound.GameManagerContract, createdByAccount : gameRound.CreatedByAccount, gameContract : gameRound.GameContract, seedCommit : gameRound.SeedCommit, seedReveal : gameRound.SeedReveal, status : GameRoundStatus.STARTED, roundDuration : gameRound.RoundDuration, bettingCloseDuration : gameRound.BettingCloseDuration, roundTimeoutDuration : gameRound.RoundTimeoutDuration, dateCreated : gameRound.DateCreated, dateUpdated : networkBlockHeader.Timestamp, dateStarted : now, dateClosed : null, blockNumberCreated : networkBlockHeader.Number); await this.GameRoundDataManager.ActivateAsync(activationTime : newRoundState.DateStarted !.Value, gameRoundId : newRoundState.GameRoundId, blockNumberCreated : newRoundState.BlockNumberCreated, transactionHash : transactionHash); await this._gameStatisticsPublisher.GameRoundStartedAsync(network : newRoundState.Network, gameRoundId : newRoundState.GameRoundId, this._gameRoundTimeCalculator.CalculateTimeLeft(gameRound: newRoundState), blockNumber : newRoundState.BlockNumberCreated); return(true); }
/// <inheritdoc /> protected override async Task <bool> ProcessEventUnderLockAsync(GameRound gameRound, EndGameRoundEventOutput eventData, TransactionHash transactionHash, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken) { if (gameRound.Status != GameRoundStatus.COMPLETING) { // Don't care what status it is in - if its not completing then this event isn't relevant return(true); } WinAmount[] winAmounts = eventData .Players.Zip(second: eventData.WinAmounts, resultSelector: (accountAddress, winAmount) => new WinAmount { AccountAddress = accountAddress, Amount = winAmount }) .ToArray(); this.Logger.LogInformation($"{networkBlockHeader.Network.Name}: {eventData.GameRoundId}. Progressive Win/Loss: {eventData.ProgressivePotWinLoss}"); await this.GameRoundDataManager.SaveEndRoundAsync(gameRoundId : gameRound.GameRoundId, blockNumberCreated : networkBlockHeader.Number, transactionHash : transactionHash, winAmounts : winAmounts, progressivePotWinLoss : eventData.ProgressivePotWinLoss, gameResult : eventData.GameResult, history : eventData.History); await this._gameStatisticsPublisher.GameRoundEndedAsync(network : networkBlockHeader.Network, gameRoundId : gameRound.GameRoundId, blockNumber : networkBlockHeader.Number, startBlockNumber : gameRound.BlockNumberCreated); return(true); }
public Task ProcessNetworkAsync(INetworkBlockHeader blockHeader, bool isLatestBlock, CancellationToken cancellationToken) { if (!isLatestBlock) { return(Task.CompletedTask); } return(this._endGameService.EndGameRoundsAsync(blockHeader: blockHeader, cancellationToken: cancellationToken)); }
/// <inheritdoc /> public async Task EndGameRoundsAsync(INetworkBlockHeader blockHeader, CancellationToken cancellationToken) { // n.b. can't use the network time as blocks are not regular DateTime now = this._dateTimeSource.UtcNow(); IReadOnlyList <GameRound> gameRoundsToClose = await this._gameRoundDataManager.GetAllForClosingAsync(network : blockHeader.Network, dateTimeOnNetwork : now); await Task.WhenAll(gameRoundsToClose.Select(gameRound => this.EndGameRoundAsync(gameRound: gameRound, blockHeader: blockHeader, cancellationToken: cancellationToken))); }
/// <inheritdoc /> public async Task RecoverAsync(INetworkBlockHeader blockHeader, CancellationToken cancellationToken) { // n.b. can't use the network time as blocks are not regular DateTime now = this._dateTimeSource.UtcNow(); IReadOnlyList <GameRound> brokenGames = await this._gameRoundDataManager.GetGamesToFixAsync(network : blockHeader.Network, dateTimeOnNetwork : now); this._logger.LogInformation($"{blockHeader.Network.Name}: Found {brokenGames.Count} games that need fixing"); foreach (GameRound game in brokenGames) { this._logger.LogInformation($"{game.Network.Name}: {game.GameRoundId}"); await this.FixGameAsync(blockHeader : blockHeader, game : game, cancellationToken : cancellationToken); } }
public void UpdateHeader(INetworkBlockHeader networkBlockHeader) { if (this._currentTime != null) { if (this._currentTime.NetworkBlockHeader.Hash == networkBlockHeader.Hash) { return; } } DateTime now = this._dateTimeSource.UtcNow(); // Not sure if the timestamp from the block is good enough - need to experiment or just use the server's time. TimeSpan offset = now - networkBlockHeader.Timestamp; this._currentTime = new Current(networkBlockHeader: networkBlockHeader, blockSeenTime: now, currentServerTimeOffset: offset); }
private async Task FixPendingGameAsync(INetworkBlockHeader blockHeader, GameRound game, CancellationToken cancellationToken) { this._logger.LogWarning($"{blockHeader.Network.Name}: {game.GameRoundId} - Needs fixing to start"); bool handled = await this.AttemptToResolveEventAsync <StartGameRoundEventHandler, StartGameRoundEvent, StartGameRoundEventOutput>( blockHeader : blockHeader, gameRoundId : game.GameRoundId, cancellationToken : cancellationToken); if (!handled) { try { INetworkSigningAccount account = this._ethereumAccountManager.GetAccount(new NetworkAccount(network: blockHeader.Network, address: game.CreatedByAccount)); await this._gameManager.StartGameAsync(account : account, game : game, networkBlockHeader : blockHeader, cancellationToken : cancellationToken); } catch { await this._gameRoundDataManager.MarkAsBrokenAsync(gameRoundId : game.GameRoundId, closingBlockNumber : blockHeader.Number, exceptionMessage : "Did not start"); } } }
/// <inheritdoc /> protected override async Task <bool> ProcessEventUnderLockAsync(GameRound gameRound, NoMoreBetsEventOutput eventData, TransactionHash transactionHash, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken) { if (gameRound.Status != GameRoundStatus.BETTING_STOPPING) { // Don't care what status it is in - if its not completing then this event isn't relevant return(true); } this.Logger.LogInformation($"{networkBlockHeader.Network.Name}: {eventData.GameRoundId}. Betting over"); await this.GameRoundDataManager.MarkAsBettingCompleteAsync(gameRoundId : gameRound.GameRoundId, blockNumber : networkBlockHeader.Number, transactionHash : transactionHash); await this._gameStatisticsPublisher.BettingEndedAsync(network : networkBlockHeader.Network, gameRoundId : gameRound.GameRoundId, blockNumber : networkBlockHeader.Number, startBlockNumber : gameRound.BlockNumberCreated); return(true); }
/// <inheritdoc /> public async Task <FaucetDrip> OpenAsync(IPAddress ipAddress, INetworkAccount recipient, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken) { const string sourceName = @"Test Network Faucet"; if (!this._faucetContract.Addresses.TryGetValue(key: recipient.Network, out ContractAddress? faucetContractAddress)) { this._logger.LogCritical($"{recipient.Network.Name}: Cannot issue {recipient.Network.NativeCurrency} from faucet. Faucet not available on network"); throw new InsufficientTokenException(); } INetworkSigningAccount networkSigningAccount = this._ethereumAccountManager.GetAccount(network: recipient.Network); (EthereumAmount sourceAccountBalance, EthereumAmount recipientEthBalance) = await this.GetNativeCurrencyBalancesAsync( recipient : recipient, faucetContractAddress : faucetContractAddress, networkBlockHeader : networkBlockHeader, cancellationToken : cancellationToken); (Token sourceBalanceForToken, Token recipientBalanceForToken) = await this.GetTokenBalancesAsync(recipient : recipient, networkBlockHeader : networkBlockHeader, faucetContractAddress : faucetContractAddress, cancellationToken : cancellationToken); (EthereumAmount nativeCurrencyAmount, Token tokenAmount) = this.CalculateFundsToIssue(recipient: recipient, recipientNativeCurrencyBalance: recipientEthBalance, recipientTokenBalance: recipientBalanceForToken, faucetNativeCurrencyBalance: sourceAccountBalance, faucetTokenBalance: sourceBalanceForToken); if (!await this.IsAllowedToIssueFromFaucetAsync(ipAddress: ipAddress, recipientAddress: recipient.Address)) { throw new TooFrequentTokenException(); } Issuance issuance = new(recipient : recipient, ethToIssue : nativeCurrencyAmount, tokenToIssue : tokenAmount, sourceAccountBalance : sourceAccountBalance, sourceTokenBalance : sourceBalanceForToken, sourceName : sourceName, new NetworkContract(network : recipient.Network, contractAddress : faucetContractAddress), contractInfo : this._faucetContract); try { PendingTransaction tx = await this.IssueFundsAsync(networkSigningAccount : networkSigningAccount, issuance : issuance, new TransactionContext(contextType : WellKnownContracts.Faucet, recipient.Address.ToString())); await this.RecordSuccessfulFaucetDripAsync(recipient : recipient, nativeCurrencyAmount : nativeCurrencyAmount, tokenAmount : tokenAmount, ipAddress : ipAddress); return(new FaucetDrip(transaction: tx, ethAmount: nativeCurrencyAmount, tokenAmount: tokenAmount)); } catch (TransactionWillAlwaysFailException exception) { this._logger.LogCritical(new EventId(exception.HResult), exception: exception, $"{issuance.Recipient.Network.Name}: Cannot issue {this._tokenContract.Symbol} from faucet: {exception.Message}"); throw new InsufficientTokenException(message: "Could not request funds from faucet", innerException: exception); } }
/// <inheritdoc /> public async Task StopBettingAsync(INetworkSigningAccount account, GameRoundId gameRoundId, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken) { GameRound?game = await this._gameRoundDataManager.GetAsync(gameRoundId); if (game == null) { throw new NotSupportedException(); } PendingTransaction pendingTransaction; try { StopBettingInput input = new(roundId : gameRoundId); pendingTransaction = await this._transactionService.SubmitAsync(account : account, transactionContext : new TransactionContext(contextType: @"GAMEROUND", gameRoundId.ToString()), input : input, cancellationToken : cancellationToken); } catch (TransactionWillAlwaysFailException exception) { this._logger.LogError(new EventId(exception.HResult), exception: exception, $"{account.Network.Name}: Failed to stop betting for game {gameRoundId}: {exception.Message}"); #if BROKEN await this._gameRoundDataManager.MarkAsBrokenAsync(gameRoundId : gameRoundId, closingBlockNumber : networkBlockHeader.Number, exceptionMessage : exception.Message); #endif await this._gameStatisticsPublisher.GameRoundBrokenAsync(network : account.Network, gameRoundId : gameRoundId); return; } this._logger.LogInformation($"{account.Network.Name}: Stop betting for game {gameRoundId}: tx {pendingTransaction.TransactionHash}"); await this._gameRoundDataManager.MarkAsBettingClosingAsync(gameRoundId : gameRoundId, blockNumber : networkBlockHeader.Number, transactionHash : pendingTransaction.TransactionHash); await this._gameStatisticsPublisher.BettingEndingAsync(network : account.Network, gameRoundId : gameRoundId, transactionHash : pendingTransaction.TransactionHash); }
private async Task <(Token sourceBalanceForToken, Token recipientBalanceForToken)> GetTokenBalancesAsync(INetworkAccount recipient, INetworkBlockHeader networkBlockHeader, ContractAddress faucetContractAddress, CancellationToken cancellationToken) { IReadOnlyDictionary <EthereumAddress, Erc20TokenBalance> balances = await this._ethereumAccountBalanceSource.GetErc20TokenBalancesAsync( addresses : new EthereumAddress[] { faucetContractAddress, recipient.Address }, networkBlockHeader : networkBlockHeader, tokenContract : this._tokenContract, cancellationToken : cancellationToken); if (!balances.TryGetValue(key: faucetContractAddress, out Erc20TokenBalance sourceTokenBalance)) { this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve {this._tokenContract.Symbol} balance for {faucetContractAddress}"); throw new InsufficientTokenException(); } if (!balances.TryGetValue(key: recipient.Address, out Erc20TokenBalance recipientTokenBalance)) { this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve {this._tokenContract.Symbol} balance for {recipient.Address}"); throw new InsufficientTokenException(); } return(sourceBalanceForToken : new Token(sourceTokenBalance), recipientBalanceForToken : new Token(recipientTokenBalance)); }
/// <inheritdoc /> public async Task StartGameAsync(INetworkSigningAccount account, GameRound game, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken) { PendingTransaction pendingTransaction; try { StartGameRoundInput input = new(roundId : game.GameRoundId, gameAddress : game.GameContract, entropyCommit : game.SeedCommit); pendingTransaction = await this._transactionService.SubmitAsync(account : account, transactionContext : new TransactionContext(contextType: @"GAMEROUND", game.GameRoundId.ToString()), input : input, cancellationToken : cancellationToken); } catch (TransactionWillAlwaysFailException exception) { this._logger.LogError(new EventId(exception.HResult), exception: exception, $"{networkBlockHeader.Network.Name}: Failed to start game {game.GameRoundId}: {exception.Message}"); await this._gameRoundDataManager.MarkAsBrokenAsync(gameRoundId : game.GameRoundId, closingBlockNumber : networkBlockHeader.Number, exceptionMessage : exception.Message); await this._gameStatisticsPublisher.GameRoundBrokenAsync(network : account.Network, gameRoundId : game.GameRoundId); return; } this._logger.LogInformation($"{pendingTransaction.Network.Name}: Created game {game.GameRoundId}: tx {pendingTransaction.TransactionHash}"); }
/// <inheritdoc /> public Task StartGameAsync(EthereumNetwork network, ContractAddress gameContract, INetworkBlockHeader blockHeader, CancellationToken cancellationToken) { INetworkSigningAccount account = this._ethereumAccountManager.GetAccount(network); return(this.TryToStartGameAsync(networkSigningAccount: account, gameContract: gameContract, blockHeader: blockHeader, cancellationToken: cancellationToken)); }
/// <inheritdoc /> public async Task <FaucetDrip> OpenAsync(IPAddress ipAddress, INetworkAccount recipient, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken) { const string sourceName = @"Test Network Faucet"; // ETH & FUN source (Faucet account - no need to be a signing account) if (!this._faucetContract.Addresses.TryGetValue(key: recipient.Network, out ContractAddress? contractAddress)) { this._logger.LogCritical($"{recipient.Network.Name}: Cannot issue ETH from faucet. Faucet not available on network"); throw new InsufficientTokenException(); } INetworkSigningAccount networkSigningAccount = this._ethereumAccountManager.GetAccount(network: recipient.Network); NetworkContract fundsSourceContract = new(network : recipient.Network, contractAddress : contractAddress); IReadOnlyList <EthereumAddress> networkAccounts = new EthereumAddress[] { contractAddress, recipient.Address }; IReadOnlyDictionary <EthereumAddress, EthereumAmount> accountBalances = await this._ethereumAccountBalanceSource.GetAccountBalancesAsync(networkAccounts : networkAccounts, networkBlockHeader : networkBlockHeader, cancellationToken : CancellationToken.None); // get the SOURCE account's balance if (!accountBalances.TryGetValue(key: contractAddress, out EthereumAmount? sourceAccountBalance)) { this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve balance for {networkSigningAccount.Address}"); throw new InsufficientTokenException(); } // get the account's current ETH balance if (!accountBalances.TryGetValue(key: recipient.Address, out EthereumAmount? recipientEthBalance)) { this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve balance for {recipient.Address}"); throw new InsufficientTokenException(); } IReadOnlyDictionary <EthereumAddress, Erc20TokenBalance> balances = await this._ethereumAccountBalanceSource.GetErc20TokenBalancesAsync( addresses : networkAccounts, networkBlockHeader : networkBlockHeader, tokenContract : this._tokenContract, cancellationToken : cancellationToken); // get the SOURCE account's current FUN balance if (!balances.TryGetValue(key: fundsSourceContract.Address, out Erc20TokenBalance sourceTokenBalance)) { this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve balance for {fundsSourceContract.Address}"); throw new InsufficientTokenException(); } Token sourceBalanceForToken = new(sourceTokenBalance); // get the recipient account's current FUN balance if (!balances.TryGetValue(key: recipient.Address, out Erc20TokenBalance recipientTokenBalance)) { this._logger.LogCritical($"{recipient.Network.Name}: Could not retrieve balance for {recipient.Address}"); throw new InsufficientTokenException(); } Token recipientBalanceForToken = new(recipientTokenBalance); bool giveToken = recipientBalanceForToken < this._maximumRecipientTokenBalance; bool giveEth = recipientEthBalance < this._maximumRecipientEthBalance; this._logger.LogInformation( $"{recipient.Network.Name}: Source: {sourceAccountBalance.ToFormattedUnitWithSymbol()} {sourceBalanceForToken.ToFormattedUnitWithSymbol()} Recipient: {recipientEthBalance.ToFormattedUnitWithSymbol()} {recipientBalanceForToken.ToFormattedUnitWithSymbol()} Issue: ETH: {giveEth} FUN: {giveToken} Max Eth: {this._maximumRecipientEthBalance.ToFormattedUnitWithSymbol()} Max FUN: {this._maximumRecipientTokenBalance.ToFormattedUnitWithSymbol()}"); if (!giveToken && !giveEth) { if (this._executionEnvironment.IsDevelopmentOrTest()) { this._logger.LogWarning($"{recipient.Network.Name}: Could not issue eth to {recipient.Address} - Recipient balance > max"); } throw new TooMuchTokenException(); } if (!await this.IsAllowedToIssueFromFaucetAsync(ipAddress: ipAddress, recipientAddress: recipient.Address)) { throw new TooFrequentTokenException(); } EthereumAmount ethAmount = giveEth ? this.CalculateAmountOfEthToIssueFromFaucet(recipientEthBalance) : EthereumAmount.Zero; Token tokenAmount = giveToken ? this.CalculateAmountOfFunToIssueFromFaucet(recipientBalanceForToken) : Token.Zero; Issuance issuance = new(recipient : recipient, ethToIssue : ethAmount, tokenToIssue : tokenAmount, sourceAccountBalance : sourceAccountBalance, sourceTokenBalance : sourceBalanceForToken, sourceName : sourceName, fundsSourceContract : fundsSourceContract, contractInfo : this._faucetContract); try { PendingTransaction tx = await this.IssueFundsAsync(networkSigningAccount : networkSigningAccount, issuance : issuance, new TransactionContext(contextType : WellKnownContracts.Faucet, recipient.Address.ToString())); return(new FaucetDrip(transaction: tx, ethAmount: ethAmount, tokenAmount: tokenAmount)); } catch (TransactionWillAlwaysFailException exception) { this._logger.LogCritical(new EventId(exception.HResult), exception: exception, $"{issuance.Recipient.Network.Name}: Cannot issue FUN from faucet: {exception.Message}"); throw new InsufficientTokenException(message: "Could not request fun from faucet", innerException: exception); } }
public Task ProcessNetworkForBlockRemovalAsync(INetworkBlockHeader blockHeader, INetworkBlockHeader newBlockHeader, CancellationToken cancellationToken) { return(Task.CompletedTask); }
/// <summary> /// Process the event. /// </summary> /// <param name="gameRound">The game round that is being processed.</param> /// <param name="eventData">The event data.</param> /// <param name="transactionHash">The transaction hash that the transaction was mined in.</param> /// <param name="networkBlockHeader">The network block header for the block the transaction was mined in.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <returns>true, if the event was successfully processed; otherwise, false.</returns> protected abstract Task <bool> ProcessEventUnderLockAsync(GameRound gameRound, TEvent eventData, TransactionHash transactionHash, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken);
/// <inheritdoc /> public Task ProcessNetworkAsync(INetworkBlockHeader blockHeader, bool isLatestBlock, CancellationToken cancellationToken) { return(this._brokenGameRecovery.RecoverAsync(blockHeader: blockHeader, cancellationToken: cancellationToken)); }
private async Task <bool> AttemptToResolveEventAsync <TEventHandler, TEvent, TEventOutput>(INetworkBlockHeader blockHeader, GameRoundId gameRoundId, CancellationToken cancellationToken) where TEventHandler : IEventHandler <TEventOutput> where TEvent : Event <TEventOutput>, new() where TEventOutput : EventOutput { TEvent evt = this._contractInfo.Event <TEvent>(); EventSignature eventSignature = this._eventSignatureFactory.Create(evt); IReadOnlyList <TransactionHash> transactionHashes = await this._gameRoundDataManager.GetTransactionsAsync(gameRoundId : gameRoundId, functionName : evt.Name); IReadOnlyList <IPendingNetworkTransaction> transactions = await this._transactionLoader.GetTransactionsAsync(network : blockHeader.Network, transactionHashes : transactionHashes, cancellationToken : cancellationToken); IReadOnlyList <NetworkTransactionReceipt> receipts = await this._transactionLoader.GetTransactionReceiptsAsync(network : blockHeader.Network, transactionHashes : transactionHashes, cancellationToken : cancellationToken); bool handled = false; foreach (NetworkTransactionReceipt?receipt in receipts) { IPendingNetworkTransaction transaction = transactions.First(tx => tx.TransactionHash == receipt.TransactionHash); IReadOnlyList <TransactionEventLogEntry> logs = receipts.SelectMany(r => r.Logs?.Where(l => l.Topics[0] .ToEventSignature() == eventSignature) ?? Array.Empty <TransactionEventLogEntry>()) .ToArray(); IEventDispatcher ed = new EventDispatcher <TEventHandler, TEvent, TEventOutput>(contractInfo: this._contractInfo, eventSignature: eventSignature, eventHandlerFactory: this._eventHandlerFactory, eventDataManager: this._eventDataManager, eventDecoder: this._eventDecoder, confirmationsReadinessChecker: this._confirmationsReadinessChecker, logger: this._logger); handled |= await ed.DispatchAsync(network : blockHeader.Network, logs : logs, networkBlockHeader : blockHeader, latestBlockNumberOnNetwork : blockHeader.Number, isFresh : false, gasUsed : receipt.GasUsed, gasPrice : transaction.GasPrice, retrievalStrategy : EventRetrievalStrategy.RISKY); } return(handled); }
public Current(INetworkBlockHeader networkBlockHeader, DateTime blockSeenTime, TimeSpan currentServerTimeOffset) { this.NetworkBlockHeader = networkBlockHeader; this.BlockSeenTime = blockSeenTime; this.CurrentServerTimeOffset = currentServerTimeOffset; }
/// <inheritdoc /> public async Task TryToStartGameAsync(INetworkSigningAccount networkSigningAccount, ContractAddress gameContract, INetworkBlockHeader blockHeader, CancellationToken cancellationToken) { if (!this._contractInfo.Addresses.TryGetValue(key: networkSigningAccount.Network, out ContractAddress? gameManagerContractAddress)) { // Contract not supported on the network return; } await using (IObjectLock <EthereumAddress>?gameManagerLock = await this._gameManagerLockManager.TakeLockAsync(gameManagerContractAddress)) { if (gameManagerLock == null) { // something else has the game manager locked so is probably doing something important with it return; } bool canGameBeStarted = await this._gameRoundDataManager.CanStartAGameAsync(gameManagerContract: gameManagerContractAddress, (int)GameRoundParameters.InterGameDelay.TotalSeconds); if (!canGameBeStarted) { // Has active games don't start a new one return; } this._logger.LogInformation($"{blockHeader.Network.Name}: Starting new game of game contract {gameContract} using game manager: {gameManagerContractAddress}"); await this._gameManager.StartGameAsync(account : networkSigningAccount, gameContract : gameContract, networkBlockHeader : blockHeader, cancellationToken : cancellationToken); } }
/// <inheritdoc /> public async Task StartGameAsync(INetworkSigningAccount account, ContractAddress gameContract, INetworkBlockHeader networkBlockHeader, CancellationToken cancellationToken) { if (!this._lowBalanceWatcher.DoesAccountHaveEnoughBalance(account)) { this._logger.LogWarning($"{account.Network.Name}: There was no enough {account.Network.NativeCurrency} for house address {account.Address} to create a game"); return; } if (!this._gameManager.Addresses.TryGetValue(key: account.Network, out ContractAddress? gameManagerContract)) { return; } GameRoundId gameRoundId = new(bytes : this._randomSource.Generate(count : GameRoundId.RequiredByteLength)); // The commit that's public is generated by using the one way hash from the reveal. Seed seedReveal = new(bytes : this._randomSource.Generate(count : Seed.RequiredByteLength)); Seed seedCommit = new(this._hasher.Hash(seedReveal.ToSpan())); StartGameRoundInput input = new(roundId : gameRoundId, gameAddress : gameContract, entropyCommit : seedCommit); PendingTransaction pendingTransaction = await this._transactionService.SubmitAsync(account : account, transactionContext : new TransactionContext(contextType: @"GAMEROUND", gameRoundId.ToString()), input : input, cancellationToken : cancellationToken); this._logger.LogInformation($"{pendingTransaction.Network.Name}: Created game {gameRoundId} tx {pendingTransaction.TransactionHash}"); await this._gameRoundDataManager.SaveStartRoundAsync(gameRoundId : gameRoundId, createdByAccount : account.Address, network : account.Network, gameContract : gameContract, gameManagerContract : gameManagerContract, seedCommit : seedCommit, seedReveal : seedReveal, roundDuration : GameRoundParameters.RoundDuration, bettingCloseDuration : GameRoundParameters.BettingCloseDuration, roundTimeoutDuration : GameRoundParameters.RoundTimeoutDuration, blockNumberCreated : networkBlockHeader.Number, transactionHash : pendingTransaction.TransactionHash); await this._gameStatisticsPublisher.GameRoundStartingAsync(network : account.Network, gameRoundId : gameRoundId, transactionHash : pendingTransaction.TransactionHash); }