Parallel Execution
ACS2 - Parallel Execution Standard#
ACS2 helps with parallel transaction execution.
Interface#
Contracts using ACS2 need to implement one method:
Methods#
Method Name | Request Type | Response Type | Description |
---|---|---|---|
GetResourceInfo | aelf.Transaction | acs2.ResourceInfo | Gets the resource info that the transaction execution depends on. |
Types#
acs2.ResourceInfo
Field | Type | Description |
---|---|---|
write_paths | aelf.ScopedStatePath | State paths for writing. |
read_paths | aelf.ScopedStatePath | State paths for reading. |
non_parallelizable | bool | If the transaction isn't parallel. |
aelf.Address
Field | Type | Description |
---|---|---|
value | bytes |
aelf.BinaryMerkleTree
Field | Type | Description |
---|---|---|
nodes | Hash | Leaf nodes. |
root | Hash | Root node hash. |
leaf_count | int32 | Count of leaf nodes. |
aelf.Hash
Field | Type | Description |
---|---|---|
value | bytes |
aelf.LogEvent
Field | Type | Description |
---|---|---|
address | Address | Contract address. |
name | string | Log event name. |
indexed | bytes | Indexed data. |
non_indexed | bytes | Non-indexed data. |
aelf.MerklePath
Field | Type | Description |
---|---|---|
merkle_path_nodes | MerklePathNode | Merkle path nodes. |
aelf.MerklePathNode
Field | Type | Description |
---|---|---|
hash | Hash | Node hash. |
is_left_child_node | bool | If it's a left child node. |
aelf.SInt32Value
Field | Type | Description |
---|---|---|
value | sint32 |
aelf.SInt64Value
Field | Type | Description |
---|---|---|
value | sint64 |
aelf.ScopedStatePath
Field | Type | Description |
---|---|---|
address | Address | Contract address. |
path | StatePath | Path of contract state. |
aelf.SmartContractRegistration
Field | Type | Description |
---|---|---|
category | sint32 | Contract code category (0: C#). |
code | bytes | Contract code byte array. |
code_hash | Hash | Contract code hash. |
is_system_contract | bool | If it's a system contract. |
version | int32 | Current contract version. |
aelf.StatePath
Field | Type | Description |
---|---|---|
parts | string | State path parts. |
aelf.Transaction
Field | Type | Description |
---|---|---|
from | Address | Sender address. |
to | Address | Contract address. |
ref_block_number | int64 | Referenced block height. |
ref_block_prefix | bytes | First 4 bytes of referenced block hash. |
method_name | string | Method name in the contract. |
params | bytes | Method parameters. |
signature | bytes | Signature of the transaction. |
aelf.TransactionExecutingStateSet
Field | Type | Description |
---|---|---|
writes | TransactionExecutingStateSet.WritesEntry | Changed states. |
reads | TransactionExecutingStateSet.ReadsEntry | Read states. |
deletes | TransactionExecutingStateSet.DeletesEntry | Deleted states. |
aelf.TransactionExecutingStateSet.DeletesEntry
Field | Type | Description |
---|---|---|
key | string | |
value | bool |
aelf.TransactionExecutingStateSet.ReadsEntry
Field | Type | Description |
---|---|---|
key | string | |
value | bool |
aelf.TransactionExecutingStateSet.WritesEntry
Field | Type | Description |
---|---|---|
key | string | |
value | bytes |
aelf.TransactionResult
Field | Type | Description |
---|---|---|
transaction_id | Hash | Transaction ID. |
status | TransactionResultStatus | Transaction result status. |
logs | LogEvent | Log events. |
bloom | bytes | Bloom filter for transaction logs. |
return_value | bytes | Return value of the transaction execution. |
block_number | int64 | Block height that packages the transaction. |
block_hash | Hash | Block hash that packages the transaction. |
error | string | Failed execution error message. |
aelf.TransactionResultStatus
Name | Number | Description |
---|---|---|
NOT_EXISTED | Transaction result does not exist. | |
PENDING | 1 | Transaction is waiting to be packaged. |
FAILED | 2 | Transaction execution failed. |
MINED | 3 | Transaction was successfully executed and packaged. |
CONFLICT | 4 | Transaction has conflicts with other transactions. |
PENDING_VALIDATION | 5 | Transaction is waiting for validation. |
NODE_VALIDATION_FAILED | 6 | Transaction validation failed. |
Usage#
aelf uses a key-value database to store data. State Path determines the key for contract execution data.
For example, a Token contract defines a balance property:
1public MappedState<Address, string, long> Balances { get; set; }
To access the balance of an address (2EM5uV6bSJh6xJfZTUa1pZpYsYcCUAdPvZvFUJzMDJEx3rbioz) for a Token contract address (Nmjj7noTpMqZ522j76SDsFLhiKkThv1u3d4TxqJMD8v89tWmE), use its key in the database:
1Nmjj7noTpMqZ522j76SDsFLhiKkThv1u3d4TxqJMD8v89tWmE/Balances/2EM5uV6bSJh6xJfZTUa1pZpYsYcCUAdPvZvFUJzMDJEx3rbioz/ELF
Parallel execution groups transactions by their State Paths. If two methods don’t access the same StatePath, they can be executed in parallel.
If State Paths mismatch, the transaction is canceled and labeled as “cannot be grouped”.
For more details, check the ITransactionGrouper and IParallelTransactionExecutingService code.
Implementation#
Example: Token Contract For the Transfer method, notify ITransactionGrouper via GetResourceInfo about the ELF balances of address A and B:
1var args = TransferInput.Parser.ParseFrom(txn.Params);2var resourceInfo = new ResourceInfo3{4Paths =5{6GetPath(nameof(TokenContractState.Balances), txn.From.ToString(), args.Symbol),7GetPath(nameof(TokenContractState.Balances), args.To.ToString(), args.Symbol),8}9};10return resourceInfo;
The GetPath method forms a ScopedStatePath from key data:
1private ScopedStatePath GetPath(params string[] parts)2{3return new ScopedStatePath4{5Address = Context.Self,6Path = new StatePath7{8Parts =9{10parts11}12}13}14}
Testing#
Construct two transactions and pass them to ITransactionGrouper to test if they can run in parallel using GroupAsync.
Prepare two stubs with different addresses:
1var keyPair1 = SampleECKeyPairs.KeyPairs[0];2var acs2DemoContractStub1 = GetACS2DemoContractStub(keyPair1);3var keyPair2 = SampleECKeyPairs.KeyPairs[1];4var acs2DemoContractStub2 = GetACS2DemoContractStub(keyPair2);
1var transactionGrouper = Application.ServiceProvider.GetRequiredService<ITransactionGrouper>();2var blockchainService = Application.ServiceProvider.GetRequiredService<IBlockchainService>();3var chain = await blockchainService.GetChainAsync();
Check with transactionGrouper:
1// Situation can be parallel executed.2{3var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext4{5BlockHash = chain.BestChainHash,6BlockHeight = chain.BestChainHeight7}, new List<Transaction>8{9acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput10{11To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),12Symbol = "ELF",13Amount = 114}),15acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput16{17To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[3].PublicKey),18Symbol = "ELF",19Amount = 120}),21});22groupedTransactions.Parallelizables.Count.ShouldBe(2);23}24// Situation cannot.25{26var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext27{28BlockHash = chain.BestChainHash,29BlockHeight = chain.BestChainHeight30}, new List<Transaction>31{32acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput33{34To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),35Symbol = "ELF",36Amount = 137}),38acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput39{40To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),41Symbol = "ELF",42Amount = 143}),44});45groupedTransactions.Parallelizables.Count.ShouldBe(1);46}
Example#
Refer to the MultiToken contract implementation for GetResourceInfo. Note that Transfer method needs to handle transaction fees along with keys.
ACS2 - Parallel Execution Standard
ACS2 enables parallel execution of transactions by providing necessary resource information.
Interface#
A contract inheriting ACS2 must implement:
Methods
Method Name | Request Type | Response Type | Description |
---|---|---|---|
GetResourceInfo | aelf.Transaction | acs2.ResourceInfo | Retrieves resource dependencies for transaction exec. |
Types#
acs2.ResourceInfo
Field | Type | Description | Label |
---|---|---|---|
write_paths | aelf.ScopedStatePath | State paths written during execution | repeated |
read_paths | aelf.ScopedStatePath | State paths read during execution | repeated |
non_parallelizable | bool | Indicates if transaction is non-parallelizable. |
Other Types (Omitted for brevity)
Several other types like aelf.Address, aelf.BinaryMerkleTree, aelf.LogEvent, etc., are used within acs2.ResourceInfo.
Usage#
aelf uses State Paths to manage data storage, ensuring transaction grouping based on accessed paths for efficient parallel execution.
Implementation#
Token contract, for example, modifies balances through method Transfer. GetResourceInfo must notify ITransactionGrouper of accessed state paths.
1var args = TransferInput.Parser.ParseFrom(txn.Params);2var resourceInfo = new ResourceInfo3{4Paths =5{6GetPath(nameof(TokenContractState.Balances), txn.From.ToString(), args.Symbol),7GetPath(nameof(TokenContractState.Balances), args.To.ToString(), args.Symbol),8}9};10return resourceInfo;
Test#
Test transaction parallelizability using ITransactionGrouper's GroupAsync method with sample transactions.
1var keyPair1 = SampleECKeyPairs.KeyPairs[0];2var acs2DemoContractStub1 = GetACS2DemoContractStub(keyPair1);3var keyPair2 = SampleECKeyPairs.KeyPairs[1];4var acs2DemoContractStub2 = GetACS2DemoContractStub(keyPair2);56var transactionGrouper = Application.ServiceProvider.GetRequiredService<ITransactionGrouper>();7var blockchainService = Application.ServiceProvider.GetRequiredService<IBlockchainService>();8var chain = await blockchainService.GetChainAsync();910// Test parallel execution scenario11{12var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext13{14BlockHash = chain.BestChainHash,15BlockHeight = chain.BestChainHeight16}, new List<Transaction>17{18acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput19{20To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),21Symbol = "ELF",22Amount = 123}),24acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput25{26To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[3].PublicKey),27Symbol = "ELF",28Amount = 129}),30});31groupedTransactions.Parallelizables.Count.ShouldBe(2);32}3334// Test non-parallel execution scenario35{36var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext37{38BlockHash = chain.BestChainHash,39BlockHeight = chain.BestChainHeight40}, new List<Transaction>41{42acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput43{44To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),45Symbol = "ELF",46Amount = 147}),48acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput49{50To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),51Symbol = "ELF",52Amount = 153}),54});55groupedTransactions.Parallelizables.Count.ShouldBe(1);56}
Example#
For an example of implementing GetResourceInfo, refer to the MultiToken contract, ensuring transaction fees are considered for the keys involved.
Edited on: 15 July 2024 04:40:14 GMT+0