public async Task <Transaction> AddTransaction(DateTime date, Category category, double amount, string comments, bool recurring) { var id = Guid.NewGuid(); var now = DateTime.Now; const string commandText = "INSERT INTO Transactions(Id,[Date],Category,Amount,Comments,CreatedTime,CreatedAccount,LastUpdateTime,LastUpdateAccount,Deleted,Recurring) " + "VALUES (@id,@date,(SELECT Id FROM Categories WHERE Name = @category),@amount,@comments,@createdTime,@createdAccount,@lastUpdateTime,@lastUpdateAccount,@deleted,@recurring)"; int lines; using (var dbConnection = GetConnection()) { using (var command = new SQLiteCommand(dbConnection) { CommandText = commandText }) { command.Parameters.AddWithValue("@id", id); command.Parameters.AddWithValue("@date", date); command.Parameters.AddWithValue("@category", category.Name); command.Parameters.AddWithValue("@amount", amount); command.Parameters.AddWithValue("@comments", comments); command.Parameters.AddWithValue("@createdTime", now); command.Parameters.AddWithValue("@createdAccount", _account); command.Parameters.AddWithValue("@lastUpdateTime", now); command.Parameters.AddWithValue("@lastUpdateAccount", _account); command.Parameters.AddWithValue("@deleted", 0); command.Parameters.AddWithValue("@recurring", recurring); dbConnection.Open(); lines = await command.ExecuteNonQueryAsync(); } } if (lines == 0) { return(null); } var newTransaction = await GetTransaction(id); TransactionAdded?.Invoke(this, new TransactionEventArgs(newTransaction)); return(newTransaction); }
protected virtual void OnTransactionAdded(Transaction transaction) { TransactionAdded?.Invoke(this, transaction); }
public OperatingError Add(TransactionReceipt receipt, bool notify = true) { if (receipt is null) { throw new ArgumentNullException(nameof(receipt)); } /* don't add to transaction pool transactions with the same hashes */ if (_transactions.ContainsKey(receipt.Hash)) { return(OperatingError.AlreadyExists); } /* verify transaction before adding */ if (GetNextNonceForAddress(receipt.Transaction.From) < receipt.Transaction.Nonce || _transactionManager.CalcNextTxNonce(receipt.Transaction.From) > receipt.Transaction.Nonce) { return(OperatingError.InvalidNonce); } /* special case for system transactions */ if (receipt.Transaction.From.IsZero()) { // system transaction will not be replaced by same nonce // do we need to check nonce for system txes??? if (GetNextNonceForAddress(receipt.Transaction.From) != receipt.Transaction.Nonce) { return(OperatingError.InvalidNonce); } if (!_poolRepository.ContainsTransactionByHash(receipt.Hash)) { _poolRepository.AddTransaction(receipt); } return(OperatingError.Ok); } /* check if the address has enough gas */ if (!IsBalanceValid(receipt)) { return(OperatingError.InsufficientBalance); } // Stop accept regular txes 100 blocks before Hardfork_6 if (HardforkHeights.IsHardfork_9Active(BlockHeight() + 100) && !HardforkHeights.IsHardfork_9Active(BlockHeight())) { if (!receipt.Transaction.To.Equals(ContractRegisterer.GovernanceContract) && !receipt.Transaction.To.Equals(ContractRegisterer.StakingContract)) { return(OperatingError.UnsupportedTransaction); } } // we use next height here because block header should be signed with the same chainId // and this txes will go to the next block bool useNewChainId = HardforkHeights.IsHardfork_9Active(BlockHeight() + 1); var result = _transactionManager.Verify(receipt, useNewChainId); if (result != OperatingError.Ok) { return(result); } _transactionVerifier.VerifyTransaction(receipt, useNewChainId); bool oldTxExist = false; TransactionReceipt?oldTx = null; lock (_transactions) { if (GetNextNonceForAddress(receipt.Transaction.From) != receipt.Transaction.Nonce) { /* this tx will try to replace an old one */ oldTxExist = _transactionHashTracker.TryGetTransactionHash(receipt.Transaction.From, receipt.Transaction.Nonce, out var oldTxHash); if (!oldTxExist) { Logger.LogWarning($"Max nonce for address {receipt.Transaction.From} is " + $"{GetNextNonceForAddress(receipt.Transaction.From)}. But cannot find transaction " + $"for nonce {receipt.Transaction.Nonce}"); return(OperatingError.TransactionLost); } if (!_transactions.TryGetValue(oldTxHash !, out oldTx)) { Logger.LogTrace( $"Transaction {receipt.Hash.ToHex()} cannot replace the old transaction {oldTxHash!.ToHex()}. " + $"Probable reason: old transaction is already proposed to block"); return(OperatingError.TransactionLost); } if (oldTx.Transaction.GasPrice >= receipt.Transaction.GasPrice) { // discard new transaction, it has less gas price than the old one Logger.LogTrace( $"Transaction {receipt.Hash.ToHex()} with nonce: {receipt.Transaction.Nonce} and gasPrice: " + $"{receipt.Transaction.GasPrice} trying to replace transaction {oldTx.Hash.ToHex()} with nonce: " + $"{oldTx.Transaction.Nonce} and gasPrice: {oldTx.Transaction.GasPrice} but cannot due to lower gasPrice"); return(OperatingError.Underpriced); } if (!IdenticalTransaction(oldTx.Transaction, receipt.Transaction)) { Logger.LogTrace( $"Transaction {receipt.Hash.ToHex()} with nonce: {receipt.Transaction.Nonce} and gasPrice: " + $"{receipt.Transaction.GasPrice} trying to replace transaction {oldTx.Hash.ToHex()} with nonce: " + $"{oldTx.Transaction.Nonce} and gasPrice: {oldTx.Transaction.GasPrice} but cannot due to different value is some fields"); return(OperatingError.DuplicatedTransaction); } // remove the old transaction from pool _transactionsQueue.Remove(oldTx); _nonceCalculator.TryRemove(oldTx); _transactions.TryRemove(oldTxHash !, out var _); Logger.LogTrace( $"Transaction {oldTxHash!.ToHex()} with nonce: {oldTx.Transaction.Nonce} and gasPrice: {oldTx.Transaction.GasPrice}" + $" in pool is replaced by transaction {receipt.Hash.ToHex()} with nonce: {receipt.Transaction.Nonce} and gasPrice: " + $"{receipt.Transaction.GasPrice}"); } /* put transaction to pool queue */ _transactions[receipt.Hash] = receipt; _transactionsQueue.Add(receipt); /* add to the _nonceCalculator to efficiently calculate nonce */ _nonceCalculator.TryAdd(receipt); } /* write transaction to persistence storage */ if (!oldTxExist && !_poolRepository.ContainsTransactionByHash(receipt.Hash)) { _poolRepository.AddTransaction(receipt); } else if (oldTxExist) { _poolRepository.AddAndRemoveTransaction(receipt, oldTx !); } Logger.LogTrace($"Added transaction {receipt.Hash.ToHex()} to pool"); if (notify) { TransactionAdded?.Invoke(this, receipt); } return(OperatingError.Ok); }