/// <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);
        }
Ejemplo n.º 3
0
        /// <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;
        }
Ejemplo n.º 4
0
        /// <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());
                }
            }
        }
Ejemplo n.º 6
0
        /// <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);
        }