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