/// <summary> /// Execute the contract and add all relevant fees and refunds to the block. /// </summary> /// <remarks>TODO: At some point we need to change height to a ulong.</remarks> private IContractExecutionResult ExecuteSmartContract(TxMempoolEntry mempoolEntry) { GetSenderResult getSenderResult = this.senderRetriever.GetSender(mempoolEntry.Transaction, this.coinView, this.inBlock.Select(x => x.Transaction).ToList()); if (!getSenderResult.Success) { throw new ConsensusErrorException(new ConsensusError("sc-block-assembler-addcontracttoblock", getSenderResult.Error)); } IContractTransactionContext transactionContext = new ContractTransactionContext((ulong)this.height, this.coinbaseAddress, mempoolEntry.Fee, getSenderResult.Sender, mempoolEntry.Transaction); IContractExecutor executor = this.executorFactory.CreateExecutor(this.stateSnapshot, transactionContext); IContractExecutionResult result = executor.Execute(transactionContext); this.blockGasConsumed += result.GasConsumed; // As we're not storing receipts, can use only consensus fields. var receipt = new Receipt( new uint256(this.stateSnapshot.Root), result.GasConsumed, result.Logs.ToArray() ); this.receipts.Add(receipt); return(result); }
/// <summary> /// Execute the contract and add all relevant fees and refunds to the block. /// </summary> /// <remarks>TODO: At some point we need to change height to a ulong.</remarks> private IContractExecutionResult ExecuteSmartContract(TxMempoolEntry mempoolEntry) { // This coinview object can be altered by consensus whilst we're mining. // If this occurred, we would be mining on top of the wrong tip anyway, so // it's okay to throw a ConsensusError which is handled by the miner, and continue. GetSenderResult getSenderResult = this.senderRetriever.GetSender(mempoolEntry.Transaction, this.coinView, this.inBlock.Select(x => x.Transaction).ToList()); if (!getSenderResult.Success) { throw new ConsensusErrorException(new ConsensusError("sc-block-assembler-addcontracttoblock", getSenderResult.Error)); } IContractTransactionContext transactionContext = new ContractTransactionContext((ulong)this.height, this.coinbaseAddress, mempoolEntry.Fee, getSenderResult.Sender, mempoolEntry.Transaction); IContractExecutor executor = this.executorFactory.CreateExecutor(this.stateSnapshot, transactionContext); IContractExecutionResult result = executor.Execute(transactionContext); this.blockGasConsumed += result.GasConsumed; // As we're not storing receipts, can use only consensus fields. var receipt = new Receipt( new uint256(this.stateSnapshot.Root), result.GasConsumed, result.Logs.ToArray() ); this.receipts.Add(receipt); return(result); }
/// <summary> /// Executes the smart contract part of a transaction /// </summary> protected void ExecuteContractTransaction(RuleContext context, Transaction transaction) { IContractTransactionContext txContext = GetSmartContractTransactionContext(context, transaction); IContractExecutor executor = this.ContractCoinviewRule.ExecutorFactory.CreateExecutor(this.ContractCoinviewRule.OriginalStateRoot, txContext); IContractExecutionResult result = executor.Execute(txContext); var receipt = new Receipt( new uint256(this.ContractCoinviewRule.OriginalStateRoot.Root), result.GasConsumed, result.Logs.ToArray(), txContext.TransactionHash, txContext.Sender, result.To, result.NewContractAddress, !result.Revert, result.ErrorMessage ) { BlockHash = context.ValidationContext.BlockToValidate.GetHash() }; this.receipts.Add(receipt); ValidateRefunds(result.Refund, context.ValidationContext.BlockToValidate.Transactions[0]); if (result.InternalTransaction != null) this.generatedTransaction = result.InternalTransaction; }
/// <summary> /// Overrides the <see cref="AddToBlock(TxMempoolEntry)"/> behaviour of <see cref="BlockDefinition"/>. /// <para> /// Determine whether or not the mempool entry contains smart contract execution /// code. If not, then add to the block as per normal. Else extract and deserialize /// the smart contract code from the TxOut's ScriptPubKey. /// </para> /// </summary> /// <param name="mempoolEntry">The mempool entry containing the transactions to include.</param> public override void AddToBlock(TxMempoolEntry mempoolEntry) { TxOut smartContractTxOut = mempoolEntry.Transaction.TryGetSmartContractTxOut(); if (smartContractTxOut == null) { this.logger.LogDebug("Transaction does not contain smart contract information."); base.AddTransactionToBlock(mempoolEntry.Transaction); base.UpdateBlockStatistics(mempoolEntry); base.UpdateTotalFees(mempoolEntry.Fee); } else { this.logger.LogDebug("Transaction contains smart contract information."); if (this.blockGasConsumed >= GasPerBlockLimit) { this.logger.LogDebug("The gas limit for this block has been reached."); return; } IContractExecutionResult result = this.ExecuteSmartContract(mempoolEntry); // If including this transaction would put us over the block gas limit, then don't include it // and roll back all of the execution we did. if (this.blockGasConsumed > GasPerBlockLimit) { // Remove the last receipt. this.receipts.RemoveAt(this.receipts.Count - 1); // Set our state to where it was before this execution. uint256 lastState = this.receipts.Last().PostState; this.stateSnapshot.SyncToRoot(lastState.ToBytes()); return; } this.AddTransactionToBlock(mempoolEntry.Transaction); this.UpdateBlockStatistics(mempoolEntry); this.UpdateTotalFees(result.Fee); // If there are refunds, add them to the block. if (result.Refund != null) { this.refundOutputs.Add(result.Refund); this.logger.LogDebug("refund was added with value {0}.", result.Refund.Value); } // Add internal transactions made during execution. if (result.InternalTransaction != null) { this.AddTransactionToBlock(result.InternalTransaction); this.logger.LogDebug("Internal {0}:{1} was added.", nameof(result.InternalTransaction), result.InternalTransaction.GetHash()); } } }
/// <summary> /// Overrides the <see cref="AddToBlock(TxMempoolEntry)"/> behaviour of <see cref="BlockDefinitionProofOfWork"/>. /// <para> /// Determine whether or not the mempool entry contains smart contract execution /// code. If not, then add to the block as per normal. Else extract and deserialize /// the smart contract code from the TxOut's ScriptPubKey. /// </para> /// </summary> public override void AddToBlock(TxMempoolEntry mempoolEntry) { TxOut smartContractTxOut = mempoolEntry.Transaction.TryGetSmartContractTxOut(); if (smartContractTxOut == null) { this.logger.LogTrace("Transaction does not contain smart contract information."); base.AddTransactionToBlock(mempoolEntry.Transaction); base.UpdateBlockStatistics(mempoolEntry); base.UpdateTotalFees(mempoolEntry.Fee); } else { this.logger.LogTrace("Transaction contains smart contract information."); if (this.blockGasConsumed >= GasPerBlockLimit) { return; } // We HAVE to firstly execute the smart contract contained in the transaction // to ensure its validity before we can add it to the block. IContractExecutionResult result = this.ExecuteSmartContract(mempoolEntry); this.AddTransactionToBlock(mempoolEntry.Transaction); this.UpdateBlockStatistics(mempoolEntry); this.UpdateTotalFees(result.Fee); // If there are refunds, add them to the block. if (result.Refund != null) { this.refundOutputs.Add(result.Refund); this.logger.LogTrace("refund was added with value {0}.", result.Refund.Value); } // Add internal transactions made during execution. if (result.InternalTransaction != null) { this.AddTransactionToBlock(result.InternalTransaction); this.logger.LogTrace("Internal {0}:{1} was added.", nameof(result.InternalTransaction), result.InternalTransaction.GetHash()); } } }
/// <summary> /// Executes the smart contract part of a transaction /// </summary> public void ExecuteContractTransaction(RuleContext context, Transaction transaction) { IContractTransactionContext txContext = this.GetSmartContractTransactionContext(context, transaction); this.CheckFeeAccountsForGas(txContext.Data, txContext.MempoolFee); IContractExecutor executor = this.executorFactory.CreateExecutor(this.mutableStateRepository, txContext); Result <ContractTxData> deserializedCallData = this.callDataSerializer.Deserialize(txContext.Data); IContractExecutionResult result = executor.Execute(txContext); var receipt = new Receipt( new uint256(this.mutableStateRepository.Root), result.GasConsumed, result.Logs.ToArray(), txContext.TransactionHash, txContext.Sender, result.To, result.NewContractAddress, !result.Revert, result.Return?.ToString(), result.ErrorMessage, deserializedCallData.Value.GasPrice, txContext.TxOutValue, deserializedCallData.Value.IsCreateContract ? null : deserializedCallData.Value.MethodName, txContext.BlockHeight) { BlockHash = context.ValidationContext.BlockToValidate.GetHash() }; this.receipts.Add(receipt); if (result.Refund != null) { this.ValidateRefunds(result.Refund, context.ValidationContext.BlockToValidate.Transactions[0]); } if (result.InternalTransaction != null) { this.generatedTransaction = result.InternalTransaction; } this.CheckBlockGasLimit(result.GasConsumed); }
/// <summary> /// Execute the contract and add all relevant fees and refunds to the block. /// </summary> /// <remarks>TODO: At some point we need to change height to a ulong.</remarks> /// <param name="mempoolEntry">The mempool entry containing the smart contract transaction.</param> private IContractExecutionResult ExecuteSmartContract(TxMempoolEntry mempoolEntry) { // This coinview object can be altered by consensus whilst we're mining. // If this occurred, we would be mining on top of the wrong tip anyway, so // it's okay to throw a ConsensusError which is handled by the miner, and continue. GetSenderResult getSenderResult = this.senderRetriever.GetSender(mempoolEntry.Transaction, this.coinView, this.inBlock.Select(x => x.Transaction).ToList()); if (!getSenderResult.Success) { throw new ConsensusErrorException(new ConsensusError("sc-block-assembler-addcontracttoblock", getSenderResult.Error)); } IContractTransactionContext transactionContext = new ContractTransactionContext((ulong)this.height, this.coinbaseAddress, mempoolEntry.Fee, getSenderResult.Sender, mempoolEntry.Transaction); IContractExecutor executor = this.executorFactory.CreateExecutor(this.stateSnapshot, transactionContext); IContractExecutionResult result = executor.Execute(transactionContext); Result <ContractTxData> deserializedCallData = this.callDataSerializer.Deserialize(transactionContext.Data); this.blockGasConsumed += result.GasConsumed; // Store all fields. We will reuse these in CoinviewRule. var receipt = new Receipt( new uint256(this.stateSnapshot.Root), result.GasConsumed, result.Logs.ToArray(), transactionContext.TransactionHash, transactionContext.Sender, result.To, result.NewContractAddress, !result.Revert, result.Return?.ToString(), result.ErrorMessage, deserializedCallData.Value.GasPrice, transactionContext.TxOutValue, deserializedCallData.Value.IsCreateContract ? null : deserializedCallData.Value.MethodName); this.receipts.Add(receipt); return(result); }
/// <summary> /// Executes the smart contract part of a transaction /// </summary> public void ExecuteContractTransaction(RuleContext context, Transaction transaction) { IContractTransactionContext txContext = GetSmartContractTransactionContext(context, transaction); this.CheckFeeAccountsForGas(txContext.Data, txContext.MempoolFee); IContractExecutor executor = this.ContractCoinviewRule.ExecutorFactory.CreateExecutor(this.mutableStateRepository, txContext); IContractExecutionResult result = executor.Execute(txContext); var receipt = new Receipt( new uint256(this.mutableStateRepository.Root), result.GasConsumed, result.Logs.ToArray(), txContext.TransactionHash, txContext.Sender, result.To, result.NewContractAddress, !result.Revert, result.Return?.ToString(), result.ErrorMessage ) { BlockHash = context.ValidationContext.BlockToValidate.GetHash() }; this.receipts.Add(receipt); if (result.Refund != null) { ValidateRefunds(result.Refund, context.ValidationContext.BlockToValidate.Transactions[0]); } if (result.InternalTransaction != null) { this.generatedTransaction = result.InternalTransaction; } }
public void Create_Contract_Failure() { var contractTxData = new ContractTxData(1, 1, (Gas)1000, new byte[] { 0xAA, 0xBB, 0xCC }); StateTransitionResult stateTransitionResult = StateTransitionResult.Fail((Gas)100, StateTransitionErrorKind.VmError); var fixture = new ExecutorFixture(contractTxData); IState snapshot = fixture.State.Object.Snapshot(); fixture.StateProcessor .Setup(s => s.Apply(snapshot, It.IsAny <ExternalCreateMessage>())) .Returns(stateTransitionResult); var sut = new ContractExecutor( fixture.LoggerFactory, fixture.CallDataSerializer.Object, fixture.ContractStateRoot.Object, fixture.RefundProcessor.Object, fixture.TransferProcessor.Object, fixture.StateFactory.Object, fixture.StateProcessor.Object, fixture.ContractPrimitiveSerializer.Object); IContractExecutionResult result = sut.Execute(fixture.ContractTransactionContext); fixture.CallDataSerializer.Verify(s => s.Deserialize(fixture.Data), Times.Once); fixture.StateFactory.Verify(sf => sf .Create( fixture.ContractStateRoot.Object, It.IsAny <IBlock>(), fixture.ContractTransactionContext.TxOutValue, fixture.ContractTransactionContext.TransactionHash), Times.Once); // We only apply the message to the snapshot. fixture.StateProcessor.Verify(sm => sm.Apply(fixture.State.Object.Snapshot(), It.Is <ExternalCreateMessage>(m => m.Code == contractTxData.ContractExecutionCode && m.Parameters == contractTxData.MethodParameters)), Times.Once); // We do not transition to the snapshot because the applying the message was unsuccessful. fixture.State.Verify(sm => sm.TransitionTo(fixture.State.Object.Snapshot()), Times.Never); fixture.TransferProcessor.Verify(t => t .Process( snapshot.ContractState, null, fixture.ContractTransactionContext, snapshot.InternalTransfers, true), Times.Once); fixture.RefundProcessor.Verify(t => t .Process( contractTxData, fixture.MempoolFee, fixture.ContractTransactionContext.Sender, It.IsAny <Gas>(), false), Times.Once); Assert.Null(result.To); Assert.Null(result.NewContractAddress); Assert.Equal(stateTransitionResult.Error.VmError, result.ErrorMessage); Assert.True(result.Revert); Assert.Equal(stateTransitionResult.GasConsumed, result.GasConsumed); Assert.Null(result.Return); Assert.Equal(fixture.InternalTransaction, result.InternalTransaction); Assert.Equal(fixture.Fee, (Money)result.Fee); Assert.Equal(fixture.Refund, result.Refund); Assert.Equal(fixture.State.Object.GetLogs(fixture.ContractPrimitiveSerializer.Object), result.Logs); }
public void Call_Contract_Failure() { var parameters = new object[] { }; var contractTxData = new ContractTxData(1, 1, (Gas)1000, uint160.One, "TestMethod", parameters); StateTransitionResult stateTransitionResult = StateTransitionResult.Fail((Gas)100, StateTransitionErrorKind.VmError); var fixture = new ExecutorFixture(contractTxData); IState snapshot = fixture.State.Object.Snapshot(); fixture.StateProcessor .Setup(s => s.Apply(snapshot, It.IsAny <ExternalCallMessage>())) .Returns(stateTransitionResult); var sut = new ContractExecutor( fixture.LoggerFactory, fixture.CallDataSerializer.Object, fixture.ContractStateRoot.Object, fixture.RefundProcessor.Object, fixture.TransferProcessor.Object, fixture.StateFactory.Object, fixture.StateProcessor.Object, fixture.ContractPrimitiveSerializer.Object); IContractExecutionResult result = sut.Execute(fixture.ContractTransactionContext); fixture.CallDataSerializer.Verify(s => s.Deserialize(fixture.Data), Times.Once); fixture.StateFactory.Verify(sf => sf .Create( fixture.ContractStateRoot.Object, It.IsAny <IBlock>(), fixture.ContractTransactionContext.TxOutValue, fixture.ContractTransactionContext.TransactionHash), Times.Once); // We only apply the message to the snapshot. fixture.StateProcessor.Verify(sm => sm.Apply(snapshot, It.Is <ExternalCallMessage>(m => m.Method.Name == contractTxData.MethodName && m.Method.Parameters == contractTxData.MethodParameters && m.Amount == fixture.ContractTransactionContext.TxOutValue && m.From == fixture.ContractTransactionContext.Sender && m.To == contractTxData.ContractAddress)), Times.Once); // We do not transition to the snapshot because the applying the message was unsuccessful. fixture.State.Verify(sm => sm.TransitionTo(snapshot), Times.Never); // Transfer processor is called with null for new contract address and true for reversion required. fixture.TransferProcessor.Verify(t => t .Process( snapshot.ContractState, null, fixture.ContractTransactionContext, snapshot.InternalTransfers, true), Times.Once); fixture.RefundProcessor.Verify(t => t .Process( contractTxData, fixture.MempoolFee, fixture.ContractTransactionContext.Sender, stateTransitionResult.GasConsumed, false), Times.Once); Assert.Equal(contractTxData.ContractAddress, result.To); Assert.Null(result.NewContractAddress); Assert.Equal(stateTransitionResult.Error.VmError, result.ErrorMessage); Assert.True(result.Revert); Assert.Equal(stateTransitionResult.GasConsumed, result.GasConsumed); Assert.Null(result.Return); Assert.Equal(fixture.InternalTransaction, result.InternalTransaction); Assert.Equal(fixture.Fee, (Money)result.Fee); Assert.Equal(fixture.Refund, result.Refund); Assert.Equal(fixture.State.Object.GetLogs(fixture.ContractPrimitiveSerializer.Object), result.Logs); }
public void Call_Contract_Success() { var parameters = new object[] { }; var contractTxData = new ContractTxData(1, 1, (Gas)1000, uint160.One, "TestMethod", parameters); VmExecutionResult vmExecutionResult = VmExecutionResult.Ok(new object(), null); StateTransitionResult stateTransitionResult = StateTransitionResult.Ok((Gas)100, uint160.One, vmExecutionResult.Success.Result); var fixture = new ExecutorFixture(contractTxData); IState snapshot = fixture.State.Object.Snapshot(); fixture.StateProcessor .Setup(s => s.Apply(snapshot, It.IsAny <ExternalCallMessage>())) .Returns(stateTransitionResult); var sut = new ContractExecutor( fixture.LoggerFactory, fixture.CallDataSerializer.Object, fixture.ContractStateRoot.Object, fixture.RefundProcessor.Object, fixture.TransferProcessor.Object, fixture.StateFactory.Object, fixture.StateProcessor.Object, fixture.ContractPrimitiveSerializer.Object); IContractExecutionResult result = sut.Execute(fixture.ContractTransactionContext); fixture.CallDataSerializer.Verify(s => s.Deserialize(fixture.ContractTransactionContext.Data), Times.Once); fixture.StateFactory.Verify(sf => sf .Create( fixture.ContractStateRoot.Object, It.IsAny <IBlock>(), fixture.ContractTransactionContext.TxOutValue, fixture.ContractTransactionContext.TransactionHash), Times.Once); // We only apply the message to the snapshot. fixture.StateProcessor.Verify(sm => sm.Apply(snapshot, It.Is <ExternalCallMessage>(m => m.Method.Name == contractTxData.MethodName && m.Method.Parameters == contractTxData.MethodParameters && m.Amount == fixture.ContractTransactionContext.TxOutValue && m.From == fixture.ContractTransactionContext.Sender && m.To == contractTxData.ContractAddress)), Times.Once); // Must transition to the snapshot. fixture.State.Verify(sm => sm.TransitionTo(snapshot), Times.Once); fixture.TransferProcessor.Verify(t => t .Process( snapshot.ContractState, stateTransitionResult.Success.ContractAddress, fixture.ContractTransactionContext, snapshot.InternalTransfers, false), Times.Once); fixture.RefundProcessor.Verify(t => t .Process( contractTxData, fixture.MempoolFee, fixture.ContractTransactionContext.Sender, It.IsAny <Gas>(), false), Times.Once); Assert.Equal(contractTxData.ContractAddress, result.To); Assert.Null(result.NewContractAddress); Assert.Null(result.ErrorMessage); Assert.False(result.Revert); Assert.Equal(stateTransitionResult.GasConsumed, result.GasConsumed); Assert.Equal(stateTransitionResult.Success.ExecutionResult, result.Return); Assert.Equal(fixture.InternalTransaction, result.InternalTransaction); Assert.Equal(fixture.Fee, (Money)result.Fee); Assert.Equal(fixture.Refund, result.Refund); Assert.Equal(fixture.State.Object.GetLogs(fixture.ContractPrimitiveSerializer.Object), result.Logs); }