Skip to content

Commit

Permalink
Merge pull request #3613 from AElfProject/feature/multi-tx
Browse files Browse the repository at this point in the history
Add MultiTransaction type and one new web api to handle this
  • Loading branch information
JimAelf authored Sep 30, 2024
2 parents 813c602 + 6912ed3 commit 88d4e10
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 3 deletions.
10 changes: 10 additions & 0 deletions protobuf/aelf/core.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ message Transaction {
bytes signature = 10000;
}

message TransactionAndChainId {
Transaction transaction = 1;
int32 chain_id = 2;
}

message MultiTransaction {
repeated TransactionAndChainId transactions = 1;
bytes signature = 10000;
}

message StatePath {
// The partial path of the state path.
repeated string parts = 1;
Expand Down
67 changes: 67 additions & 0 deletions src/AElf.Types/Types/MultiTransaction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Linq;
using Google.Protobuf;

namespace AElf.Types
{
public partial class MultiTransaction
{
private Hash _transactionId;

public Hash GetHash()
{
if (_transactionId == null)
_transactionId = HashHelper.ComputeFrom(GetSignatureData());

return _transactionId;
}

public ValidationStatus VerifyFields()
{
if (Transactions.Count < 2)
return ValidationStatus.OnlyOneTransaction;

if (!AllTransactionsHaveSameFrom())
return ValidationStatus.MoreThanOneFrom;

if (Transactions.Any(transaction => string.IsNullOrEmpty(transaction.Transaction.MethodName)))
return ValidationStatus.MethodNameIsEmpty;

if (Transactions.Any(transaction => transaction.Transaction.Signature.IsEmpty))
{
return ValidationStatus.UserSignatureIsEmpty;
}

return ValidationStatus.Success;
}

public enum ValidationStatus
{
Success,
OnlyOneTransaction,
MoreThanOneFrom,
MethodNameIsEmpty,
UserSignatureIsEmpty
}

private bool AllTransactionsHaveSameFrom()
{
var firstFrom = Transactions[0].Transaction.From;
return Transactions.All(transaction => transaction.Transaction.From == firstFrom);
}

private byte[] GetSignatureData()
{
var verifyResult = VerifyFields();
if (verifyResult != ValidationStatus.Success)
throw new InvalidOperationException($"Invalid multi transaction, {verifyResult.ToString()}: {this}");

if (Signature.IsEmpty)
return this.ToByteArray();

var multiTransaction = Clone();
multiTransaction.Signature = ByteString.Empty;
return multiTransaction.ToByteArray();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ public override void ConfigureServices(ServiceConfigurationContext context)

context.Services
.AddSingleton<ITransactionResultStatusCacheProvider, TransactionResultStatusCacheProvider>();

Configure<MultiTransactionOptions>(context.Services.GetConfiguration()
.GetSection("MultiTransaction"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace AElf.WebApp.Application.Chain.Dto;

public class SendMultiTransactionInput : SendTransactionsInput
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace AElf.WebApp.Application.Chain.Dto;

public class SendMultiTransactionOutput
{
public string[] TransactionIds { get; set; }
}
6 changes: 5 additions & 1 deletion src/AElf.WebApp.Application.Chain/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ public static class Error
public const int InvalidOffset = 20006;
public const int InvalidLimit = 20007;
public const int InvalidTransaction = 20008;
public const int InvalidXTransaction = 20009;
public const int InvalidContractAddress = 20010;
public const int NoMatchMethodInContractAddress = 20011;
public const int InvalidParams = 20012;
public const int InvalidSignature = 20013;
public const int InvalidGatewaySignature = 20014;
public const string NeedBasicAuth = "User name and password for basic auth should be set";

public static readonly Dictionary<int, string> Message = new()
Expand All @@ -26,9 +28,11 @@ public static class Error
{ InvalidOffset, "Offset must greater than or equal to 0" },
{ InvalidLimit, "Limit must between 0 and 100" },
{ InvalidTransaction, "Invalid transaction information" },
{ InvalidXTransaction, "Invalid multi-transaction information" },
{ InvalidContractAddress, "Invalid contract address" },
{ NoMatchMethodInContractAddress, "No match method in contract address" },
{ InvalidParams, "Invalid params" },
{ InvalidSignature, "Invalid signature" }
{ InvalidSignature, "Invalid signature" },
{ InvalidGatewaySignature, "Invalid gateway signature" }
};
}
7 changes: 7 additions & 0 deletions src/AElf.WebApp.Application.Chain/MultiTransactionOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace AElf.WebApp.Application.Chain;

public class MultiTransactionOptions
{
public string GatewayAddress { get; set; }
public string GatewayContractAddress { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AElf.Cryptography;
using AElf.Kernel;
using AElf.Kernel.Blockchain.Application;
using AElf.Kernel.FeeCalculation.Extensions;
Expand Down Expand Up @@ -36,6 +37,8 @@ public interface ITransactionAppService

Task<SendTransactionOutput> SendTransactionAsync(SendTransactionInput input);

Task<SendMultiTransactionOutput> SendMultiTransactionAsync(SendMultiTransactionInput input);

Task<string[]> SendTransactionsAsync(SendTransactionsInput input);

Task<CalculateTransactionFeeOutput> CalculateTransactionFeeAsync(CalculateTransactionFeeInput input);
Expand All @@ -49,19 +52,21 @@ public class TransactionAppService : AElfAppService, ITransactionAppService
private readonly ITransactionResultStatusCacheProvider _transactionResultStatusCacheProvider;
private readonly IPlainTransactionExecutingService _plainTransactionExecutingService;
private readonly WebAppOptions _webAppOptions;

private readonly MultiTransactionOptions _multiTransactionOptions;

public TransactionAppService(ITransactionReadOnlyExecutionService transactionReadOnlyExecutionService,
IBlockchainService blockchainService, IObjectMapper<ChainApplicationWebAppAElfModule> objectMapper,
ITransactionResultStatusCacheProvider transactionResultStatusCacheProvider,
IPlainTransactionExecutingService plainTransactionExecutingService,
IOptionsMonitor<WebAppOptions> webAppOptions)
IOptionsMonitor<WebAppOptions> webAppOptions,
IOptionsSnapshot<MultiTransactionOptions> multiTransactionSignerOptions)
{
_transactionReadOnlyExecutionService = transactionReadOnlyExecutionService;
_blockchainService = blockchainService;
_objectMapper = objectMapper;
_transactionResultStatusCacheProvider = transactionResultStatusCacheProvider;
_plainTransactionExecutingService = plainTransactionExecutingService;
_multiTransactionOptions = multiTransactionSignerOptions.Value;
_webAppOptions = webAppOptions.CurrentValue;

LocalEventBus = NullLocalEventBus.Instance;
Expand Down Expand Up @@ -238,6 +243,64 @@ public async Task<SendTransactionOutput> SendTransactionAsync(SendTransactionInp
};
}

public async Task<SendMultiTransactionOutput> SendMultiTransactionAsync(SendMultiTransactionInput input)
{
var multiTxBytes = ByteArrayHelper.HexStringToByteArray(input.RawTransactions);
var multiTransaction = MultiTransaction.Parser.ParseFrom(multiTxBytes);
if (multiTransaction.VerifyFields() != MultiTransaction.ValidationStatus.Success)
{
throw new UserFriendlyException(Error.Message[Error.InvalidTransaction],
Error.InvalidTransaction.ToString());
}

CryptoHelper.RecoverPublicKey(multiTransaction.Signature.ToByteArray(), multiTransaction.GetHash().ToByteArray(), out var pubkey);

if (!await IsGatewayAddress(Address.FromPublicKey(pubkey)))
{
throw new UserFriendlyException(Error.Message[Error.InvalidGatewaySignature],
Error.InvalidGatewaySignature.ToString());
}

var chain = await _blockchainService.GetChainAsync();
var txListOfCurrentChain = multiTransaction.Transactions
.Where(t => t.ChainId == chain.Id)
.Select(t => t.Transaction.ToByteArray().ToHex()).ToArray();
var txIds = await PublishTransactionsAsync(txListOfCurrentChain);

return new SendMultiTransactionOutput
{
TransactionIds = txIds
};
}

private async Task<bool> IsGatewayAddress(Address address)
{
if (string.IsNullOrEmpty(_multiTransactionOptions.GatewayAddress) &&
string.IsNullOrEmpty(_multiTransactionOptions.GatewayContractAddress))
{
return true;
}

if (!string.IsNullOrEmpty(_multiTransactionOptions.GatewayContractAddress))
{
var chain = await _blockchainService.GetChainAsync();
var isGatewayAddressBytes = await CallReadOnlyAsync(new Transaction
{
From = address,
To = Address.FromBase58(_multiTransactionOptions.GatewayContractAddress),
MethodName = "IsGatewayAddress",
Params = address.ToByteString(),
RefBlockNumber = chain.BestChainHeight,
RefBlockPrefix = BlockHelper.GetRefBlockPrefix(chain.BestChainHash)
});
var isGatewayAddress = new BoolValue();
isGatewayAddress.MergeFrom(isGatewayAddressBytes);
return isGatewayAddress.Value;
}

return _multiTransactionOptions.GatewayAddress == address.ToBase58();
}

/// <summary>
/// Broadcast multiple transactions
/// </summary>
Expand Down

0 comments on commit 88d4e10

Please sign in to comment.