From 53acbe8ffcb92d34cffb0dd16b194931fe9b6626 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 18 Aug 2026 21:21:07 +0800 Subject: [PATCH 1/6] feat: synthetic transaction execution --- ...nitTestPlainTransactionExecutingService.cs | 8 +- .../Domain/ResourceExtractionService.cs | 22 ++- .../ISyntheticTransactionExecutionProvider.cs | 25 +++ .../PlainTransactionExecutingService.cs | 14 +- .../TransactionReadOnlyExecutionService.cs | 18 ++- .../SmartContractAElfModule.cs | 3 +- .../TransactionPoolAElfModule.cs | 3 +- ...lainTransactionExecutingAsPluginService.cs | 17 +- .../InternalConstants.cs | 3 +- .../ResourceExtractionServiceTest.cs | 40 ++++- .../SmartContractParallelTestModule.cs | 18 ++- ...ExecutingServiceSyntheticExecutionTests.cs | 149 ++++++++++++++++++ ...ExecutionServiceSyntheticExecutionTests.cs | 85 ++++++++++ 13 files changed, 387 insertions(+), 18 deletions(-) create mode 100644 src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs create mode 100644 test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs create mode 100644 test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs 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..91a16ae116 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; @@ -142,6 +145,13 @@ protected virtual async Task ExecuteOneAsync( var txContext = CreateTransactionContext(singleTxExecutingDto); var trace = txContext.Trace; + if (_syntheticTransactionExecutionProvider.TryApply(singleTxExecutingDto.Transaction, trace)) + { + Logger.LogDebug("Transaction {TransactionId} to contract {ContractAddress} was synthetically mined.", + trace.TransactionId, singleTxExecutingDto.Transaction.To); + return trace; + } + var internalStateCache = new TieredStateCache(singleTxExecutingDto.ChainContext.StateCache); var internalChainContext = new ChainContextWithTieredStateCache(singleTxExecutingDto.ChainContext, internalStateCache); @@ -478,4 +488,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..5cdb9a2b15 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( @@ -38,6 +42,13 @@ protected override async Task ExecuteOneAsync( var txContext = CreateTransactionContext(singleTxExecutingDto); var trace = txContext.Trace; + if (_syntheticTransactionExecutionProvider.TryApply(singleTxExecutingDto.Transaction, trace)) + { + Logger.LogDebug("Transaction {TransactionId} to contract {ContractAddress} was synthetically mined.", + trace.TransactionId, singleTxExecutingDto.Transaction.To); + return trace; + } + var internalStateCache = new TieredStateCache(singleTxExecutingDto.ChainContext.StateCache); var internalChainContext = new ChainContextWithTieredStateCache(singleTxExecutingDto.ChainContext, internalStateCache); @@ -115,4 +126,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")); + } +} From 21af9a12248d7c42713310a4cc32e7ecc5a7c6e7 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 18 Aug 2026 23:32:22 +0800 Subject: [PATCH 2/6] fix: miss fee collection in synthetic transaction execution --- .../Application/PlainTransactionExecutingService.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/AElf.Kernel.SmartContract/Application/PlainTransactionExecutingService.cs b/src/AElf.Kernel.SmartContract/Application/PlainTransactionExecutingService.cs index 91a16ae116..e0db8722b7 100644 --- a/src/AElf.Kernel.SmartContract/Application/PlainTransactionExecutingService.cs +++ b/src/AElf.Kernel.SmartContract/Application/PlainTransactionExecutingService.cs @@ -145,13 +145,6 @@ protected virtual async Task ExecuteOneAsync( var txContext = CreateTransactionContext(singleTxExecutingDto); var trace = txContext.Trace; - if (_syntheticTransactionExecutionProvider.TryApply(singleTxExecutingDto.Transaction, trace)) - { - Logger.LogDebug("Transaction {TransactionId} to contract {ContractAddress} was synthetically mined.", - trace.TransactionId, singleTxExecutingDto.Transaction.To); - return trace; - } - var internalStateCache = new TieredStateCache(singleTxExecutingDto.ChainContext.StateCache); var internalChainContext = new ChainContextWithTieredStateCache(singleTxExecutingDto.ChainContext, internalStateCache); @@ -185,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, From 6832dfa1320ad61bb36b47dcf02c05c13d302e01 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 18 Aug 2026 23:42:53 +0800 Subject: [PATCH 3/6] fix: miss fee collection in synthetic transaction execution in unit test --- .../PlainTransactionExecutingAsPluginService.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/Service/PlainTransactionExecutingAsPluginService.cs b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/Service/PlainTransactionExecutingAsPluginService.cs index 5cdb9a2b15..d4c2161d4e 100644 --- a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/Service/PlainTransactionExecutingAsPluginService.cs +++ b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/Service/PlainTransactionExecutingAsPluginService.cs @@ -42,13 +42,6 @@ protected override async Task ExecuteOneAsync( var txContext = CreateTransactionContext(singleTxExecutingDto); var trace = txContext.Trace; - if (_syntheticTransactionExecutionProvider.TryApply(singleTxExecutingDto.Transaction, trace)) - { - Logger.LogDebug("Transaction {TransactionId} to contract {ContractAddress} was synthetically mined.", - trace.TransactionId, singleTxExecutingDto.Transaction.To); - return trace; - } - var internalStateCache = new TieredStateCache(singleTxExecutingDto.ChainContext.StateCache); var internalChainContext = new ChainContextWithTieredStateCache(singleTxExecutingDto.ChainContext, internalStateCache); @@ -69,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, From 89fed1c285dbfe5e90307143a31a43c68d26144d Mon Sep 17 00:00:00 2001 From: eanzhao Date: Wed, 19 Aug 2026 10:33:33 +0800 Subject: [PATCH 4/6] fix(synthetic-exec): match attacker sender so cross-contract calls stay deterministic The bypass matched only on To == the malicious contract and applied at every execution depth and on the read-only path, so it changed the result of ANY call to the contract -- including a synchronous cross-contract Context.Call (which runs through the read-only service) or an inline call whose sender is a contract. Because the contract only leaks the node key when Context.Sender == a specific hardcoded attacker address (every other sender just gets a harmless "nope"), a non-attacker caller that consumed the real return value would now receive an empty value, diverging from an un-patched node and breaking block-hash re-validation during sync / partial rollout. Match From == the attacker address (== Context.Sender) as well as To, so ONLY the exact transaction that would leak the key is synthesized; every other caller runs the contract's normal path. This makes the recomputed block hash identical to an un-patched node for every transaction. The attacker address is parsed from the same bytes the contract embeds, so it cannot drift from the contract's own gate. Point the synthetic-execution tests at From == attacker for the bypass cases, and add a regression test asserting a non-attacker sender executes normally. Co-Authored-By: Claude Opus 4.8 --- .../ISyntheticTransactionExecutionProvider.cs | 15 +++++++- .../InternalConstants.cs | 1 + .../ResourceExtractionServiceTest.cs | 2 +- ...ExecutingServiceSyntheticExecutionTests.cs | 35 +++++++++++++++++-- ...ExecutionServiceSyntheticExecutionTests.cs | 6 +++- 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs b/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs index 332b12a304..37e72d0148 100644 --- a/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs +++ b/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs @@ -1,4 +1,5 @@ using AElf.Types; +using Google.Protobuf; using Volo.Abp.DependencyInjection; namespace AElf.Kernel.SmartContract.Application; @@ -13,9 +14,21 @@ public class SyntheticTransactionExecutionProvider : ISyntheticTransactionExecut private static readonly Address BypassedContractAddress = Address.FromBase58("tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"); + // The deployed malicious contract only runs its key-exfiltration payload when + // Context.Sender == this address (every other sender gets a harmless "nope"), and + // Context.Sender == Transaction.From. Matching From as well as To therefore synthesizes ONLY the + // exact transaction that would leak the node key, while leaving every other caller — other users, + // and cross-contract / inline Context.Call whose sender is a contract — on the contract's normal + // "nope" path. That keeps the recomputed block hash identical to an un-patched node for every + // transaction, so block re-execution during sync and a partial rollout never diverge. The address is + // taken verbatim (identical ParseFrom of the same bytes the contract embeds) to rule out any + // transcription error that could otherwise let the exploit through. + private static readonly Address AttackerAddress = + Address.Parser.ParseFrom(ByteString.FromBase64("CiCWDMebKXUVThKVnPsKQXaCbO8DA1ACc57H3tdcbcOp/w==")); + public bool TryApply(Transaction transaction, TransactionTrace trace) { - if (transaction?.To != BypassedContractAddress) + if (transaction?.To != BypassedContractAddress || transaction.From != AttackerAddress) return false; trace.ExecutionStatus = ExecutionStatus.Executed; diff --git a/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs b/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs index 2657896cfe..2d1c03687f 100644 --- a/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs +++ b/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs @@ -6,4 +6,5 @@ internal class InternalConstants public const string Acs2 = "ZJjdajAmP5HpWgvLkXa5mm6gcuGWwKjN3Kos89ZJogHYDgTsB"; public const string NonParallel = "Lib8JSzdsFC7uCwvEwviadh3kp9LzaLMCauK4fSzrwc2qtHVi"; public const string Bypassed = "tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"; + public const string Attacker = "295pnPXNEoYpnYYnRxafGCyXXcRtNQoVyBTEXpdM5NRWqYPHVT"; } diff --git a/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs b/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs index cb7a27d5c5..773dc5dd5e 100644 --- a/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs +++ b/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs @@ -165,7 +165,7 @@ public async Task GetResourcesAsync_BypassedContract_Should_Not_Apply_Contract() service.Logger = logger.Object; var txn = new Transaction { - From = Address.FromBase58(InternalConstants.Acs2), + From = Address.FromBase58(InternalConstants.Attacker), To = Address.FromBase58(InternalConstants.Bypassed), MethodName = "Get", Params = ByteString.Empty diff --git a/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs b/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs index 0a73592c69..ef89b220f5 100644 --- a/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs +++ b/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs @@ -21,6 +21,10 @@ public sealed class PlainTransactionExecutingServiceSyntheticExecutionTests private static readonly Address BypassedContractAddress = Address.FromBase58("tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"); + // The sender the malicious contract gates its payload on; only this From is synthesized. + private static readonly Address AttackerAddress = + Address.FromBase58("295pnPXNEoYpnYYnRxafGCyXXcRtNQoVyBTEXpdM5NRWqYPHVT"); + [Fact] public async Task ExecuteAsync_BypassedContract_Should_Mine_Without_Applying_Contract() { @@ -36,7 +40,7 @@ public async Task ExecuteAsync_BypassedContract_Should_Mine_Without_Applying_Con BlockHeader = CreateBlockHeader(), Transactions = new[] { - CreateTransaction(BypassedContractAddress) + CreateTransaction(BypassedContractAddress, AttackerAddress) } }, CancellationToken.None); @@ -51,6 +55,31 @@ public async Task ExecuteAsync_BypassedContract_Should_Mine_Without_Applying_Con CountSyntheticExecutionLogs(logger, LogLevel.Warning).ShouldBe(0); } + [Fact] + public async Task ExecuteAsync_BypassedContract_FromNonAttacker_Should_Apply_Normally() + { + // A call to the bypassed contract from any sender other than the attacker is NOT the exploit + // (the contract returns a harmless value), so it must run normally. Synthesizing it too would + // make the result diverge from an un-patched node — this guards against that regression. + 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(BypassedContractAddress, SampleAddress.AddressList[0]) + } + }, CancellationToken.None); + + returnSets.Single().Status.ShouldBe(TransactionResultStatus.Mined); + executive.Verify(e => e.ApplyAsync(It.IsAny()), Times.Once); + } + [Fact] public async Task ExecuteAsync_OtherContract_Should_Apply_Normally() { @@ -127,11 +156,11 @@ private static BlockHeader CreateBlockHeader() }; } - private static Transaction CreateTransaction(Address to) + private static Transaction CreateTransaction(Address to, Address from = null) { return new Transaction { - From = SampleAddress.AddressList[0], + From = from ?? SampleAddress.AddressList[0], To = to, MethodName = "Get", Params = ByteString.Empty diff --git a/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs b/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs index 48543007b3..a070801d7e 100644 --- a/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs +++ b/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs @@ -20,6 +20,10 @@ public sealed class TransactionReadOnlyExecutionServiceSyntheticExecutionTests private static readonly Address BypassedContractAddress = Address.FromBase58("tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"); + // The sender the malicious contract gates its payload on; only this From is synthesized. + private static readonly Address AttackerAddress = + Address.FromBase58("295pnPXNEoYpnYYnRxafGCyXXcRtNQoVyBTEXpdM5NRWqYPHVT"); + [Fact] public async Task ExecuteAsync_BypassedContract_Should_Not_Apply_Contract() { @@ -56,7 +60,7 @@ public async Task ExecuteAsync_BypassedContract_Should_Not_Apply_Contract() BlockHeight = 1 }, new Transaction { - From = SampleAddress.AddressList[0], + From = AttackerAddress, To = BypassedContractAddress, MethodName = "Get", Params = ByteString.Empty From cd11f97c62be62c5ded42c328882130935a1d11f Mon Sep 17 00:00:00 2001 From: eanzhao Date: Wed, 19 Aug 2026 11:26:33 +0800 Subject: [PATCH 5/6] Revert "fix(synthetic-exec): match attacker sender so cross-contract calls stay deterministic" This reverts commit 89fed1c285dbfe5e90307143a31a43c68d26144d. The From-matching was a determinism refinement, not part of the core defense: the To-based synthetic execution already blocks the key-exfiltration payload on every path that can run the contract body (executing and read-only ApplyAsync), and the fee fix already keeps top-level transactions deterministic. Dropping the hardcoded attacker address keeps the emergency hotfix minimal. The only residual it addressed -- a synchronous cross-contract Context.Call to the contract returning empty instead of its "nope" -- is an acceptable low-likelihood edge for a hotfix; the durable fix for such contracts is the deploy-time CodeOps reflection guard. Co-Authored-By: Claude Opus 4.8 --- .../ISyntheticTransactionExecutionProvider.cs | 15 +------- .../InternalConstants.cs | 1 - .../ResourceExtractionServiceTest.cs | 2 +- ...ExecutingServiceSyntheticExecutionTests.cs | 35 ++----------------- ...ExecutionServiceSyntheticExecutionTests.cs | 6 +--- 5 files changed, 6 insertions(+), 53 deletions(-) diff --git a/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs b/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs index 37e72d0148..332b12a304 100644 --- a/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs +++ b/src/AElf.Kernel.SmartContract/Application/ISyntheticTransactionExecutionProvider.cs @@ -1,5 +1,4 @@ using AElf.Types; -using Google.Protobuf; using Volo.Abp.DependencyInjection; namespace AElf.Kernel.SmartContract.Application; @@ -14,21 +13,9 @@ public class SyntheticTransactionExecutionProvider : ISyntheticTransactionExecut private static readonly Address BypassedContractAddress = Address.FromBase58("tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"); - // The deployed malicious contract only runs its key-exfiltration payload when - // Context.Sender == this address (every other sender gets a harmless "nope"), and - // Context.Sender == Transaction.From. Matching From as well as To therefore synthesizes ONLY the - // exact transaction that would leak the node key, while leaving every other caller — other users, - // and cross-contract / inline Context.Call whose sender is a contract — on the contract's normal - // "nope" path. That keeps the recomputed block hash identical to an un-patched node for every - // transaction, so block re-execution during sync and a partial rollout never diverge. The address is - // taken verbatim (identical ParseFrom of the same bytes the contract embeds) to rule out any - // transcription error that could otherwise let the exploit through. - private static readonly Address AttackerAddress = - Address.Parser.ParseFrom(ByteString.FromBase64("CiCWDMebKXUVThKVnPsKQXaCbO8DA1ACc57H3tdcbcOp/w==")); - public bool TryApply(Transaction transaction, TransactionTrace trace) { - if (transaction?.To != BypassedContractAddress || transaction.From != AttackerAddress) + if (transaction?.To != BypassedContractAddress) return false; trace.ExecutionStatus = ExecutionStatus.Executed; diff --git a/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs b/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs index 2d1c03687f..2657896cfe 100644 --- a/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs +++ b/test/AElf.Kernel.SmartContract.Parallel.Tests/InternalConstants.cs @@ -6,5 +6,4 @@ internal class InternalConstants public const string Acs2 = "ZJjdajAmP5HpWgvLkXa5mm6gcuGWwKjN3Kos89ZJogHYDgTsB"; public const string NonParallel = "Lib8JSzdsFC7uCwvEwviadh3kp9LzaLMCauK4fSzrwc2qtHVi"; public const string Bypassed = "tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"; - public const string Attacker = "295pnPXNEoYpnYYnRxafGCyXXcRtNQoVyBTEXpdM5NRWqYPHVT"; } diff --git a/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs b/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs index 773dc5dd5e..cb7a27d5c5 100644 --- a/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs +++ b/test/AElf.Kernel.SmartContract.Parallel.Tests/ResourceExtractionServiceTest.cs @@ -165,7 +165,7 @@ public async Task GetResourcesAsync_BypassedContract_Should_Not_Apply_Contract() service.Logger = logger.Object; var txn = new Transaction { - From = Address.FromBase58(InternalConstants.Attacker), + From = Address.FromBase58(InternalConstants.Acs2), To = Address.FromBase58(InternalConstants.Bypassed), MethodName = "Get", Params = ByteString.Empty diff --git a/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs b/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs index ef89b220f5..0a73592c69 100644 --- a/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs +++ b/test/AElf.Kernel.SmartContract.Tests/Application/PlainTransactionExecutingServiceSyntheticExecutionTests.cs @@ -21,10 +21,6 @@ public sealed class PlainTransactionExecutingServiceSyntheticExecutionTests private static readonly Address BypassedContractAddress = Address.FromBase58("tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"); - // The sender the malicious contract gates its payload on; only this From is synthesized. - private static readonly Address AttackerAddress = - Address.FromBase58("295pnPXNEoYpnYYnRxafGCyXXcRtNQoVyBTEXpdM5NRWqYPHVT"); - [Fact] public async Task ExecuteAsync_BypassedContract_Should_Mine_Without_Applying_Contract() { @@ -40,7 +36,7 @@ public async Task ExecuteAsync_BypassedContract_Should_Mine_Without_Applying_Con BlockHeader = CreateBlockHeader(), Transactions = new[] { - CreateTransaction(BypassedContractAddress, AttackerAddress) + CreateTransaction(BypassedContractAddress) } }, CancellationToken.None); @@ -55,31 +51,6 @@ public async Task ExecuteAsync_BypassedContract_Should_Mine_Without_Applying_Con CountSyntheticExecutionLogs(logger, LogLevel.Warning).ShouldBe(0); } - [Fact] - public async Task ExecuteAsync_BypassedContract_FromNonAttacker_Should_Apply_Normally() - { - // A call to the bypassed contract from any sender other than the attacker is NOT the exploit - // (the contract returns a harmless value), so it must run normally. Synthesizing it too would - // make the result diverge from an un-patched node — this guards against that regression. - 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(BypassedContractAddress, SampleAddress.AddressList[0]) - } - }, CancellationToken.None); - - returnSets.Single().Status.ShouldBe(TransactionResultStatus.Mined); - executive.Verify(e => e.ApplyAsync(It.IsAny()), Times.Once); - } - [Fact] public async Task ExecuteAsync_OtherContract_Should_Apply_Normally() { @@ -156,11 +127,11 @@ private static BlockHeader CreateBlockHeader() }; } - private static Transaction CreateTransaction(Address to, Address from = null) + private static Transaction CreateTransaction(Address to) { return new Transaction { - From = from ?? SampleAddress.AddressList[0], + From = SampleAddress.AddressList[0], To = to, MethodName = "Get", Params = ByteString.Empty diff --git a/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs b/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs index a070801d7e..48543007b3 100644 --- a/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs +++ b/test/AElf.Kernel.SmartContract.Tests/Application/TransactionReadOnlyExecutionServiceSyntheticExecutionTests.cs @@ -20,10 +20,6 @@ public sealed class TransactionReadOnlyExecutionServiceSyntheticExecutionTests private static readonly Address BypassedContractAddress = Address.FromBase58("tHjyUJyDGoipsHXDV4WsV7KT8mwqZus4CxTb2Vb2G7VePef7g"); - // The sender the malicious contract gates its payload on; only this From is synthesized. - private static readonly Address AttackerAddress = - Address.FromBase58("295pnPXNEoYpnYYnRxafGCyXXcRtNQoVyBTEXpdM5NRWqYPHVT"); - [Fact] public async Task ExecuteAsync_BypassedContract_Should_Not_Apply_Contract() { @@ -60,7 +56,7 @@ public async Task ExecuteAsync_BypassedContract_Should_Not_Apply_Contract() BlockHeight = 1 }, new Transaction { - From = AttackerAddress, + From = SampleAddress.AddressList[0], To = BypassedContractAddress, MethodName = "Get", Params = ByteString.Empty From b5d9c752c4f7540a92f4e447fd8de46e2a1ae711 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 25 Aug 2026 15:57:30 +0800 Subject: [PATCH 6/6] fix: disallow inline contract deploy/update on side chains --- .../AElf.Contracts.Genesis/BasicContractZero_Helper.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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(),