ACS2 - 并行执行标准¶
ACS2用于提供交易并行执行的信息。
接口¶
继承了ACS2的合约仅需要实现一个接口:
- GetResourceInfo
参数为Transaction类型,返回值为定义在acs2.proto中的ResourceInfo类型:
message ResourceInfo {
repeated aelf.ScopedStatePath paths = 1;
bool non_parallelizable = 2;
}
其中,aelf.ScopedStatePath定义在aelfcore.proto中:
message ScopedStatePath {
Address address = 1;
StatePath path = 2;
}
message StatePath {
repeated string parts = 1;
}
应用¶
AElf使用key-value数据库来存储数据,对于合约执行过程中产生的数据,使用一种名为State Path的机制来确定某一条有意义的数据的key。
例如在Token合约的State文件中中定义一个类型为MappedState<Address, string, long>、名为Balances的属性,我们可以通过该属性来访问、修改某一个Address所代表的账户下的某种token的余额。
假设Token合约的地址为Nmjj7noTpMqZ522j76SDsFLhiKkThv1u3d4TxqJMD8v89tWmE,想要知道地址为2EM5uV6bSJh6xJfZTUa1pZpYsYcCUAdPvZvFUJzMDJEx3rbioz的账户的ELF余额,可以直接使用这个key来访问redis/ssdb:
Nmjj7noTpMqZ522j76SDsFLhiKkThv1u3d4TxqJMD8v89tWmE/Balances/2EM5uV6bSJh6xJfZTUa1pZpYsYcCUAdPvZvFUJzMDJEx3rbioz/ELF
而在AElf中,交易并行执行的实现也是基于以上key的组织逻辑,开发者需要提供某个方法可能访问到的StatePath,那么这个方法对应的交易在执行之前,会得到适当的分组:如果两个方法不会访问同样的StatePath,那么就可以放心地放在不同的组中。
注意,如果该方法提供的StatePath与实际执行过程使用到的StatePath不一致,会在实际执行的过程中取消并行,并且合约会被标记为不能并行。
如果对相关的逻辑感兴趣,可以查看AElf代码中ITransactionGrouper这个接口的实现,以及IParallelTransactionExecutingService接口的实现。
接口实现¶
一个例子:Token合约中的Transfer方法,其核心逻辑为修改地址A和地址B的ELF的余额,会访问两次上面提到的Balances属性。
此时,我们就需要把地址A和地址B的ELF的余额的key通过GetResourceInfo方法通知ITransactionGrouper:
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;
其中,GetPath就是把组成key的几段数据构成一个ScopedStatePath类型:
private ScopedStatePath GetPath(params string[] parts)
{
return new ScopedStatePath
{
Address = Context.Self,
Path = new StatePath
{
Parts =
{
parts
}
}
}
}
测试¶
可以自行构造两个交易,直接将交易ITransactionGrouper的实现实例的GroupAsync方法,来得知这两个交易能否并行。
我们准备两个实现了ACS2的合约的Stub,它们的地址不同,即模拟不同的地址做Transfer:
var keyPair1 = SampleECKeyPairs.KeyPairs[0];
var acs2DemoContractStub1 = GetACS2DemoContractStub(keyPair1);
var keyPair2 = SampleECKeyPairs.KeyPairs[1];
var acs2DemoContractStub2 = GetACS2DemoContractStub(keyPair2);
然后从Application中取出测试所需要的一些服务和数据:
var transactionGrouper = Application.ServiceProvider.GetRequiredService<ITransactionGrouper>();
var blockchainService = Application.ServiceProvider.GetRequiredService<IBlockchainService>();
var chain = await blockchainService.GetChainAsync();
最后通过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);
}
示例¶
可以参考MultiToken合约对GetResourceInfo的实现,注意其中对于Tranfer方法提供的Resource Info,除了本文提到的两个key,还需要考虑收取交易费。