Exemplo n.º 1
0
 public TransactionManager(
     ITransactionVerifier transactionVerifier,
     IContractRegisterer contractRegisterer,
     IStateManager stateManager
     )
 {
     _stateManager        = stateManager ?? throw new ArgumentNullException(nameof(stateManager));
     _transactionVerifier = transactionVerifier ?? throw new ArgumentNullException(nameof(transactionVerifier));
     _transactionExecuter = new TransactionExecuter(contractRegisterer);
     _transactionExecuter.OnSystemContractInvoked +=
         (sender, context) => OnSystemContractInvoked?.Invoke(sender, context);
     // once a transaction is verified asynchronously, it invokes OnTransacionVerified
     // and this adds the transaction to _verifiedTransactions
     transactionVerifier.OnTransactionVerified += (sender, transaction) =>
                                                  _verifiedTransactions.TryAdd(transaction.Hash, transaction.Hash);
 }
Exemplo n.º 2
0
        private OperatingError _InvokeContract(
            UInt160 addressTo, byte[] input, TransactionReceipt receipt,
            IBlockchainSnapshot snapshot, bool isSystemContract
            )
        {
            var transaction = receipt.Transaction;
            var context     = new InvocationContext(receipt.Transaction.From, snapshot, receipt);

            try
            {
                if (receipt.GasUsed > transaction.GasLimit)
                {
                    return(OperatingError.OutOfGas);
                }
                var result = ContractInvoker.Invoke(addressTo, context, input, transaction.GasLimit - receipt.GasUsed);
                receipt.GasUsed += result.GasUsed;
                if (result.Status != ExecutionStatus.Ok)
                {
                    return(OperatingError.ContractFailed);
                }

                if (receipt.GasUsed > transaction.GasLimit)
                {
                    return(OperatingError.OutOfGas);
                }
                /* this OnSystemContractInvoked is useful for internal communication (for example - during keyGeneration) */
                if (isSystemContract)
                {
                    OnSystemContractInvoked?.Invoke(this, context);
                }
                return(OperatingError.Ok);
            }
            catch (OutOfGasException e)
            {
                receipt.GasUsed += e.GasUsed;
            }
            catch (Exception e)
            {
                Logger.LogWarning($"Failed contract execution: {e}");
                return(OperatingError.InvalidContract);
            }

            return(OperatingError.OutOfGas);
        }
Exemplo n.º 3
0
        private OperatingError _Execute(
            Block block,
            IEnumerable <TransactionReceipt> transactions,
            out List <TransactionReceipt> removeTransactions,
            out List <TransactionReceipt> relayTransactions,
            bool isEmulation,
            out ulong gasUsed,
            out Money totalFee
            )
        {
            var mode = isEmulation ? "emulate" : "commit";

            using var timer = BlockExecTime.WithLabels(mode).NewTimer();
            BlockExecCounter.WithLabels(mode).Inc();
            totalFee = Money.Zero;
            gasUsed  = 0;
            _contractTxJustExecuted = null;

            var currentTransactions = transactions
                                      .ToDictionary(tx => tx.Hash, tx => tx);

            removeTransactions = new List <TransactionReceipt>();
            relayTransactions  = new List <TransactionReceipt>();

            /* verify next block */
            var error = Verify(block);

            if (error != OperatingError.Ok)
            {
                return(error);
            }

            /* check next block index */
            var currentBlockHeader = _stateManager.LastApprovedSnapshot.Blocks.GetTotalBlockHeight();

            if (!_IsGenesisBlock(block) && currentBlockHeader + 1 != block.Header.Index)
            {
                Logger.LogError($"Error executing block {block.Header.Index}: latest block is {currentBlockHeader}");
                return(OperatingError.InvalidNonce);
            }

            var exists = _stateManager.LastApprovedSnapshot.Blocks.GetBlockByHeight(block.Header.Index);

            if (exists != null)
            {
                return(OperatingError.BlockAlreadyExists);
            }

            /* check prev block hash */
            var latestBlock = _stateManager.LastApprovedSnapshot.Blocks.GetBlockByHeight(currentBlockHeader);

            if (latestBlock != null && !block.Header.PrevBlockHash.Equals(latestBlock.Hash))
            {
                return(OperatingError.PrevBlockHashMismatched);
            }

            /* verify block signatures */
            error = VerifySignatures(block, false);
            if (error != OperatingError.Ok)
            {
                return(error);
            }

            /* check do we have all transactions specified */
            if (block.TransactionHashes.Any(txHash => !currentTransactions.ContainsKey(txHash)))
            {
                return(OperatingError.TransactionLost);
            }

            /* execute transactions */
            ulong indexInBlock = 0;

            foreach (var txHash in block.TransactionHashes)
            {
                Logger.LogTrace($"Trying to execute tx : {txHash.ToHex()}");
                /* try to find transaction by hash */
                var receipt = currentTransactions[txHash];
                if (receipt is null)
                {
                    Logger.LogError($"For tx : {txHash.ToHex()} receipt is NULL");
                }
                receipt.Block        = block.Header.Index;
                receipt.GasUsed      = GasMetering.DefaultTxCost;
                receipt.IndexInBlock = indexInBlock;
                var transaction = receipt.Transaction;
                Logger.LogInformation($"tx : {txHash.ToHex()} blockHeaderIndex:{receipt.Block} indexinBlock:{receipt.IndexInBlock}");

                try
                {
                    // to make any changes to the state, it's required to
                    // (1) create a new snapshot, (2) make changes to the snapshot
                    // (3) either approve or rollback()
                    // approving adds all the changes to the lastApprovedSnapshot
                    // and rollback() discards all the changes and lastApprovedSnapshot is not changed
                    var snapshot = _stateManager.NewSnapshot();
                    // if the "from" address of the transaction does not have enough gas to
                    // pay for the transaction, then this transaction is not executed and thus skipped.
                    var gasLimitCheck = _CheckTransactionGasLimit(transaction, snapshot);
                    if (gasLimitCheck != OperatingError.Ok)
                    {
                        removeTransactions.Add(receipt);
                        _stateManager.Rollback();
                        Logger.LogWarning(
                            $"Unable to execute transaction {txHash.ToHex()} with nonce ({transaction.Nonce}): not enough balance for gas"
                            );
                        continue;
                    }
                    else
                    {
                        Logger.LogInformation($"Gas limit is ok for tx : {txHash.ToHex()}");
                    }

                    /* try to execute transaction */
                    OperatingError result = OperatingError.Ok;
                    try
                    {
                        result = _transactionManager.Execute(block, receipt, snapshot);
                    }
                    catch (Exception e)
                    {
                        Logger.LogWarning($"Exception during tx execution: {e}");
                        result = OperatingError.InvalidContract;
                    }
                    var txFailed = result != OperatingError.Ok;
                    if (txFailed)
                    {
                        _stateManager.Rollback();
                        // what exactly is nonce of an transaction ?
                        // nonce represents the label of the transactions from a particular address
                        // for example - let's say address A has 3 transactions in the chain.
                        // their nonces must be 0, 1, 2
                        // if A sends a new transaction, it's nonce must be 3.
                        // if the nonce does not match with the expected nonce, this transaction
                        // is also removed and skipped
                        if (result == OperatingError.InvalidNonce)
                        {
                            removeTransactions.Add(receipt);
                            Logger.LogWarning(
                                $"Unable to execute transaction {txHash.ToHex()} with nonce ({transaction.Nonce}): invalid nonce"
                                );
                            continue;
                        }

                        snapshot = _stateManager.NewSnapshot();
                        // Adds this transaction to the Transactions Snapshot
                        snapshot.Transactions.AddTransaction(receipt, TransactionStatus.Failed);
                        Logger.LogTrace($"Transaction {txHash.ToHex()} failed because of error: {result}");
                    }
                    else
                    {
                        Logger.LogInformation($"Tx is not failed for tx : {txHash.ToHex()}");
                    }

                    /* check block gas limit after execution */
                    gasUsed += receipt.GasUsed;
                    if (gasUsed > GasMetering.DefaultBlockGasLimit)
                    {
                        removeTransactions.Add(receipt);
                        relayTransactions.Add(receipt);
                        _stateManager.Rollback();
                        /* this should never happen cuz that mean that someone applied overflowed block */
                        if (!isEmulation)
                        {
                            throw new InvalidBlockException(OperatingError.BlockGasOverflow);
                        }
                        Logger.LogWarning(
                            $"Unable to take transaction {txHash.ToHex()} with gas {receipt.GasUsed}, block gas limit overflowed {gasUsed}/{GasMetering.DefaultBlockGasLimit}");
                        continue;
                    }
                    else
                    {
                        Logger.LogInformation($"Block gas limit after execution ok for tx : {txHash.ToHex()}");
                    }

                    /* try to take fee from sender */
                    result = _TakeTransactionFee(receipt, snapshot, out var fee);
                    if (result != OperatingError.Ok)
                    {
                        removeTransactions.Add(receipt);
                        _stateManager.Rollback();
                        Logger.LogWarning(
                            $"Unable to execute transaction {txHash.ToHex()} with nonce ({transaction.Nonce}), cannot take fee due to {result}"
                            );
                        continue;
                    }
                    else
                    {
                        Logger.LogInformation($"Fee taken for tx : {txHash.ToHex()}");
                    }

                    totalFee += fee;

                    if (!txFailed)
                    {
                        /* mark transaction as executed */
                        Logger.LogTrace($"Transaction executed {txHash.ToHex()}");
                        snapshot.Transactions.AddTransaction(receipt, TransactionStatus.Executed);
                    }

                    _stateManager.Approve();
                    indexInBlock++;
                }
                catch (Exception ex)
                {
                    Logger.LogWarning($"Exception [{ex}] while executing tx {txHash.ToHex()}");
                    removeTransactions.Add(receipt);
                    if (_stateManager.PendingSnapshot != null)
                    {
                        _stateManager.Rollback();
                    }
                }

                if (_contractTxJustExecuted != null && !isEmulation)
                {
                    try
                    {
                        // this invocation is required to trigger key generation appropriately
                        OnSystemContractInvoked?.Invoke(this, _contractTxJustExecuted);
                    }
                    catch (Exception e)
                    {
                        Logger.LogError(
                            $"While executing block {block.Header.Index} exception occured while processing system contract call: {e}");
                    }
                    finally
                    {
                        _contractTxJustExecuted = null;
                        _localTransactionRepository.TryAddTransaction(receipt);
                    }
                }
            }

            block.GasPrice = _CalcEstimatedBlockFee(currentTransactions.Values);

            /* save block to repository */
            try
            {
                var snapshotBlock = _stateManager.NewSnapshot();
                snapshotBlock.Blocks.AddBlock(block);
                _stateManager.Approve();
            }
            catch (Exception ex)
            {
                Logger.LogWarning($"Exception [{ex.ToString()}] while adding block tx");
                if (_stateManager.PendingSnapshot != null)
                {
                    _stateManager.Rollback();
                }
            }


            return(OperatingError.Ok);
        }