예제 #1
0
        private async Task SendTransaction(TimestampDao timestamp, double gasPrice, bool isFreePlan, CancellationToken cancellationToken)
        {
            var ethSettings      = _ethHelper.GetEthSettings();
            var estimateGasPrice = gasPrice;

            var gasStationPrice = isFreePlan ? await _ethHelper.GetFreePlanGwei(ethSettings.GasStationAPIEndpoint, cancellationToken) : await _ethHelper.GetPaidPlanGwei(ethSettings.GasStationAPIEndpoint, cancellationToken);

            if (gasStationPrice != null && gasStationPrice.Gwei > double.MinValue)
            {
                estimateGasPrice = gasStationPrice.Gwei;
            }

            if (estimateGasPrice > MaxGwei)
            {
                estimateGasPrice = MaxGwei;
                var message = $"Setting to max transaction price of '{MaxGwei}' Gwei.";
                _logger.Error(message);
            }

            if (isFreePlan)
            {
                await SendRawTransaction(timestamp, _basicWeb3, ethSettings.BasicAccountSecretKey, estimateGasPrice, ethSettings);
            }
            else
            {
                await SendRawTransaction(timestamp, _premiumWeb3, ethSettings.PremiumAccountSecretKey, estimateGasPrice, ethSettings);
            }
        }
예제 #2
0
        public async Task <TimestampDao> UpdateTimestamp(TimestampDao timestamp, CancellationToken cancellationToken)
        {
            Guard.Argument(timestamp, nameof(timestamp)).NotNull();

            var response = await Client.UpsertDocumentAsync(_documentCollectionUri, timestamp, cancellationToken : cancellationToken);

            var updatedTimestamp = JsonConvert.DeserializeObject <TimestampDao>(response.Resource.ToString());

            return(updatedTimestamp);
        }
예제 #3
0
        public async Task <TimestampDao> CreateTimestamp(TimestampDao timestamp, CancellationToken cancellationToken)
        {
            Guard.Argument(timestamp, nameof(timestamp)).NotNull();

            timestamp.Timestamp = DateTime.UtcNow;
            var response = await Client.CreateDocumentAsync(_documentCollectionUri, timestamp, new RequestOptions(), false, cancellationToken);

            var newTimestamp = JsonConvert.DeserializeObject <TimestampDao>(response.Resource.ToString());

            return(newTimestamp);
        }
예제 #4
0
        public bool VerifyStamp(TimestampDao timestamp)
        {
            var fileHash  = timestamp.FileHash.ToUpper().FromBase32();
            var signature = timestamp.Signature.ToUpper().FromBase32();
            var publicKey = new Ed25519PublicKeyParameters(timestamp.PublicKey.ToUpper().FromBase32(), 0);

            var verifier = new Ed25519Signer();

            verifier.Init(false, publicKey);
            verifier.BlockUpdate(fileHash, 0, fileHash.Length);
            return(verifier.VerifySignature(signature));
        }
예제 #5
0
        public static TimestampResponse ToResponse(this TimestampDao timestamp)
        {
            if (timestamp == null)
            {
                return(null);
            }

            return(new TimestampResponse
            {
                Id = timestamp.Id,
                TransactionId = timestamp.TransactionId,
                FileName = timestamp.FileName,
                PublicKey = timestamp.PublicKey,
                Timestamp = timestamp.Timestamp,
                BlockNumber = timestamp.BlockNumber,
                FileHash = timestamp.FileHash,
                Signature = timestamp.Signature,
                Nonce = timestamp.Nonce,
                Network = timestamp.Network,
                UserId = timestamp.UserId,
                Status = timestamp.Status,
            });
        }
예제 #6
0
        public async Task <TimestampDao> GenerateTimestamp(TimestampDao timestamp, CancellationToken cancellationToken)
        {
            Guard.Argument(timestamp, nameof(timestamp)).NotNull();
            Guard.Argument(timestamp.UserId, nameof(timestamp.UserId)).NotNull().NotEmpty().NotWhiteSpace();

            var user = await _userRepository.GetUserById(timestamp.UserId, cancellationToken);

            if (user == null)
            {
                var message = $"Unable to find the user with user identifier '{timestamp.UserId}'.";
                _logger.Error(message);
                throw new UserException(message);
            }

            if (user.RemainingTimeStamps < 1)
            {
                var message = $"Not sufficient stamps left for the user '{user.Id}' with price plan '{user.CurrentPricePlanId}'.";
                _logger.Error(message);
                throw new TimestampException(message);
            }

            var pricePlan = await _pricePlanRepository.GetPricePlanById(user.CurrentPricePlanId, cancellationToken);

            if (pricePlan == null)
            {
                var message = $"Unable to find the current price plan with user identifier '{user.CurrentPricePlanId}' for an user '{user.Id}'.";
                _logger.Error(message);
                throw new UserException(message);
            }

            try
            {
                await SendTransaction(timestamp, pricePlan.GasPrice, pricePlan.Price <= 0, cancellationToken);

                var newTimestamp = await _timestampRepository.CreateTimestamp(timestamp, cancellationToken);

                await _timestampQueueService.AddTimestampMessage(new TimestampQueueMessage { TimestampId = newTimestamp.Id, TransactionId = newTimestamp.TransactionId, Created = DateTime.UtcNow, IsPremiumPlan = pricePlan.Price > 0 }, cancellationToken);

                user.RemainingTimeStamps--;
                await _userRepository.UpdateUser(user, cancellationToken);

                _logger.Information($"Successfully created time stamp for user '{user.Id}' with transaction '{newTimestamp.TransactionId}'");

                return(newTimestamp);
            }
            catch (TimestampException ex)
            {
                _logger.Error(ex.Message);
                throw ex;
            }
            catch (RpcClientUnknownException ex)
            {
                var message = $"{nameof(RpcClientUnknownException)} : {ex.Message}";
                _logger.Error(message);
                throw new RpcClientException(message);
            }
            catch (RpcClientTimeoutException ex)
            {
                var message = $"{nameof(RpcClientTimeoutException)} : {ex.Message}";
                _logger.Error(message);
                throw new RpcClientException(message);
            }
            catch (RpcClientNonceException ex)
            {
                var message = $"{nameof(RpcClientNonceException)} : {ex.Message}";
                _logger.Error(message);
                throw new RpcClientException(message);
            }
            catch (RpcClientUnderpricedException ex)
            {
                var message = $"{nameof(RpcClientUnderpricedException)} : {ex.Message}";
                _logger.Error(message);
                throw new RpcClientException(message);
            }
        }
예제 #7
0
        private async Task SendRawTransaction(TimestampDao timestamp, IWeb3 web3, string secretKey, double estimateGasPrice, EthSettings ethSettings)
        {
            if (!Enum.TryParse(ethSettings.Network, true, out Chain networkChain))
            {
                networkChain = Chain.MainNet;
                _logger.Warning($"Unable to parse '{ethSettings.Network}' to type '{typeof(Chain)}', so setting default to '{networkChain}'.");
            }

            bool proofVerified = _ethHelper.VerifyStamp(timestamp);

            if (!proofVerified)
            {
                var message = $"Unable to verify the signature '{timestamp.Signature}'.";
                _logger.Warning(message);
                throw new TimestampException(message);
            }

            string proofStr = JsonConvert.SerializeObject(
                new
            {
                file      = timestamp.FileName,
                hash      = timestamp.FileHash,
                publicKey = timestamp.PublicKey,
                signature = timestamp.Signature
            });

            var txData = HexStringUTF8ConvertorExtensions.ToHexUTF8(proofStr);

            var fromAddress = web3.TransactionManager.Account.Address;
            var futureNonce = await web3.TransactionManager.Account.NonceService.GetNextNonceAsync();

            _logger.Information($"Signed transaction on chain: {networkChain}, To: {ethSettings.ToAddress}, Nonce: {futureNonce}, GasPrice: {estimateGasPrice}, From Address :{fromAddress}");

            var offlineTransactionSigner = new TransactionSigner();
            var encoded = offlineTransactionSigner.SignTransaction(
                secretKey,
                networkChain,
                ethSettings.ToAddress,
                Web3.Convert.ToWei(0, UnitConversion.EthUnit.Gwei),
                futureNonce,
                Web3.Convert.ToWei(estimateGasPrice, UnitConversion.EthUnit.Gwei),
                new BigInteger(100000),
                txData);

            var verified = offlineTransactionSigner.VerifyTransaction(encoded);

            if (!verified)
            {
                var message = $"Unable to verify the transaction for data '{txData}'.";
                _logger.Error(message);
                throw new TimestampException(message);
            }

            try
            {
                var txId = await web3.Eth.Transactions.SendRawTransaction.SendRequestAsync("0x" + encoded);

                timestamp.Address       = fromAddress;
                timestamp.Nonce         = (long)futureNonce.Value;
                timestamp.TransactionId = txId;
                timestamp.Network       = networkChain.ToString();
                timestamp.BlockNumber   = -1;

                if (string.IsNullOrWhiteSpace(txId))
                {
                    timestamp.Status = TimestampState.Failed;
                    var message = $"Transaction failed for an user '{timestamp.UserId}' with file name '{timestamp.FileName}'.";
                    _logger.Error(message);
                }
            }
            catch (RpcResponseException ex)
            {
                await web3.TransactionManager.Account.NonceService.ResetNonce();

                if (ex.Message.Contains("nonce too low", StringComparison.InvariantCultureIgnoreCase))
                {
                    throw new RpcClientNonceException(ex.Message);
                }
                else if (ex.Message.Contains("transaction underpriced", StringComparison.InvariantCultureIgnoreCase))
                {
                    throw new RpcClientUnderpricedException(ex.Message);
                }

                throw;
            }
        }