Skip to content

Commit

Permalink
Merge pull request #3557 from AElfProject/feature/invalid-tx-result
Browse files Browse the repository at this point in the history
Store failed transaction result to database
  • Loading branch information
eanzhao authored May 21, 2024
2 parents 0d78fc6 + eb9aac5 commit c2f2e7f
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 15 deletions.
9 changes: 9 additions & 0 deletions protobuf/aelf/core.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<InvalidTransactionResult> GetInvalidTransactionResultAsync(Hash transactionId);
}

public class InvalidTransactionResultManager : IInvalidTransactionResultManager
{
private readonly IBlockchainStore<InvalidTransactionResult> _invalidTransactionResultStore;

public InvalidTransactionResultManager(IBlockchainStore<InvalidTransactionResult> invalidTransactionResultStore)
{
_invalidTransactionResultStore = invalidTransactionResultStore;
}

public async Task AddInvalidTransactionResultAsync(InvalidTransactionResult transactionResult)
{
await _invalidTransactionResultStore.SetAsync(transactionResult.TransactionId.ToStorageKey(), transactionResult);
}


public async Task<InvalidTransactionResult> GetInvalidTransactionResultAsync(Hash transactionId)
{
return await _invalidTransactionResultStore.GetAsync(transactionId.ToStorageKey());
}

}
2 changes: 1 addition & 1 deletion src/AElf.Kernel.Core/CoreKernelAElfModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public override void ConfigureServices(ServiceConfigurationContext context)
services.AddStoreKeyPrefixProvide<TransactionBlockIndex>("ti");
services.AddStoreKeyPrefixProvide<TransactionResult>("tr");
services.AddStoreKeyPrefixProvide<VersionedState>("vs");

services.AddStoreKeyPrefixProvide<InvalidTransactionResult>("ir");

services.AddTransient(typeof(IStateStore<>), typeof(StateStore<>));
services.AddSingleton(typeof(INotModifiedCachedStateStore<>), typeof(NotModifiedCachedStateStore<>));
Expand Down
Original file line number Diff line number Diff line change
@@ -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<InvalidTransactionResult> 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<InvalidTransactionResult> GetInvalidTransactionResultAsync(Hash transactionId)
{
return await _invalidTransactionResultManager.GetInvalidTransactionResultAsync(transactionId);
}
}
Original file line number Diff line number Diff line change
@@ -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<TransactionValidationStatusChangedEvent>,
ITransientDependency
{
private readonly IEnumerable<TransactionResultStatus> _failStatus = new List<TransactionResultStatus>
{
TransactionResultStatus.Failed, TransactionResultStatus.NodeValidationFailed, TransactionResultStatus.Conflict
};

private readonly IInvalidTransactionResultService _invalidTransactionResultService;
private readonly TransactionOptions _transactionOptions;

public TransactionValidationStatusFailedEventHandler(
IOptionsMonitor<TransactionOptions> 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();
}

}
8 changes: 8 additions & 0 deletions src/AElf.Kernel.TransactionPool/TransactionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,12 @@ public class TransactionOptions
/// But common node needs to enable it to prevent transaction flood attack
/// </summary>
public bool EnableTransactionExecutionValidation { get; set; } = true;


/// <summary>
/// Configuration whether to save failed transaction results
/// </summary>
public bool StoreInvalidTransactionResultEnabled { get; set; }


}
1 change: 1 addition & 0 deletions src/AElf.Launcher/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"MinerIncreaseInterval": 31536000
},
"Transaction": {
"StoreInvalidTransactionResultEnabled" : false,
"PoolLimit": 10240
},
"BasicAuth": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -39,21 +40,23 @@ 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,
IBlockchainService blockchainService,
ITransactionReadOnlyExecutionService transactionReadOnlyExecutionService,
IObjectMapper<ChainApplicationWebAppAElfModule> objectMapper,
ITransactionResultStatusCacheProvider transactionResultStatusCacheProvider,
IOptionsMonitor<WebAppOptions> optionsSnapshot)
IOptionsMonitor<WebAppOptions> optionsSnapshot, IOptionsMonitor<TransactionOptions> transactionOptions)
{
_transactionResultProxyService = transactionResultProxyService;
_transactionManager = transactionManager;
_blockchainService = blockchainService;
_transactionReadOnlyExecutionService = transactionReadOnlyExecutionService;
_objectMapper = objectMapper;
_transactionResultStatusCacheProvider = transactionResultStatusCacheProvider;
_transactionOptions = transactionOptions.CurrentValue;
_webAppOptions = optionsSnapshot.CurrentValue;

Logger = NullLogger<TransactionResultAppService>.Instance;
Expand Down Expand Up @@ -88,23 +91,36 @@ public async Task<TransactionResultDto> GetTransactionResultAsync(string transac
output.Transaction = _objectMapper.Map<Transaction, TransactionDto>(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;

}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<string, byte[]> _codes;

Expand All @@ -58,6 +61,9 @@ public BlockChainAppServiceTest(ITestOutputHelper outputHelper) : base(outputHel
_osTestHelper = GetRequiredService<OSTestHelper>();
_accountService = GetRequiredService<IAccountService>();
_blockStateSetManger = GetRequiredService<IBlockStateSetManger>();
_transactionResultProxyService = GetRequiredService<ITransactionResultProxyService>();
_transactionExecutionValidationFailedEventHandler =
GetRequiredService<TransactionValidationStatusFailedEventHandler>();
_transactionValidationStatusChangedEventHandler =
GetRequiredService<TransactionValidationStatusChangedEventHandler>();
}
Expand Down Expand Up @@ -1822,4 +1828,31 @@ await PostResponseAsObjectAsync<SendTransactionOutput>("/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<TransactionResultDto>(
$"/api/blockChain/transactionResult?transactionId={txId.ToHex()}");
response.ShouldNotBeNull();
response.Status.ShouldBe(TransactionResultStatus.NodeValidationFailed.ToString().ToUpper());
response.Error.ShouldBe("tx error");

}

}
5 changes: 5 additions & 0 deletions test/AElf.WebApp.Application.TestBase/WebAppTestAElfModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -88,5 +89,9 @@ public override void ConfigureServices(ServiceConfigurationContext context)
options.UserName = BasicAuth.DefaultUserName;
options.Password = BasicAuth.DefaultPassword;
});
Configure<TransactionOptions>(o => {
o.PoolLimit = 20;
o.StoreInvalidTransactionResultEnabled = true;
});
}
}

0 comments on commit c2f2e7f

Please sign in to comment.