diff --git a/protobuf/aelf/core.proto b/protobuf/aelf/core.proto index c990aa9df6..e45d7e581d 100644 --- a/protobuf/aelf/core.proto +++ b/protobuf/aelf/core.proto @@ -72,6 +72,15 @@ message TransactionResult { string error = 10; } +message InvalidTransactionResult{ + // The transaction id. + Hash transaction_id = 1; + // The transaction result status. + TransactionResultStatus status = 2; + // Failed execution error message. + string error = 3; +} + message LogEvent { // The contract address. Address address = 1; diff --git a/src/AElf.Kernel.Core/Blockchain/Domain/IInvalidTransactionResultManager.cs b/src/AElf.Kernel.Core/Blockchain/Domain/IInvalidTransactionResultManager.cs new file mode 100644 index 0000000000..9c29556ad1 --- /dev/null +++ b/src/AElf.Kernel.Core/Blockchain/Domain/IInvalidTransactionResultManager.cs @@ -0,0 +1,33 @@ +using System.Linq; +using AElf.Kernel.Blockchain.Infrastructure; +using AElf.Kernel.Infrastructure; + +namespace AElf.Kernel.Blockchain.Domain; + +public interface IInvalidTransactionResultManager +{ + Task AddInvalidTransactionResultAsync(InvalidTransactionResult transactionResult); + Task GetInvalidTransactionResultAsync(Hash transactionId); +} + +public class InvalidTransactionResultManager : IInvalidTransactionResultManager +{ + private readonly IBlockchainStore _invalidTransactionResultStore; + + public InvalidTransactionResultManager(IBlockchainStore invalidTransactionResultStore) + { + _invalidTransactionResultStore = invalidTransactionResultStore; + } + + public async Task AddInvalidTransactionResultAsync(InvalidTransactionResult transactionResult) + { + await _invalidTransactionResultStore.SetAsync(transactionResult.TransactionId.ToStorageKey(), transactionResult); + } + + + public async Task GetInvalidTransactionResultAsync(Hash transactionId) + { + return await _invalidTransactionResultStore.GetAsync(transactionId.ToStorageKey()); + } + +} \ No newline at end of file diff --git a/src/AElf.Kernel.Core/CoreKernelAElfModule.cs b/src/AElf.Kernel.Core/CoreKernelAElfModule.cs index 86df74b631..0dcf8b87bb 100644 --- a/src/AElf.Kernel.Core/CoreKernelAElfModule.cs +++ b/src/AElf.Kernel.Core/CoreKernelAElfModule.cs @@ -39,7 +39,7 @@ public override void ConfigureServices(ServiceConfigurationContext context) services.AddStoreKeyPrefixProvide("ti"); services.AddStoreKeyPrefixProvide("tr"); services.AddStoreKeyPrefixProvide("vs"); - + services.AddStoreKeyPrefixProvide("ir"); services.AddTransient(typeof(IStateStore<>), typeof(StateStore<>)); services.AddSingleton(typeof(INotModifiedCachedStateStore<>), typeof(NotModifiedCachedStateStore<>)); diff --git a/src/AElf.Kernel.TransactionPool/Application/IInvalidTransactionResultService.cs b/src/AElf.Kernel.TransactionPool/Application/IInvalidTransactionResultService.cs new file mode 100644 index 0000000000..0a1d13c0bf --- /dev/null +++ b/src/AElf.Kernel.TransactionPool/Application/IInvalidTransactionResultService.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using AElf.Kernel.Blockchain.Domain; +using AElf.Types; +using Volo.Abp.DependencyInjection; + +namespace AElf.Kernel.TransactionPool.Application; + + +public interface IInvalidTransactionResultService +{ + Task AddInvalidTransactionResultsAsync(InvalidTransactionResult transactionResult); + Task GetInvalidTransactionResultAsync(Hash transactionId); +} + +public class InvalidTransactionResultService : IInvalidTransactionResultService, ITransientDependency +{ + private readonly IInvalidTransactionResultManager _invalidTransactionResultManager; + + public InvalidTransactionResultService(IInvalidTransactionResultManager invalidTransactionResultManager) + { + _invalidTransactionResultManager = invalidTransactionResultManager; + } + + public async Task AddInvalidTransactionResultsAsync(InvalidTransactionResult transactionResult) + { + await _invalidTransactionResultManager.AddInvalidTransactionResultAsync(transactionResult); + } + + public async Task GetInvalidTransactionResultAsync(Hash transactionId) + { + return await _invalidTransactionResultManager.GetInvalidTransactionResultAsync(transactionId); + } +} \ No newline at end of file diff --git a/src/AElf.Kernel.TransactionPool/Handler/TransactionValidationStatusFailedEventHandler.cs b/src/AElf.Kernel.TransactionPool/Handler/TransactionValidationStatusFailedEventHandler.cs new file mode 100644 index 0000000000..414cc76036 --- /dev/null +++ b/src/AElf.Kernel.TransactionPool/Handler/TransactionValidationStatusFailedEventHandler.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using AElf.Kernel.TransactionPool.Application; +using AElf.Types; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; + +namespace AElf.Kernel.TransactionPool.Handler; + +public class TransactionValidationStatusFailedEventHandler : + ILocalEventHandler, + ITransientDependency +{ + private readonly IEnumerable _failStatus = new List + { + TransactionResultStatus.Failed, TransactionResultStatus.NodeValidationFailed, TransactionResultStatus.Conflict + }; + + private readonly IInvalidTransactionResultService _invalidTransactionResultService; + private readonly TransactionOptions _transactionOptions; + + public TransactionValidationStatusFailedEventHandler( + IOptionsMonitor transactionOptionsMonitor, + IInvalidTransactionResultService invalidTransactionResultService) + { + _invalidTransactionResultService = invalidTransactionResultService; + _transactionOptions = transactionOptionsMonitor.CurrentValue; + } + + public async Task HandleEventAsync(TransactionValidationStatusChangedEvent eventData) + { + if (!_failStatus.Contains(eventData.TransactionResultStatus)) return; + if (!_transactionOptions.StoreInvalidTransactionResultEnabled) return; + + // save to storage + await _invalidTransactionResultService.AddInvalidTransactionResultsAsync( + new InvalidTransactionResult + { + TransactionId = eventData.TransactionId, + Status = eventData.TransactionResultStatus, + Error = TakeErrorMessage(eventData.Error) + }); + } + + private string TakeErrorMessage(string transactionResultError) + { + if (string.IsNullOrWhiteSpace(transactionResultError)) + return null; + using var stringReader = new StringReader(transactionResultError); + return stringReader.ReadLine(); + } + +} \ No newline at end of file diff --git a/src/AElf.Kernel.TransactionPool/TransactionOptions.cs b/src/AElf.Kernel.TransactionPool/TransactionOptions.cs index 7847da5094..491ddd51be 100644 --- a/src/AElf.Kernel.TransactionPool/TransactionOptions.cs +++ b/src/AElf.Kernel.TransactionPool/TransactionOptions.cs @@ -17,4 +17,12 @@ public class TransactionOptions /// But common node needs to enable it to prevent transaction flood attack /// public bool EnableTransactionExecutionValidation { get; set; } = true; + + + /// + /// Configuration whether to save failed transaction results + /// + public bool StoreInvalidTransactionResultEnabled { get; set; } + + } \ No newline at end of file diff --git a/src/AElf.Launcher/appsettings.json b/src/AElf.Launcher/appsettings.json index 80b0e93f8f..45c0923284 100644 --- a/src/AElf.Launcher/appsettings.json +++ b/src/AElf.Launcher/appsettings.json @@ -31,6 +31,7 @@ "MinerIncreaseInterval": 31536000 }, "Transaction": { + "StoreInvalidTransactionResultEnabled" : false, "PoolLimit": 10240 }, "BasicAuth": { diff --git a/src/AElf.WebApp.Application.Chain/Services/TransactionResultAppService.cs b/src/AElf.WebApp.Application.Chain/Services/TransactionResultAppService.cs index 08552f42e5..483dbb4366 100644 --- a/src/AElf.WebApp.Application.Chain/Services/TransactionResultAppService.cs +++ b/src/AElf.WebApp.Application.Chain/Services/TransactionResultAppService.cs @@ -7,6 +7,7 @@ using AElf.Kernel.Blockchain.Application; using AElf.Kernel.Blockchain.Domain; using AElf.Kernel.SmartContract.Application; +using AElf.Kernel.TransactionPool; using AElf.Types; using AElf.WebApp.Application.Chain.Dto; using AElf.WebApp.Application.Chain.Infrastructure; @@ -39,6 +40,7 @@ public class TransactionResultAppService : AElfAppService, ITransactionResultApp private readonly ITransactionResultProxyService _transactionResultProxyService; private readonly ITransactionResultStatusCacheProvider _transactionResultStatusCacheProvider; private readonly WebAppOptions _webAppOptions; + private readonly TransactionOptions _transactionOptions; public TransactionResultAppService(ITransactionResultProxyService transactionResultProxyService, ITransactionManager transactionManager, @@ -46,7 +48,7 @@ public TransactionResultAppService(ITransactionResultProxyService transactionRes ITransactionReadOnlyExecutionService transactionReadOnlyExecutionService, IObjectMapper objectMapper, ITransactionResultStatusCacheProvider transactionResultStatusCacheProvider, - IOptionsMonitor optionsSnapshot) + IOptionsMonitor optionsSnapshot, IOptionsMonitor transactionOptions) { _transactionResultProxyService = transactionResultProxyService; _transactionManager = transactionManager; @@ -54,6 +56,7 @@ public TransactionResultAppService(ITransactionResultProxyService transactionRes _transactionReadOnlyExecutionService = transactionReadOnlyExecutionService; _objectMapper = objectMapper; _transactionResultStatusCacheProvider = transactionResultStatusCacheProvider; + _transactionOptions = transactionOptions.CurrentValue; _webAppOptions = optionsSnapshot.CurrentValue; Logger = NullLogger.Instance; @@ -88,23 +91,36 @@ public async Task GetTransactionResultAsync(string transac output.Transaction = _objectMapper.Map(transaction); output.TransactionSize = transaction?.CalculateSize() ?? 0; - if (transactionResult.Status == TransactionResultStatus.NotExisted) + if (transactionResult.Status != TransactionResultStatus.NotExisted) { - var validationStatus = - _transactionResultStatusCacheProvider.GetTransactionResultStatus(transactionIdHash); - if (validationStatus != null) - { - output.Status = validationStatus.TransactionResultStatus.ToString().ToUpper(); - output.Error = - TransactionErrorResolver.TakeErrorMessage(validationStatus.Error, _webAppOptions.IsDebugMode); - } + await FormatTransactionParamsAsync(output.Transaction, transaction.Params); + return output; + } + var validationStatus = _transactionResultStatusCacheProvider.GetTransactionResultStatus(transactionIdHash); + if (validationStatus != null) + { + output.Status = validationStatus.TransactionResultStatus.ToString().ToUpper(); + output.Error = + TransactionErrorResolver.TakeErrorMessage(validationStatus.Error, _webAppOptions.IsDebugMode); return output; } - - await FormatTransactionParamsAsync(output.Transaction, transaction.Params); - + + if (_transactionOptions.StoreInvalidTransactionResultEnabled) + { + var failedTransactionResult = + await _transactionResultProxyService.InvalidTransactionResultService.GetInvalidTransactionResultAsync( + transactionIdHash); + if (failedTransactionResult != null) + { + output.Status = failedTransactionResult.Status.ToString().ToUpper(); + output.Error = failedTransactionResult.Error; + return output; + } + } + return output; + } /// diff --git a/src/AElf.WebApp.Application.Chain/Services/TransactionResultProxyService.cs b/src/AElf.WebApp.Application.Chain/Services/TransactionResultProxyService.cs index 5ebfcc8598..9d13296930 100644 --- a/src/AElf.WebApp.Application.Chain/Services/TransactionResultProxyService.cs +++ b/src/AElf.WebApp.Application.Chain/Services/TransactionResultProxyService.cs @@ -7,17 +7,21 @@ public interface ITransactionResultProxyService { ITransactionPoolService TransactionPoolService { get; } ITransactionResultQueryService TransactionResultQueryService { get; } + IInvalidTransactionResultService InvalidTransactionResultService { get; } } public class TransactionResultProxyService : ITransactionResultProxyService { public TransactionResultProxyService(ITransactionPoolService transactionPoolService, - ITransactionResultQueryService transactionResultQueryService) + ITransactionResultQueryService transactionResultQueryService, + IInvalidTransactionResultService invalidTransactionResultService) { TransactionPoolService = transactionPoolService; TransactionResultQueryService = transactionResultQueryService; + InvalidTransactionResultService = invalidTransactionResultService; } public ITransactionPoolService TransactionPoolService { get; set; } public ITransactionResultQueryService TransactionResultQueryService { get; set; } + public IInvalidTransactionResultService InvalidTransactionResultService { get; } } \ No newline at end of file diff --git a/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs b/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs index b898977dc3..7aec5b5cfd 100644 --- a/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs +++ b/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs @@ -15,6 +15,7 @@ using AElf.Kernel.SmartContract.Application; using AElf.Kernel.SmartContract.Domain; using AElf.Kernel.Token; +using AElf.Kernel.TransactionPool.Handler; using AElf.Kernel.TransactionPool.Infrastructure; using AElf.OS; using AElf.Runtime.CSharp; @@ -44,6 +45,8 @@ public sealed class BlockChainAppServiceTest : WebAppTestBase private readonly ISmartContractAddressService _smartContractAddressService; private readonly ITransactionResultStatusCacheProvider _transactionResultStatusCacheProvider; private readonly TransactionValidationStatusChangedEventHandler _transactionValidationStatusChangedEventHandler; + private readonly TransactionValidationStatusFailedEventHandler _transactionExecutionValidationFailedEventHandler; + private readonly ITransactionResultProxyService _transactionResultProxyService; private readonly ITxHub _txHub; private IReadOnlyDictionary _codes; @@ -58,6 +61,9 @@ public BlockChainAppServiceTest(ITestOutputHelper outputHelper) : base(outputHel _osTestHelper = GetRequiredService(); _accountService = GetRequiredService(); _blockStateSetManger = GetRequiredService(); + _transactionResultProxyService = GetRequiredService(); + _transactionExecutionValidationFailedEventHandler = + GetRequiredService(); _transactionValidationStatusChangedEventHandler = GetRequiredService(); } @@ -1822,4 +1828,31 @@ await PostResponseAsObjectAsync("/api/blockChain/sendTran response.Transaction.Params.ShouldNotBe( AddOptionInput.Parser.ParseFrom(transaction.Params).ToString()); } + + [Fact] + public async Task InvalidTransactionResultTest() + { + + var txId = HashHelper.ComputeFrom("InvalidTransactionResultTest"); + await _transactionExecutionValidationFailedEventHandler.HandleEventAsync(new TransactionValidationStatusChangedEvent + { + TransactionId = txId, + TransactionResultStatus = TransactionResultStatus.NodeValidationFailed, + Error = "tx error" + }); + + var invalidResult = await _transactionResultProxyService.InvalidTransactionResultService + .GetInvalidTransactionResultAsync(txId); + invalidResult.ShouldNotBeNull(); + invalidResult.Status.ShouldBe(TransactionResultStatus.NodeValidationFailed); + invalidResult.Error.ShouldBe("tx error"); + + var response = await GetResponseAsObjectAsync( + $"/api/blockChain/transactionResult?transactionId={txId.ToHex()}"); + response.ShouldNotBeNull(); + response.Status.ShouldBe(TransactionResultStatus.NodeValidationFailed.ToString().ToUpper()); + response.Error.ShouldBe("tx error"); + + } + } \ No newline at end of file diff --git a/test/AElf.WebApp.Application.TestBase/WebAppTestAElfModule.cs b/test/AElf.WebApp.Application.TestBase/WebAppTestAElfModule.cs index c2d6783b8f..c9dcf809ec 100644 --- a/test/AElf.WebApp.Application.TestBase/WebAppTestAElfModule.cs +++ b/test/AElf.WebApp.Application.TestBase/WebAppTestAElfModule.cs @@ -6,6 +6,7 @@ using AElf.Kernel.FeeCalculation; using AElf.Kernel.SmartContract.Application; using AElf.Kernel.SmartContract.ExecutionPluginForMethodFee; +using AElf.Kernel.TransactionPool; using AElf.Modularity; using AElf.OS; using AElf.OS.Network.Application; @@ -88,5 +89,9 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.UserName = BasicAuth.DefaultUserName; options.Password = BasicAuth.DefaultPassword; }); + Configure(o => { + o.PoolLimit = 20; + o.StoreInvalidTransactionResultEnabled = true; + }); } } \ No newline at end of file