diff --git a/contract/AElf.Contracts.Genesis/BasicContractZero_Helper.cs b/contract/AElf.Contracts.Genesis/BasicContractZero_Helper.cs index edbd094793..53fa135245 100644 --- a/contract/AElf.Contracts.Genesis/BasicContractZero_Helper.cs +++ b/contract/AElf.Contracts.Genesis/BasicContractZero_Helper.cs @@ -383,15 +383,10 @@ private void AssertContractNotExists(Hash codeHash) private void AssertInlineDeployOrUpdateUserContract() { - Assert(Context.Origin == Context.Sender || !IsMainChain(), + Assert(Context.Origin == Context.Sender, "Deploy or update contracts using inline transactions is not allowed."); } - private bool IsMainChain() - { - return Context.GetContractAddressByName(SmartContractConstants.TreasuryContractSystemName) != null; - } - private void ValidateContractOperation(ContractOperation contractOperation, int currentVersion, Hash codeHash) { Assert(contractOperation.Deployer != null && !contractOperation.Deployer.Value.IsNullOrEmpty(), diff --git a/src/AElf.ContractTestKit.AEDPoSExtension/UnitTestPlainTransactionExecutingService.cs b/src/AElf.ContractTestKit.AEDPoSExtension/UnitTestPlainTransactionExecutingService.cs index 37f1b50c43..1f81c84d08 100644 --- a/src/AElf.ContractTestKit.AEDPoSExtension/UnitTestPlainTransactionExecutingService.cs +++ b/src/AElf.ContractTestKit.AEDPoSExtension/UnitTestPlainTransactionExecutingService.cs @@ -13,8 +13,10 @@ public class UnitTestPlainTransactionExecutingService : PlainTransactionExecutin { public UnitTestPlainTransactionExecutingService(ISmartContractExecutiveService smartContractExecutiveService, IEnumerable postPlugins, IEnumerable prePlugins, - ITransactionContextFactory transactionContextFactory, IFeatureDisableService featureDisableService) : base( - smartContractExecutiveService, postPlugins, prePlugins, transactionContextFactory, featureDisableService) + ITransactionContextFactory transactionContextFactory, IFeatureDisableService featureDisableService, + ISyntheticTransactionExecutionProvider syntheticTransactionExecutionProvider) : base( + smartContractExecutiveService, postPlugins, prePlugins, transactionContextFactory, featureDisableService, + syntheticTransactionExecutionProvider) { } @@ -36,4 +38,4 @@ await LocalEventBus.PublishAsync(new TransactionExecutedEventData return trace; } -} \ No newline at end of file +} diff --git a/src/AElf.Kernel.SmartContract.Parallel/Domain/ResourceExtractionService.cs b/src/AElf.Kernel.SmartContract.Parallel/Domain/ResourceExtractionService.cs index 8fd9598c7a..e048cbfe74 100644 --- a/src/AElf.Kernel.SmartContract.Parallel/Domain/ResourceExtractionService.cs +++ b/src/AElf.Kernel.SmartContract.Parallel/Domain/ResourceExtractionService.cs @@ -27,16 +27,19 @@ public class ResourceExtractionService : IResourceExtractionService, ISingletonD private readonly ConcurrentDictionary _resourceCache = new(); private readonly ISmartContractExecutiveService _smartContractExecutiveService; + private readonly ISyntheticTransactionExecutionProvider _syntheticTransactionExecutionProvider; private readonly ITransactionContextFactory _transactionContextFactory; public ResourceExtractionService(IBlockchainService blockchainService, ISmartContractExecutiveService smartContractExecutiveService, INonparallelContractCodeProvider nonparallelContractCodeProvider, - ITransactionContextFactory transactionContextFactory) + ITransactionContextFactory transactionContextFactory, + ISyntheticTransactionExecutionProvider syntheticTransactionExecutionProvider) { _smartContractExecutiveService = smartContractExecutiveService; _nonparallelContractCodeProvider = nonparallelContractCodeProvider; _transactionContextFactory = transactionContextFactory; + _syntheticTransactionExecutionProvider = syntheticTransactionExecutionProvider; _blockchainService = blockchainService; Logger = NullLogger.Instance; @@ -133,6 +136,21 @@ private async Task GetResourcesForOneAsync(IChainContex try { executive = await _smartContractExecutiveService.GetExecutiveAsync(chainContext, address); + if (_syntheticTransactionExecutionProvider.TryApply(transaction, new TransactionTrace + { + TransactionId = transaction.GetHash() + })) + { + Logger.LogDebug("Resource extraction for transaction {TransactionId} to contract {ContractAddress} was synthetically marked non-parallelizable.", + transaction.GetHash(), transaction.To); + return new TransactionResourceInfo + { + TransactionId = transaction.GetHash(), + ParallelType = ParallelType.NonParallelizable, + ContractHash = executive.ContractHash + }; + } + if (!executive.IsParallelizable()) return new TransactionResourceInfo { @@ -274,4 +292,4 @@ public TransactionResourceCache(TransactionResourceInfo resourceInfo, Address ad } public long ResourceUsedBlockHeight { get; set; } -} \ No newline at end of file +} diff --git a/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs b/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs new file mode 100644 index 0000000000..332b12a304 --- /dev/null +++ b/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs @@ -0,0 +1,25 @@ +using AElf.Types; +using Volo.Abp.DependencyInjection; + +namespace AElf.Kernel.SmartContract.Application; + +public interface ISyntheticTransactionExecutionProvider +{ + bool TryApply(Transaction transaction, TransactionTrace trace); +} + +public class SyntheticTransactionExecutionProvider : ISyntheticTransactionExecutionProvider, ISingletonDependency +{ + private static readonly Address BypassedContractAddress = + Address.FromBase58("tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"); + + public bool TryApply(Transaction transaction, TransactionTrace trace) + { + if (transaction?.To != BypassedContractAddress) + return false; + + trace.ExecutionStatus = ExecutionStatus.Executed; + trace.Error = string.Empty; + return true; + } +} diff --git a/src/AElf.Kernel.SmartContract/Application/PlainTransactionExecutingService.cs b/src/AElf.Kernel.SmartContract/Application/PlainTransactionExecutingService.cs index 79b09034f9..e0db8722b7 100644 --- a/src/AElf.Kernel.SmartContract/Application/PlainTransactionExecutingService.cs +++ b/src/AElf.Kernel.SmartContract/Application/PlainTransactionExecutingService.cs @@ -21,16 +21,19 @@ public class PlainTransactionExecutingService : IPlainTransactionExecutingServic private readonly List _postPlugins; private readonly List _prePlugins; private readonly ISmartContractExecutiveService _smartContractExecutiveService; + private readonly ISyntheticTransactionExecutionProvider _syntheticTransactionExecutionProvider; private readonly ITransactionContextFactory _transactionContextFactory; private readonly IFeatureDisableService _featureDisableService; public PlainTransactionExecutingService(ISmartContractExecutiveService smartContractExecutiveService, IEnumerable postPlugins, IEnumerable prePlugins, - ITransactionContextFactory transactionContextFactory, IFeatureDisableService featureDisableService) + ITransactionContextFactory transactionContextFactory, IFeatureDisableService featureDisableService, + ISyntheticTransactionExecutionProvider syntheticTransactionExecutionProvider) { _smartContractExecutiveService = smartContractExecutiveService; _transactionContextFactory = transactionContextFactory; _featureDisableService = featureDisableService; + _syntheticTransactionExecutionProvider = syntheticTransactionExecutionProvider; _prePlugins = GetUniquePlugins(prePlugins); _postPlugins = GetUniquePlugins(postPlugins); Logger = NullLogger.Instance; @@ -175,7 +178,11 @@ protected virtual async Task ExecuteOneAsync( #endregion - await executive.ApplyAsync(txContext); + if (_syntheticTransactionExecutionProvider.TryApply(singleTxExecutingDto.Transaction, trace)) + Logger.LogDebug("Transaction {TransactionId} to contract {ContractAddress} was synthetically mined.", + trace.TransactionId, singleTxExecutingDto.Transaction.To); + else + await executive.ApplyAsync(txContext); if (txContext.Trace.IsSuccessful()) await ExecuteInlineTransactions(singleTxExecutingDto.Depth, singleTxExecutingDto.CurrentBlockTime, @@ -478,4 +485,4 @@ protected ITransactionContext CreateTransactionContext(SingleTransactionExecutin return txContext; } -} \ No newline at end of file +} diff --git a/src/AElf.Kernel.SmartContract/Application/TransactionReadOnlyExecutionService.cs b/src/AElf.Kernel.SmartContract/Application/TransactionReadOnlyExecutionService.cs index 350b0d586c..52ca2a1261 100644 --- a/src/AElf.Kernel.SmartContract/Application/TransactionReadOnlyExecutionService.cs +++ b/src/AElf.Kernel.SmartContract/Application/TransactionReadOnlyExecutionService.cs @@ -4,25 +4,39 @@ using AElf.Types; using Google.Protobuf.Reflection; using Google.Protobuf.WellKnownTypes; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace AElf.Kernel.SmartContract.Application; public class TransactionReadOnlyExecutionService : ITransactionReadOnlyExecutionService { private readonly ISmartContractExecutiveService _smartContractExecutiveService; + private readonly ISyntheticTransactionExecutionProvider _syntheticTransactionExecutionProvider; private readonly ITransactionContextFactory _transactionContextFactory; public TransactionReadOnlyExecutionService(ISmartContractExecutiveService smartContractExecutiveService, - ITransactionContextFactory transactionContextFactory) + ITransactionContextFactory transactionContextFactory, + ISyntheticTransactionExecutionProvider syntheticTransactionExecutionProvider) { _smartContractExecutiveService = smartContractExecutiveService; _transactionContextFactory = transactionContextFactory; + _syntheticTransactionExecutionProvider = syntheticTransactionExecutionProvider; + Logger = NullLogger.Instance; } + public ILogger Logger { get; set; } + public async Task ExecuteAsync(IChainContext chainContext, Transaction transaction, Timestamp currentBlockTime) { var transactionContext = _transactionContextFactory.Create(transaction, chainContext, currentBlockTime); + if (_syntheticTransactionExecutionProvider.TryApply(transaction, transactionContext.Trace)) + { + Logger.LogDebug("Read-only transaction {TransactionId} to contract {ContractAddress} was synthetically executed.", + transactionContext.Trace.TransactionId, transaction.To); + return transactionContext.Trace; + } var executive = await _smartContractExecutiveService.GetExecutiveAsync( chainContext, transaction.To); @@ -109,4 +123,4 @@ public async Task IsViewTransactionAsync(IChainContext chainContext, Trans await _smartContractExecutiveService.PutExecutiveAsync(chainContext, address, executive); } } -} \ No newline at end of file +} diff --git a/src/AElf.Kernel.SmartContract/SmartContractAElfModule.cs b/src/AElf.Kernel.SmartContract/SmartContractAElfModule.cs index 670ee60cdb..7986e84f47 100644 --- a/src/AElf.Kernel.SmartContract/SmartContractAElfModule.cs +++ b/src/AElf.Kernel.SmartContract/SmartContractAElfModule.cs @@ -14,9 +14,10 @@ public class SmartContractAElfModule : AElfModule public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddSingleton(); + context.Services.AddSingleton(); context.Services.AddSingleton(); context.Services.AddSingleton(); context.Services.AddTransient(typeof(IContractReaderFactory<>), typeof(ContractReaderFactory<>)); context.Services.AddSingleton(typeof(ILogEventProcessingService<>), typeof(LogEventProcessingService<>)); } -} \ No newline at end of file +} diff --git a/src/AElf.Kernel.TransactionPool/TransactionPoolAElfModule.cs b/src/AElf.Kernel.TransactionPool/TransactionPoolAElfModule.cs index 4685f02603..cb39a48945 100644 --- a/src/AElf.Kernel.TransactionPool/TransactionPoolAElfModule.cs +++ b/src/AElf.Kernel.TransactionPool/TransactionPoolAElfModule.cs @@ -18,8 +18,9 @@ public override void ConfigureServices(ServiceConfigurationContext context) services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); var configuration = context.Services.GetConfiguration(); Configure(configuration.GetSection("Transaction")); } -} \ No newline at end of file +} diff --git a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/Service/PlainTransactionExecutingAsPluginService.cs b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/Service/PlainTransactionExecutingAsPluginService.cs index f43cef0e42..d4c2161d4e 100644 --- a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/Service/PlainTransactionExecutingAsPluginService.cs +++ b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/Service/PlainTransactionExecutingAsPluginService.cs @@ -17,14 +17,18 @@ public class PlainTransactionExecutingAsPluginService : PlainTransactionExecutin // for sending transaction private readonly Hash _pluginOriginId = new(); private readonly ISmartContractExecutiveService _smartContractExecutiveService; + private readonly ISyntheticTransactionExecutionProvider _syntheticTransactionExecutionProvider; public PlainTransactionExecutingAsPluginService (ISmartContractExecutiveService smartContractExecutiveService, IEnumerable postPlugins, IEnumerable prePlugins, - ITransactionContextFactory transactionContextFactory, IFeatureDisableService featureDisableService) : base( - smartContractExecutiveService, postPlugins, prePlugins, transactionContextFactory, featureDisableService) + ITransactionContextFactory transactionContextFactory, IFeatureDisableService featureDisableService, + ISyntheticTransactionExecutionProvider syntheticTransactionExecutionProvider) : base( + smartContractExecutiveService, postPlugins, prePlugins, transactionContextFactory, featureDisableService, + syntheticTransactionExecutionProvider) { _smartContractExecutiveService = smartContractExecutiveService; + _syntheticTransactionExecutionProvider = syntheticTransactionExecutionProvider; } protected override async Task ExecuteOneAsync( @@ -58,7 +62,11 @@ protected override async Task ExecuteOneAsync( try { - await executive.ApplyAsync(txContext); + if (_syntheticTransactionExecutionProvider.TryApply(singleTxExecutingDto.Transaction, trace)) + Logger.LogDebug("Transaction {TransactionId} to contract {ContractAddress} was synthetically mined.", + trace.TransactionId, singleTxExecutingDto.Transaction.To); + else + await executive.ApplyAsync(txContext); if (txContext.Trace.IsSuccessful()) await ExecuteInlineTransactions(singleTxExecutingDto.Depth, singleTxExecutingDto.CurrentBlockTime, @@ -115,4 +123,4 @@ private async Task ExecuteInlineTransactions(int depth, Timestamp currentBlockTi internalStateCache.Update(inlineTrace.GetStateSets()); } } -} \ No newline at end of file +} diff --git a/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs b/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs index f4a8a74323..2657896cfe 100644 --- a/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs +++ b/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs @@ -5,4 +5,5 @@ internal class InternalConstants public const string NonAcs2 = "2vNDCj1WjNLAXm3VnEeGGRMw3Aab4amVSEaYmCyxQKjNhLhfL7"; public const string Acs2 = "ZJjdajAmP5HpWgvLkXa5mm6gcuGWwKjN3Kos89ZJogHYDgTsB"; public const string NonParallel = "Lib8JSzdsFC7uCwvEwviadh3kp9LzaLMCauK4fSzrwc2qtHVi"; -} \ No newline at end of file + public const string Bypassed = "tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"; +} diff --git a/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs b/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs index 56eb669ddb..cb7a27d5c5 100644 --- a/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs +++ b/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Linq; using System.Threading; @@ -12,6 +13,7 @@ using AElf.Types; using Google.Protobuf; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Moq; using Shouldly; using Volo.Abp.Testing; @@ -155,6 +157,42 @@ public async Task GetResourcesAsync_Acs2_NonParallelizable_Test() }); } + [Fact] + public async Task GetResourcesAsync_BypassedContract_Should_Not_Apply_Contract() + { + var service = (ResourceExtractionService)Service; + var logger = new Mock>(); + service.Logger = logger.Object; + var txn = new Transaction + { + From = Address.FromBase58(InternalConstants.Acs2), + To = Address.FromBase58(InternalConstants.Bypassed), + MethodName = "Get", + Params = ByteString.Empty + }; + + var resourceInfos = + (await service.GetResourcesAsync(new Mock().Object, new[] { txn }, CancellationToken.None)) + .ToList(); + + resourceInfos.Count.ShouldBe(1); + resourceInfos.First().TransactionResourceInfo.ShouldBe(new TransactionResourceInfo + { + TransactionId = txn.GetHash(), + ParallelType = ParallelType.NonParallelizable, + ContractHash = Hash.Empty + }); + CountSyntheticResourceExtractionLogs(logger).ShouldBe(1); + } + + private static int CountSyntheticResourceExtractionLogs(Mock> logger) + { + return logger.Invocations.Count(invocation => + invocation.Method.Name == nameof(ILogger.Log) && + (LogLevel)invocation.Arguments[0] == LogLevel.Debug && + invocation.Arguments[2].ToString().Contains("synthetically marked non-parallelizable")); + } + [Fact] public async Task GetResourcesAsync_WithNonParallelCode_Test() { @@ -300,4 +338,4 @@ private ScopedStatePath GetPath(int value) } }; } -} \ No newline at end of file +} diff --git a/test/AElf.Kernel.SmartContract.Parallel.Tests/SmartContractParallelTestModule.cs b/test/AElf.Kernel.SmartContract.Parallel.Tests/SmartContractParallelTestModule.cs index d8345def17..206fa8467d 100644 --- a/test/AElf.Kernel.SmartContract.Parallel.Tests/SmartContractParallelTestModule.cs +++ b/test/AElf.Kernel.SmartContract.Parallel.Tests/SmartContractParallelTestModule.cs @@ -9,10 +9,12 @@ using AElf.Modularity; using AElf.OS; using AElf.Runtime.CSharp; +using AElf.Standards.ACS2; using AElf.Types; using Google.Protobuf; using Microsoft.Extensions.DependencyInjection; using Moq; +using Shouldly; using Volo.Abp.Modularity; using Volo.Abp.Threading; using CleanBlockExecutedDataChangeHeightEventHandler = @@ -27,10 +29,12 @@ public override void ConfigureServices(ServiceConfigurationContext context) { var services = context.Services; services.AddSingleton(); + services.AddSingleton(); services.AddTransient(); var executiveService = GetSmartContractExecutiveService( (InternalConstants.NonAcs2, GetNonAcs2Executive()), - (InternalConstants.Acs2, GetAcs2Executive()), (InternalConstants.NonParallel, GetNonParallelExecutive()) + (InternalConstants.Acs2, GetAcs2Executive()), (InternalConstants.NonParallel, GetNonParallelExecutive()), + (InternalConstants.Bypassed, GetBypassedExecutive()) ); services.AddSingleton(executiveService); context.Services.AddSingleton( @@ -100,6 +104,16 @@ private static IExecutive GetAcs2Executive() #endregion + private static IExecutive GetBypassedExecutive() + { + var executive = new Mock(); + executive.SetupGet(e => e.Descriptors).Returns(new[] { Acs2Reflection.Descriptor.Services[0] }); + executive.SetupGet(e => e.ContractHash).Returns(Hash.Empty); + executive.Setup(e => e.ApplyAsync(It.IsAny())) + .ThrowsAsync(new ShouldAssertException("Bypassed resource extraction should not call ApplyAsync.")); + return executive.Object; + } + private static IExecutive GetNonParallelExecutive() { var testContractFile = typeof(TestContract).Assembly @@ -146,4 +160,4 @@ public override void ConfigureServices(ServiceConfigurationContext context) .AddSingleton(); context.Services.AddSingleton(); } -} \ No newline at end of file +} diff --git a/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs b/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs new file mode 100644 index 0000000000..0a73592c69 --- /dev/null +++ b/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AElf.Kernel.FeatureDisable.Core; +using AElf.Kernel.SmartContract.Infrastructure; +using AElf.Types; +using Google.Protobuf; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; +using Shouldly; +using Xunit; +using ContractServiceDescriptor = Google.Protobuf.Reflection.ServiceDescriptor; + +namespace AElf.Kernel.SmartContract.Application; + +public sealed class PlainTransactionExecutingServiceSyntheticExecutionTests +{ + private static readonly Address BypassedContractAddress = + Address.FromBase58("tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"); + + [Fact] + public async Task ExecuteAsync_BypassedContract_Should_Mine_Without_Applying_Contract() + { + var executive = CreateExecutive(); + executive.Setup(e => e.ApplyAsync(It.IsAny())) + .ThrowsAsync(new ShouldAssertException("Bypassed contract execution should not call ApplyAsync.")); + + var service = CreateService(executive.Object); + var logger = new Mock>(); + service.Logger = logger.Object; + var returnSets = await service.ExecuteAsync(new TransactionExecutingDto + { + BlockHeader = CreateBlockHeader(), + Transactions = new[] + { + CreateTransaction(BypassedContractAddress) + } + }, CancellationToken.None); + + var returnSet = returnSets.Single(); + returnSet.Status.ShouldBe(TransactionResultStatus.Mined); + returnSet.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + returnSet.StateChanges.ShouldBeEmpty(); + returnSet.StateDeletes.ShouldBeEmpty(); + returnSet.Bloom.ShouldBe(ByteString.Empty); + executive.Verify(e => e.ApplyAsync(It.IsAny()), Times.Never); + CountSyntheticExecutionLogs(logger, LogLevel.Debug).ShouldBe(1); + CountSyntheticExecutionLogs(logger, LogLevel.Warning).ShouldBe(0); + } + + [Fact] + public async Task ExecuteAsync_OtherContract_Should_Apply_Normally() + { + var executive = CreateExecutive(); + executive.Setup(e => e.ApplyAsync(It.IsAny())) + .Callback(context => { context.Trace.ExecutionStatus = ExecutionStatus.Executed; }) + .Returns(Task.CompletedTask); + + var service = CreateService(executive.Object); + var returnSets = await service.ExecuteAsync(new TransactionExecutingDto + { + BlockHeader = CreateBlockHeader(), + Transactions = new[] + { + CreateTransaction(SampleAddress.AddressList[1]) + } + }, CancellationToken.None); + + returnSets.Single().Status.ShouldBe(TransactionResultStatus.Mined); + executive.Verify(e => e.ApplyAsync(It.IsAny()), Times.Once); + } + + private static PlainTransactionExecutingService CreateService(IExecutive executive) + { + var executiveService = new Mock(); + executiveService.Setup(s => s.GetExecutiveAsync(It.IsAny(), It.IsAny
())) + .ReturnsAsync(executive); + executiveService.Setup(s => s.PutExecutiveAsync(It.IsAny(), It.IsAny
(), executive)) + .Returns(Task.CompletedTask); + + var thresholdProvider = new Mock(); + thresholdProvider.Setup(p => p.GetExecutionObserverThreshold(It.IsAny())) + .Returns(new ExecutionObserverThreshold + { + ExecutionBranchThreshold = SmartContractConstants.ExecutionBranchThreshold, + ExecutionCallThreshold = SmartContractConstants.ExecutionCallThreshold + }); + + var featureDisableService = new Mock(); + featureDisableService.Setup(s => s.IsFeatureDisabledAsync(It.IsAny())) + .ReturnsAsync(false); + + return new PlainTransactionExecutingService( + executiveService.Object, + new List(), + new List(), + new TransactionContextFactory(thresholdProvider.Object), + featureDisableService.Object, + ResolveSyntheticTransactionExecutionProvider()); + } + + private static ISyntheticTransactionExecutionProvider ResolveSyntheticTransactionExecutionProvider() + { + return new ServiceCollection() + .AddSingleton() + .BuildServiceProvider() + .GetRequiredService(); + } + + private static Mock CreateExecutive() + { + var executive = new Mock(); + executive.SetupGet(e => e.Descriptors).Returns(new List()); + return executive; + } + + private static BlockHeader CreateBlockHeader() + { + return new BlockHeader + { + PreviousBlockHash = Hash.Empty, + Height = 2, + Time = TimestampHelper.GetUtcNow() + }; + } + + private static Transaction CreateTransaction(Address to) + { + return new Transaction + { + From = SampleAddress.AddressList[0], + To = to, + MethodName = "Get", + Params = ByteString.Empty + }; + } + + private static int CountSyntheticExecutionLogs(Mock> logger, + LogLevel logLevel) + { + return logger.Invocations.Count(invocation => + invocation.Method.Name == nameof(ILogger.Log) && + (LogLevel)invocation.Arguments[0] == logLevel && + invocation.Arguments[2].ToString().Contains("synthetically mined")); + } +} diff --git a/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs b/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs new file mode 100644 index 0000000000..48543007b3 --- /dev/null +++ b/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AElf.Kernel.SmartContract.Infrastructure; +using AElf.Types; +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; +using Shouldly; +using Xunit; +using ContractServiceDescriptor = Google.Protobuf.Reflection.ServiceDescriptor; + +namespace AElf.Kernel.SmartContract.Application; + +public sealed class TransactionReadOnlyExecutionServiceSyntheticExecutionTests +{ + private static readonly Address BypassedContractAddress = + Address.FromBase58("tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"); + + [Fact] + public async Task ExecuteAsync_BypassedContract_Should_Not_Apply_Contract() + { + var executive = new Mock(); + executive.SetupGet(e => e.Descriptors).Returns(new List()); + executive.Setup(e => e.ApplyAsync(It.IsAny())) + .ThrowsAsync(new ShouldAssertException("Bypassed read-only execution should not call ApplyAsync.")); + + var executiveService = new Mock(); + executiveService.Setup(s => s.GetExecutiveAsync(It.IsAny(), BypassedContractAddress)) + .ReturnsAsync(executive.Object); + executiveService.Setup(s => + s.PutExecutiveAsync(It.IsAny(), BypassedContractAddress, executive.Object)) + .Returns(Task.CompletedTask); + + var thresholdProvider = new Mock(); + thresholdProvider.Setup(p => p.GetExecutionObserverThreshold(It.IsAny())) + .Returns(new ExecutionObserverThreshold + { + ExecutionBranchThreshold = SmartContractConstants.ExecutionBranchThreshold, + ExecutionCallThreshold = SmartContractConstants.ExecutionCallThreshold + }); + + var service = new TransactionReadOnlyExecutionService( + executiveService.Object, + new TransactionContextFactory(thresholdProvider.Object), + ResolveSyntheticTransactionExecutionProvider()); + var logger = new Mock>(); + service.Logger = logger.Object; + + var trace = await service.ExecuteAsync(new ChainContext + { + BlockHash = Hash.Empty, + BlockHeight = 1 + }, new Transaction + { + From = SampleAddress.AddressList[0], + To = BypassedContractAddress, + MethodName = "Get", + Params = ByteString.Empty + }, TimestampHelper.GetUtcNow()); + + trace.ExecutionStatus.ShouldBe(ExecutionStatus.Executed); + executive.Verify(e => e.ApplyAsync(It.IsAny()), Times.Never); + CountSyntheticExecutionLogs(logger).ShouldBe(1); + } + + private static ISyntheticTransactionExecutionProvider ResolveSyntheticTransactionExecutionProvider() + { + return new ServiceCollection() + .AddSingleton() + .BuildServiceProvider() + .GetRequiredService(); + } + + private static int CountSyntheticExecutionLogs(Mock> logger) + { + return logger.Invocations.Count(invocation => + invocation.Method.Name == nameof(ILogger.Log) && + (LogLevel)invocation.Arguments[0] == LogLevel.Debug && + invocation.Arguments[2].ToString().Contains("synthetically executed")); + } +}