2. SDK

2.1. 概述

长安链SDK是业务模块与长安链交互的桥梁,支持双向TLS认证,提供安全可靠的加密通信信道。

长安链提供了多种语言的SDK,包括:Go SDKJava SDKPython SDK(实现中)、Nodejs SDK(实现中)方便开发者根据需要进行选用。

提供的SDK接口,覆盖合约管理、链配置管理、证书管理、多签收集、各类查询操作、事件订阅等场景,满足了不同的业务场景需要。

2.2. 约定概念

  • Node(节点):代表一个链节点的基本信息,包括:节点地址、连接数、是否启用TLS认证等信息

  • ChainClient(链客户端):所有客户端对链节点的操作接口都来自ChainClient

  • 压缩证书:可以为ChainClient开启证书压缩功能,开启后可以减小交易包大小,提升处理性能

2.3. 开发指南

2.3.1. Go SDK

2.3.1.1. 环境依赖

golang

版本为1.16或以上

下载地址:https://golang.org/dl/

若已安装,请通过命令查看版本:

$ go version
go version go1.16 linux/amd64

2.3.1.2. 下载安装

$ git clone -b v2.2.1 https://git.chainmaker.org.cn/chainmaker/sdk-go.git

2.3.1.3. 示例代码

2.3.1.3.1. 创建节点
// 创建节点
func createNode(nodeAddr string, connCnt int) *NodeConfig {
	node := NewNodeConfig(
		// 节点地址,格式:127.0.0.1:12301
		WithNodeAddr(nodeAddr),
		// 节点连接数
		WithNodeConnCnt(connCnt),
		// 节点是否启用TLS认证
		WithNodeUseTLS(true),
		// 根证书路径,支持多个
		WithNodeCAPaths(caPaths),
		// TLS Hostname
		WithNodeTLSHostName(tlsHostName),
	)

	return node
}
2.3.1.3.2. 以参数形式创建ChainClient

更多内容请参看:sdk_client_test.go

注:示例中证书采用路径方式去设置,也可以使用证书内容去设置,具体请参看createClientWithCaCerts方法

// 创建ChainClient
func createClient() (*ChainClient, error) {
	if node1 == nil {
		// 创建节点1
		node1 = createNode(nodeAddr1, connCnt1)
	}

	if node2 == nil {
		// 创建节点2
		node2 = createNode(nodeAddr2, connCnt2)
	}

	chainClient, err := NewChainClient(
		// 设置归属组织
		WithChainClientOrgId(chainOrgId),
		// 设置链ID
		WithChainClientChainId(chainId),
		// 设置logger句柄,若不设置,将采用默认日志文件输出日志
		WithChainClientLogger(getDefaultLogger()),
		// 设置客户端用户私钥路径
		WithUserKeyFilePath(userKeyPath),
		// 设置客户端用户证书
		WithUserCrtFilePath(userCrtPath),
		// 添加节点1
		AddChainClientNodeConfig(node1),
		// 添加节点2
		AddChainClientNodeConfig(node2),
		)

	if err != nil {
		return nil, err
	}

	//启用证书压缩(开启证书压缩可以减小交易包大小,提升处理性能)
	err = chainClient.EnableCertHash()
	if err != nil {
		log.Fatal(err)
	}

	return chainClient, nil
}
2.3.1.3.3. 以配置文件形式创建ChainClient

注:参数形式和配置文件形式两个可以同时使用,同时配置时,以参数传入为准

func createClientWithConfig() (*ChainClient, error) {

	chainClient, err := NewChainClient(
		WithConfPath("./testdata/sdk_config.yml"),
	)

	if err != nil {
		return nil, err
	}

	//启用证书压缩(开启证书压缩可以减小交易包大小,提升处理性能)
	err = chainClient.EnableCertHash()
	if err != nil {
		return nil, err
	}

	return chainClient, nil
}
2.3.1.3.4. 创建wasm合约

sdk_user_contract_claim_test.go

func testUserContractClaimCreate(t *testing.T, client *ChainClient,
	admin1, admin2, admin3, admin4 *ChainClient, withSyncResult bool, isIgnoreSameContract bool) {

	resp, err := createUserContract(client, admin1, admin2, admin3, admin4,
		claimContractName, claimVersion, claimByteCodePath, common.RuntimeType_WASMER, []*common.KeyValuePair{}, withSyncResult)
	if !isIgnoreSameContract {
		require.Nil(t, err)
	}

	fmt.Printf("CREATE claim contract resp: %+v\n", resp)
}

func createUserContract(client *ChainClient, admin1, admin2, admin3, admin4 *ChainClient,
	contractName, version, byteCodePath string, runtime common.RuntimeType, kvs []*common.KeyValuePair, withSyncResult bool) (*common.TxResponse, error) {

	payloadBytes, err := client.CreateContractCreatePayload(contractName, version, byteCodePath, runtime, kvs)
	if err != nil {
		return nil, err
	}

	// 各组织Admin权限用户签名
	signedPayloadBytes1, err := admin1.SignContractManagePayload(payloadBytes)
	if err != nil {
		return nil, err
	}

	signedPayloadBytes2, err := admin2.SignContractManagePayload(payloadBytes)
	if err != nil {
		return nil, err
	}

	signedPayloadBytes3, err := admin3.SignContractManagePayload(payloadBytes)
	if err != nil {
		return nil, err
	}

	signedPayloadBytes4, err := admin4.SignContractManagePayload(payloadBytes)
	if err != nil {
		return nil, err
	}

	// 收集并合并签名
	mergeSignedPayloadBytes, err := client.MergeContractManageSignedPayload([][]byte{signedPayloadBytes1,
		signedPayloadBytes2, signedPayloadBytes3, signedPayloadBytes4})
	if err != nil {
		return nil, err
	}

	// 发送创建合约请求
	resp, err := client.SendContractManageRequest(mergeSignedPayloadBytes, createContractTimeout, withSyncResult)
	if err != nil {
		return nil, err
	}

	err = checkProposalRequestResp(resp, true)
	if err != nil {
		return nil, err
	}

	return resp, nil
2.3.1.3.5. 调用wasm合约

sdk_user_contract_claim_test.go

func testUserContractClaimInvoke(client *ChainClient,
	method string, withSyncResult bool) (string, error) {

	curTime := fmt.Sprintf("%d", CurrentTimeMillisSeconds())
	fileHash := uuid.GetUUID()
	params := map[string]string{
		"time":      curTime,
		"file_hash": fileHash,
		"file_name": fmt.Sprintf("file_%s", curTime),
	}

	err := invokeUserContract(client, claimContractName, method, "", params, withSyncResult)
	if err != nil {
		return "", err
	}

	return fileHash, nil
}

func invokeUserContract(client *ChainClient, contractName, method, txId string, params map[string]string, withSyncResult bool) error {

	resp, err := client.InvokeContract(contractName, method, txId, params, -1, withSyncResult)
	if err != nil {
		return err
	}

	if resp.Code != common.TxStatusCode_SUCCESS {
		return fmt.Errorf("invoke contract failed, [code:%d]/[msg:%s]\n", resp.Code, resp.Message)
	}

	if !withSyncResult {
		fmt.Printf("invoke contract success, resp: [code:%d]/[msg:%s]/[txId:%s]\n", resp.Code, resp.Message, resp.ContractResult.Result)
	} else {
		fmt.Printf("invoke contract success, resp: [code:%d]/[msg:%s]/[contractResult:%s]\n", resp.Code, resp.Message, resp.ContractResult)
	}

	return nil
}
2.3.1.3.6. 创建及调用evm合约

sdk-go/examples/user_contract_evm_balance/main.go(https://git.chainmaker.org.cn/chainmaker/sdk-go/-/blob/master/examples/user_contract_evm_balance/main.go)

2.3.1.3.7. 更多示例和用法

更多示例和用法,请参看单元测试用例

功能 单测代码
用户合约 sdk_user_contract_test.go
系统合约 sdk_system_contract_test.go
链配置 sdk_chain_config_test.go
证书管理 sdk_cert_manage_test.go
消息订阅 sdk_subscribe_test.go

2.3.1.4. 接口说明

请参看:《chainmaker-go-sdk》

2.3.2. Java SDK

2.3.2.1. 环境依赖

java

jdk 1.8.0_202或以下

下载地址:https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html

若jdk版本较高,请在路径为cryto/ChainmakerX509CryptoSuite.java的代码中修改import sun.security.ec.CurveDBimport sun.security.util.CurveDB

若已安装,请通过命令查看版本:

$ java -version
java version "1.8.0_202"

2.3.2.2. 下载安装

$ git clone -b v2.2.0 https://git.chainmaker.org.cn/chainmaker/sdk-java.git

2.3.2.3. jar包依赖

需将sdk中依赖的jar包导入本地工程中, 同时,需将sdklib目录下的netty-tcnative-openssl-static-2.0.39.Final.jar包导入工程中,以便适配国密tls通信。

2.3.2.4. 应用demo

java sdk应用示例,请参考 sdk-java-demo

2.3.2.5. 示例代码

2.3.2.5.1. 以参数形式创建ChainClient

更多内容请参看:TestBase

public void initWithNoConfig() throws SdkException {
    byte[][] tlsCaCerts = new byte[][]{FileUtils.getResourceFileBytes(ORG1_CERT_PATH)};
    
    SdkConfig sdkConfig = new SdkConfig();
    ChainClientConfig chainClientConfig = new ChainClientConfig();
    sdkConfig.setChainClient(chainClientConfig);

    RpcClientConfig rpcClientConfig = new RpcClientConfig();
    rpcClientConfig.setMaxReceiveMessageSize(MAX_MESSAGE_SIZE);

    ArchiveConfig archiveConfig = new ArchiveConfig();
    archiveConfig.setDest(DEST);
    archiveConfig.setType(TYPE);
    archiveConfig.setSecretKey(SECRET_KEY);

    NodeConfig nodeConfig = new NodeConfig();
    nodeConfig.setTrustRootBytes(tlsCaCerts);
    nodeConfig.setTlsHostName(TLS_HOST_NAME1);
    nodeConfig.setEnableTls(true);
    nodeConfig.setNodeAddr(NODE_GRPC_URL1);
    nodeConfig.setConnCnt(CONNECT_COUNT);

    NodeConfig[] nodeConfigs = new NodeConfig[]{nodeConfig};

    chainManager = ChainManager.getInstance();
    chainClient = chainManager.getChainClient(CHAIN_ID);

    chainClientConfig.setOrgId(ORG_ID1);
    chainClientConfig.setChainId(CHAIN_ID);
    chainClientConfig.setUserKeyBytes(FileUtils.getResourceFileBytes(CLIENT1_TLS_KEY_PATH));
    chainClientConfig.setUserCrtBytes(FileUtils.getResourceFileBytes(CLIENT1_TLS_CERT_PATH));
    chainClientConfig.setUserSignKeyBytes(FileUtils.getResourceFileBytes(CLIENT1_KEY_PATH));
    chainClientConfig.setUserSignCrtBytes(FileUtils.getResourceFileBytes(CLIENT1_CERT_PATH));
    chainClientConfig.setRpcClient(rpcClientConfig);
    chainClientConfig.setArchive(archiveConfig);
    chainClientConfig.setNodes(nodeConfigs);

    if (chainClient == null) {
        chainClient = chainManager.createChainClient(sdkConfig);
    }
}
2.3.2.5.2. 以配置文件形式创建ChainClient

更多内容请参看:TestBase

public void init() throws IOException, SdkException {
    Yaml yaml = new Yaml();
    InputStream in = TestBase.class.getClassLoader().getResourceAsStream(SDK_CONFIG);

    SdkConfig sdkConfig;
    sdkConfig = yaml.loadAs(in, SdkConfig.class);
    assert in != null;
    in.close();

    for (NodeConfig nodeConfig : sdkConfig.getChain_client().getNodes()) {
        List<byte[]> tlsCaCertList = new ArrayList<>();
        for (String rootPath : nodeConfig.getTrustRootPaths()){
            List<String> filePathList = FileUtils.getFilesByPath(rootPath);
            for (String filePath : filePathList) {
                tlsCaCertList.add(FileUtils.getFileBytes(filePath));
            }
        }
        byte[][] tlsCaCerts = new byte[tlsCaCertList.size()][];
        tlsCaCertList.toArray(tlsCaCerts);
        nodeConfig.setTrustRootBytes(tlsCaCerts);
    }

    chainManager = ChainManager.getInstance();
    chainClient = chainManager.getChainClient(sdkConfig.getChain_client().getChainId());

    if (chainClient == null) {
        chainClient = chainManager.createChainClient(sdkConfig);
    }
}
2.3.2.5.3. 创建合约

更多内容请参看:TestUserContract

public void testCreateContract() throws IOException, InterruptedException, ExecutionException, TimeoutException {
    ResultOuterClass.TxResponse responseInfo = null;
    try {
        byte[] byteCode = FileUtils.getResourceFileBytes(CONTRACT_FILE_PATH);
 
        // 1. create payload
        Request.Payload payload = chainClient.createContractCreatePayload(CONTRACT_NAME, "1", byteCode,
                ContractOuterClass.RuntimeType.WASMER, null);
 
        //2. create payloads with endorsement
        Request.EndorsementEntry[] endorsementEntries = SdkUtils.getEndorsers(payload, new User[]{adminUser1, adminUser2, adminUser3});
 
        // 3. send request
        responseInfo = chainClient.sendContractManageRequest(payload, endorsementEntries, rpcCallTimeout, syncResultTimeout);
    } catch (SdkException e) {
        e.printStackTrace();
        Assert.fail(e.getMessage());
    }
    Assert.assertNotNull(responseInfo);
}
2.3.2.5.4. 调用合约

更多内容请参看:TestUserContract

public void testInvokeContract() throws Exception {
    ResultOuterClass.TxResponse responseInfo = null;
    try {
        responseInfo = chainClient.invokeContract(CONTRACT_NAME, INVOKE_CONTRACT_METHOD,
                null, null, rpcCallTimeout, syncResultTimeout);
    } catch (Exception e) {
        e.printStackTrace();
        Assert.fail(e.getMessage());
    }
    Assert.assertNotNull(responseInfo);
}
2.3.2.5.5. 更多示例和用法

更多示例和用法,请参看单元测试用例

功能 单测代码
基础配置 TestBase
用户合约 TestUserContract
系统合约 TestSystemContract
链配置 TestChainConfig
证书管理 TestBaseCertManage
消息订阅 TestSubscribe
线上多签 TestContractMultisign
公钥身份 TestPubkeyManage

2.3.2.6. 接口说明

请参看:《chainmaker-java-sdk》

2.3.3. Nodejs SDK

2.3.3.1. 环境依赖

nodejs

nodejs 14.0.0+

下载地址:https://nodejs.org/dist/

若已安装,请通过命令查看版本:

$ node --version
v14.0.0

2.3.3.2. 下载安装

$ git clone -b v2.0.0 https://git.chainmaker.org.cn/chainmaker/sdk-nodejs.git

2.3.3.3. 示例代码

2.3.3.3.1. 创建节点

更多内容请参看:sdkInit.js

// 创建节点
this.node = new Node(nodeConfigArray, timeout);
2.3.3.3.2. 参数形式创建ChainClient

更多内容请参看:sdkInit.js

// 创建ChainClient
const ChainClient = new Sdk(chainID, orgID, userKeyPathFile, userCertPathFile, nodeConfigArray, 30000, archiveConfig);
2.3.3.3.3. 以配置文件形式创建ChainClient

更多内容请参看:sdkinit.js

const ChainClient = new LoadFromYaml(path.join(__dirname, './sdk_config.yaml'));
2.3.3.3.4. 创建合约

更多内容请参看:testUserContractMgr.js

  const testCreateUserContract = async (sdk, contractName, contractVersion, contractFilePath) => {
		const response = await sdk.userContractMgr.createUserContract({
			contractName,
			contractVersion,
			contractFilePath,
			runtimeType: utils.common.RuntimeType.GASM,
			params: {
				key1: 'value1',
				key2: 'value2',
			},
		});
		return response;
	};
2.3.3.3.5. 调用合约

更多内容请参看:testUserContractMgr.js

  const testInvokeUserContract = async (sdk, contractName) => {
		const response = await sdk.callUserContract.invokeUserContract({
			contractName, method: 'save', params: {
				file_hash: '1234567890',
				file_name: 'test.txt',
			},
		});
		return response;
	};
2.3.3.3.6. 更多示例和用法

更多示例和用法,请参看单元测试用例

安装mocha:

$ npm install -g mocha

使用脚本搭建chainmaker运行环境(4组织4节点),将build文件中的cryptogen复制到当前项目的test/testFile文件中

运行测试命令:

$ npm test
功能 单测代码
基础配置 sdkInit.js
用户合约 userContract.js
系统合约 systemContract.js
链配置 chainConfig.js
证书管理 cert.js
消息订阅 subscribe.js

2.3.3.4. 接口说明

请参看:《chainmaker-nodejs-sdk》

2.3.4. Python SDK

2.3.4.1. 环境依赖

Python

版本为Python3.8.0以上

下载地址:https://www.python.org/downloads/

若已安装,请通过命令查看版本:

$ python3 --version
Python 3.8.2

2.3.4.2. 安装方式

  1. 使用pip命令,在线安装(需要安装Git)

$ pip3 install git+https://git.chainmaker.org.cn/chainmaker/sdk-python.git
  1. 克隆或下载sdk-python源码,在项目根目录下运行

$ python3 setup.py install

2.3.4.3. 示例代码

2.3.4.3.1. 创建节点

注️:需要拷贝目标链chainmaker-go/build/crypto-config到脚本所在目录

from chainmaker.node import Node

# 创建节点
node = Node(
    node_addr='127.0.0.1:12301',
    conn_cnt=10,
    enable_tls=True,
    cas=['./testdata/crypto-config/wx-org1.chainmaker.org/ca', './testdata/crypto-config/wx-org2.chainmaker.org/ca'],
    tls_host_name='chainmaker.org'
)
2.3.4.3.2. 以参数形式创建ChainClient

更多内容请参考:tests/test_chain_client.py

注:示例中证书采用路径方式去设置,也可以使用证书内容去设置,具体请参考createClientWithCaCerts方法

from chainmaker.chain_client import ChainClient
from chainmaker.node import Node
from chainmaker.user import User
from chainmaker.utils import file_utils
    
user = User('wx-org1.chainmaker.org',
            sign_key_bytes=file_utils.read_file_bytes('./testdata/crypto-config/wx-org1.chainmaker.org/user/client1/client1.tls.key'),
            sign_cert_bytes=file_utils.read_file_bytes('./testdata/crypto-config/wx-org1.chainmaker.org/user/client1/client1.tls.crt'),
            tls_key_bytes=file_utils.read_file_bytes('./testdata/crypto-config/wx-org1.chainmaker.org/user/client1/client1.sign.key'),
            tls_cert_bytes=file_utils.read_file_bytes('./testdata/crypto-config/wx-org1.chainmaker.org/user/client1/client1.sign.crt')
            )

node = Node(
    node_addr='127.0.0.1:12301',
    conn_cnt=1,
    enable_tls=True,
    trust_cas=[
        file_utils.read_file_bytes('./testdata/crypto-config/wx-org1.chainmaker.org/ca/ca.crt'),
        file_utils.read_file_bytes('./testdata/crypto-config/wx-org2.chainmaker.org/ca/ca.crt')
    ],
    tls_host_name='chainmaker.org'
)

cc = ChainClient(chain_id='chain1', user=user, nodes=[node])
print(cc.get_chainmaker_server_version())
2.3.4.3.3. 以配置文件形式创建ChainClient

注:参数形式和配置文件形式两个可以同时使用,同时配置时,以参数传入为准

from chainmaker.chain_client import ChainClient

# ./testdata/sdk_config.yml 中私钥/证书等如果使用相对路径应相对于当前运行起始目录
cc = ChainClient.from_conf('./testdata/sdk_config.yml')
2.3.4.3.4. 创建合约
from google.protobuf import json_format
from chainmaker.chain_client import ChainClient
from chainmaker.utils.evm_utils import calc_evm_contract_name
from chainmaker.keys import RuntimeType

endorsers_config = [{'org_id': 'wx-org1.chainmaker.org',
                  'user_sign_crt_file_path': './testdata/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.sign.crt',
                  'user_sign_key_file_path': './testdata/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.sign.key'},
                  {'org_id': 'wx-org2.chainmaker.org',
                  'user_sign_crt_file_path': './testdata/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.sign.crt',
                  'user_sign_key_file_path': './testdata/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.sign.key'},
                  {'org_id': 'wx-org3.chainmaker.org',
                  'user_sign_crt_file_path': './testdata/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.sign.crt',
                  'user_sign_key_file_path': './testdata/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.sign.key'},
                  ]

cc = ChainClient.from_conf('./testdata/sdk_config.yml')

def create_contract(contract_name: str, version: str, byte_code_path: str, runtime_type: RuntimeType, params: dict = None, 
                    with_sync_result=True) -> dict:
    """创建合约"""
    # 创建请求payload
    payload = cc.create_contract_create_payload(contract_name, version, byte_code_path, runtime_type, params)
    # 创建背书
    endorsers = cc.create_endorsers(payload, endorsers_config)
    # 携带背书发送请求
    res = cc.send_request_with_sync_result(payload, with_sync_result=with_sync_result, endorsers=endorsers)
    # 交易响应结构体转为字典格式
    return json_format.MessageToDict(res)

# 创建WASM合约,本地合约文件./testdata/claim-wasm-demo/rust-fact-2.0.0.wasm应存在
result1 = create_contract('fact', '1.0', './testdata/claim-wasm-demo/rust-fact-2.0.0.wasm', RuntimeType.WASMER, {})
print(result1)

# 创建EVM合约,本地合约文件./testdata/balance-evm-demo/ledger_balance.bin应存在

contract_name = calc_evm_contract_name('balance001')
result2 = create_contract(contract_name, '1.0', './testdata/balance-evm-demo/ledger_balance.bin', RuntimeType.EVM)
print(result2)
2.3.4.3.5. 调用合约
from google.protobuf import json_format
from chainmaker.chain_client import ChainClient
from chainmaker.utils.evm_utils import calc_evm_contract_name, calc_evm_method_params

# 创建客户端
cc = ChainClient.from_conf('./testdata/sdk_config.yml')

# 调用WASM合约
res1 = cc.invoke_contract('fact', 'save', {"file_name":"name007","file_hash":"ab3456df5799b87c77e7f88","time":"6543234"},
                          with_sync_result=True)
# 交易响应结构体转为字典格式
print(json_format.MessageToDict(res1))

# 调用EVM合约
evm_contract_name = calc_evm_contract_name('balance001')
evm_method, evm_params = calc_evm_method_params('updateBalance', [{"uint256": "10000"}, {"address": "0xa166c92f4c8118905ad984919dc683a7bdb295c1"}])
res2 = cc.invoke_contract(evm_contract_name, evm_method, evm_params, with_sync_result=True)
# 交易响应结构体转为字典格式
print(json_format.MessageToDict(res2))
2.3.4.3.6. 更多示例和用法

更多示例和用法,请参考单元测试用例

功能 单测代码
用户合约 tests/test_user_contract.py
系统合约 tests/test_system_contract.py
链配置 tests/test_chain_config.py
证书管理 tests/test_cert_manage.py
消息订阅 tests/test_user_contract.py

2.3.4.4. 接口说明

请参看:《chainmaker-python-sdk》