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}");
                }
            }
        }
Esempio n. 2
0
        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);
        }
Esempio n. 5
0
        /// <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);
        }
Esempio n. 6
0
            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);
            }
        }
Esempio n. 9
0
            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);
        }
Esempio n. 12
0
        /// <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);
            }
        }
Esempio n. 13
0
        /// <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);
        }
Esempio n. 14
0
        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));
        }
Esempio n. 15
0
        /// <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}");
        }
Esempio n. 16
0
        /// <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);
            }
        }
Esempio n. 18
0
 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);
        }
Esempio n. 22
0
 public Current(INetworkBlockHeader networkBlockHeader, DateTime blockSeenTime, TimeSpan currentServerTimeOffset)
 {
     this.NetworkBlockHeader      = networkBlockHeader;
     this.BlockSeenTime           = blockSeenTime;
     this.CurrentServerTimeOffset = currentServerTimeOffset;
 }
Esempio n. 23
0
        /// <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);
            }
        }
Esempio n. 24
0
        /// <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);
        }