Skip to content

Hyperledger-Caliper

FISCO BCOS v2 基于Caliper性能测试

环境要求

1. nodejs

sh
#安装wget
yum -y install wget
#下载nodejs安装包
wget https://npm.taobao.org/mirrors/node/v16.15.1/node-v16.15.1-linux-x64.tar.xz

#卸载原有的node
rm -rf /usr/local/node
#压缩,安装,配置环境变量
tar -xvf node-v16.15.1-linux-x64.tar.xz
mv node-v16.15.1-linux-x64/ /usr/local/node
#环境变量
echo "export PATH=$PATH:/usr/local/node/bin" >> /etc/profile
source /etc/profile
#sudo
ln -s /usr/local/node/bin/* /usr/bin/

#查看版本
node -v
npm -v

2. caliper CLI

  • 使用npm安装
sh
npm install --only=prod @hyperledger/caliper-cli@0.5.0
  • 查看是否安装成功
sh
[root@localhost package]# npx caliper --version
0.5.0

3. 绑定sdk

sh
npx caliper bind --caliper-bind-sut fisco-bcos:latest

4. 下载demo

sh
git clone https://gitee.com/hyperledger/caliper-benchmarks.git

5. 执行HelloWorld合约测试

sh
npx caliper launch manager --caliper-workspace ./ --caliper-benchconfig benchmarks/samples/fisco-bcos/helloworld/config.yaml  --caliper-networkconfig networks/fisco-bcos/4nodes1group/fisco-bcos.json

配置文件

1. Benchmark configuration file

1.1 Hello World
  • config.yaml
yaml
test:
  name: Hello World
  description: This is a helloworld benchmark of FISCO BCOS for caliper
  workers:
    number: 1
  rounds:
    - label: get
      description: Test performance of getting name
      txNumber: 10000
      rateControl:
          type: fixed-rate
          opts:
            tps: 1000
      workload:
        module: benchmarks/get.js
    - label: set
      description: Test performance of setting name
      txNumber: 10000
      rateControl:
          type: fixed-rate
          opts:
            tps: 1000
      workload:
        module: benchmarks/set.js
1.2 transfer
  • config.yaml
yaml
test:
  name: Solidity Transfer
  description: This is a solidity transfer benchmark of FISCO BCOS for caliper
  workers:
    number: 4
  rounds:
    - label: addUser
      description: generate users for transfer test later
      txNumber:
        - 1000
      rateControl:
        - type: fixed-rate
          opts:
            tps: 1000
      workload:
        module: benchmarks/samples/fisco-bcos/transfer/solidity/addUser.js
    - label: transfer
      description: transfer money between users
      txNumber:
        - 10000
      rateControl:
        - type: fixed-rate
          opts:
            tps: 1000
      workload:
        module: benchmarks/samples/fisco-bcos/transfer/solidity/transfer.js
        arguments:
          txnPerBatch: 10

2. Network configuration file

2.1 Custom
  • networkConfig.yaml
json
{
    "caliper": {
        "blockchain": "fisco-bcos",
    },
    "fisco-bcos": {
        "config": {
            "privateKey": "bcec428d5205abe0f0cc8a734083908d9eb8563e31f943d760786edf42ad67dd",
            "account": "0x64fa644d2a694681bd6addd6c5e36cccd8dcdde3"
        },
        "network": {
            "nodes": [
                {
                    "ip": "127.0.0.1",
                    "rpcPort": "8545",
                    "channelPort": "20200"
                },
                {
                    "ip": "127.0.0.1",
                    "rpcPort": "8546",
                    "channelPort": "20201"
                },
                {
                    "ip": "127.0.0.1",
                    "rpcPort": "8547",
                    "channelPort": "20202"
                },
                {
                    "ip": "127.0.0.1",
                    "rpcPort": "8548",
                    "channelPort": "20203"
                }
            ],
            "authentication": {
                "key": "/root/fisco/nodes/127.0.0.1/sdk/sdk.key",
                "cert": "/root/fisco/nodes/127.0.0.1/sdk/sdk.crt",
                "ca": "/root/fisco/nodes/127.0.0.1/sdk/ca.crt"
            },
            "groupID": 1,
            "timeout": 100000
        },
        "smartContracts": [
            {
                "id": "helloworld",
                "path": "benchmarks/HelloWorld.sol",
                "language": "solidity",
                "version": "v0"
            },
        ]
    },
    "info": {
        "Version": "2.0.0",
        "Size": "4 Nodes",
        "Distribution": "Single Host"
    }
}
2.2 Samlpe
  • networkConfig.yaml
json
{
    "caliper": {
        "blockchain": "fisco-bcos",
        "command": {
            "start": "docker-compose -f networks/fisco-bcos/4nodes1group/docker-compose.yaml up -d; sleep 3s",
            "end": "docker-compose -f networks/fisco-bcos/4nodes1group/docker-compose.yaml down"
        }
    },
    "fisco-bcos": {
        "config": {
            "privateKey": "bcec428d5205abe0f0cc8a734083908d9eb8563e31f943d760786edf42ad67dd",
            "account": "0x64fa644d2a694681bd6addd6c5e36cccd8dcdde3"
        },
        "network": {
            "nodes": [
                {
                    "ip": "127.0.0.1",
                    "rpcPort": "8914",
                    "channelPort": "20914"
                },
                {
                    "ip": "127.0.0.1",
                    "rpcPort": "8915",
                    "channelPort": "20915"
                },
                {
                    "ip": "127.0.0.1",
                    "rpcPort": "8916",
                    "channelPort": "20916"
                },
                {
                    "ip": "127.0.0.1",
                    "rpcPort": "8917",
                    "channelPort": "20917"
                }
            ],
            "authentication": {
                "key": "./networks/fisco-bcos/4nodes1group/sdk/node.key",
                "cert": "./networks/fisco-bcos/4nodes1group/sdk/node.crt",
                "ca": "./networks/fisco-bcos/4nodes1group/sdk/ca.crt"
            },
            "groupID": 1,
            "timeout": 100000
        },
        "smartContracts": [
            {
                "id": "helloworld",
                "path": "src/fisco-bcos/helloworld/HelloWorld.sol",
                "language": "solidity",
                "version": "v0"
            },
            {
                "id": "parallelok",
                "path": "src/fisco-bcos/transfer/ParallelOk.sol",
                "language": "solidity",
                "version": "v0"
            },
            {
                "id": "dagtransfer",
                "address": "0x0000000000000000000000000000000000005002",
                "language": "precompiled",
                "version": "v0"
            }
        ]
    },
    "info": {
        "Version": "2.0.0",
        "Size": "4 Nodes",
        "Distribution": "Single Host"
    }
}

3. Workload modules

3.1 Hello World
  • get.js
javascript
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const { WorkloadModuleBase } = require('@hyperledger/caliper-core');

/**
 * Workload module for the benchmark round.
 */
class GetWorkload extends WorkloadModuleBase {
    /**
     * Assemble TXs for the round.
     * @return {Promise<TxStatus[]>}
     */
    async submitTransaction() {
        const args = {
            contractId: 'helloworld',
            args: {
                transaction_type: 'get()'
            },
            readOnly: true
        };
        await this.sutAdapter.sendRequests(args);
    }
}

/**
 * Create a new instance of the workload module.
 * @return {WorkloadModuleInterface}
 */
function createWorkloadModule() {
    return new GetWorkload();
}

module.exports.createWorkloadModule = createWorkloadModule;
  • set.js
javascript
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const { WorkloadModuleBase } = require('@hyperledger/caliper-core');

/**
 * Workload module for the benchmark round.
 */
class SetWorkload extends WorkloadModuleBase {
    /**
     * Initializes the workload module instance.
     */
    constructor() {
        super();
    }

    /**
     * Assemble TXs for the round.
     * @return {Promise<TxStatus[]>}
     */
    async submitTransaction() {
        const args = {
            contractId: 'helloworld',
            args: {
                transaction_type: 'set(string)',
                name: 'hello! - from ' + this.workerIndex.toString()
            },
            readOnly: false
        };
        await this.sutAdapter.sendRequests(args);
    }
}

/**
 * Create a new instance of the workload module.
 * @return {WorkloadModuleInterface}
 */
function createWorkloadModule() {
    return new SetWorkload();
}

module.exports.createWorkloadModule = createWorkloadModule;
3.2 transfer
  • addUser.js
javascript
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const { WorkloadModuleBase } = require('@hyperledger/caliper-core');

let accountList = [];
const initMoney = 100000000;

/**
 * Workload module for the benchmark round.
 */
class AddUserWorkload extends WorkloadModuleBase {
    /**
     * Initializes the workload module instance.
     */
    constructor() {
        super();
        this.prefix = '';
    }

    /**
     * Generate unique account key for the transaction
     * @param {Number} index account index
     * @returns {String} account key
     */
    _generateAccount(index) {
        return this.prefix + index.toString();
    }

    /**
     * Generates simple workload
     * @returns {Object} array of json objects
     */
    _generateWorkload() {
        let workload = [];
        let index = accountList.length;
        let accountID = this._generateAccount(index);
        accountList.push({
            'accountID': accountID,
            'balance': initMoney
        });

        workload.push({
            contractId: 'parallelok',
            args: {
                transaction_type: 'set(string,uint256)',
                name: accountID,
                num: initMoney
            }
        });
        return workload;
    }

    /**
     * Initialize the workload module with the given parameters.
     * @param {number} workerIndex The 0-based index of the worker instantiating the workload module.
     * @param {number} totalWorkers The total number of workers participating in the round.
     * @param {number} roundIndex The 0-based index of the currently executing round.
     * @param {Object} roundArguments The user-provided arguments for the round from the benchmark configuration file.
     * @param {ConnectorBase} sutAdapter The adapter of the underlying SUT.
     * @param {Object} sutContext The custom context object provided by the SUT adapter.
     * @async
     */
    async initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext) {
        await super.initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext);

        this.prefix = this.workerIndex.toString();

        const args = {
            contractId: 'parallelok',
            args: {
                transaction_type: 'enableParallel()'
            }
        };

        // Enable parallel transaction executor first, this transaction should *NOT* be recorded by context
        await this.sutAdapter.sendRequests(args);
    }

    /**
     * Assemble TXs for the round.
     * @return {Promise<TxStatus[]>}
     */
    async submitTransaction() {
        const args = this._generateWorkload();
        await this.sutAdapter.sendRequests(args);
    }
}

/**
 * Create a new instance of the workload module.
 * @return {WorkloadModuleInterface}
 */
function createWorkloadModule() {
    return new AddUserWorkload();
}

module.exports.createWorkloadModule = createWorkloadModule;
module.exports.accountList = accountList;
  • transfer.js
javascript
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const { WorkloadModuleBase } = require('@hyperledger/caliper-core');

/**
 * Workload module for the benchmark round.
 */
class TransferWorkload extends WorkloadModuleBase {
    /**
     * Initializes the workload module instance.
     */
    constructor() {
        super();
        this.index = 0;
        this.accountList = [];
        this.txnPerBatch = 1;
    }

    /**
     * Generates simple workload
     * @return {Object} array of json objects
     */
    _generateWorkload() {
        let workload = [];
        for (let i = 0; i < this.txnPerBatch; i++) {
            let fromIndex = this.index % this.accountList.length;
            let toIndex = (this.index + Math.floor(this.accountList.length / 2)) % this.accountList.length;
            let value = Math.floor(Math.random() * 100);
            let args = {
                contractId: 'parallelok',
                args: {
                    transaction_type: 'transfer(string,string,uint256)',
                    from: this.accountList[fromIndex].accountID,
                    to: this.accountList[toIndex].accountID,
                    num: value
                }
            };
            workload.push(args);

            this.index++;
            this.accountList[fromIndex].balance -= value;
            this.accountList[toIndex].balance += value;
        }
        return workload;
    }

    /**
     * Initialize the workload module with the given parameters.
     * @param {number} workerIndex The 0-based index of the worker instantiating the workload module.
     * @param {number} totalWorkers The total number of workers participating in the round.
     * @param {number} roundIndex The 0-based index of the currently executing round.
     * @param {Object} roundArguments The user-provided arguments for the round from the benchmark configuration file.
     * @param {ConnectorBase} sutAdapter The adapter of the underlying SUT.
     * @param {Object} sutContext The custom context object provided by the SUT adapter.
     * @async
     */
    async initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext) {
        await super.initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext);

        this.txnPerBatch = this.roundArguments.txnPerBatch || 1;

        const addUser = require('./addUser');
        this.accountList = addUser.accountList;
    }

    /**
     * Assemble TXs for the round.
     * @return {Promise<TxStatus[]>}
     */
    async submitTransaction() {
        const workload = this._generateWorkload();
        await this.sutAdapter.sendRequests(workload);
    }

    async cleanupWorkloadModule() {
        console.info('Start balance validation ...');
        let correctAcccountNum = this.accountList.length;
        for (let i = 0; i < this.accountList.length; ++i) {
            let account = this.accountList[i];
            let accountID = account.accountID;
            let balance = account.balance;
            const queryArgs = {
                contractId: 'parallelok',
                args: {
                    transaction_type: 'balanceOf(string)',
                    name: accountID
                },
                readOnly: true
            };
            let state = await this.sutAdapter.sendRequests(queryArgs);
            let remoteBalance = state.status.result.result.output;
            remoteBalance = parseInt(remoteBalance, 16);
            if (remoteBalance !== balance) {
                console.error(`Abnormal account state: AccountID=${accountID}, LocalBalance=${balance}, RemoteBalance=${remoteBalance}`);
                correctAcccountNum--;
            }
        }

        if (correctAcccountNum === this.accountList.length) {
            console.info('Balance validation succeeded');
        }
        else {
            throw new Error(`Balance validation failed: success=${correctAcccountNum}, fail=${this.accountList.length - correctAcccountNum}`);
        }
    }
}

/**
 * Create a new instance of the workload module.
 * @return {WorkloadModuleInterface}
 */
function createWorkloadModule() {
    return new TransferWorkload();
}

module.exports.createWorkloadModule = createWorkloadModule;

启动命令

sh
# bash
npx caliper launch manager --caliper-workspace ./ --caliper-benchconfig benchmarks/config.yaml --caliper-networkconfig benchmarks/networkConfig.json

智能合约

1. HelloWorld

  • HelloWorld.sol
solidity
pragma solidity ^0.4.2;

contract HelloWorld {
    string name;

    constructor() public {
       name = "Hello, World!";
    }

    function get() public view returns(string) {
        return name;
    }

    function  set(string n) public {
    	name = n;
    }
}

2. transfer

  • ParallelContract.sol
solidity
pragma solidity ^0.4.25;

contract ParallelConfigPrecompiled
{
    function registerParallelFunctionInternal(address, string, uint256) public returns (int);
    function unregisterParallelFunctionInternal(address, string) public returns (int);
}

contract ParallelContract
{
    ParallelConfigPrecompiled precompiled = ParallelConfigPrecompiled(0x1006);

    function registerParallelFunction(string functionName, uint256 criticalSize) public
    {
        precompiled.registerParallelFunctionInternal(address(this), functionName, criticalSize);
    }

    function unregisterParallelFunction(string functionName) public
    {
        precompiled.unregisterParallelFunctionInternal(address(this), functionName);
    }

    function enableParallel() public;
    function disableParallel() public;
}
  • ParallelOk.sol
solidity
pragma solidity ^0.4.25;

import "./ParallelContract.sol";

// A parallel contract example
contract ParallelOk is ParallelContract
{
    mapping (string => uint256) _balance;
    
     // Just an example, overflow is ok, use 'SafeMath' if needed
    function transfer(string from, string to, uint256 num) public
    {
        _balance[from] -= num;
        _balance[to] += num;
    }

    // Just for testing whether the parallel revert function is working well, no practical use
    function transferWithRevert(string from, string to, uint256 num) public
    {
        _balance[from] -= num;
        _balance[to] += num;
        require(num <= 100);
    }

    function set(string name, uint256 num) public
    {
        _balance[name] = num;
    }

    function balanceOf(string name) public view returns (uint256)
    {
        return _balance[name];
    }
    
    // Register parallel function
    function enableParallel() public
    {
        // critical number is to define how many critical params from start
        registerParallelFunction("transfer(string,string,uint256)", 2); // critical: string string
        registerParallelFunction("set(string,uint256)", 1); // critical: string
    } 

    // Disable register parallel function
    function disableParallel() public
    {
        unregisterParallelFunction("transfer(string,string,uint256)"); 
        unregisterParallelFunction("set(string,uint256)");
    } 
}

报错合集

1.Deploying helloworld 卡死

sh
# 报错代码
[root@localhost caliper-bcos]# npx caliper launch manager --caliper-workspace ./ --caliper-benchconfig benchmarks/config.yaml --caliper-networkconfig benchmarks/networkConfig.json 
2022.11.16-02:21:37.984 info  [caliper] [cli-launch-manager]    Set workspace path: /root/caliper-bcos
2022.11.16-02:21:37.986 info  [caliper] [cli-launch-manager]    Set benchmark configuration path: /root/caliper-bcos/benchmarks/config.yaml
2022.11.16-02:21:37.986 info  [caliper] [cli-launch-manager]    Set network configuration path: /root/caliper-bcos/benchmarks/networkConfig.json
2022.11.16-02:21:37.986 info  [caliper] [cli-launch-manager]    Set SUT type: fisco-bcos
2022.11.16-02:21:38.906 info  [caliper] [benchmark-validator]   No observer specified, will default to `none`
2022.11.16-02:21:38.906 info  [caliper] [caliper-engine]        Starting benchmark flow
2022.11.16-02:21:38.907 info  [caliper] [caliper-engine]        Network configuration attribute "caliper.command.start" is not present, skipping start command
2022.11.16-02:21:38.931 info  [caliper] [caliper-engine]        Executed "init" step in 0 seconds
2022.11.16-02:21:38.932 info  [caliper] [installSmartContract.js]       Deploying smart contracts ...
2022.11.16-02:21:38.933 info  [caliper] [installSmartContract.js]       Deploying helloworld ...

# 原因
2022.11.16-02:12:28.282 info  [caliper] [fiscoBcosApi.js]       RequestError: Error: connect ECONNREFUSED 192.168.31.223:8545
2022.11.16-02:12:30.287 info  [caliper] [fiscoBcosApi.js]       RequestError: Error: connect ECONNREFUSED 192.168.31.224:8545
2022.11.16-02:12:32.292 info  [caliper] [fiscoBcosApi.js]       RequestError: Error: connect ECONNREFUSED 192.168.31.223:8545
2022.11.16-02:12:34.296 info  [caliper] [fiscoBcosApi.js]       RequestError: Error: connect ECONNREFUSED 192.168.31.223:8545
2022.11.16-02:12:36.301 info  [caliper] [fiscoBcosApi.js]       RequestError: Error: connect ECONNREFUSED 192.168.31.222:8545
2022.11.16-02:12:38.305 info  [caliper] [fiscoBcosApi.js]       RequestError: Error: connect ECONNREFUSED 192.168.31.221:8545
2022.11.16-02:12:40.311 info  [caliper] [fiscoBcosApi.js]       RequestError: Error: connect ECONNREFUSED 192.168.31.221:8545

# 因为nodejs只支持使用jsonrpc连接BCOS,端口为8545。
# 和java不一样,java使用的是rpc,端口20200。

# 解决方案
查看配置文件config.ini 8545端口是否对外开放。

[rpc]
    channel_listen_ip=0.0.0.0
    channel_listen_port=20200
    jsonrpc_listen_ip=0.0.0.0 #修改此处,默认为127.0.0.1
    jsonrpc_listen_port=8545
[p2p]
    listen_ip=0.0.0.0
    listen_port=30300
    ; nodes to connect
    node.0=192.168.31.221:30300
    node.1=192.168.31.222:30300
    node.2=192.168.31.223:30300
    node.3=192.168.31.224:30300