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);
        }
Exemple #2
0
 protected virtual void OnTransactionAdded(Transaction transaction)
 {
     TransactionAdded?.Invoke(this, transaction);
 }
Exemple #3
0
        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);
        }