FISCO BCOS parallel contract development framework (with practical tutorial)

FISCO BCOS parallel contract development framework (with practical tutorial)

Author: Shi Xiang|FISCO BCOS core developer

This special series of articles is up to now, maybe you will want to ask, how to use the parallelism of FISCO BCOS? As the end of the topic, this article will reveal the "true face of Lushan Mountain" and teach you how to use the parallel features of FISCO BCOS! FISCO BCOS provides a framework for parallel contract development. Contracts written by developers according to the framework specifications can be executed in parallel by FISCO BCOS nodes. The advantages of parallel contracts are:

  • High throughput : multiple independent transactions are executed at the same time, which can maximize the use of the CPU resources of the machine, thus having a high TPS
  • Scalable : The performance of transaction execution can be improved by improving the configuration of the machine to support the continuous expansion of business scale

Next, I will introduce how to write FISCO BCOS parallel contracts, and how to deploy and execute parallel contracts.

Preliminary knowledge

parallel mutex

Whether two transactions can be executed in parallel depends on whether the two transactions are mutually exclusive . Mutual exclusion refers to the intersection of the sets of storage variables of the respective operation contracts of two transactions .

For example, in a transfer scenario, a transaction is a transfer operation between users. Use transfer(X, Y) to represent the transfer interface from user X to user Y. Mutual exclusions are as follows:

insert image description here

A more specific definition is given here:

  • Mutually exclusive parameters: In the contract interface , parameters related to the "read/write" operation of contract storage variables. For example, the transfer interface transfer(X, Y), X and Y are mutually exclusive parameters.

  • Mutual exclusion object : In a transaction , the specific mutual exclusion content is extracted according to the mutual exclusion parameters. For example, the transfer interface transfer(X, Y), in a transaction that calls this interface, the specific parameter is transfer(A, B), then the mutually exclusive object of this operation is [A, B]; another transaction , the parameter of the call is transfer(A, C), then the mutex object of this operation is [A, C].

Judging whether two transactions can be executed in parallel at the same time is to judge whether the mutually exclusive objects of the two transactions overlap. Transactions whose intersection with each other is empty can be executed in parallel.

Write parallel contracts

FISCO BCOS provides a parallel contract development framework . Developers only need to develop contracts according to the framework specifications and define the mutually exclusive parameters of each contract interface to realize contracts that can be executed in parallel. When the contract is deployed, FISCO BCOS will automatically resolve the mutex object before executing the transaction, and at the same time, the transactions without dependencies will be executed in parallel as much as possible.

Currently, FISCO BCOS provides two parallel contract development frameworks, solidity and precompiled contracts.

Parallel framework for solidity contracts

To write parallel solidity contracts, the development process is the same as that of developing ordinary solidity contracts. On this basis, you only need to use ParallelContract as the base class of the contract that needs to be parallel, and call registerParallelFunction() to register the interface that can be parallel.

Give a complete example first. The ParallelOk contract in the example implements the function of parallel transfer:

pragma solidity ^0.4.25;
import "./ParallelContract.sol";  // 引入ParallelContract.sol
contract ParallelOk is ParallelContract // 将ParallelContract 作为基类
{
    // 合约实现
    mapping (string => uint256) _balance;
    
    function transfer(string from, string to, uint256 num) public
{
        // 此处为简单举例,实际生产中请用SafeMath代替直接加减
        _balance[from] -= num; 
        _balance[to] += num;
    }
    function set(string name, uint256 num) public
{
        _balance[name] = num;
    }
    function balanceOf(string name) public view returns (uint256)
{
        return _balance[name];
    }
    
    // 注册可以并行的合约接口
    function enableParallel() public
{
        // 函数定义字符串(注意","后不能有空格),参数的前几个是互斥参数(设计函数时互斥参数必须放在前面
        registerParallelFunction("transfer(string,string,uint256)", 2); // 冲突参数: string string
        registerParallelFunction("set(string,uint256)", 1); // 冲突参数: string
    } 
    // 注销并行合约接口
    function disableParallel() public
{
        unregisterParallelFunction("transfer(string,string,uint256)");
        unregisterParallelFunction("set(string,uint256)"); 
    } 
}

Specific steps are as follows:

step1 Use ParallelContract as the base class of the contract

pragma solidity ^0.4.25;
import "./ParallelContract.sol"; // 引入ParallelContract.sol
contract ParallelOk is ParallelContract // 将ParallelContract 作为基类
{
   // 合约实现
   
   // 注册可以并行的合约接口
   function enableParallel() public;
   
   // 注销并行合约接口
   function disableParallel() public;
}

step2 Write a parallel contract interface

The public function in the contract is the interface of the contract. Writing a parallel contract interface is to implement a public function in a contract according to certain rules.

Determine if an interface is parallelizable

A parallel contract interface must satisfy:

  • No calls to external contracts
  • No interface for calling other functions
Determine Mutual Exclusion Parameters

Before writing the interface, first determine the mutual exclusion parameters of the interface. The mutual exclusion of the interface is the mutual exclusion of global variables. The determination rules of the mutual exclusion parameters are:

  • The interface accesses the global mapping, and the key of the mapping is a mutually exclusive parameter
  • The interface accesses the global array, and the subscript of the array is a mutually exclusive parameter
  • The interface accesses simple-type global variables, all simple-type global variables share a mutex parameter, and use different variable names as mutex objects
Determine parameter type and order

After determining the mutually exclusive parameters, determine the parameter type and order according to the rules, the rules are:

  • Interface parameters are limited to: string, address, uint256, int256 (more types will be supported in the future)
  • The mutex parameters must all appear in the interface parameters
  • All mutually exclusive parameters are arranged at the top of the interface parameters
mapping (string => uint256) _balance; // 全局mapping
// 互斥变量from、to排在最前,作为transfer()开头的两个参数
function transfer(string from, string to, uint256 num) public
{
    _balance[from] -= num;  // from 是全局mapping的key,是互斥参数
    _balance[to] += num; // to 是全局mapping的key,是互斥参数 
}
// 互斥变量name排在最前,作为set()开头的参数
function set(string name, uint256 num) public
{
    _balance[name] = num;
}

step3 Register the parallel contract interface in the framework

Implement the enableParallel() function in the contract, and call registerParallelFunction() to register the parallel contract interface. At the same time, it is also necessary to implement the disableParallel() function so that the contract has the ability to cancel parallel execution.

// 注册可以并行的合约接口
function enableParallel() public
{
    // 函数定义字符串(注意","后不能有空格),参数的前几个是互斥参数
    registerParallelFunction("transfer(string,string,uint256)", 2); // transfer接口,前2个是互斥参数
    registerParallelFunction("set(string,uint256)", 1); // transfer接口,前1个四互斥参数
}  
// 注销并行合约接口
function disableParallel() public
{
    unregisterParallelFunction("transfer(string,string,uint256)");
    unregisterParallelFunction("set(string,uint256)"); 
}

step4 Deploy/execute parallel contracts

Use the console or Web3SDK to compile and deploy the contract, here is the console as an example:

deploy contract

[group:1]> deploy ParallelOk.sol

Call the enableParallel() interface to enable ParallelOk to execute in parallel

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 enableParallel

Send parallel transaction set()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 set "jimmyshi" 100000

Send parallel transaction transfer()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 transfer "jimmyshi" "jinny" 80000

View transaction execution results balanceOf()

[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 balanceOf "jinny"80000

An example of using the SDK to send a large number of transactions will be given in the following examples.

Parallel framework for precompiled contracts

To write parallel precompiled contracts, the development process is the same as that of developing ordinary precompiled contracts. Ordinary precompiled contracts use Precompile as the base class, on which the contract logic is implemented. Based on this, the base class of Precompile also provides two virtual functions for parallelism. By continuing to implement these two functions, parallel precompiled contracts can be realized.

step1 defines the contract to support parallelism

bool isParallelPrecompiled() override { return true; }

step2 Define parallel interface and mutex parameters

Note that once defined to support parallelism, all interfaces need to be defined. If it returns empty, it means that this interface does not have any mutex object. Mutual exclusion parameters are related to the implementation of pre-compiled contracts. This involves the understanding of FISCO BCOS storage. For specific implementation, you can directly read the code or ask relevant experienced programmers.

// 根据并行接口,从参数中取出互斥对象,返回互斥对象
std::vector<std::string> getParallelTag(bytesConstRef param) override
{
    // 获取被调用的函数名(func)和参数(data)
    uint32_t func = getParamFunc(param);
    bytesConstRef data = getParamData(param);
    std::vector<std::string> results;
    if (func == name2Selector[DAG_TRANSFER_METHOD_TRS_STR2_UINT]) // 函数是并行接口
    {  
        // 接口为:userTransfer(string,string,uint256)
        // 从data中取出互斥对象
        std::string fromUser, toUser;
        dev::u256 amount;
        abi.abiOut(data, fromUser, toUser, amount);
        
        if (!invalidUserName(fromUser) && !invalidUserName(toUser))
        {
            // 将互斥对象写到results中
            results.push_back(fromUser);
            results.push_back(toUser);
        }
    }
    else if ... // 所有的接口都需要给出互斥对象,返回空表示无任何互斥对象
        
   return results;  //返回互斥
}

step3 Compile and restart the node

For the method of manually compiling nodes, refer to the FISCO BCOS technical documentation . After compiling, close the node, replace the original node binary file, and restart the node.

Example: Parallel transfer

Parallel examples of solidity contracts and precompiled contracts are given here.

Configuration Environment

This example requires the following execution environment:

  • Web3SDK client
  • A FISCO BCOS chain

If you need to test the maximum performance, you need at least:

  • Only 3 Web3SDKs can generate enough transactions
  • 4 nodes, and all Web3SDKs are configured with all node information on the chain, so that transactions are evenly sent to each node, so that the chain can receive enough transactions

Parallel Solidity contracts: ParallelOk

Transfer based on the account model is a typical business operation. The ParallelOk contract is an example of the account model, which can realize the parallel transfer function. The ParallelOk contract is given above.

FISCO BCOS has a built-in ParallelOk contract in Web3SDK, here is the operation method of using Web3SDK to send a large number of parallel transactions.

step1 Use the SDK to deploy the contract, create a new user, and enable the parallel capability of the contract

# 参数:<groupID> add <创建的用户数量> <此创建操作请求的TPS> <生成的用户信息文件名>
java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.parallelok.PerformanceDT 1 add 10000 2500 user
# 在group1上创建了 10000个用户,创建操作以2500TPS发送的,生成的用户信息保存在user中

After successful execution, ParallelOk is deployed to the blockchain, the created user information is saved in the user file, and the parallel capability of ParallelOk is enabled.

step2 Send parallel transfer transactions in batches

Note: Before batch sending, please adjust the SDK log level to ERROR to have enough sending capacity.

# 参数:<groupID> transfer <总交易数量> <此转账操作请求的TPS上限> <需要的用户信息文件> <交易互斥百分比:0~10>
java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.parallelok.PerformanceDT 1 transfer 100000 4000 user 2
# 向group1发送了 100000比交易,发送的TPS上限是4000,用的之前创建的user文件里的用户。

step3 verify parallel correctness

After the execution of the parallel transaction is completed, Web3SDK will print out the execution result. TPS is the TPS of the transaction sent by this SDK executed on the node. Validation is the check of the execution result of the transfer transaction.

Total transactions:  100000
Total time: 34412ms
TPS: 2905.9630361501804
Avg time cost: 4027ms
Error rate: 0%
Return Error rate: 0%
Time area:
0    < time <  50ms   : 0  : 0.0%
50   < time <  100ms  : 44  : 0.044000000000000004%
100  < time <  200ms  : 2617  : 2.617%
200  < time <  400ms  : 6214  : 6.214%
400  < time <  1000ms : 14190  : 14.19%
1000 < time <  2000ms : 9224  : 9.224%
2000 < time           : 67711  : 67.711%
validation:
   user count is 10000
   verify_success count is 10000
   verify_failed count is 0

It can be seen that the TPS executed in this transaction is 2905. After performing the result verification, there is no error (verify_failed count is 0).

step4 Calculate the total TPS

A single Web3SDK cannot send enough transactions to reach the upper limit of the node's parallel execution capability. Multiple Web3SDKs are required to send transactions at the same time. After multiple Web3SDKs send transactions at the same time, the TPS obtained by simply adding the TPS in the results is not accurate enough, and it is necessary to obtain the TPS directly from the node.

Calculate TPS from log file with script

cd tools
sh get_tps.sh log/log_2019031821.00.log 21:26:24 21:26:59 # 参数:<日志文件> <计算开始时间> <计算结束时间>

Get TPS (3 SDK, 4 nodes, 8 cores, 16G memory)

statistic_end = 21:26:58.631195
statistic_start = 21:26:24.051715
total transactions = 193332, execute_time = 34580ms, tps = 5590 (tx/s)

Parallel precompiled contract: DagTransferPrecompiled

Similar to the function of the ParallelOk contract, FISCO BCOS has a built-in example of a parallel precompiled contract (DagTransferPrecompiled), which realizes a simple transfer function based on the account model. The contract can manage the deposits of multiple users, and provides a transfer interface that supports parallelism to realize the parallel processing of transfer operations between users.

Note: DagTransferPrecompiled is only used as an example, please do not apply it directly to the production environment.

step1 generate user

Use Web3SDK to send the operation of creating a user, and the created user information is saved in the user file. The command parameters are the same as parallelOk, the only difference is that the object called by the command is precompile.

java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.precompile.PerformanceDT 1 add 10000 2500 user

step2 Send parallel transfer transactions in batches

Use Web3SDK to send parallel transfer transactions.

Note: Before batch sending, please adjust the log level of the SDK to ERROR to have enough sending capacity.

java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.precompile.PerformanceDT 1 transfer 100000 4000 user 2

step3 verify parallel correctness

After the execution of the parallel transaction is completed, Web3SDK will print out the execution result. TPS is the TPS of the transaction sent by this SDK executed on the node. Validation is the check of the execution result of the transfer transaction.

Total transactions:  80000
Total time: 25451ms
TPS: 3143.2949589407094
Avg time cost: 5203ms
Error rate: 0%
Return Error rate: 0%
Time area:
0    < time <  50ms   : 0  : 0.0%
50   < time <  100ms  : 0  : 0.0%
100  < time <  200ms  : 0  : 0.0%
200  < time <  400ms  : 0  : 0.0%
400  < time <  1000ms : 403  : 0.50375%
1000 < time <  2000ms : 5274  : 6.592499999999999%
2000 < time           : 74323  : 92.90375%
validation:
    user count is 10000
    verify_success count is 10000
    verify_failed count is 0

It can be seen that the TPS executed in this transaction is 3143. After performing the result verification, there is no error (verify_failed count is 0).

step4 Calculate the total TPS

A single Web3SDK cannot send enough transactions to reach the upper limit of the node's parallel execution capability. Multiple Web3SDKs are required to send transactions at the same time. After multiple Web3SDKs send transactions at the same time, the TPS obtained by simply adding the TPS in the results is not accurate enough, and it is necessary to obtain the TPS directly from the node.

Calculate TPS from log file with script

cd tools
sh get_tps.sh log/log_2019031311.17.log 11:25 11:30 # 参数:<日志文件> <计算开始时间> <计算结束时间>

Get TPS (3 SDK, 4 nodes, 8 cores, 16G memory)

statistic_end = 11:29:59.587145
statistic_start = 11:25:00.642866
total transactions = 3340000, execute_time = 298945ms, tps = 11172 (tx/s)

Result description

The performance results in the examples in this article are measured under 3SDK, 4 nodes, 8 cores, 16G memory, and 1G network. Each SDK and node is deployed in a different VPS, and the hard disk is a cloud disk. The actual TPS will vary according to your hardware configuration, operating system and network bandwidth.

If you encounter obstacles during the deployment process or have questions to consult, you can enter the FISCO BCOS official technical exchange group to seek answers. (To enter the group, please long press the QR code below to identify and add a small assistant)

Guess you like

Origin blog.csdn.net/ws327443752/article/details/124129790