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