Skip to content

Commit 88d4e10

Browse files
authored
Merge pull request #3613 from AElfProject/feature/multi-tx
Add MultiTransaction type and one new web api to handle this
2 parents 813c602 + 6912ed3 commit 88d4e10

File tree

8 files changed

+169
-3
lines changed

8 files changed

+169
-3
lines changed

protobuf/aelf/core.proto

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ message Transaction {
2222
bytes signature = 10000;
2323
}
2424

25+
message TransactionAndChainId {
26+
Transaction transaction = 1;
27+
int32 chain_id = 2;
28+
}
29+
30+
message MultiTransaction {
31+
repeated TransactionAndChainId transactions = 1;
32+
bytes signature = 10000;
33+
}
34+
2535
message StatePath {
2636
// The partial path of the state path.
2737
repeated string parts = 1;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using System.Linq;
3+
using Google.Protobuf;
4+
5+
namespace AElf.Types
6+
{
7+
public partial class MultiTransaction
8+
{
9+
private Hash _transactionId;
10+
11+
public Hash GetHash()
12+
{
13+
if (_transactionId == null)
14+
_transactionId = HashHelper.ComputeFrom(GetSignatureData());
15+
16+
return _transactionId;
17+
}
18+
19+
public ValidationStatus VerifyFields()
20+
{
21+
if (Transactions.Count < 2)
22+
return ValidationStatus.OnlyOneTransaction;
23+
24+
if (!AllTransactionsHaveSameFrom())
25+
return ValidationStatus.MoreThanOneFrom;
26+
27+
if (Transactions.Any(transaction => string.IsNullOrEmpty(transaction.Transaction.MethodName)))
28+
return ValidationStatus.MethodNameIsEmpty;
29+
30+
if (Transactions.Any(transaction => transaction.Transaction.Signature.IsEmpty))
31+
{
32+
return ValidationStatus.UserSignatureIsEmpty;
33+
}
34+
35+
return ValidationStatus.Success;
36+
}
37+
38+
public enum ValidationStatus
39+
{
40+
Success,
41+
OnlyOneTransaction,
42+
MoreThanOneFrom,
43+
MethodNameIsEmpty,
44+
UserSignatureIsEmpty
45+
}
46+
47+
private bool AllTransactionsHaveSameFrom()
48+
{
49+
var firstFrom = Transactions[0].Transaction.From;
50+
return Transactions.All(transaction => transaction.Transaction.From == firstFrom);
51+
}
52+
53+
private byte[] GetSignatureData()
54+
{
55+
var verifyResult = VerifyFields();
56+
if (verifyResult != ValidationStatus.Success)
57+
throw new InvalidOperationException($"Invalid multi transaction, {verifyResult.ToString()}: {this}");
58+
59+
if (Signature.IsEmpty)
60+
return this.ToByteArray();
61+
62+
var multiTransaction = Clone();
63+
multiTransaction.Signature = ByteString.Empty;
64+
return multiTransaction.ToByteArray();
65+
}
66+
}
67+
}

src/AElf.WebApp.Application.Chain/ChainApplicationWebAppAElfModule.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,8 @@ public override void ConfigureServices(ServiceConfigurationContext context)
2222

2323
context.Services
2424
.AddSingleton<ITransactionResultStatusCacheProvider, TransactionResultStatusCacheProvider>();
25+
26+
Configure<MultiTransactionOptions>(context.Services.GetConfiguration()
27+
.GetSection("MultiTransaction"));
2528
}
2629
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace AElf.WebApp.Application.Chain.Dto;
2+
3+
public class SendMultiTransactionInput : SendTransactionsInput
4+
{
5+
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace AElf.WebApp.Application.Chain.Dto;
2+
3+
public class SendMultiTransactionOutput
4+
{
5+
public string[] TransactionIds { get; set; }
6+
}

src/AElf.WebApp.Application.Chain/Error.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ public static class Error
1111
public const int InvalidOffset = 20006;
1212
public const int InvalidLimit = 20007;
1313
public const int InvalidTransaction = 20008;
14+
public const int InvalidXTransaction = 20009;
1415
public const int InvalidContractAddress = 20010;
1516
public const int NoMatchMethodInContractAddress = 20011;
1617
public const int InvalidParams = 20012;
1718
public const int InvalidSignature = 20013;
19+
public const int InvalidGatewaySignature = 20014;
1820
public const string NeedBasicAuth = "User name and password for basic auth should be set";
1921

2022
public static readonly Dictionary<int, string> Message = new()
@@ -26,9 +28,11 @@ public static class Error
2628
{ InvalidOffset, "Offset must greater than or equal to 0" },
2729
{ InvalidLimit, "Limit must between 0 and 100" },
2830
{ InvalidTransaction, "Invalid transaction information" },
31+
{ InvalidXTransaction, "Invalid multi-transaction information" },
2932
{ InvalidContractAddress, "Invalid contract address" },
3033
{ NoMatchMethodInContractAddress, "No match method in contract address" },
3134
{ InvalidParams, "Invalid params" },
32-
{ InvalidSignature, "Invalid signature" }
35+
{ InvalidSignature, "Invalid signature" },
36+
{ InvalidGatewaySignature, "Invalid gateway signature" }
3337
};
3438
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace AElf.WebApp.Application.Chain;
2+
3+
public class MultiTransactionOptions
4+
{
5+
public string GatewayAddress { get; set; }
6+
public string GatewayContractAddress { get; set; }
7+
}

src/AElf.WebApp.Application.Chain/Services/TransactionAppService.cs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Threading;
66
using System.Threading.Tasks;
7+
using AElf.Cryptography;
78
using AElf.Kernel;
89
using AElf.Kernel.Blockchain.Application;
910
using AElf.Kernel.FeeCalculation.Extensions;
@@ -36,6 +37,8 @@ public interface ITransactionAppService
3637

3738
Task<SendTransactionOutput> SendTransactionAsync(SendTransactionInput input);
3839

40+
Task<SendMultiTransactionOutput> SendMultiTransactionAsync(SendMultiTransactionInput input);
41+
3942
Task<string[]> SendTransactionsAsync(SendTransactionsInput input);
4043

4144
Task<CalculateTransactionFeeOutput> CalculateTransactionFeeAsync(CalculateTransactionFeeInput input);
@@ -49,19 +52,21 @@ public class TransactionAppService : AElfAppService, ITransactionAppService
4952
private readonly ITransactionResultStatusCacheProvider _transactionResultStatusCacheProvider;
5053
private readonly IPlainTransactionExecutingService _plainTransactionExecutingService;
5154
private readonly WebAppOptions _webAppOptions;
52-
55+
private readonly MultiTransactionOptions _multiTransactionOptions;
5356

5457
public TransactionAppService(ITransactionReadOnlyExecutionService transactionReadOnlyExecutionService,
5558
IBlockchainService blockchainService, IObjectMapper<ChainApplicationWebAppAElfModule> objectMapper,
5659
ITransactionResultStatusCacheProvider transactionResultStatusCacheProvider,
5760
IPlainTransactionExecutingService plainTransactionExecutingService,
58-
IOptionsMonitor<WebAppOptions> webAppOptions)
61+
IOptionsMonitor<WebAppOptions> webAppOptions,
62+
IOptionsSnapshot<MultiTransactionOptions> multiTransactionSignerOptions)
5963
{
6064
_transactionReadOnlyExecutionService = transactionReadOnlyExecutionService;
6165
_blockchainService = blockchainService;
6266
_objectMapper = objectMapper;
6367
_transactionResultStatusCacheProvider = transactionResultStatusCacheProvider;
6468
_plainTransactionExecutingService = plainTransactionExecutingService;
69+
_multiTransactionOptions = multiTransactionSignerOptions.Value;
6570
_webAppOptions = webAppOptions.CurrentValue;
6671

6772
LocalEventBus = NullLocalEventBus.Instance;
@@ -238,6 +243,64 @@ public async Task<SendTransactionOutput> SendTransactionAsync(SendTransactionInp
238243
};
239244
}
240245

246+
public async Task<SendMultiTransactionOutput> SendMultiTransactionAsync(SendMultiTransactionInput input)
247+
{
248+
var multiTxBytes = ByteArrayHelper.HexStringToByteArray(input.RawTransactions);
249+
var multiTransaction = MultiTransaction.Parser.ParseFrom(multiTxBytes);
250+
if (multiTransaction.VerifyFields() != MultiTransaction.ValidationStatus.Success)
251+
{
252+
throw new UserFriendlyException(Error.Message[Error.InvalidTransaction],
253+
Error.InvalidTransaction.ToString());
254+
}
255+
256+
CryptoHelper.RecoverPublicKey(multiTransaction.Signature.ToByteArray(), multiTransaction.GetHash().ToByteArray(), out var pubkey);
257+
258+
if (!await IsGatewayAddress(Address.FromPublicKey(pubkey)))
259+
{
260+
throw new UserFriendlyException(Error.Message[Error.InvalidGatewaySignature],
261+
Error.InvalidGatewaySignature.ToString());
262+
}
263+
264+
var chain = await _blockchainService.GetChainAsync();
265+
var txListOfCurrentChain = multiTransaction.Transactions
266+
.Where(t => t.ChainId == chain.Id)
267+
.Select(t => t.Transaction.ToByteArray().ToHex()).ToArray();
268+
var txIds = await PublishTransactionsAsync(txListOfCurrentChain);
269+
270+
return new SendMultiTransactionOutput
271+
{
272+
TransactionIds = txIds
273+
};
274+
}
275+
276+
private async Task<bool> IsGatewayAddress(Address address)
277+
{
278+
if (string.IsNullOrEmpty(_multiTransactionOptions.GatewayAddress) &&
279+
string.IsNullOrEmpty(_multiTransactionOptions.GatewayContractAddress))
280+
{
281+
return true;
282+
}
283+
284+
if (!string.IsNullOrEmpty(_multiTransactionOptions.GatewayContractAddress))
285+
{
286+
var chain = await _blockchainService.GetChainAsync();
287+
var isGatewayAddressBytes = await CallReadOnlyAsync(new Transaction
288+
{
289+
From = address,
290+
To = Address.FromBase58(_multiTransactionOptions.GatewayContractAddress),
291+
MethodName = "IsGatewayAddress",
292+
Params = address.ToByteString(),
293+
RefBlockNumber = chain.BestChainHeight,
294+
RefBlockPrefix = BlockHelper.GetRefBlockPrefix(chain.BestChainHash)
295+
});
296+
var isGatewayAddress = new BoolValue();
297+
isGatewayAddress.MergeFrom(isGatewayAddressBytes);
298+
return isGatewayAddress.Value;
299+
}
300+
301+
return _multiTransactionOptions.GatewayAddress == address.ToBase58();
302+
}
303+
241304
/// <summary>
242305
/// Broadcast multiple transactions
243306
/// </summary>

0 commit comments

Comments
 (0)