public async Task get_transaction_should_return_null_if_receipt_or_transaction_is_null() { var hash = TestItem.KeccakA; var result = await _ndmBridge.GetTransactionAsync(hash); result.Should().BeNull(); _blockchainBridge.Received().GetTransaction(hash); }
public async Task get_transaction_should_invoke_proxy_eth_getTransactionByHash_and_eth_getTransactionReceipt_and_return_null_if_receipt_or_transaction_is_null() { var hash = TestItem.KeccakA; var result = await _ndmBridge.GetTransactionAsync(hash); await _proxy.Received().eth_getTransactionByHash(hash); await _proxy.Received().eth_getTransactionReceipt(hash); result.Should().BeNull(); }
public void update_gas_price_should_fail_if_sending_new_transaction_fails() { var transactionHash = TestItem.KeccakA; var transaction = Build.A.Transaction.TestObject; _blockchainBridge.GetTransactionAsync(transactionHash) .Returns(new NdmTransaction(transaction, true, 1, TestItem.KeccakB, 1)); Func <Task> act = () => _transactionService.UpdateGasPriceAsync(transactionHash, 1); act.Should().Throw <InvalidOperationException>() .WithMessage("Transaction was not sent (received an empty hash)."); _blockchainBridge.Received().GetTransactionAsync(transactionHash); _blockchainBridge.Received().GetNetworkIdAsync(); _blockchainBridge.Received().SendOwnTransactionAsync(transaction); }
public async Task try_confirm_should_not_confirm_deposit_if_transaction_is_pending() { var deposit = GetDepositDetails(); var transaction = GetTransaction(true); _blockchainBridge.GetTransactionAsync(deposit.Transaction.Hash).Returns(transaction); await _depositConfirmationService.TryConfirmAsync(deposit); deposit.Confirmed.Should().BeFalse(); deposit.Transaction.State.Should().Be(TransactionState.Pending); await _blockchainBridge.Received().GetTransactionAsync(deposit.Transaction.Hash); }
public async Task try_confirm_should_skip_further_processing_if_block_was_not_found() { const int latestBlockNumber = 3; var deposit = GetDepositDetails(); var transaction = GetTransaction(); _blockchainBridge.GetTransactionAsync(deposit.TransactionHash).Returns(transaction); _blockchainBridge.GetLatestBlockNumberAsync().Returns(latestBlockNumber); await _depositConfirmationService.TryConfirmAsync(deposit); await _blockchainBridge.Received().GetLatestBlockNumberAsync(); await _blockchainBridge.Received().FindBlockAsync(latestBlockNumber); await _depositService.DidNotReceive().VerifyDepositAsync(deposit.Consumer, deposit.Id, Arg.Any <long>()); }
public async Task can_send_transaction_when_claim_status_unknown() { PaymentClaim claim = GenerateTestClaim(PaymentClaimStatus.Unknown, "can_send_transaction_when_claim_status_unknown"); var testTransaction = new NdmTransaction(new Transaction(), false, 5, Keccak.OfAnEmptyString, 0); await paymentClaimRepository.AddAsync(claim); blockchainBridge.GetTransactionAsync(claim.Transaction.Hash).Returns(Task.FromResult(testTransaction)); transactionVerifier.VerifyAsync(testTransaction).Returns(Task.FromResult(new TransactionVerifierResult(true, 5, 2))); WaitForPaymentClaimsProcessing(); var addedClaim = await paymentClaimRepository.GetAsync(claim.Id); Assert.IsTrue(addedClaim.Status == PaymentClaimStatus.Sent); }
public async Task TryConfirmAsync(DepositDetails deposit) { if (deposit.Confirmed || deposit.Rejected) { return; } var transactionHash = deposit.TransactionHash; var transaction = await _blockchainBridge.GetTransactionAsync(deposit.TransactionHash); if (transaction is null) { if (_logger.IsInfo) { _logger.Info($"Transaction was not found for hash: '{transactionHash}' for deposit: '{deposit.Id}' to be confirmed."); } return; } var headNumber = await _blockchainBridge.GetLatestBlockNumberAsync(); var(confirmations, rejected) = await VerifyDepositConfirmationsAsync(deposit, transaction, headNumber); if (rejected) { deposit.Reject(); await _depositRepository.UpdateAsync(deposit); await _consumerNotifier.SendDepositRejectedAsync(deposit.Id); return; } if (_logger.IsInfo) { _logger.Info($"Deposit: '{deposit.Id}' has {confirmations} confirmations (required at least {_requiredBlockConfirmations}) for transaction hash: '{transactionHash}' to be confirmed."); } var confirmed = confirmations >= _requiredBlockConfirmations; if (confirmed) { if (_logger.IsInfo) { _logger.Info($"Deposit with id: '{deposit.Deposit.Id}' has been confirmed."); } } if (confirmations != deposit.Confirmations || confirmed) { deposit.SetConfirmations(confirmations); await _depositRepository.UpdateAsync(deposit); } await _consumerNotifier.SendDepositConfirmationsStatusAsync(deposit.Id, deposit.DataAsset.Name, confirmations, _requiredBlockConfirmations, deposit.ConfirmationTimestamp, confirmed); }
public async Task try_confirm_should_skip_further_processing_if_transaction_is_pending() { var deposit = GetDepositDetails(); var transaction = GetTransaction(true); _blockchainBridge.GetTransactionAsync(deposit.Transaction.Hash).Returns(transaction); await _depositConfirmationService.TryConfirmAsync(deposit); await _blockchainBridge.Received().GetTransactionAsync(deposit.Transaction.Hash); await _depositService.DidNotReceive().VerifyDepositAsync(deposit.Consumer, deposit.Id, Arg.Any <long>()); }
private async Task TryConfirmClaimAsync(DepositDetails deposit, string type) { var depositId = deposit.Id; var transactionHash = deposit.TransactionHash; var transaction = await _blockchainBridge.GetTransactionAsync(transactionHash); if (transaction is null) { if (_logger.IsInfo) { _logger.Info($"Transaction was not found for hash: '{transactionHash}' for deposit: '{depositId}' to claim the {type}refund."); } return; } if (_logger.IsInfo) { _logger.Info($"Trying to claim the {type}refund (transaction hash: '{transactionHash}') for deposit: '{depositId}'."); } var verifierResult = await _transactionVerifier.VerifyAsync(transaction); if (!verifierResult.BlockFound) { if (_logger.IsWarn) { _logger.Warn($"Block number: {transaction.BlockNumber}, hash: '{transaction.BlockHash}' was not found for transaction hash: '{transactionHash}' - {type}refund claim for deposit: '{depositId}' will not confirmed."); } return; } if (_logger.IsInfo) { _logger.Info($"The {type}refund claim (transaction hash: '{transactionHash}') for deposit: '{depositId}' has {verifierResult.Confirmations} confirmations (required at least {verifierResult.RequiredConfirmations})."); } if (!verifierResult.Confirmed) { return; } deposit.SetRefundClaimed(); await _depositRepository.UpdateAsync(deposit); if (_logger.IsInfo) { _logger.Info($"The {type}refund claim (transaction hash: '{transactionHash}') for deposit: '{depositId}' has been confirmed."); } }
public void update_gas_price_should_fail_if_transaction_is_not_pending() { var transactionHash = TestItem.KeccakA; var transaction = Build.A.Transaction.TestObject; _blockchainBridge.GetTransactionAsync(transactionHash) .Returns(new NdmTransaction(transaction, false, 1, TestItem.KeccakB, 1)); Func <Task> act = () => _transactionService.UpdateGasPriceAsync(transactionHash, 1); act.Should().Throw <InvalidOperationException>() .WithMessage($"Transaction with hash: '{transactionHash}' is not pending."); _blockchainBridge.Received().GetTransactionAsync(transactionHash); }
private async Task <Keccak> UpdateAsync(Keccak transactionHash, Action <Transaction> update) { if (transactionHash is null) { throw new ArgumentException("Transaction hash cannot be null.", nameof(transactionHash)); } var transactionDetails = await _blockchainBridge.GetTransactionAsync(transactionHash); if (transactionDetails is null) { throw new ArgumentException($"Transaction was not found for hash: '{transactionHash}'.", nameof(transactionHash)); } if (!transactionDetails.IsPending) { throw new InvalidOperationException($"Transaction with hash: '{transactionHash}' is not pending."); } var transaction = transactionDetails.Transaction; update(transaction); _wallet.Sign(transaction, await _blockchainBridge.GetNetworkIdAsync()); var hash = await _blockchainBridge.SendOwnTransactionAsync(transaction); if (hash is null) { throw new InvalidOperationException("Transaction was not sent (received an empty hash)."); } if (_logger.IsInfo) { _logger.Info($"Received a new transaction hash: '{hash}' (previous transaction hash: '{transactionHash}')."); } return(hash); }
public async Task TryConfirmAsync(DepositDetails deposit) { if (deposit.Confirmed || deposit.Rejected || deposit.Cancelled || deposit.Transaction is null) { return; } NdmTransaction? transactionDetails = null; TransactionInfo includedTransaction = deposit.Transactions.SingleOrDefault(t => t.State == TransactionState.Included); IOrderedEnumerable <TransactionInfo> pendingTransactions = deposit.Transactions .Where(t => t.State == TransactionState.Pending) .OrderBy(t => t.Timestamp); if (_logger.IsInfo) { _logger.Info($"Deposit: '{deposit.Id}' pending transactions: {string.Join(", ", pendingTransactions.Select(t => $"{t.Hash} [{t.Type}]"))}"); } if (includedTransaction is null) { foreach (TransactionInfo transaction in pendingTransactions) { Keccak?transactionHash = transaction.Hash; if (transactionHash is null) { if (_logger.IsInfo) { _logger.Info($"Transaction was not found for hash: '{null}' for deposit: '{deposit.Id}' to be confirmed."); } continue; } transactionDetails = await _blockchainBridge.GetTransactionAsync(transactionHash); if (transactionDetails is null) { if (_logger.IsInfo) { _logger.Info($"Transaction was not found for hash: '{transactionHash}' for deposit: '{deposit.Id}' to be confirmed."); } continue; } if (transactionDetails.IsPending) { if (_logger.IsInfo) { _logger.Info($"Transaction with hash: '{transactionHash}' for deposit: '{deposit.Id}' is still pending."); } continue; } deposit.SetIncludedTransaction(transactionHash); if (_logger.IsInfo) { _logger.Info($"Transaction with hash: '{transactionHash}', type: '{transaction.Type}' for deposit: '{deposit.Id}' was included into block: {transactionDetails.BlockNumber}."); } await _depositRepository.UpdateAsync(deposit); includedTransaction = transaction; break; } } else if (includedTransaction.Type == TransactionType.Cancellation) { return; } else { transactionDetails = includedTransaction.Hash == null ? null : await _blockchainBridge.GetTransactionAsync(includedTransaction.Hash); if (transactionDetails is null) { if (_logger.IsWarn) { _logger.Warn($"Transaction (set as included) was not found for hash: '{includedTransaction.Hash}' for deposit: '{deposit.Id}'."); } return; } } if (includedTransaction is null) { return; } long headNumber = await _blockchainBridge.GetLatestBlockNumberAsync(); (uint confirmations, bool rejected) = await VerifyDepositConfirmationsAsync(deposit, transactionDetails !, headNumber); if (rejected) { deposit.Reject(); await _depositRepository.UpdateAsync(deposit); await _consumerNotifier.SendDepositRejectedAsync(deposit.Id); return; } if (_logger.IsInfo) { _logger.Info($"Deposit: '{deposit.Id}' has {confirmations} confirmations (required at least {_requiredBlockConfirmations}) for transaction hash: '{includedTransaction.Hash}' to be confirmed."); } bool confirmed = confirmations >= _requiredBlockConfirmations; if (confirmed) { if (_logger.IsInfo) { _logger.Info($"Deposit with id: '{deposit.Deposit.Id}' has been confirmed."); } } if (confirmations != deposit.Confirmations || confirmed) { deposit.SetConfirmations(confirmations); await _depositRepository.UpdateAsync(deposit); } await _consumerNotifier.SendDepositConfirmationsStatusAsync(deposit.Id, deposit.DataAsset.Name, confirmations, _requiredBlockConfirmations, deposit.ConfirmationTimestamp, confirmed); }
private async Task <bool> TryConfirmClaimAsync(DepositDetails deposit, string type) { var claimType = $"{type}refund"; var depositId = deposit.Id; var transactionHash = deposit.ClaimedRefundTransaction.Hash; var transaction = await _blockchainBridge.GetTransactionAsync(transactionHash); if (transaction is null) { if (_logger.IsInfo) { _logger.Info($"Transaction was not found for hash: '{transactionHash}' for deposit: '{depositId}' to claim the {claimType}."); } return(false); } if (transaction.IsPending) { if (_logger.IsInfo) { _logger.Info($"Transaction with hash: '{transactionHash}' for deposit: '{deposit.Id}' ({claimType}) is still pending."); } return(false); } if (deposit.ClaimedRefundTransaction.State == TransactionState.Pending) { deposit.ClaimedRefundTransaction.SetIncluded(); if (_logger.IsInfo) { _logger.Info($"Transaction with hash: '{transactionHash}' for deposit: '{deposit.Id}' ({claimType}) was included into block: {transaction.BlockNumber}."); } await _depositRepository.UpdateAsync(deposit); } if (_logger.IsInfo) { _logger.Info($"Trying to claim the {claimType} (transaction hash: '{transactionHash}') for deposit: '{depositId}'."); } var verifierResult = await _transactionVerifier.VerifyAsync(transaction); if (!verifierResult.BlockFound) { if (_logger.IsWarn) { _logger.Warn($"Block number: {transaction.BlockNumber}, hash: '{transaction.BlockHash}' was not found for transaction hash: '{transactionHash}' - {claimType} claim for deposit: '{depositId}' will not confirmed."); } return(false); } if (_logger.IsInfo) { _logger.Info($"The {claimType} claim (transaction hash: '{transactionHash}') for deposit: '{depositId}' has {verifierResult.Confirmations} confirmations (required at least {verifierResult.RequiredConfirmations})."); } if (!verifierResult.Confirmed) { return(false); } deposit.SetRefundClaimed(); await _depositRepository.UpdateAsync(deposit); if (_logger.IsInfo) { _logger.Info($"The {claimType} claim (transaction hash: '{transactionHash}') for deposit: '{depositId}' has been confirmed."); } return(true); }
private async Task <bool> TryConfirmClaimAsync(DepositDetails deposit, string type) { string claimType = $"{type}refund"; Keccak depositId = deposit.Id; NdmTransaction? transactionDetails = null; TransactionInfo includedTransaction = deposit.Transactions.SingleOrDefault(t => t.State == TransactionState.Included); IOrderedEnumerable <TransactionInfo> pendingTransactions = deposit.Transactions .Where(t => t.State == TransactionState.Pending) .OrderBy(t => t.Timestamp); if (_logger.IsInfo) { _logger.Info($"Deposit: '{deposit.Id}' refund claim pending transactions: {string.Join(", ", pendingTransactions.Select(t => $"{t.Hash} [{t.Type}]"))}"); } if (includedTransaction is null) { foreach (TransactionInfo transaction in pendingTransactions) { Keccak?transactionHash = transaction.Hash; if (transactionHash is null) { if (_logger.IsInfo) { _logger.Info($"Transaction was not found for hash: '{null}' for deposit: '{depositId}' to claim the {claimType}."); } return(false); } transactionDetails = await _blockchainBridge.GetTransactionAsync(transactionHash); if (transactionDetails is null) { if (_logger.IsInfo) { _logger.Info($"Transaction was not found for hash: '{transactionHash}' for deposit: '{depositId}' to claim the {claimType}."); } return(false); } if (transactionDetails.IsPending) { if (_logger.IsInfo) { _logger.Info($"Transaction with hash: '{transactionHash}' for deposit: '{deposit.Id}' {claimType} claim is still pending."); } return(false); } deposit.SetIncludedClaimedRefundTransaction(transactionHash); if (_logger.IsInfo) { _logger.Info($"Transaction with hash: '{transactionHash}', type: '{transaction.Type}' for deposit: '{deposit.Id}' {claimType} claim was included into block: {transactionDetails.BlockNumber}."); } await _depositRepository.UpdateAsync(deposit); includedTransaction = transaction; break; } } else if (includedTransaction.Type == TransactionType.Cancellation) { return(false); } else { transactionDetails = includedTransaction.Hash == null ? null : await _blockchainBridge.GetTransactionAsync(includedTransaction.Hash); if (transactionDetails is null) { if (_logger.IsWarn) { _logger.Warn($"Transaction (set as included) was not found for hash: '{includedTransaction.Hash}' for deposit: '{deposit.Id}' {claimType} claim."); } return(false); } } if (includedTransaction is null) { return(false); } if (_logger.IsInfo) { _logger.Info($"Trying to claim the {claimType} (transaction hash: '{includedTransaction.Hash}') for deposit: '{depositId}'."); } TransactionVerifierResult verifierResult = await _transactionVerifier.VerifyAsync(transactionDetails !); if (!verifierResult.BlockFound) { if (_logger.IsWarn) { _logger.Warn($"Block number: {transactionDetails!.BlockNumber}, hash: '{transactionDetails.BlockHash}' was not found for transaction hash: '{includedTransaction.Hash}' - {claimType} claim for deposit: '{depositId}' will not confirmed."); } return(false); } if (_logger.IsInfo) { _logger.Info($"The {claimType} claim (transaction hash: '{includedTransaction.Hash}') for deposit: '{depositId}' has {verifierResult.Confirmations} confirmations (required at least {verifierResult.RequiredConfirmations})."); } if (!verifierResult.Confirmed) { return(false); } deposit.SetRefundClaimed(); await _depositRepository.UpdateAsync(deposit); if (_logger.IsInfo) { _logger.Info($"The {claimType} claim (transaction hash: '{includedTransaction.Hash}') for deposit: '{depositId}' has been confirmed."); } return(true); }