Skip to main content

ACS2 - Parallel Execution Standard

ACS2 helps with parallel transaction execution.

Interface

Contracts using ACS2 need to implement one method:

Methods

Method NameRequest TypeResponse TypeDescription
GetResourceInfoaelf.Transactionacs2.ResourceInfoGets the resource info that the transaction execution depends on.

Types

acs2.ResourceInfo

FieldTypeDescription
write_pathsaelf.ScopedStatePathState paths for writing.
read_pathsaelf.ScopedStatePathState paths for reading.
non_parallelizableboolIf the transaction isn't parallel.

aelf.Address

FieldTypeDescription
valuebytes

aelf.BinaryMerkleTree

FieldTypeDescription
nodesHashLeaf nodes.
rootHashRoot node hash.
leaf_countint32Count of leaf nodes.

aelf.Hash

FieldTypeDescription
valuebytes

aelf.LogEvent

FieldTypeDescription
addressAddressContract address.
namestringLog event name.
indexedbytesIndexed data.
non_indexedbytesNon-indexed data.

aelf.MerklePath

FieldTypeDescription
merkle_path_nodesMerklePathNodeMerkle path nodes.

aelf.MerklePathNode

FieldTypeDescription
hashHashNode hash.
is_left_child_nodeboolIf it's a left child node.

aelf.SInt32Value

FieldTypeDescription
valuesint32

aelf.SInt64Value

FieldTypeDescription
valuesint64

aelf.ScopedStatePath

FieldTypeDescription
addressAddressContract address.
pathStatePathPath of contract state.

aelf.SmartContractRegistration

FieldTypeDescription
categorysint32Contract code category (0: C#).
codebytesContract code byte array.
code_hashHashContract code hash.
is_system_contractboolIf it's a system contract.
versionint32Current contract version.

aelf.StatePath

FieldTypeDescription
partsstringState path parts.

aelf.Transaction

FieldTypeDescription
fromAddressSender address.
toAddressContract address.
ref_block_numberint64Referenced block height.
ref_block_prefixbytesFirst 4 bytes of referenced block hash.
method_namestringMethod name in the contract.
paramsbytesMethod parameters.
signaturebytesSignature of the transaction.

aelf.TransactionExecutingStateSet

FieldTypeDescription
writesTransactionExecutingStateSet.WritesEntryChanged states.
readsTransactionExecutingStateSet.ReadsEntryRead states.
deletesTransactionExecutingStateSet.DeletesEntryDeleted states.

aelf.TransactionExecutingStateSet.DeletesEntry

FieldTypeDescription
keystring
valuebool

aelf.TransactionExecutingStateSet.ReadsEntry

FieldTypeDescription
keystring
valuebool

aelf.TransactionExecutingStateSet.WritesEntry

FieldTypeDescription
keystring
valuebytes

aelf.TransactionResult

FieldTypeDescription
transaction_idHashTransaction ID.
statusTransactionResultStatusTransaction result status.
logsLogEventLog events.
bloombytesBloom filter for transaction logs.
return_valuebytesReturn value of the transaction execution.
block_numberint64Block height that packages the transaction.
block_hashHashBlock hash that packages the transaction.
errorstringFailed execution error message.

aelf.TransactionResultStatus

NameNumberDescription
NOT_EXISTED0Transaction result does not exist.
PENDING1Transaction is waiting to be packaged.
FAILED2Transaction execution failed.
MINED3Transaction was successfully executed and packaged.
CONFLICT4Transaction has conflicts with other transactions.
PENDING_VALIDATION5Transaction is waiting for validation.
NODE_VALIDATION_FAILED6Transaction 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:

public 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:

Nmjj7noTpMqZ522j76SDsFLhiKkThv1u3d4TxqJMD8v89tWmE/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:

var args = TransferInput.Parser.ParseFrom(txn.Params);
var resourceInfo = new ResourceInfo
{
Paths =
{
GetPath(nameof(TokenContractState.Balances), txn.From.ToString(), args.Symbol),
GetPath(nameof(TokenContractState.Balances), args.To.ToString(), args.Symbol),
}
};
return resourceInfo;

The GetPath method forms a ScopedStatePath from key data:

private ScopedStatePath GetPath(params string[] parts)
{
return new ScopedStatePath
{
Address = Context.Self,
Path = new StatePath
{
Parts =
{
parts
}
}
}
}

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:

var keyPair1 = SampleECKeyPairs.KeyPairs[0];
var acs2DemoContractStub1 = GetACS2DemoContractStub(keyPair1);
var keyPair2 = SampleECKeyPairs.KeyPairs[1];
var acs2DemoContractStub2 = GetACS2DemoContractStub(keyPair2);
var transactionGrouper = Application.ServiceProvider.GetRequiredService<ITransactionGrouper>();
var blockchainService = Application.ServiceProvider.GetRequiredService<IBlockchainService>();
var chain = await blockchainService.GetChainAsync();

Check with transactionGrouper:

// Situation can be parallel executed.
{
var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext
{
BlockHash = chain.BestChainHash,
BlockHeight = chain.BestChainHeight
}, new List<Transaction>
{
acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput
{
To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),
Symbol = "ELF",
Amount = 1
}),
acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput
{
To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[3].PublicKey),
Symbol = "ELF",
Amount = 1
}),
});
groupedTransactions.Parallelizables.Count.ShouldBe(2);
}
// Situation cannot.
{
var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext
{
BlockHash = chain.BestChainHash,
BlockHeight = chain.BestChainHeight
}, new List<Transaction>
{
acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput
{
To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),
Symbol = "ELF",
Amount = 1
}),
acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput
{
To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),
Symbol = "ELF",
Amount = 1
}),
});
groupedTransactions.Parallelizables.Count.ShouldBe(1);
}

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 NameRequest TypeResponse TypeDescription
GetResourceInfoaelf.Transactionacs2.ResourceInfoRetrieves resource dependencies for transaction exec.

Types

acs2.ResourceInfo

FieldTypeDescriptionLabel
write_pathsaelf.ScopedStatePathState paths written during executionrepeated
read_pathsaelf.ScopedStatePathState paths read during executionrepeated
non_parallelizableboolIndicates 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.

var args = TransferInput.Parser.ParseFrom(txn.Params);
var resourceInfo = new ResourceInfo
{
Paths =
{
GetPath(nameof(TokenContractState.Balances), txn.From.ToString(), args.Symbol),
GetPath(nameof(TokenContractState.Balances), args.To.ToString(), args.Symbol),
}
};
return resourceInfo;

Test

Test transaction parallelizability using ITransactionGrouper's GroupAsync method with sample transactions.

var keyPair1 = SampleECKeyPairs.KeyPairs[0];
var acs2DemoContractStub1 = GetACS2DemoContractStub(keyPair1);
var keyPair2 = SampleECKeyPairs.KeyPairs[1];
var acs2DemoContractStub2 = GetACS2DemoContractStub(keyPair2);

var transactionGrouper = Application.ServiceProvider.GetRequiredService<ITransactionGrouper>();
var blockchainService = Application.ServiceProvider.GetRequiredService<IBlockchainService>();
var chain = await blockchainService.GetChainAsync();

// Test parallel execution scenario
{
var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext
{
BlockHash = chain.BestChainHash,
BlockHeight = chain.BestChainHeight
}, new List<Transaction>
{
acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput
{
To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),
Symbol = "ELF",
Amount = 1
}),
acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput
{
To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[3].PublicKey),
Symbol = "ELF",
Amount = 1
}),
});
groupedTransactions.Parallelizables.Count.ShouldBe(2);
}

// Test non-parallel execution scenario
{
var groupedTransactions = await transactionGrouper.GroupAsync(new ChainContext
{
BlockHash = chain.BestChainHash,
BlockHeight = chain.BestChainHeight
}, new List<Transaction>
{
acs2DemoContractStub1.TransferCredits.GetTransaction(new TransferCreditsInput
{
To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),
Symbol = "ELF",
Amount = 1
}),
acs2DemoContractStub2.TransferCredits.GetTransaction(new TransferCreditsInput
{
To = Address.FromPublicKey(SampleECKeyPairs.KeyPairs[2].PublicKey),
Symbol = "ELF",
Amount = 1
}),
});
groupedTransactions.Parallelizables.Count.ShouldBe(1);
}

Example

For an example of implementing GetResourceInfo, refer to the MultiToken contract, ensuring transaction fees are considered for the keys involved.