| @@ -1,128 +1,67 @@ | |||
| [TOC] | |||
| #JD区块链 | |||
|  | |||
| <div align="center"> | |||
| [](https://www.apache.org/licenses/LICENSE-2.0.html) | |||
| [](https://maven-badges.herokuapp.com/maven-central/com.jd.blockchain/sdk-pack/) | |||
| [](https://travis-ci.org/blockchain-jd-com/jdchain) | |||
| </div> | |||
| 一个面向企业应用场景的通用区块链框架系统,能够作为企业级基础设施,为业务创新提供高效、灵活和安全的解决方案。 | |||
| ## 源码构建 | |||
| ------------------------------------------------------------------------ | |||
| `JD Chain`源码通过`git`及`git submodule`进行管理,如下操作可快速构建: | |||
| ## 一、项目介绍 | |||
| JD Chain 的目标是实现一个面向企业应用场景的通用区块链框架系统,能够作为企业级基础设施,为业务创新提供高效、灵活和安全的解决方案。 | |||
| ## 二、部署模型 | |||
| JD Chain 主要部署组件包括以下几种: | |||
| - 共识节点 | |||
| 共识节点即参与共识的节点,这是系统的核心组件,承担了运行共识协议、管理账本数据、运行智能合约的职责。 | |||
| 一个区块链网络由多个共识节点组成,共识节点的数量范围由选择的共识协议决定。 | |||
| 共识节点和账本是两个不同的概念,共识节点是个物理上的概念,账本是个逻辑上的概念。JD Chain 是一个多账本区块链系统,一个共识节点上可以装载运行多个账本。账本是数据维度的独立管理单元。共识节点和账本的关系,就像关系数据库系统中,数据库服务器和数据库实例的关系。 | |||
| 共识节点通常都部署在参与方的内部网络中,通过由网络管理员指定的安全的网络出口与其它的共识节点建立通讯连接。 | |||
| 共识节点在形态上是服务器中的一个处理进程,背后需要连接一个本地或者内网的NoSQL数据库系统作为账本的存储。当前版本,共识节点目前是单进程的,未来版本将实现多进程以及多服务器集群模式。 | |||
| - 网关节点 | |||
| 网关节点是负责终端接入的节点,负责终端连接、协议转换、交易准入、本地密码运算、密钥管理等职责。 | |||
| 网关节点是一种轻量节点,需要绑定一个特定参与方的密钥对,连接到一个或多个共识节点。 | |||
| 网关节点向共识节点的连接是需要通过认证的,绑定的参与方的密钥对必须事先已经注册到区块链账本中,且得到接入授权。 | |||
| - 终端 | |||
| 终端泛指可以提交交易的客户端,典型来说,包括人、自动化设备、链外的信息系统等。 | |||
| 终端只能通过网关节点来提交交易。终端提交的交易需要用体现该终端身份的私钥来签署,产生一份电子签名。随后当交易提交给网关节点时,网关节点需要在把交易提交到共识节点之前,对交易请求以网关节点绑定的私钥追加一项“节点签名”。 | |||
| - 备份节点 | |||
| 仅对账本数据提供备份,但不参与交易共识的节点。(注:目前版本中尚未实现,将在后续版本中提供) | |||
|  | |||
| ```bash | |||
| $ git clone https://github.com/blockchain-jd-com/jdchain.git jdchain | |||
| $ cd jdchain | |||
| ## 三、构建源代码 | |||
| # 此处仅以 master 分支为例,正常情况下 master 分支可无障碍构建成功 | |||
| # 不推荐使用 develop 分支,submodule 代码可能未对齐 | |||
| # 推荐切换到具体已发布的版本分支 | |||
| $ git checkout master | |||
| 1. 安装 Maven 环境 | |||
| $ chmod +x build/*.sh | |||
| JD Chain 当前版本以 Java 语言开发,需要安装配置 JVM 和 Maven,JDK 版本不低于1.8 。(没有特殊要求,请按标准方法安装,此处不赘述) | |||
| 2. 安装 Git 工具 | |||
| 为了能够执行 git clone 命令获取代码仓库。 (没有特殊要求,请按标准方法安装,此处不赘述) | |||
| 3. 项目库说明 | |||
| # 执行完整的构建,包括执行”集成测试“和”打包“两部分;提供两个参数: | |||
| # --skipTests :跳过集成测试部分; | |||
| # --update :从远程仓库更新子模块。注意,采用此参数会导致子模块本地仓库丢失尚未 commit 的代码。 | |||
| # 不附带此参数的情况下不会更新子模块仓库。 | |||
| $ build/build.sh --update | |||
| JD Chain 源代码包括 3 个代码仓库 | |||
| # 跳过子模块代码更新和集成测试,直接编译和打包; | |||
| $ build/build.sh --skipTests | |||
| - jdchain 项目库: | |||
| - URL:git@github.com:blockchain-jd-com/jdchain.git | |||
| - 说明:主项目库,包含说明文档、示例代码,用于集成构建和打包; | |||
| #### `主项目库包含以下 6 个子模块仓库,通过执行脚本 <主项目库根目录>/build/build.sh 便可以一键完成子模块的下载和整体的编译、测试和打包操作.` | |||
| # 首次代码拉取,跳过继承测试编译打包可执行: | |||
| build/build.sh --update --skipTests | |||
| ``` | |||
| - project 项目库: | |||
| - URL:git@github.com:blockchain-jd-com/jdchain-project.git | |||
| - 说明:公共的父项目,定义公共的依赖; | |||
| - framework 项目库: | |||
| - URL:git@github.com:blockchain-jd-com/jdchain-framework.git | |||
| - 说明:框架源码库,定义公共数据类型、框架、模块组件接口、SDK、SPI、工具; | |||
| - core 项目库: | |||
| - URL:git@github.com:blockchain-jd-com/jdchain-core.git | |||
| - 说明:模块组件实现的源码库; | |||
| - explorer 项目库: | |||
| - URL:git@github.com:blockchain-jd-com/explorer.git | |||
| - 说明:相关产品的前端模块的源码库; | |||
| - libs/bft-smart 项目库: | |||
| - URL:git@github.com:blockchain-jd-com/bftsmart.git | |||
| - 说明:BFT-SMaRT 共识算法的源码库; | |||
| - test 项目库: | |||
| - URL:git@github.com:blockchain-jd-com/jdchain-test.git | |||
| - 说明:集成测试用例的源码库; | |||
| 构建完成后会在`deploy`模块,`deploy-gateway`和`deploy-peer`目录`target`中生成网关安装部署包(`jdchain-gateway-*.zip`)和共识节点安装部署包(`jdchain-peer-*.zip`)。 | |||
| 4. 构建操作 | |||
| ## 部署使用 | |||
| ### 快速部署 | |||
| ```sh | |||
| $ git clone git@github.com:blockchain-jd-com/jdchain.git jdchain | |||
| 使用[源码构建](#源码构建)生成的部署安装包,或者下载[官方部署安装包](http://ledger.jd.com/downloadapps.html) 参照[快速部署文档](docs/quick_start.md)可快速部署运行`JD Chain`网络。 | |||
| $ cd jdchain | |||
| ### 数据上链 | |||
| $ git checkout develop | |||
| 1. 命令行工具 | |||
| $ chmod +x build/*.sh | |||
| `JD Chain` 命令行工具集,即[jdchain-cli](docs/jdchain_cli.md),提供密钥管理,实时交易,链上信息查询,离线交易,共识节点变更等操作。可快速执行数据上链和链上数据查询。 | |||
| # 执行完整的构建,包括执行”集成测试“和”打包“两部分;提供两个参数: | |||
| # --skipTests :跳过集成测试部分; | |||
| # --update :从远程仓库更新子模块。注意,采用此参数会导致子模块本地仓库丢失尚未 commit 的代码。 | |||
| # 不附带此参数的情况下不会更新子模块仓库。 | |||
| $ build/build.sh --update | |||
| # 跳过集成测试,直接编译和打包; | |||
| $ build/build.sh --skipTests | |||
| 2. SDK | |||
| # 只执行集成测试; | |||
| $ build/test.sh | |||
| ``` | |||
| `JD Chain`提供了`Java`和`Go`版本的`SDK`。实际项目开发中`Java`可参照[示例代码](https://github.com/blockchain-jd-com/jdchain/tree/master/samples),`Go`语言`SDK`参照[framework-go](https://github.com/blockchain-jd-com/framework-go)。 | |||
| 5. jdchain 的安装包 | |||
| ### 更多 | |||
| 当编译完成后,安装包位于主项目库的 deploy 目录中: | |||
| `JD Chain`功能开发,使用问题等欢迎`issue`中探讨,也欢迎广大开发者积极参与`JD Chain`社区活动及代码开发~ | |||
| - 共识节点的安装包: | |||
| - <主项目库根目录>/deploy/deploy-peer/target/jdchain-peer-**.zip | |||
| - 网关节点的安装包: | |||
| - <主项目库根目录>/deploy/deploy-gateway/target/jdchain-gateway-**.zip | |||
| - `JD Chain`官方网站:https://ledger.jd.com/ | |||
| - 文档:[wiki](https://github.com/blockchain-jd-com/jdchain/wiki),[docs](docs/) | |||
| - 京东智臻链官网:https://blockchain.jd.com/ | |||
| - 服务邮箱:jdchain-support@jd.com | |||
| - `FAQ`:https://github.com/blockchain-jd-com/jdchain/wiki/FAQ | |||
| @@ -1,361 +0,0 @@ | |||
| # 1. maven坐标 | |||
| ```java | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>sdk-client</artifactId> | |||
| <version>1.4.0.RELEASE</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-starter</artifactId> | |||
| <version>1.4.0.RELEASE</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>crypto-classic</artifactId> | |||
| <version>1.4.0.RELEASE</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>crypto-sm</artifactId> | |||
| <version>1.4.0.RELEASE</version> | |||
| </dependency> | |||
| ``` | |||
| # 2. 数据快速上链 | |||
| ## 2.1. 服务连接 | |||
| ```java | |||
| //使用已注册用户信息进行连接; | |||
| String GW_PUB_KEY = "3snxxx"; | |||
| String GW_PRIV_KEY = "177xxx"; | |||
| String GW_PASSWORD = "xxx"; | |||
| PrivKey gwPrivkey0 = KeyGenUtils.decodePrivKey(GW_PRIV_KEY, GW_PASSWORD); | |||
| PubKey gwPubKey0 = KeyGenUtils.decodePubKey(GW_PUB_KEY); | |||
| BlockchainKeypair adminKey = new BlockchainKeypair(gwPubKey0, gwPrivkey0); | |||
| //创建服务代理 | |||
| final String GATEWAY_IP = "127.0.0.1"; | |||
| final int GATEWAY_PORT = 80; | |||
| final boolean SECURE = false; | |||
| GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IP, GATEWAY_PORT, SECURE, | |||
| adminKey); | |||
| // 创建服务代理; | |||
| BlockchainService service = serviceFactory.getBlockchainService(); | |||
| HashDigest[] ledgerHashs = service.getLedgerHashs(); | |||
| // 获取当前账本Hash | |||
| HashDigest ledgerHash = ledgerHashs[0]; | |||
| ``` | |||
| ## 2.2. 用户注册 | |||
| ```java | |||
| // 创建服务代理; | |||
| BlockchainService service = serviceFactory.getBlockchainService(); | |||
| // 在本地定义注册账号的 TX; | |||
| TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
| BlockchainKeypair user = BlockchainKeyGenerator.getInstance().generate(); | |||
| txTemp.users().register(user.getIdentity()); | |||
| // TX 准备就绪; | |||
| PreparedTransaction prepTx = txTemp.prepare(); | |||
| // 使用私钥进行签名; | |||
| prepTx.sign(adminKey); | |||
| // 提交交易; | |||
| prepTx.commit(); | |||
| ``` | |||
| ## 2.3. 数据账户注册 | |||
| ```java | |||
| // 创建服务代理; | |||
| BlockchainService service = serviceFactory.getBlockchainService(); | |||
| // 在本地定义注册账号的 TX; | |||
| TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
| BlockchainKeypair dataAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
| txTemp.dataAccounts().register(dataAccount.getIdentity()); | |||
| // TX 准备就绪; | |||
| PreparedTransaction prepTx = txTemp.prepare(); | |||
| // 使用私钥进行签名; | |||
| prepTx.sign(adminKey); | |||
| // 提交交易; | |||
| prepTx.commit(); | |||
| ``` | |||
| ## 2.4. 写入数据 | |||
| ```java | |||
| // 创建服务代理; | |||
| BlockchainService service = serviceFactory.getBlockchainService(); | |||
| // 在本地定义注册账号的 TX; | |||
| TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
| // -------------------------------------- | |||
| // 将商品信息写入到指定的账户中; | |||
| // 对象将被序列化为 JSON 形式存储,并基于 JSON 结构建立查询索引; | |||
| String commodityDataAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; | |||
| txTemp.dataAccount(commodityDataAccount).setText("ASSET_CODE", "value1", -1); | |||
| // TX 准备就绪; | |||
| PreparedTransaction prepTx = txTemp.prepare(); | |||
| String txHash = Base64Utils.encodeToUrlSafeString(prepTx.getHash().toBytes()); | |||
| // 使用私钥进行签名; | |||
| prepTx.sign(adminKey); | |||
| // 提交交易; | |||
| prepTx.commit(); | |||
| ``` | |||
| ## 2.5. 查询数据 | |||
| > 注:详细的查询可参考模块sdk-samples中SDK_GateWay_Query_Test_相关测试用例 | |||
| ```java | |||
| // 创建服务代理; | |||
| BlockchainService service = serviceFactory.getBlockchainService(); | |||
| // 查询区块信息; | |||
| // 区块高度; | |||
| long ledgerNumber = service.getLedger(ledgerHash).getLatestBlockHeight(); | |||
| // 最新区块; | |||
| LedgerBlock latestBlock = service.getBlock(ledgerHash, ledgerNumber); | |||
| // 区块中的交易的数量; | |||
| long txCount = service.getTransactionCount(ledgerHash, latestBlock.getHash()); | |||
| // 获取交易列表; | |||
| LedgerTransaction[] txList = service.getTransactions(ledgerHash, ledgerNumber, 0, 100); | |||
| // 遍历交易列表 | |||
| for (LedgerTransaction ledgerTransaction : txList) { | |||
| TransactionContent txContent = ledgerTransaction.getTransactionContent(); | |||
| Operation[] operations = txContent.getOperations(); | |||
| if (operations != null && operations.length > 0) { | |||
| for (Operation operation : operations) { | |||
| operation = ClientResolveUtil.read(operation); | |||
| // 操作类型:数据账户注册操作 | |||
| if (operation instanceof DataAccountRegisterOperation) { | |||
| DataAccountRegisterOperation daro = (DataAccountRegisterOperation) operation; | |||
| BlockchainIdentity blockchainIdentity = daro.getAccountID(); | |||
| } | |||
| // 操作类型:用户注册操作 | |||
| else if (operation instanceof UserRegisterOperation) { | |||
| UserRegisterOperation uro = (UserRegisterOperation) operation; | |||
| BlockchainIdentity blockchainIdentity = uro.getUserID(); | |||
| } | |||
| // 操作类型:账本注册操作 | |||
| else if (operation instanceof LedgerInitOperation) { | |||
| LedgerInitOperation ledgerInitOperation = (LedgerInitOperation)operation; | |||
| LedgerInitSetting ledgerInitSetting = ledgerInitOperation.getInitSetting(); | |||
| ParticipantNode[] participantNodes = ledgerInitSetting.getConsensusParticipants(); | |||
| } | |||
| // 操作类型:合约发布操作 | |||
| else if (operation instanceof ContractCodeDeployOperation) { | |||
| ContractCodeDeployOperation ccdo = (ContractCodeDeployOperation) operation; | |||
| BlockchainIdentity blockchainIdentity = ccdo.getContractID(); | |||
| } | |||
| // 操作类型:合约执行操作 | |||
| else if (operation instanceof ContractEventSendOperation) { | |||
| ContractEventSendOperation ceso = (ContractEventSendOperation) operation; | |||
| } | |||
| // 操作类型:KV存储操作 | |||
| else if (operation instanceof DataAccountKVSetOperation) { | |||
| DataAccountKVSetOperation.KVWriteEntry[] kvWriteEntries = | |||
| ((DataAccountKVSetOperation) operation).getWriteSet(); | |||
| if (kvWriteEntries != null && kvWriteEntries.length > 0) { | |||
| for (DataAccountKVSetOperation.KVWriteEntry kvWriteEntry : kvWriteEntries) { | |||
| BytesValue bytesValue = kvWriteEntry.getValue(); | |||
| DataType dataType = bytesValue.getType(); | |||
| Object showVal = ClientResolveUtil.readValueByBytesValue(bytesValue); | |||
| System.out.println("writeSet.key=" + kvWriteEntry.getKey()); | |||
| System.out.println("writeSet.value=" + showVal); | |||
| System.out.println("writeSet.type=" + dataType); | |||
| System.out.println("writeSet.version=" + kvWriteEntry.getExpectedVersion()); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // 根据交易的 hash 获得交易;注:客户端生成 PrepareTransaction 时得到交易hash; | |||
| HashDigest txHash = txList[0].getTransactionContent().getHash(); | |||
| Transaction tx = service.getTransactionByContentHash(ledgerHash, txHash); | |||
| // 获取数据; | |||
| String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; | |||
| String[] objKeys = new String[] { "x001", "x002" }; | |||
| TypedKVEntry[] kvData = service.getDataEntries(ledgerHash, commerceAccount, objKeys); | |||
| long payloadVersion = kvData[0].getVersion(); | |||
| // 获取数据账户下所有的KV列表 | |||
| TypedKVEntry[] kvDatas = service.getDataEntries(ledgerHash, commerceAccount, 0, 100); | |||
| if (kvData != null && kvData.length > 0) { | |||
| for (TypedKVEntry kvDatum : kvDatas) { | |||
| System.out.println("kvData.key=" + kvDatum.getKey()); | |||
| System.out.println("kvData.version=" + kvDatum.getVersion()); | |||
| System.out.println("kvData.type=" + kvDatum.getType()); | |||
| System.out.println("kvData.value=" + kvDatum.getValue()); | |||
| } | |||
| } | |||
| ``` | |||
| ## 2.6. 合约发布 | |||
| ```java | |||
| // 创建服务代理; | |||
| BlockchainService service = serviceFactory.getBlockchainService(); | |||
| // 在本地定义TX模板 | |||
| TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
| // 合约内容读取 | |||
| byte[] contractBytes = FileUtils.readBytes(new File("CONTRACT_FILE")); | |||
| // 生成用户 | |||
| BlockchainKeypair contractKeyPair = BlockchainKeyGenerator.getInstance().generate(); | |||
| // 发布合约 | |||
| txTemp.contracts().deploy(contractKeyPair.getIdentity(), contractBytes); | |||
| // TX 准备就绪; | |||
| PreparedTransaction prepTx = txTemp.prepare(); | |||
| // 使用私钥进行签名; | |||
| prepTx.sign(adminKey); | |||
| // 提交交易; | |||
| TransactionResponse transactionResponse = prepTx.commit(); | |||
| assertTrue(transactionResponse.isSuccess()); | |||
| // 打印合约地址 | |||
| System.out.println(contractKeyPair.getIdentity().getAddress().toBase58()); | |||
| ``` | |||
| ## 2.7. 合约执行 | |||
| ```java | |||
| // 创建服务代理; | |||
| BlockchainService service = serviceFactory.getBlockchainService(); | |||
| // 在本地定义TX模板 | |||
| TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
| // 合约地址 | |||
| String contractAddress = ""; | |||
| // 使用接口方式调用合约 | |||
| TransferContract transferContract = txTemp.contract(contractAddress, TransferContract.class); | |||
| // 使用decode方式调用合约内部方法(create方法) | |||
| // 返回GenericValueHolder可通过get方法获取结果,但get方法需要在commit调用后执行 | |||
| String address = "address"; | |||
| String account = "fill account"; | |||
| long money = 100000000L; | |||
| GenericValueHolder<String> result = ContractReturnValue.decode(transferContract.create(address, account, money)); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse transactionResponse = ptx.commit(); | |||
| String cotractExecResult = result.get(); | |||
| // TransactionResponse也提供了可供查询结果的接口 | |||
| OperationResult[] operationResults = transactionResponse.getOperationResults(); | |||
| // 通过OperationResult获取结果 | |||
| for (int i = 0; i < operationResults.length; i++) { | |||
| OperationResult opResult = operationResults[i]; | |||
| System.out.printf("Operation[%s].result = %s \r\n", | |||
| opResult.getIndex(), BytesValueEncoding.decode(opResult.getResult())); | |||
| } | |||
| ``` | |||
| ## 2.8. 事件账户注册 | |||
| ```java | |||
| BlockchainService service = serviceFactory.getBlockchainService(); | |||
| TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
| BlockchainKeypair eventAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
| txTemp.eventAccounts().register(eventAccount.getIdentity()); | |||
| // TX 准备就绪; | |||
| PreparedTransaction prepTx = txTemp.prepare(); | |||
| // 使用私钥进行签名; | |||
| prepTx.sign(adminKey); | |||
| // 提交交易; | |||
| prepTx.commit(); | |||
| ``` | |||
| ## 2.9. 事件发布 | |||
| ```java | |||
| BlockchainService service = serviceFactory.getBlockchainService(); | |||
| TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
| // 发布事件到指定的账户中; | |||
| String eventAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; | |||
| txTemp.eventAccount(eventAccount).publish("event_name", "string", -1) | |||
| .publish("event_name", 0, 0); | |||
| // TX 准备就绪; | |||
| PreparedTransaction prepTx = txTemp.prepare(); | |||
| // 使用私钥进行签名; | |||
| prepTx.sign(adminKey); | |||
| // 提交交易; | |||
| prepTx.commit(); | |||
| ``` | |||
| ## 2.10. 事件监听 | |||
| - 系统事件 | |||
| > 目前仅支持新区块产生事件 | |||
| ```java | |||
| EventListenerHandle<SystemEventPoint> handler = blockchainService.monitorSystemEvent(ledgerHash, | |||
| SystemEvent.NEW_BLOCK_CREATED, 0, new SystemEventListener<SystemEventPoint>() { | |||
| @Override | |||
| public void onEvents(Event[] eventMessages, EventContext<SystemEventPoint> eventContext) { | |||
| for (Event eventMessage : eventMessages) { | |||
| BytesValue content = eventMessage.getContent(); | |||
| // content中存放的是当前链上最新高度 | |||
| System.out.println(BytesUtils.toLong(content.getBytes().toBytes())); | |||
| } | |||
| // 关闭监听的两种方式:1 | |||
| eventContext.getHandle().cancel(); | |||
| } | |||
| }); | |||
| // 关闭监听的两种方式:2 | |||
| handler.cancel(); | |||
| ``` | |||
| - 用户自定义事件 | |||
| ```java | |||
| EventListenerHandle<UserEventPoint> handler = blockchainService.monitorUserEvent(ledgerHash, | |||
| eventAccount.getAddress().toBase58(), eventName, 0, new UserEventListener<UserEventPoint>() { | |||
| @Override | |||
| public void onEvent(Event eventMessage, EventContext<UserEventPoint> eventContext) { | |||
| BytesValue content = eventMessage.getContent(); | |||
| switch (content.getType()) { | |||
| case TEXT: | |||
| System.out.println(content.getBytes().toUTF8String()); | |||
| break; | |||
| case INT64: | |||
| System.out.println(BytesUtils.toLong(content.getBytes().toBytes())); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| // 关闭监听的两种方式:1 | |||
| eventContext.getHandle().cancel(); | |||
| } | |||
| }); | |||
| // 关闭监听的两种方式:2 | |||
| handler.cancel(); | |||
| ``` | |||
| @@ -1,436 +0,0 @@ | |||
| # 智能合约开发手册 | |||
| 版本|修改时间|修改人 | |||
| ---|:--:|---: | |||
| V1.0|2020-04-17|huanghaiquan | |||
| --- | |||
| ### 1. 简介 | |||
| JD Chain 智能合约系统由5个部分组成:合约代码语言、合约引擎、合约账户、合约开发框架、合约开发插件。 | |||
| 合约代码语言是用来编写智能合约的编程语言,合约引擎是解释和执行合约代码的虚拟机。 | |||
| JD Chain 账本中以合约账户的方式对合约代码进行管理。一份部署上链的合约代码需要关联到一个唯一的公钥上,并生成与公钥对应的区块链账户地址,在账本中注册为一个合约账户。在执行之前,系统从账本中读出合约代码并将其加载到合约引擎,由交易执行器调用合约引擎触发合约执行。 | |||
| JD Chain 账本定义了一组标准的账本操作指令,合约代码的执行过程实质上是向账本输出一串操作指令序列,这些指令对账本中的数据产生了变更,形成合约执行的最终结果。 | |||
| 合约开发框架定义了进行合约代码开发中需要依赖的一组编程接口和类库。合约开发插件提供了更方便与IDE集成的合约编译、部署工具,可以简化操作,并与持续集成过程结合。 | |||
| JD Chain 以 Java 语言作为合约代码语言,合约引擎是基于 JVM 构建的安全沙盒。为了实现与主流的应用开发方式无缝兼容, JD Chain 支持以 Maven 来管理合约代码的工程项目,并提供相应的 maven 插件来简化合约的编译和部署。 | |||
| >智能合约是一种可以由计算机执行的合同/协议。不同于现实生活中的合同是由自然语言来编写并约定相关方的权利和义务,智能合约是用合约代码语言来编写,以合约代码的形式存在和被执行。通过账本中的数据状态来表示合同/协议相关条款信息,合约代码的运行过程体现了合同/协议条款的执行,并记录相应的结果。 | |||
| ### 2. 快速入门 | |||
| #### 2.1. 准备开发环境 | |||
| 按照正常的 Java 应用开发环境要求进行准备,以 Maven 作为代码工程的构建管理工具,无其它特殊要求。 | |||
| >检查 JDK 版本不低于 1.8 ,Maven 版本不低于 3.0。 | |||
| #### 2.2. 创建合约代码工程 | |||
| 创建一个普通的 Java Maven 工程,打开 pom.xml 把 packaging 设为 contract . | |||
| ``` xml | |||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | |||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <groupId>your.group.id</groupId> | |||
| <artifactId>your.project</artifactId> | |||
| <version>0.0.1-SNAPSHOT</version> | |||
| <!-- 声明为合约代码工程,编译输出扩展名为".car"合约代码 --> | |||
| <packaging>contract</packaging> | |||
| <dependencies> | |||
| <!-- 合约项目的依赖 --> | |||
| </dependencies> | |||
| <build> | |||
| <plugins> | |||
| <!-- 合约项目的插件 --> | |||
| </plugins> | |||
| </build> | |||
| </project> | |||
| ``` | |||
| > 注:合约代码工程也是一个普通的 Java Maven 工程,因此尽管不同 IDE 创建 Maven 工程有不同的操作方式,由于对于合约开发而言并无特殊要求,故在此不做详述。 | |||
| #### 2.3. 加入合约开发依赖 | |||
| 在合约代码工程 pom.xml 加入对合约开发 SDK 的依赖: | |||
| ``` xml | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-starter</artifactId> | |||
| <version>${jdchain.version}</version> | |||
| </dependency> | |||
| ``` | |||
| #### 2.4. 加入合约插件 | |||
| 在合约代码工程的 pom.xml 加入 contract-maven-plugin 插件: | |||
| ``` xml | |||
| <plugin> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-maven-plugin</artifactId> | |||
| <version>${jdchain.version}</version> | |||
| <extensions>true</extensions> | |||
| </plugin> | |||
| ``` | |||
| 完整的 pom.xml 如下: | |||
| ``` xml | |||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | |||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <groupId>your.group.id</groupId> | |||
| <artifactId>your.project</artifactId> | |||
| <version>0.0.1-SNAPSHOT</version> | |||
| <!-- 声明为合约代码工程,编译输出扩展名为".car"合约代码 --> | |||
| <packaging>contract</packaging> | |||
| <properties> | |||
| <jdchain.version>1.2.0.RELEASE</jdchain.version> | |||
| </properties> | |||
| <dependencies> | |||
| <!-- 合约项目的依赖 --> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-starter</artifactId> | |||
| <version>${jdchain.version}</version> | |||
| </dependency> | |||
| </dependencies> | |||
| <build> | |||
| <plugins> | |||
| <plugin> | |||
| <groupId>org.apache.maven.plugins</groupId> | |||
| <artifactId>maven-compiler-plugin</artifactId> | |||
| <version>3.8.1</version> | |||
| <configuration> | |||
| <source>1.8</source> | |||
| <target>1.8</target> | |||
| <encoding>UTF-8</encoding> | |||
| <optimize>false</optimize> | |||
| <debug>true</debug> | |||
| <showDeprecation>false</showDeprecation> | |||
| <showWarnings>false</showWarnings> | |||
| </configuration> | |||
| </plugin> | |||
| <!-- 合约项目的插件 --> | |||
| <plugin> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-maven-plugin</artifactId> | |||
| <version>${jdchain.version}</version> | |||
| <extensions>true</extensions> | |||
| </plugin> | |||
| </plugins> | |||
| </build> | |||
| </project> | |||
| ``` | |||
| #### 2.5. 编写合约代码 | |||
| 2.5.1. **注意事项** | |||
| ``` | |||
| 1、不允许合约(包括合约接口和合约实现类)使用com.jd.blockchain开头的package; | |||
| 2、必须有且只有一个接口使用@Contract注解,且其中的event必须大于等于一个; | |||
| 3、使用@Contract注解的接口有且只有一个实现类; | |||
| 4、黑名单调用限制(具体黑名单可查看配置文件),需要注意的是,黑名单分析策略会递归分析类实现的接口和父类,也就是说调用一个实现了指定黑名单接口的类也是不允许的; | |||
| 目前设置的黑名单如下: | |||
| java.io.File | |||
| java.io.InputStream | |||
| java.io.OutputStream | |||
| java.io.DataInput | |||
| java.io.DataOutput | |||
| java.io.Reader | |||
| java.io.Writer | |||
| java.io.Flushable | |||
| java.nio.channels.* | |||
| java.nio.file.* | |||
| java.net.* | |||
| java.sql.* | |||
| java.lang.reflect.* | |||
| java.lang.Class | |||
| java.lang.ClassLoader | |||
| java.util.Random | |||
| java.lang.System-currentTimeMillis | |||
| java.lang.System-nanoTime | |||
| com.jd.blockchain.ledger.BlockchainKeyGenerator | |||
| ``` | |||
| 2.5.2. **声明合约** | |||
| ``` java | |||
| /** | |||
| * 声明合约接口; | |||
| **/ | |||
| @Contract | |||
| public interface AssetContract { | |||
| @ContractEvent(name = "transfer") | |||
| String transfer(String address, String from, String to, long amount); | |||
| } | |||
| ``` | |||
| 2.5.3. **实现合约** | |||
| ``` java | |||
| /** | |||
| * 实现合约; | |||
| * | |||
| * 实现 EventProcessingAware 接口是可选的,目的获得 ContractEventContext 上下文对象, | |||
| * 通过该对象可以进行账本操作; | |||
| */ | |||
| public class AssetContractImpl implements AssetContract, EventProcessingAware { | |||
| // 合约事件上下文; | |||
| private ContractEventContext eventContext; | |||
| /** | |||
| * 执行交易请求中对 AssetContract 合约的 transfer 调用操作; | |||
| */ | |||
| public String transfer(String address, String from, String to, long amount) { | |||
| //当前账本的哈希; | |||
| HashDigest ledgerHash = eventContext.getCurrentLedgerHash(); | |||
| //当前账本上下文; | |||
| LedgerContext ledgerContext = eventContext.getLedger(); | |||
| //做操作; | |||
| // ledgerContext. | |||
| //返回合约操作的结果; | |||
| return "success"; | |||
| } | |||
| /** | |||
| * 准备执行交易中的合约调用操作; | |||
| */ | |||
| @Override | |||
| public void beforeEvent(ContractEventContext eventContext) { | |||
| this.eventContext = eventContext; | |||
| } | |||
| /** | |||
| * 完成执行交易中的合约调用操作; | |||
| */ | |||
| @Override | |||
| public void postEvent(ContractEventContext eventContext, Exception error) { | |||
| this.eventContext = null; | |||
| } | |||
| } | |||
| ``` | |||
| #### 2.6. 编译打包合约代码 | |||
| 合约代码工程的编译打包操作与普通的 maven 工程是相同的,在工程的根目录下输入以下命令: | |||
| ``` bash | |||
| mvn clean package | |||
| ``` | |||
| 执行成功之后,在 target 目录中输出合约代码文件 \<project-name>.\<version>.car 。 | |||
| 如果合约代码加入了除 com.jd.blockchain:contract-starter 之外的其它依赖,默认配置下,第三方依赖包将与 .car 文件一起打包一起部署。(也可以把第三方依赖包独立打包,具体参见以下 “3. 合约插件详细配置” | |||
| > 注意:合约代码虽然利用了 Java 语言,遵照 Java 语法进行编写,但本质上是作为一种运行于受限环境(合约虚拟机)的语言来使用,因而一些 Java 语法和 SDK 的 API 是不被允许使用的,在编译过程中将对此进行检查。 | |||
| #### 2.7. 部署合约代码 | |||
| ##### 2.7.1. 在项目中部署合约代码 | |||
| 如果希望在构建打包的同时将合约代码部署到指定的区块链网络,可以在合约代码工程 pom.xml 的 contract-maven-plugin 插件配置中加入合约部署相关的信息(具体更详细的配置可以参考“3. 合约插件详细配置”)。 | |||
| ``` xml | |||
| <plugin> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-maven-plugin</artifactId> | |||
| <version>1.2.0.RELEASE</version> | |||
| <extensions>true</extensions> | |||
| <configuration> | |||
| <!-- 合约部署配置 --> | |||
| <deployment> | |||
| <!-- 合约要部署的目标账本的哈希;Base58 格式; --> | |||
| <ledger>j5rpuGWVxSuUbU3gK7MDREfui797AjfdHzvAMiSaSzydu7</ledger> | |||
| <!-- 区块链网络的网关地址 --> | |||
| <gateway> | |||
| <host>192.168.10.10</host> | |||
| <port>8081</port> | |||
| </gateway> | |||
| <!-- 合约账户 --> | |||
| <ContractAddress> | |||
| <pubKey>3snPdw7i7Po4fYcXFxS4QztR8Dm4kLBdBpjsemuGPZRyZRBmtn5Z5u</pubKey> | |||
| </ContractAddress> | |||
| <!-- 合约部署交易的签名账户;该账户必须具备合约部署的权限; --> | |||
| <signer> | |||
| <pubKey>7VeRLdGtSz1Y91gjLTqEdnkotzUfaAqdap3xw6fQ1yKHkvVq</pubKey> | |||
| <privKey>177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x</privKey> | |||
| <privKeyPwd>DYu3G8aGTMBW1WrTw76zxQJQU4DHLw9MLyy7peG4LKkY</privKeyPwd> | |||
| </signer> | |||
| </deployment> | |||
| </configuration> | |||
| </plugin> | |||
| ``` | |||
| 加入部署配置信息之后,对工程执行编译打包操作,输出的合约代码(.car)将自动部署到指定的区块链网络。 | |||
| ``` bash | |||
| mvn clean deploy | |||
| ``` | |||
| ##### 2.7.2. 发布已编译好的car | |||
| 如果已经通过插件的打包方式,编译打包完成一个合约文件(.car),可通过命令行的方式进行发布,命令行要求与开发环境一致的Maven环境(包括环境变量及Setting都已配置完成)。 | |||
| ``` bash | |||
| mvn com.jd.blockchain:contract-maven-plugin:${version}:deploy | |||
| -DcarPath= | |||
| -Dledger= | |||
| -DgatewayHost= | |||
| -DgatewayPort= | |||
| -DcontractPubKey= | |||
| -DcontractAddress= | |||
| -DsignerPubKey= | |||
| -DsignerPrivKey= | |||
| -DsignerPrivKeyPwd= | |||
| ``` | |||
| 各参数说明如下: | |||
| | 参数名 | 含义 |是否必填| | |||
| | ---- | ---- | ---- | | |||
| | ${version} | 合约插件的版本号 | 否,系统会自动选择发布的最新的RELEASE版本,SNAPSHOT版本必须填写 | | |||
| | carPath | 合约文件所在路径 | 是 | | |||
| | ledger | 账本Hash(Base58编码) | 否,会自动选择线上第一个账本| | |||
| | gatewayHost | 可访问的网关节点地址,域名或IP地址 | 是| | |||
| | gatewayPort | 网关节点监听端口 | 是 | | |||
| | contractPubKey | 合约账户的公钥(Base58编码)| 否,会自动创建 | | |||
| | contractAddress | 合约账户的地址(Base58编码)|否,会根据contractPubKey生成| | |||
| | signerPubKey | 合约签名公钥信息(Base58编码)|是| | |||
| | signerPrivKey | 合约签名私钥信息(Base58编码)|是| | |||
| | signerPrivKeyPwd | 合约签名私钥解密密钥(Base58编码)|是| | |||
| 下面是一个示例,供参考: | |||
| ``` bash | |||
| mvn com.jd.blockchain:contract-maven-plugin:1.2.0.RELEASE:deploy \ | |||
| -DcarPath=/root/jdchain/contracts/contract-test-1.0-SNAPSHOT.car \ | |||
| -Dledger=j5tW5HUvMjEtm2yB7E6MHoSByoH1DXvMwvF2HurEgMSaLW \ | |||
| -DgatewayHost=127.0.0.1 \ | |||
| -DgatewayPort=11000 \ | |||
| -DcontractPubKey= 7VeRBsHM2nsGwP8b2ufRxz36hhNtSqjKTquzoa4WVKWty5sD \ | |||
| -DcontractAddress= LdeNt7sEmTirh9PmE7axKvA2txTrbB9kxz6KB \ | |||
| -DsignerPubKey=7VeRLdGtSz1Y91gjLTqEdnkotzUfaAqdap3xw6fQ1yKHkvVq \ | |||
| -DsignerPrivKey=177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x \ | |||
| -DsignerPrivKeyPwd=DYu3G8aGTMBW1WrTw76zxQJQU4DHLw9MLyy7peG4LKkY | |||
| ``` | |||
| > 重点说明: | |||
| 命令行中输入参数的优先级高于配置文件,就是说通过2.7.1方式发布合约时也可以采用命令行的参数(指-D相关配置),其优先级高于配置文件。 | |||
| ### 3. 合约插件详细配置 | |||
| ``` xml | |||
| <plugin> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-maven-plugin</artifactId> | |||
| <version>1.2.0.RELEASE</version> | |||
| <extensions>true</extensions> | |||
| <configuration> | |||
| <!-- 是否把所有的依赖项打包输出到一个独立的 “库文件(.lib)”,默认为 false--> | |||
| <!-- 设置为 false 时 ,合约代码和依赖项一起打包输出到 “合约代码文件(.car)” --> | |||
| <!-- 设置为 true ,合约代码和依赖项分别打包,分别输出 “合约代码文件(.car)” 和 “库文件(.lib)” --> | |||
| <!-- 注: | |||
| 1. 如果“合约代码文件(.car)”的尺寸超出最大尺寸将引发异常,可把此项配置设置为 true 以减小“合约代码文件(.car)”的尺寸。 | |||
| 2. “合约代码文件(.car)”的默认最大尺寸为 1 MB,由区块链网络的配置设定,如果不满足则需要由区块链网络的管理员进行调整。 | |||
| 3. “合约库文件(.lib)”的尺寸不受“合约代码文件(.car)”的最大尺寸限制,部署过程只有“哈希上链”,库文件通过链下的分发网络自动同步至各个共识节点。 | |||
| --> | |||
| <outputLibrary>false</outputLibrary> | |||
| <!-- 合约代码最大字节数;可选;--> | |||
| <!-- 默认为 1 (MB);如果超出该值将给予错误提示;如果值小于等于 0,则不做校验 --> | |||
| <!-- 注:此参数仅影响编译打包时的本地校验,实际部署时仍然由区块链网络上的配置决定 --> | |||
| <maxCarSize>1</maxCarSize> | |||
| <!-- 合约代码最大字节数的单位;--> | |||
| <!-- 合法值的格式为“整数值+单位”;可选单位有: Byte, KB, MB;不区分大小写;--> | |||
| <maxCarSizeUnit>MB</maxCarSizeUnit> | |||
| <!-- 合约部署配置;可选 --> | |||
| <deployment> | |||
| <!-- 账本的哈希;Base58 格式;非必填项,会自动选择线上第一个账本 --> | |||
| <ledger></ledger> | |||
| <!-- 区块链网络的网关地址 --> | |||
| <gateway> | |||
| <host></host> | |||
| <port></port> | |||
| </gateway> | |||
| <!-- 合约账户 --> | |||
| <!-- 合约账户的地址address(Base58编码),会根据pubKey生成> --> | |||
| <contractAddress> | |||
| <pubKey></pubKey> | |||
| <address></address> | |||
| </contractAddress> | |||
| <!-- 合约部署交易的签名账户;该账户必须具备合约部署的权限; --> | |||
| <signer> | |||
| <!-- 账户公钥;Base58 格式; --> | |||
| <pubKey></pubKey> | |||
| <!-- 账户私钥;Base58 格式; --> | |||
| <privKey></privKey> | |||
| <!-- 账户私钥解密密码;Base58 格式; --> | |||
| <privKeyPwd></privKeyPwd> | |||
| </signer> | |||
| </deployment> | |||
| </configuration> | |||
| </plugin> | |||
| ``` | |||
| ### 4. 最简化合约插件配置示例 | |||
| 在pom.xml中有部分配置是非必填项,下面是一份最简化的合约发布(deploy)配置示例,供参考: | |||
| ``` xml | |||
| <plugin> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-maven-plugin</artifactId> | |||
| <version>1.2.0.RELEASE</version> | |||
| <extensions>true</extensions> | |||
| <configuration> | |||
| <!-- 合约部署配置--> | |||
| <deployment> | |||
| <!-- 区块链网络的网关地址 --> | |||
| <gateway> | |||
| <host>127.0.0.1</host> | |||
| <port>8081</port> | |||
| </gateway> | |||
| <!-- 合约部署交易的签名账户;该账户必须具备合约部署的权限; --> | |||
| <signer> | |||
| <pubKey>7VeRLdGtSz1Y91gjLTqEdnkotzUfaAqdap3xw6fQ1yKHkvVq</pubKey> | |||
| <privKey>177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x</privKey> | |||
| <privKeyPwd>DYu3G8aGTMBW1WrTw76zxQJQU4DHLw9MLyy7peG4LKkY</privKeyPwd> | |||
| </signer> | |||
| </deployment> | |||
| </configuration> | |||
| </plugin> | |||
| ``` | |||
| @@ -0,0 +1,80 @@ | |||
| ## 区块 | |||
| 采用`BFT-SMaRt`共识协议,即时出块,单个区块交易数限制默认为`2000`(`bftsmart.config`中参数`system.totalordermulticast.maxbatchsize`) | |||
| ### 结构 | |||
| - `LedgerBlock`: | |||
| ```java | |||
| @DataContract(code = DataCodes.BLOCK) | |||
| public interface LedgerBlock extends BlockBody { | |||
| /** | |||
| * 区块哈希; | |||
| */ | |||
| @DataField(order = 1, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getHash(); | |||
| } | |||
| ``` | |||
| - `BlockBody`: | |||
| ```java | |||
| @DataContract(code= DataCodes.BLOCK_BODY) | |||
| public interface BlockBody extends LedgerDataSnapshot{ | |||
| // 上一个区块哈希 | |||
| @DataField(order=2, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getPreviousHash(); | |||
| // 账本哈希 | |||
| @DataField(order=3, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getLedgerHash(); | |||
| // 区块高度 | |||
| @DataField(order=4, primitiveType= PrimitiveType.INT64) | |||
| long getHeight(); | |||
| // 交易数据集哈希 | |||
| @DataField(order=5, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getTransactionSetHash(); | |||
| // 区块时间戳,毫秒 | |||
| @DataField(order=6, primitiveType = PrimitiveType.INT64) | |||
| long getTimestamp(); | |||
| } | |||
| ``` | |||
| - `LedgerDataSnapshot`: | |||
| ```java | |||
| @DataContract(code=DataCodes.DATA_SNAPSHOT) | |||
| public interface LedgerDataSnapshot { | |||
| // 管理数据集哈希 | |||
| @DataField(order=1, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getAdminAccountHash(); | |||
| // 用户集哈希 | |||
| @DataField(order=2, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getUserAccountSetHash(); | |||
| // 数据账户集哈希 | |||
| @DataField(order=3, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getDataAccountSetHash(); | |||
| // 合约集哈希 | |||
| @DataField(order=4, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getContractAccountSetHash(); | |||
| // 系统事件集哈希 | |||
| @DataField(order=5, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getSystemEventSetHash(); | |||
| // 用户事件集哈希 | |||
| @DataField(order=6, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getUserEventSetHash(); | |||
| } | |||
| ``` | |||
| @@ -0,0 +1,120 @@ | |||
| ### 密钥管理 | |||
| `jdchain-cli`提供基于本地目录的密钥管理:[密钥对列表](#密钥对列表),[添加密钥对](#添加密钥对),[更新私钥密码](#更新私钥密码),[删除密钥对](#删除密钥对) | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh keys -h | |||
| Usage: git status [<options>...] [--] [<pathspec>...] | |||
| List, create, update or delete keypairs. | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| Default: ../ | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| Commands: | |||
| list List all the keypairs. | |||
| add Create a new keypair. | |||
| update Update privkey password. | |||
| delete Delete keypair. | |||
| help Displays help information about the specified command | |||
| ``` | |||
| #### 密钥对列表 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh keys list -h | |||
| List all the keypairs. | |||
| Usage: jdchain-cli keys list [-hV] [--pretty] [--home=<path>] | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `home`,指定密钥存储相关目录,`${home}/config/keys` | |||
| 如: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh keys list | |||
| NAME ALGORITHM ADDRESS PUBKEY | |||
| ``` | |||
| - `NAME`,名称 | |||
| - `ALGORITHM`,算法 | |||
| - `ADDRESS`,地址 | |||
| - `PUBKEY`,公钥 | |||
| #### 添加密钥对 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh keys add -h | |||
| Create a new keypair. | |||
| Usage: jdchain-cli keys add [-hV] [--pretty] [-a=<algorithm>] [--home=<path>] | |||
| -n=<name> | |||
| -a, --algorithm=<algorithm> | |||
| Crypto algorithm | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| -n, --name=<name> Name of the key | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `a`/`algorithm`,密钥算法:`SM2`,`ED25519`等,默认`ED25519` | |||
| - `name`,密钥对名称 | |||
| 如: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh keys add -n k1 | |||
| please input password: > | |||
| // 输入私钥密码 | |||
| 1 | |||
| NAME ALGORITHM ADDRESS PUBKEY | |||
| k1 ED25519 LdeP1iczD3zpmcayKAxTfSywict9y2r6Jpq6n 7VeRBamwPeMb7jzTNg3Ap2DscBiy3QE3PK5NqBvv9tUjQVk4 | |||
| ``` | |||
| #### 更新私钥密码 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh keys update -h | |||
| Update privkey password. | |||
| Usage: jdchain-cli keys update [-hV] [--pretty] [--home=<path>] -n=<name> | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| -n, --name=<name> Name of the key | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `name`,密钥对名称 | |||
| 如: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh keys update -n k1 | |||
| input the current password: > | |||
| // 输入当前密码 | |||
| 1 | |||
| input new password: > | |||
| // 输入新密码 | |||
| 2 | |||
| NAME ALGORITHM ADDRESS PUBKEY | |||
| k1 ED25519 LdeP1iczD3zpmcayKAxTfSywict9y2r6Jpq6n 7VeRBamwPeMb7jzTNg3Ap2DscBiy3QE3PK5NqBvv9tUjQVk4 | |||
| ``` | |||
| #### 删除密钥对 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh keys delete -h | |||
| Delete keypair. | |||
| Usage: jdchain-cli keys delete [-hV] [--pretty] [--home=<path>] -n=<name> | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| -n, --name=<name> Name of the key | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `name`,密钥对名称 | |||
| 如: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh keys delete -n k1 | |||
| input the current password: > | |||
| // 输入当前密码 | |||
| 2 | |||
| [k1] deleted | |||
| ``` | |||
| @@ -0,0 +1,219 @@ | |||
| ### 共识节点变更 | |||
| 借助`BFT-SMaRt`共识提供的Reconfig操作元语,`JD Chain`实现了在不停机的情况下快速更新共识网络拓扑,实现添加共识节点,移除共识节点,更新共识信息 等功能。 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh participant -h | |||
| Usage: jdchain-cli participant [-hV] [--pretty] [--home=<path>] [COMMAND] | |||
| Add, update or delete participant. | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| Default: ../ | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| Commands: | |||
| register Register new participant. | |||
| active Active participant. | |||
| update Update participant. | |||
| inactive Inactive participant. | |||
| help Displays help information about the specified command | |||
| ``` | |||
| - `register` [注册新节点](#注册新节点) | |||
| - `active` [激活节点](#激活节点) | |||
| - `active` [更新节点](#更新节点) | |||
| - `inactive` [移除节点](#移除节点) | |||
| #### 注册新节点 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh participant register -h | |||
| Register new participant. | |||
| Usage: jdchain-cli participant register [-hV] [--pretty] [--gw-host=<gwHost>] | |||
| [--gw-port=<gwPort>] [--home=<path>] | |||
| --name=<name> | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --name=<name> Name of the participant | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `name`,新节点名称 | |||
| 注册新节点: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh participant register --name node4 | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| // 选择账本 | |||
| > 0 | |||
| // 选择待注册节点公私钥(链上必须不存在此公私钥对应的用户) | |||
| select keypair to register, input the index: | |||
| 0 k1 LdeNq3862vtUCeptww1T5mVvLbAeppYqVNdqD | |||
| 1 1627618939 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 2 node4 LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T | |||
| 2 | |||
| input password of the key: | |||
| > 1 | |||
| // 选择此交易签名用户(必须是链上存在的用户,且有相应操作权限) | |||
| select keypair to sign tx, input the index: | |||
| 0 k1 LdeNq3862vtUCeptww1T5mVvLbAeppYqVNdqD | |||
| 1 1627618939 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 2 node4 LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T | |||
| 1 | |||
| input password of the key: | |||
| > 1 | |||
| register participant: [LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T] | |||
| ``` | |||
| 成功在账本`j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg`中注册了新的节点`LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T` | |||
| 可通过[共识节点列表](query.md#共识节点列表)查看新的账本列表: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh query participants | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| > 0 | |||
| [{"address":"LdeNwsiuo7n6HULWhNKc87PBXJXAfGKFon9RE","id":2,"name":"2","participantNodeState":"CONSENSUS","pubKey":"7VeRFF1ednwhrFoe5cngKwPUJ2N4iFKD9Jt53GxSCc1MmPQ6"},{"address":"LdeNiXZbsBCsTc2ZGp1PGBX81aUxPekhwEwmY","id":1,"name":"1","participantNodeState":"CONSENSUS","pubKey":"7VeREmuT4fF9yRPEMbSSaNLKbLa3qoTpfGHRgwpnSWUn5tqW"},{"address":"LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T","id":4,"name":"node4","participantNodeState":"READY","pubKey":"7VeRKiWHcHjNoYH9kJk2fxoJxgBrstVJ7bHRecKewJAKcvUD"},{"address":"LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw","id":0,"name":"0","participantNodeState":"CONSENSUS","pubKey":"7VeRJpb2XX8XKAaC7G5zQg9DbgKM8gmLhUBtGFmerFbhJTZn"},{"address":"LdeP2ji8PR1DPsLt5NoFeiBnhpckrLHgCJge6","id":3,"name":"3","participantNodeState":"CONSENSUS","pubKey":"7VeRGE4V9MR7HgAqTrkxGvJvaaKRZ3fAjHUjYzpNBGcjfAvr"}] | |||
| register participant: [LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T] | |||
| ``` | |||
| 可以看出`node4`注册成功,地址为`LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T` | |||
| #### 激活节点 | |||
| 激活节点前请正确配置并启动新节点,以下以刚注册成功的`node4`为例 | |||
| 1. 配置`node4` | |||
| 解压`peer`压缩包,复制待加入账本其他节点中数据最新的节点数据库数据,然后修改`config/ledger-binding.conf`: | |||
| ```bash | |||
| # Base58编码的账本哈希 | |||
| ledger.bindings=<账本hash> | |||
| # 账本名字,与账本相关的其他其他peer保持一致 | |||
| binding.<账本HASH>.name=<节点名称> | |||
| # peer4的名名称,与[向现有共识网络注册新的参与方]操作中保持一致 | |||
| binding.<账本hash>.parti.name=<节点名称> | |||
| # peer4的用户地址 | |||
| binding.<账本hash>.parti.address=<peer4的用户地址> | |||
| # 新参与方base58编码的私钥,与[向现有共识网络注册新的参与方]操作中保持一致 | |||
| binding.<账本hash>.parti.pk=<新参与方base58编码的私钥> | |||
| # 新参与方base58编码的私钥读取密码,与[向现有共识网络注册新的参与方]操作中保持一致 | |||
| binding.<账本hash>.parti.pwd=<新参与方base58编码的私钥读取密码> | |||
| # 新参与方对应的账本数据库连接uri,即peer4的账本数据库存放位置,参照其他peer修改,不可与其他peer混用 | |||
| binding.<账本hash>.db.uri=<账本数据库连接> | |||
| ``` | |||
| 2. 启动`node4` | |||
| | **一定注意在启动新参与方节点进程之前确保完成了账本数据库的复制工作** | |||
| 执行`peer4`中`bin`目录下`peer-startup.sh`脚本启动启动新参与方`peer4`节点进程: | |||
| ```bash | |||
| ./peer-startup.sh | |||
| ``` | |||
| 3. 激活新节点 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh participant active -h | |||
| Active participant. | |||
| Usage: jdchain-cli participant active [-hV] [--pretty] [--shutdown] | |||
| --consensus-port=<consensusPort> | |||
| [--home=<path>] --host=<host> | |||
| --ledger=<ledger> --port=<port> | |||
| --syn-host=<synHost> --syn-port=<synPort> | |||
| --consensus-port=<consensusPort> | |||
| Set the participant consensus port. | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --host=<host> Set the participant host. | |||
| --ledger=<ledger> Set the ledger. | |||
| --port=<port> Set the participant service port. | |||
| --pretty Pretty json print | |||
| --shutdown Restart the node server. | |||
| --syn-host=<synHost> Set synchronization participant host. | |||
| --syn-port=<synPort> Set synchronization participant port. | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `ledger`,账本哈希 | |||
| - `host`,新节点地址 | |||
| - `port`,新节点服务端口 | |||
| - `consensus-port`,新节点共识端口 | |||
| - `syn-host`,数据同步节点地址 | |||
| - `syn-port`,数据同步节点服务端口 | |||
| 在账本`j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg`中激活`node4`(以步骤2中启动的服务地址和端口为`127.0.0.1`和`7084`例),共识端口设置为`10088`,同步节点地址和端口为`127.0.0.1`和`7080`为例: | |||
| ```bash | |||
| ./jdchain-cli.sh participant active --ledger j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg --host 127.0.0.1 --port 7084 --consensus-port 10088 --syn-host 127.0.0.1 --syn-port 7080 | |||
| participant activated | |||
| ``` | |||
| 成功后可通过[共识节点列表](query.md#共识节点列表)查询最新共识节点列表状态,`node4`为`CONSENSUS` | |||
| #### 更新节点 | |||
| 通过[激活节点](#激活节点)操作除了激活新增的节点外,还可以动态修改已经处于激活状态的共识节点的`IP`和`共识端口`信息,从而实现本机的共识端口变更,不同机器之间进行`账本迁移`。 | |||
| | **在进行节点信息变更时,要求暂停向共识网络中发起新的业务数据上链请求** | |||
| 1. 变更共识端口 | |||
| | **操作前请确保变更到的端口未被占用** | |||
| 如将`node4`共识端口由`10088`修改为`10188`,操作指令如下: | |||
| ```bash | |||
| ./jdchain-cli.sh participant update --ledger j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg --host 127.0.0.1 --port 7084 --consensus-port 10188 --syn-host 127.0.0.1 --syn-port 7080 | |||
| participant updated | |||
| ``` | |||
| 指令成功执行后,`peer1`的共识端口将自动变更为`10188` | |||
| 2. 账本迁移 | |||
| 账本迁移指将一台机器(`IP`)上的共识节点迁移到另一台机器(`IP`)上,主要操作流程如下: | |||
| | **操作前请确保变更到的端口未被占用** | |||
| 如将`node4`共识`IP`由`127.0.0.1`修改为`192.168.1.100`(另一台机器),操作指令如下: | |||
| ```bash | |||
| ./jdchain-cli.sh participant update --ledger j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg --host 192.168.1.100 --port 7084 --consensus-port 10188 --syn-host 127.0.0.1 --syn-port 7080 -shutdown | |||
| participant updated | |||
| ``` | |||
| **特别注意**:`-shutdown`会停止当前运行的当前账本共识服务,为必填选项,否则可能将导致整个网络需要重启。 | |||
| #### 移除节点 | |||
| ``` | |||
| :bin$ ./jdchain-cli.sh participant inactive -h | |||
| Inactive participant. | |||
| Usage: jdchain-cli participant inactive [-hV] [--pretty] --address=<address> | |||
| [--home=<path>] --host=<host> | |||
| --ledger=<ledger> --port=<port> | |||
| --syn-host=<synHost> | |||
| --syn-port=<synPort> | |||
| --address=<address> Set the participant address. | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --host=<host> Set the participant host. | |||
| --ledger=<ledger> Set the ledger. | |||
| --port=<port> Set the participant service port. | |||
| --pretty Pretty json print | |||
| --syn-host=<synHost> Set synchronization participant host. | |||
| --syn-port=<synPort> Set synchronization participant port. | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `ledger`,账本哈希 | |||
| - `address`,待移除节点共识端口 | |||
| - `host`,待移除节点服务地址 | |||
| - `port`,待移除节点服务端口 | |||
| - `syn-host`,数据同步节点地址 | |||
| - `syn-port`,数据同步节点服务端口 | |||
| 如移除`node4`: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh participant inactive --ledger j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg --address LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T --host 127.0.0.1 --port 7084 --syn-host 127.0.0.1 --syn-port 7080 | |||
| participant inactivated | |||
| ``` | |||
| 成功后可通过[共识节点列表](query.md#共识节点列表)查询最新共识节点列表状态,`node4`为`DEACTIVATED` | |||
| @@ -0,0 +1,531 @@ | |||
| ### 交易 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx -h | |||
| Usage: git status [<options>...] [--] [<pathspec>...] | |||
| Build, sign or send transaction. | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| Default: ../ | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| Commands: | |||
| user-register Register new user. | |||
| role Create or config role. | |||
| authorization User role authorization. | |||
| data-account-register Register new data account. | |||
| kv Set key-value. | |||
| event Publish event. | |||
| contract-deploy Deploy or update contract. | |||
| contract Call contract method. | |||
| event-account-register Register event account. | |||
| sign Sign transaction. | |||
| send Send transaction. | |||
| help Displays help information about the specified command | |||
| ``` | |||
| 参数: | |||
| - `export`,导出交易到指定位置,用于离线交易相关命令 | |||
| - `gw-host`,网关服务地址,默认`127.0.0.1` | |||
| - `gw-port`,网关服务端口,默认`8080` | |||
| - `home`,指定密钥存储相关目录,`${home}/config/keys` | |||
| 命令: | |||
| - `user-register`,[注册用户](#注册用户) | |||
| - `role`,[角色管理](#角色管理) | |||
| - `authorization`,[权限配置](#权限配置) | |||
| - `data-account-register`,[注册数据账户](#注册数据账户) | |||
| - `kv`,[KV设值](#KV设值) | |||
| - `event-account-register`,[注册事件账户](#注册事件账户) | |||
| - `event`,[发布事件](#发布事件) | |||
| - `contract-deploy`,[部署合约](#部署合约) | |||
| - `contract`,[合约调用](#合约调用) | |||
| - `sign`,[离线交易签名](#离线交易签名) | |||
| - `send`,[离线交易发送](#离线交易发送) | |||
| #### 注册用户 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx user-register -h | |||
| Register new user. | |||
| Usage: jdchain-cli tx user-register [-hV] [--pretty] [--export=<export>] | |||
| [--gw-host=<gwHost>] [--gw-port=<gwPort>] | |||
| [--home=<path>] | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| 从`${home}/config/keys`目录下密钥对选择密钥注册到网关服务对应的区块链网络。 | |||
| 如: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx user-register | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| // 选择账本,当前网关服务只有上面一个可用账本 | |||
| > 0 | |||
| select keypair to register: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| // 选择公私钥对用于注册用户 | |||
| > 1 | |||
| input password of the key: | |||
| // 输入所选择公私钥对密钥密码 | |||
| > 1 | |||
| select keypair to sign tx: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| // 选择链上已存在且有注册用户权限的用户所对应的公私钥对,用于交易签名 | |||
| > 0 | |||
| input password of the key: | |||
| // 输入签名私钥密码 | |||
| > 1 | |||
| register user: [LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC] | |||
| ``` | |||
| 会在链上注册地址为`LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC`的用户账户信息。 | |||
| #### 角色管理 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx role -h | |||
| Create or config role. | |||
| Usage: jdchain-cli tx role [-hV] [--pretty] [--export=<export>] | |||
| [--gw-host=<gwHost>] [--gw-port=<gwPort>] | |||
| [--home=<path>] --name=<role> | |||
| [--disable-ledger-perms=<disableLedgerPerms>[, | |||
| <disableLedgerPerms>...]]... | |||
| [--disable-transaction-perms=<disableTransactionPerms | |||
| >[,<disableTransactionPerms>...]]... | |||
| [--enable-ledger-perms=<enableLedgerPerms>[, | |||
| <enableLedgerPerms>...]]... | |||
| [--enable-transaction-perms=<enableTransactionPerms> | |||
| [,<enableTransactionPerms>...]]... | |||
| --disable-ledger-perms=<disableLedgerPerms>[,<disableLedgerPerms>...] | |||
| Disable ledger permissions | |||
| --disable-transaction-perms=<disableTransactionPerms>[, | |||
| <disableTransactionPerms>...] | |||
| Disable transaction permissions | |||
| --enable-ledger-perms=<enableLedgerPerms>[,<enableLedgerPerms>...] | |||
| Enable ledger permissions | |||
| --enable-transaction-perms=<enableTransactionPerms>[, | |||
| <enableTransactionPerms>...] | |||
| Enable transaction permissions | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --name=<role> Role name | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `name`,角色名称,不存在则创建 | |||
| - `disable-ledger-perms`,禁用的账本权限列表,半角逗号分割 | |||
| - `disable-transaction-perms`,禁用的交易权限列表,半角逗号分割 | |||
| - `enable-ledger-perms`,的账本权限列表,半角逗号分割 | |||
| - `enable-transaction-perms`,禁用的交易权限列表,半角逗号分割 | |||
| 如: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx role --name ROLE1 --enable-ledger-perms REGISTER_USER,REGISTER_DATA_ACCOUNT --enable-transaction-perms DIRECT_OPERATION,CONTRACT_OPERATION | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| // 选择账本 | |||
| > 0 | |||
| select keypair to sign tx: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| // 选择签名账户 | |||
| > 0 | |||
| input password of the key: | |||
| // 输入签名账户私钥密码 | |||
| > 1 | |||
| Role config success! | |||
| ``` | |||
| #### 权限配置 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx authorization -h | |||
| User role authorization. | |||
| Usage: jdchain-cli tx authorization [-hV] [--pretty] --address=<address> | |||
| [--export=<export>] [--gw-host=<gwHost>] | |||
| [--gw-port=<gwPort>] [--home=<path>] | |||
| [--policy=<policy>] | |||
| [--authorize=<authorizeRoles>[, | |||
| <authorizeRoles>...]]... | |||
| [--unauthorize=<unauthorizeRoles>[, | |||
| <unauthorizeRoles>...]]... | |||
| --address=<address> User address | |||
| --authorize=<authorizeRoles>[,<authorizeRoles>...] | |||
| Authorize roles | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --policy=<policy> Role policy | |||
| --pretty Pretty json print | |||
| --unauthorize=<unauthorizeRoles>[,<unauthorizeRoles>...] | |||
| Unauthorize roles | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `address`,用户地址 | |||
| - `authorize`,赋予角色列表,半角逗号分割 | |||
| - `unauthorize`,移除角色列表,半角逗号分割 | |||
| - `policy`,角色策略,`UNION`/`INTERSECT`,默认`UNION`合并所有角色权限 | |||
| 如: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx authorization --address LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC --authorize ROLE1 | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| > 0 | |||
| select keypair to sign tx: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| > 0 | |||
| input password of the key: | |||
| > 1 | |||
| Authorization config success! | |||
| ``` | |||
| #### 注册数据账户 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx data-account-register -h | |||
| Register new data account. | |||
| Usage: jdchain-cli tx data-account-register [-hV] [--pretty] | |||
| [--export=<export>] [--gw-host=<gwHost>] [--gw-port=<gwPort>] | |||
| [--home=<path>] [--pubkey=<pubkey>] | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --pretty Pretty json print | |||
| --pubkey=<pubkey> The pubkey of the exist data account | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `pubkey`,待注册数据账户私钥 | |||
| 如: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx data-account-register --pubkey 7VeRFk4ANQHjWjAmAoL7492fuykTpXujihJeAgbXT2J9H9Yk | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| > 0 | |||
| select keypair to sign tx: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| > 0 | |||
| input password of the key: | |||
| > 1 | |||
| register data account: [LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC] | |||
| ``` | |||
| 会在链上注册地址为`LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC`的数据账户信息。 | |||
| #### KV设值 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx kv -h | |||
| Set key-value. | |||
| Usage: jdchain-cli tx kv [-hV] [--pretty] --address=<address> | |||
| [--export=<export>] [--gw-host=<gwHost>] | |||
| [--gw-port=<gwPort>] [--home=<path>] --key=<key> | |||
| --value=<value> [--ver=<version>] | |||
| --address=<address> Data account address | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --key=<key> Key to set | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| --value=<value> Value to set | |||
| --ver=<version> Version of the key-value | |||
| ``` | |||
| - `address`,数据账户地址 | |||
| - `key`,键 | |||
| - `value`,值 | |||
| - `ver`,版本 | |||
| 如向账户地址`LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC`写入`k1`:`v1`:`-1`键值对数据: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx kv --address LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC --key k1 --value v1 --ver -1 | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| > 0 | |||
| select keypair to sign tx: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| > 0 | |||
| input password of the key: | |||
| > 1 | |||
| set kv success | |||
| ``` | |||
| #### 注册事件账户 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx event-account-register -h | |||
| Register event account. | |||
| Usage: jdchain-cli tx event-account-register [-hV] [--pretty] | |||
| [--export=<export>] [--gw-host=<gwHost>] [--gw-port=<gwPort>] | |||
| [--home=<path>] [--pubkey=<pubkey>] | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --pretty Pretty json print | |||
| --pubkey=<pubkey> The pubkey of the exist event account | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `pubkey`,待注册事件账户私钥 | |||
| 如: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx event-account-register --pubkey 7VeRFk4ANQHjWjAmAoL7492fuykTpXujihJeAgbXT2J9H9Yk | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| > 0 | |||
| select keypair to sign tx: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| > 0 | |||
| input password of the key: | |||
| > 1 | |||
| register event account: [LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC] | |||
| ``` | |||
| 会在链上注册地址为`LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC`的事件账户信息。 | |||
| #### 发布事件 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx event -h | |||
| Publish event. | |||
| Usage: jdchain-cli tx event [-hV] [--pretty] --address=<address> | |||
| --content=<value> [--export=<export>] | |||
| [--gw-host=<gwHost>] [--gw-port=<gwPort>] | |||
| [--home=<path>] [--sequence=<sequence>] | |||
| --name=<name> | |||
| --address=<address> Contract address | |||
| --content=<value> Event content | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --pretty Pretty json print | |||
| --sequence=<sequence> Sequence of the event | |||
| --name=<name> Event name | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `address`,事件账户地址 | |||
| - `name`,事件名 | |||
| - `content`,事件内容 | |||
| - `sequence`,事件序号 | |||
| 如向账户地址`LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC`发布事件`n1`:`c1`:`-1`: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx event --address LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC --name n1 --content c1 --sequence -1 | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| > 0 | |||
| select keypair to sign tx: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| > 0 | |||
| input password of the key: | |||
| > 1 | |||
| event publish success | |||
| ``` | |||
| #### 部署合约 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx contract-deploy -h | |||
| Deploy or update contract. | |||
| Usage: jdchain-cli tx contract-deploy [-hV] [--pretty] --car=<car> | |||
| [--export=<export>] [--gw-host=<gwHost>] | |||
| [--gw-port=<gwPort>] [--home=<path>] | |||
| [--pubkey=<pubkey>] | |||
| --car=<car> The car file path | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --pretty Pretty json print | |||
| --pubkey=<pubkey> The pubkey of the exist contract | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `pubkey`,合约公钥,更新合约时使用 | |||
| - `car`,合约`car`文件 | |||
| 如将`contract-samples-1.5.0.RELEASE.car`文件中的合约部署上链: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx contract-deploy --car /home/imuge/Desktop/jdchain-cli/1.5.0/contract-samples-1.5.0.RELEASE.car | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| > 0 | |||
| select keypair to sign tx: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| > 0 | |||
| input password of the key: | |||
| > 1 | |||
| deploy contract: [LdeNyF6jdNry5iCqmHdAFTQPvC8UkbJ9avoXH] | |||
| ``` | |||
| 合约地址:`LdeNyF6jdNry5iCqmHdAFTQPvC8UkbJ9avoXH` | |||
| #### 合约调用 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx contract -h | |||
| Call contract method. | |||
| Usage: jdchain-cli tx contract [-hV] [--pretty] --address=<address> | |||
| [--export=<export>] [--gw-host=<gwHost>] | |||
| [--gw-port=<gwPort>] [--home=<path>] | |||
| --method=<method> [--args=<args>[,<args>...]]... | |||
| --address=<address> Contract address | |||
| --args=<args>[,<args>...] | |||
| Method arguments | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --method=<method> Contract method | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `address`,合约地址 | |||
| - `method`,合约方法 | |||
| - `args`,合约参数,半角逗号分割,命令行中会将所有参数处理为字符串,非字符串参数方法调用暂无法通过命令行工具调用 | |||
| 如调用合约`LdeNyF6jdNry5iCqmHdAFTQPvC8UkbJ9avoXH`中`registerUser`方法,传参`ed386a148fcb48b281b325f66103c805`: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx contract --address LdeNyF6jdNry5iCqmHdAFTQPvC8UkbJ9avoXH --method registerUser --args ed386a148fcb48b281b325f66103c805 | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| > 0 | |||
| select keypair to sign tx: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| > 0 | |||
| input password of the key: | |||
| > 1 | |||
| call contract success | |||
| return string: LdeNqvSjL4izfpMNsGpQiBpTBse4g6qLxZ6j5 | |||
| ``` | |||
| 调用成功并返回了字符串:`LdeNqvSjL4izfpMNsGpQiBpTBse4g6qLxZ6j5` | |||
| #### 离线交易签名 | |||
| 1. 离线交易 | |||
| 执行以上所有交易时,若`export`参数不为空,则会执行交易写入本地操作,而非签名并发送交易,离线交易可以 | |||
| 如构造合约调用操作交易并保存到本地: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx contract --address LdeNyF6jdNry5iCqmHdAFTQPvC8UkbJ9avoXH --method registerUser --args ed386a148fcb48b281b325f66103c810 --export /txs | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| > 0 | |||
| export transaction success: /txs/j5xR8ty8YbujTYKNRshmbfMYsL4jfe3yRUtMparmeHppd3 | |||
| ``` | |||
| 交易内容会被序列化报存在`/txs/j5xR8ty8YbujTYKNRshmbfMYsL4jfe3yRUtMparmeHppd3`中,其中`j5xR8ty8YbujTYKNRshmbfMYsL4jfe3yRUtMparmeHppd3`是该交易哈希。 | |||
| 2. 离线签名 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx sign -h | |||
| Sign transaction. | |||
| Usage: jdchain-cli tx sign [-hV] [--pretty] [--export=<export>] | |||
| [--gw-host=<gwHost>] [--gw-port=<gwPort>] | |||
| [--home=<path>] [--tx=<txFile>] | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --tx=<txFile> Local transaction file | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `import`,离线交易路径 | |||
| 如对步骤1中创建的离线交易添加终端用户(此用户需是链上存在的且有相关权限的用户)签名: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx sign --tx /txs/j5xR8ty8YbujTYKNRshmbfMYsL4jfe3yRUtMparmeHppd3 | |||
| select keypair to sign tx: | |||
| INDEX KEY ADDRESS | |||
| 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw | |||
| 1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC | |||
| > 0 | |||
| input password of the key: | |||
| > 1 | |||
| Sign transaction success! | |||
| ``` | |||
| #### 离线交易发送 | |||
| 发送本地已经签名完成的交易 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx send -h | |||
| Send transaction. | |||
| Usage: jdchain-cli tx send [-hV] [--pretty] [--export=<export>] | |||
| [--gw-host=<gwHost>] [--gw-port=<gwPort>] | |||
| [--home=<path>] [--tx=<txFile>] | |||
| --export=<export> Transaction export directory | |||
| --gw-host=<gwHost> Set the gateway host. Default: 127.0.0.1 | |||
| --gw-port=<gwPort> Set the gateway port. Default: 8080 | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --tx=<txFile> Local transaction file | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| ``` | |||
| - `import`,离线交易路径 | |||
| 如发送`/txs/j5xR8ty8YbujTYKNRshmbfMYsL4jfe3yRUtMparmeHppd3`中包含的交易数据: | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh tx send --tx /home/imuge/Desktop/jdchain-cli/1.5.0/txs/j5xR8ty8YbujTYKNRshmbfMYsL4jfe3yRUtMparmeHppd3 | |||
| select ledger, input the index: | |||
| INDEX LEDGER | |||
| 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg | |||
| > 0 | |||
| Send transaction success: j5xR8ty8YbujTYKNRshmbfMYsL4jfe3yRUtMparmeHppd3 | |||
| ``` | |||
| @@ -0,0 +1,549 @@ | |||
| # 智能合约 | |||
| ### 1. 简介 | |||
| JD Chain 智能合约系统由5个部分组成:合约代码语言、合约引擎、合约账户、合约开发框架、合约开发插件。 | |||
| 合约代码语言是用来编写智能合约的编程语言,合约引擎是解释和执行合约代码的虚拟机。 | |||
| JD Chain 账本中以合约账户的方式对合约代码进行管理。一份部署上链的合约代码需要关联到一个唯一的公钥上,并生成与公钥对应的区块链账户地址,在账本中注册为一个合约账户。在执行之前,系统从账本中读出合约代码并将其加载到合约引擎,由交易执行器调用合约引擎触发合约执行。 | |||
| JD Chain 账本定义了一组标准的账本操作指令,合约代码的执行过程实质上是向账本输出一串操作指令序列,这些指令对账本中的数据产生了变更,形成合约执行的最终结果。 | |||
| 合约开发框架定义了进行合约代码开发中需要依赖的一组编程接口和类库。合约开发插件提供了更方便与IDE集成的合约编译、部署工具,可以简化操作,并与持续集成过程结合。 | |||
| JD Chain 以 Java 语言作为合约代码语言,合约引擎是基于 JVM 构建的安全沙盒。为了实现与主流的应用开发方式无缝兼容, JD Chain 支持以 Maven 来管理合约代码的工程项目,并提供相应的 maven 插件来简化合约的编译和部署。 | |||
| >智能合约是一种可以由计算机执行的合同/协议。不同于现实生活中的合同是由自然语言来编写并约定相关方的权利和义务,智能合约是用合约代码语言来编写,以合约代码的形式存在和被执行。通过账本中的数据状态来表示合同/协议相关条款信息,合约代码的运行过程体现了合同/协议条款的执行,并记录相应的结果。 | |||
| ### 2. 快速入门 | |||
| #### 2.1. 准备开发环境 | |||
| 按照正常的 Java 应用开发环境要求进行准备,以 Maven 作为代码工程的构建管理工具,无其它特殊要求。 | |||
| >检查 JDK 版本不低于 1.8 ,Maven 版本不低于 3.0。 | |||
| #### 2.2. 创建合约代码工程 | |||
| 创建一个普通的 Java Maven 工程,打开 pom.xml 把 packaging 设为 contract . | |||
| ```xml | |||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | |||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <groupId>your.group.id</groupId> | |||
| <artifactId>your.project</artifactId> | |||
| <version>0.0.1-SNAPSHOT</version> | |||
| <!-- 声明为合约代码工程,编译输出扩展名为".car"合约代码 --> | |||
| <packaging>contract</packaging> | |||
| <dependencies> | |||
| <!-- 合约项目的依赖 --> | |||
| </dependencies> | |||
| <build> | |||
| <plugins> | |||
| <!-- 合约项目的插件 --> | |||
| </plugins> | |||
| </build> | |||
| </project> | |||
| ``` | |||
| > 注:合约代码工程也是一个普通的 Java Maven 工程,因此尽管不同 IDE 创建 Maven 工程有不同的操作方式,由于对于合约开发而言并无特殊要求,故在此不做详述。 | |||
| #### 2.3. 加入合约开发依赖 | |||
| 在合约代码工程 pom.xml 加入对合约开发 SDK 的依赖: | |||
| ```xml | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-starter</artifactId> | |||
| <version>${jdchain.version}</version> | |||
| </dependency> | |||
| ``` | |||
| #### 2.4. 加入合约插件 | |||
| 在合约代码工程的 pom.xml 加入 contract-maven-plugin 插件: | |||
| ```xml | |||
| <plugin> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-maven-plugin</artifactId> | |||
| <version>${jdchain.version}</version> | |||
| <extensions>true</extensions> | |||
| </plugin> | |||
| ``` | |||
| 完整的 pom.xml 如下: | |||
| ```xml | |||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | |||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <groupId>your.group.id</groupId> | |||
| <artifactId>your.project</artifactId> | |||
| <version>0.0.1-SNAPSHOT</version> | |||
| <!-- 声明为合约代码工程,编译输出扩展名为".car"合约代码 --> | |||
| <packaging>contract</packaging> | |||
| <properties> | |||
| <jdchain.version>1.4.2.RELEASE</jdchain.version> | |||
| </properties> | |||
| <dependencies> | |||
| <!-- 合约项目的依赖 --> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-starter</artifactId> | |||
| <version>${jdchain.version}</version> | |||
| </dependency> | |||
| </dependencies> | |||
| <build> | |||
| <plugins> | |||
| <plugin> | |||
| <groupId>org.apache.maven.plugins</groupId> | |||
| <artifactId>maven-compiler-plugin</artifactId> | |||
| <version>3.8.1</version> | |||
| <configuration> | |||
| <source>1.8</source> | |||
| <target>1.8</target> | |||
| <encoding>UTF-8</encoding> | |||
| <optimize>false</optimize> | |||
| <debug>true</debug> | |||
| <showDeprecation>false</showDeprecation> | |||
| <showWarnings>false</showWarnings> | |||
| </configuration> | |||
| </plugin> | |||
| <!-- 合约项目的插件 --> | |||
| <plugin> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-maven-plugin</artifactId> | |||
| <version>${jdchain.version}</version> | |||
| <extensions>true</extensions> | |||
| </plugin> | |||
| </plugins> | |||
| </build> | |||
| </project> | |||
| ``` | |||
| #### 2.5. 编写合约代码 | |||
| 2.5.1. **注意事项** | |||
| 1. 不允许合约(包括合约接口和合约实现类)使用com.jd.blockchain开头的package; | |||
| 2. 必须有且只有一个接口使用@Contract注解,且其中的event必须大于等于一个; | |||
| 3. 使用@Contract注解的接口有且只有一个实现类; | |||
| 4. 黑名单调用限制(具体黑名单可查看配置文件),需要注意的是,黑名单分析策略会递归分析类实现的接口和父类,也就是说调用一个实现了指定黑名单接口的类也是不允许的; | |||
| 目前设置的黑名单如下: | |||
| ```conf | |||
| java.io.File | |||
| java.io.InputStream | |||
| java.io.OutputStream | |||
| java.io.DataInput | |||
| java.io.DataOutput | |||
| java.io.Reader | |||
| java.io.Writer | |||
| java.io.Flushable | |||
| java.nio.channels.* | |||
| java.nio.file.* | |||
| java.net.* | |||
| java.sql.* | |||
| java.lang.reflect.* | |||
| java.lang.Class | |||
| java.lang.ClassLoader | |||
| java.util.Random | |||
| java.lang.System-currentTimeMillis | |||
| java.lang.System-nanoTime | |||
| com.jd.blockchain.ledger.BlockchainKeyGenerator | |||
| ``` | |||
| 2.5.2. **声明合约** | |||
| ```java | |||
| /** | |||
| * 声明合约接口; | |||
| **/ | |||
| @Contract | |||
| public interface AssetContract { | |||
| @ContractEvent(name = "transfer") | |||
| String transfer(String address, String from, String to, long amount); | |||
| } | |||
| ``` | |||
| 2.5.3. **实现合约** | |||
| ```java | |||
| /** | |||
| * 实现合约; | |||
| * | |||
| * 实现 EventProcessingAware 接口是可选的,目的获得 ContractEventContext 上下文对象, | |||
| * 通过该对象可以进行账本操作; | |||
| */ | |||
| public class AssetContractImpl implements AssetContract, EventProcessingAware { | |||
| // 合约事件上下文; | |||
| private ContractEventContext eventContext; | |||
| /** | |||
| * 执行交易请求中对 AssetContract 合约的 transfer 调用操作; | |||
| */ | |||
| public String transfer(String address, String from, String to, long amount) { | |||
| //当前账本的哈希; | |||
| HashDigest ledgerHash = eventContext.getCurrentLedgerHash(); | |||
| //当前账本上下文; | |||
| LedgerContext ledgerContext = eventContext.getLedger(); | |||
| //做操作; | |||
| // ledgerContext. | |||
| //返回合约操作的结果; | |||
| return "success"; | |||
| } | |||
| /** | |||
| * 准备执行交易中的合约调用操作; | |||
| */ | |||
| @Override | |||
| public void beforeEvent(ContractEventContext eventContext) { | |||
| this.eventContext = eventContext; | |||
| } | |||
| /** | |||
| * 完成执行交易中的合约调用操作; | |||
| */ | |||
| @Override | |||
| public void postEvent(ContractEventContext eventContext, Exception error) { | |||
| this.eventContext = null; | |||
| } | |||
| } | |||
| ``` | |||
| **账本数据可见范围**: | |||
| `ContractEventContext`中`getUncommittedLedger`方法可访问执行中的未提交区块数据,此方法的合理使用可以解决客户并发调用合约方法涉及数据版本/事件序列冲突的问题。 | |||
| ```java | |||
| /** | |||
| * 当前包含未提交区块数据账本查询上下文; | |||
| */ | |||
| LedgerQueryService getUncommittedLedger(); | |||
| ``` | |||
| `ContractEventContext`中`getLedger`方法访问的是链上已提交的最新区块数据,不包含未提交交易,所以存在未提交交易中多个合约方法调用操作间数据不可见,导致并发时数据版本等冲突问题。 | |||
| ```java | |||
| /** | |||
| * 账本操作上下文; | |||
| */ | |||
| LedgerContext getLedger(); | |||
| ``` | |||
| 合约方法中对账本的操作通过调用`LedgerContext`中相关方法,可参照[示例合约](https://github.com/blockchain-jd-com/jdchain/tree/master/samples/contract-samples/src/main/java/com/jdchain/samples/contract) | |||
| #### 2.6. 编译打包合约代码 | |||
| 合约代码工程的编译打包操作与普通的 maven 工程是相同的,在工程的根目录下输入以下命令: | |||
| ```bash | |||
| mvn clean package | |||
| ``` | |||
| 执行成功之后,在 target 目录中输出合约代码文件 \<project-name>.\<version>.car 。 | |||
| 如果合约代码加入了除 com.jd.blockchain:contract-starter 之外的其它依赖,默认配置下,第三方依赖包将与 .car 文件一起打包一起部署。(也可以把第三方依赖包独立打包,具体参见以下 “3. 合约插件详细配置” | |||
| > 注意:合约代码虽然利用了 Java 语言,遵照 Java 语法进行编写,但本质上是作为一种运行于受限环境(合约虚拟机)的语言来使用,因而一些 Java 语法和 SDK 的 API 是不被允许使用的,在编译过程中将对此进行检查。 | |||
| #### 2.7. 部署合约代码 | |||
| ##### 2.7.1. 在项目中部署合约代码 | |||
| 如果希望在构建打包的同时将合约代码部署到指定的区块链网络,可以在合约代码工程 pom.xml 的 contract-maven-plugin 插件配置中加入合约部署相关的信息(具体更详细的配置可以参考“3. 合约插件详细配置”)。 | |||
| ```xml | |||
| <plugin> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-maven-plugin</artifactId> | |||
| <version>1.2.0.RELEASE</version> | |||
| <extensions>true</extensions> | |||
| <configuration> | |||
| <!-- 合约部署配置 --> | |||
| <deployment> | |||
| <!-- 合约要部署的目标账本的哈希;Base58 格式; --> | |||
| <ledger>j5rpuGWVxSuUbU3gK7MDREfui797AjfdHzvAMiSaSzydu7</ledger> | |||
| <!-- 区块链网络的网关地址 --> | |||
| <gateway> | |||
| <host>192.168.10.10</host> | |||
| <port>8081</port> | |||
| </gateway> | |||
| <!-- 合约账户 --> | |||
| <ContractAddress> | |||
| <pubKey>7VeRMpXVeTY4cqPogUHeNoZNk86CGAejBh9Xbd5ndFZXNFj3</pubKey> | |||
| </ContractAddress> | |||
| <!-- 合约部署交易的签名账户;该账户必须具备合约部署的权限; --> | |||
| <signer> | |||
| <pubKey>7VeRLdGtSz1Y91gjLTqEdnkotzUfaAqdap3xw6fQ1yKHkvVq</pubKey> | |||
| <privKey>177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x</privKey> | |||
| <privKeyPwd>DYu3G8aGTMBW1WrTw76zxQJQU4DHLw9MLyy7peG4LKkY</privKeyPwd> | |||
| </signer> | |||
| </deployment> | |||
| </configuration> | |||
| </plugin> | |||
| ``` | |||
| 加入部署配置信息之后,对工程执行编译打包操作,输出的合约代码(.car)将自动部署到指定的区块链网络。 | |||
| ```bash | |||
| mvn clean deploy | |||
| ``` | |||
| ##### 2.7.2. 发布已编译好的car | |||
| 如果已经通过插件的打包方式,编译打包完成一个合约文件(.car),可通过命令行的方式进行发布,命令行要求与开发环境一致的Maven环境(包括环境变量及Setting都已配置完成)。 | |||
| ```bash | |||
| mvn com.jd.blockchain:contract-maven-plugin:${version}:deploy | |||
| -DcarPath= | |||
| -Dledger= | |||
| -DgatewayHost= | |||
| -DgatewayPort= | |||
| -DcontractPubKey= | |||
| -DcontractAddress= | |||
| -DsignerPubKey= | |||
| -DsignerPrivKey= | |||
| -DsignerPrivKeyPwd= | |||
| ``` | |||
| 各参数说明如下: | |||
| | 参数名 | 含义 |是否必填| | |||
| | ---- | ---- | ---- | | |||
| | ${version} | 合约插件的版本号 | 否,系统会自动选择发布的最新的RELEASE版本,SNAPSHOT版本必须填写 | | |||
| | carPath | 合约文件所在路径 | 是 | | |||
| | ledger | 账本Hash(Base58编码) | 否,会自动选择线上第一个账本| | |||
| | gatewayHost | 可访问的网关节点地址,域名或IP地址 | 是| | |||
| | gatewayPort | 网关节点监听端口 | 是 | | |||
| | contractPubKey | 合约账户的公钥(Base58编码)| 否,会自动创建 | | |||
| | contractAddress | 合约账户的地址(Base58编码)|否,会根据contractPubKey生成| | |||
| | signerPubKey | 合约签名公钥信息(Base58编码)|是| | |||
| | signerPrivKey | 合约签名私钥信息(Base58编码)|是| | |||
| | signerPrivKeyPwd | 合约签名私钥解密密钥(Base58编码)|是| | |||
| 下面是一个示例,供参考: | |||
| ```bash | |||
| mvn com.jd.blockchain:contract-maven-plugin:1.2.0.RELEASE:deploy \ | |||
| -DcarPath=/root/jdchain/contracts/contract-test-1.0-SNAPSHOT.car \ | |||
| -Dledger=j5tW5HUvMjEtm2yB7E6MHoSByoH1DXvMwvF2HurEgMSaLW \ | |||
| -DgatewayHost=127.0.0.1 \ | |||
| -DgatewayPort=11000 \ | |||
| -DcontractPubKey= 7VeRBsHM2nsGwP8b2ufRxz36hhNtSqjKTquzoa4WVKWty5sD \ | |||
| -DcontractAddress= LdeNt7sEmTirh9PmE7axKvA2txTrbB9kxz6KB \ | |||
| -DsignerPubKey=7VeRLdGtSz1Y91gjLTqEdnkotzUfaAqdap3xw6fQ1yKHkvVq \ | |||
| -DsignerPrivKey=177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x \ | |||
| -DsignerPrivKeyPwd=DYu3G8aGTMBW1WrTw76zxQJQU4DHLw9MLyy7peG4LKkY | |||
| ``` | |||
| > 重点说明: | |||
| 命令行中输入参数的优先级高于配置文件,就是说通过2.7.1方式发布合约时也可以采用命令行的参数(指-D相关配置),其优先级高于配置文件。 | |||
| ### 3. 合约插件详细配置 | |||
| ```xml | |||
| <plugin> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-maven-plugin</artifactId> | |||
| <version>1.2.0.RELEASE</version> | |||
| <extensions>true</extensions> | |||
| <configuration> | |||
| <!-- 是否把所有的依赖项打包输出到一个独立的 “库文件(.lib)”,默认为 false--> | |||
| <!-- 设置为 false 时 ,合约代码和依赖项一起打包输出到 “合约代码文件(.car)” --> | |||
| <!-- 设置为 true ,合约代码和依赖项分别打包,分别输出 “合约代码文件(.car)” 和 “库文件(.lib)” --> | |||
| <!-- 注: | |||
| 1. 如果“合约代码文件(.car)”的尺寸超出最大尺寸将引发异常,可把此项配置设置为 true 以减小“合约代码文件(.car)”的尺寸。 | |||
| 2. “合约代码文件(.car)”的默认最大尺寸为 1 MB,由区块链网络的配置设定,如果不满足则需要由区块链网络的管理员进行调整。 | |||
| 3. “合约库文件(.lib)”的尺寸不受“合约代码文件(.car)”的最大尺寸限制,部署过程只有“哈希上链”,库文件通过链下的分发网络自动同步至各个共识节点。 | |||
| --> | |||
| <outputLibrary>false</outputLibrary> | |||
| <!-- 合约代码最大字节数;可选;--> | |||
| <!-- 默认为 1 (MB);如果超出该值将给予错误提示;如果值小于等于 0,则不做校验 --> | |||
| <!-- 注:此参数仅影响编译打包时的本地校验,实际部署时仍然由区块链网络上的配置决定 --> | |||
| <maxCarSize>1</maxCarSize> | |||
| <!-- 合约代码最大字节数的单位;--> | |||
| <!-- 合法值的格式为“整数值+单位”;可选单位有: Byte, KB, MB;不区分大小写;--> | |||
| <maxCarSizeUnit>MB</maxCarSizeUnit> | |||
| <!-- 合约部署配置;可选 --> | |||
| <deployment> | |||
| <!-- 账本的哈希;Base58 格式;非必填项,会自动选择线上第一个账本 --> | |||
| <ledger></ledger> | |||
| <!-- 区块链网络的网关地址 --> | |||
| <gateway> | |||
| <host></host> | |||
| <port></port> | |||
| </gateway> | |||
| <!-- 合约账户 --> | |||
| <!-- 合约账户的地址address(Base58编码),会根据pubKey生成> --> | |||
| <contractAddress> | |||
| <pubKey></pubKey> | |||
| <address></address> | |||
| </contractAddress> | |||
| <!-- 合约部署交易的签名账户;该账户必须具备合约部署的权限; --> | |||
| <signer> | |||
| <!-- 账户公钥;Base58 格式; --> | |||
| <pubKey></pubKey> | |||
| <!-- 账户私钥;Base58 格式; --> | |||
| <privKey></privKey> | |||
| <!-- 账户私钥解密密码;Base58 格式; --> | |||
| <privKeyPwd></privKeyPwd> | |||
| </signer> | |||
| </deployment> | |||
| </configuration> | |||
| </plugin> | |||
| ``` | |||
| ### 4. 最简化合约插件配置示例 | |||
| 在pom.xml中有部分配置是非必填项,下面是一份最简化的合约发布(deploy)配置示例,供参考: | |||
| ```xml | |||
| <plugin> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>contract-maven-plugin</artifactId> | |||
| <version>1.2.0.RELEASE</version> | |||
| <extensions>true</extensions> | |||
| <configuration> | |||
| <!-- 合约部署配置--> | |||
| <deployment> | |||
| <!-- 区块链网络的网关地址 --> | |||
| <gateway> | |||
| <host>127.0.0.1</host> | |||
| <port>8081</port> | |||
| </gateway> | |||
| <!-- 合约部署交易的签名账户;该账户必须具备合约部署的权限; --> | |||
| <signer> | |||
| <pubKey>7VeRLdGtSz1Y91gjLTqEdnkotzUfaAqdap3xw6fQ1yKHkvVq</pubKey> | |||
| <privKey>177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x</privKey> | |||
| <privKeyPwd>DYu3G8aGTMBW1WrTw76zxQJQU4DHLw9MLyy7peG4LKkY</privKeyPwd> | |||
| </signer> | |||
| </deployment> | |||
| </configuration> | |||
| </plugin> | |||
| ``` | |||
| ### 5. 合约SDK | |||
| 除上述使用 maven 命令方式部署合约外,JD Chain SDK 提供了 Java 和 Go 语言的合约部署/升级,合约调用等方法。 | |||
| 以下以 Java SDK 为例讲述主要步骤,完整代码参照[JD Chain Samples](https://github.com/blockchain-jd-com/jdchain/tree/master/samples)合约部分。 | |||
| #### 5.1 合约部署 | |||
| ```java | |||
| // 新建交易 | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 生成合约账户 | |||
| BlockchainKeypair contractAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
| System.out.println("合约地址:" + contractAccount.getAddress()); | |||
| // 部署合约 | |||
| txTemp.contracts().deploy(contractAccount.getIdentity(), FileUtils.readBytes("src/main/resources/contract-samples-1.4.2.RELEASE.car")); | |||
| ``` | |||
| #### 5.2 合约升级 | |||
| ```java | |||
| // 新建交易 | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 解析合约身份信息 | |||
| BlockchainIdentity contractIdentity = new BlockchainIdentityData(KeyGenUtils.decodePubKey("7VeRCfSaoBW3uRuvTqVb26PYTNwvQ1iZ5HBY92YKpEVN7Qht")); | |||
| System.out.println("合约地址:" + contractIdentity.getAddress()); | |||
| // 指定合约地址,升级合约,如合约地址不存在会创建该合约账户 | |||
| txTemp.contracts().deploy(contractIdentity, FileUtils.readBytes("src/main/resources/contract-samples-1.4.2.RELEASE.car")); | |||
| ``` | |||
| #### 5.3 合约调用 | |||
| 5.3.1 动态代理方式 | |||
| 基于动态代理方式合约调用,需要依赖合约接口 | |||
| ```java | |||
| // 新建交易 | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 一次交易中可调用多个(多次调用)合约方法 | |||
| // 调用合约的 registerUser 方法 | |||
| SampleContract sampleContract = txTemp.contract("LdeNr7H1CUbqe3kWjwPwiqHcmd86zEQz2VRye", SampleContract.class); | |||
| GenericValueHolder<String> userAddress = ContractReturnValue.decode(sampleContract.registerUser(UUID.randomUUID().toString())); | |||
| // 准备交易 | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| // 交易签名 | |||
| ptx.sign(adminKey); | |||
| // 提交交易 | |||
| TransactionResponse response = ptx.commit(); | |||
| Assert.assertTrue(response.isSuccess()); | |||
| // 获取返回值 | |||
| System.out.println(userAddress.get()); | |||
| ``` | |||
| 5.3.2 非动态代理方式 | |||
| 不需要依赖合约接口及实现,传入参数构造合约调用操作 | |||
| ```java | |||
| // 新建交易 | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| ContractEventSendOperationBuilder builder = txTemp.contract(); | |||
| // 一次交易中可调用多个(多次调用)合约方法 | |||
| // 调用合约的 registerUser 方法,传入合约地址,合约方法名,合约方法参数列表 | |||
| builder.send("LdeNr7H1CUbqe3kWjwPwiqHcmd86zEQz2VRye", "registerUser", | |||
| new BytesDataList(new TypedValue[]{ | |||
| TypedValue.fromText(UUID.randomUUID().toString()) | |||
| }) | |||
| ); | |||
| // 准备交易 | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| // 交易签名 | |||
| ptx.sign(adminKey); | |||
| // 提交交易 | |||
| TransactionResponse response = ptx.commit(); | |||
| Assert.assertTrue(response.isSuccess()); | |||
| Assert.assertEquals(1, response.getOperationResults().length); | |||
| // 解析合约方法调用返回值 | |||
| for (int i = 0; i < response.getOperationResults().length; i++) { | |||
| BytesValue content = response.getOperationResults()[i].getResult(); | |||
| switch (content.getType()) { | |||
| case TEXT: | |||
| System.out.println(content.getBytes().toUTF8String()); | |||
| break; | |||
| case INT64: | |||
| System.out.println(BytesUtils.toLong(content.getBytes().toBytes())); | |||
| break; | |||
| case BOOLEAN: | |||
| System.out.println(BytesUtils.toBoolean(content.getBytes().toBytes()[0])); | |||
| break; | |||
| default: // byte[], Bytes | |||
| System.out.println(content.getBytes().toBase58()); | |||
| break; | |||
| } | |||
| } | |||
| ``` | |||
| @@ -0,0 +1,63 @@ | |||
| ## 数据账户 | |||
| `JD Chain`存放`KV`数据的数据结构。 | |||
| ### 1. 基本概念 | |||
| 可类比传统数据库的表的概念,上层应用需要保存的应用数据最终都应表现为`KV`类型数据写入到数据账户中。 | |||
| 写入`KV`数据之前,需要创建或使用已存在数据账户。 | |||
| 一个账本可以创建无限多个数据账户,一个数据账户中可以写入无限多个`KV`数据。 | |||
| `KV`数据有`Version`(数据版本)的概念,以`KEY`作为唯一标识,`VALUE`的更新写入需要提供当前该`KEY`的最高版本,`KEY`不存在时为`-1`。 | |||
| ### 2. SDK | |||
| 以下只描述主要步骤,完整示例代码可参照[JD Chain Samples](samples.md)数据账户部分。 | |||
| #### 2.1 注册数据账户 | |||
| 创建数据账户: | |||
| ```java | |||
| BlockchainKeypair dataAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
| System.out.println("数据账户地址:" + dataAccount.getAddress()); | |||
| // 注册数据账户 | |||
| txTemp.dataAccounts().register(dataAccount.getIdentity()); | |||
| ``` | |||
| 从已存在数据账户公钥恢复: | |||
| ```java | |||
| PubKey pubKey = KeyGenUtils.decodePubKey("7VeRLdGtSz1Y91gjLTqEdnkotzUfaAqdap3xw6fQ1yKHkvVq"); | |||
| BlockchainIdentity dataAccountIdentity = new BlockchainIdentityData(pubKey); | |||
| System.out.println("数据账户地址:" + dataAccountIdentity.getAddress()); | |||
| // 注册数据账户 | |||
| txTemp.dataAccounts().register(dataAccountIdentity); | |||
| ``` | |||
| #### 2.2 写入数据 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| txTemp.dataAccount("LdeNr7H1CUbqe3kWjwPwiqHcmd86zEQz2VRye") | |||
| .setText("key1", "value1", -1) | |||
| .setText("key1", "value1", 0) | |||
| .setInt64("key2", 1, -1) | |||
| .setJSON("key3", "{}", -1) | |||
| .setBytes("key4", Bytes.fromInt(2), -1); | |||
| ``` | |||
| 支持写入数据类型: | |||
| - `setText`,字符类型 | |||
| - `setInt64`,长整型 | |||
| - `setJSON`,`JSON`串 | |||
| - `setBytes`,字节数组 | |||
| - `setTimestamp`,时间戳,长整型 | |||
| - `setXML`,`XML`文本 | |||
| - `setImage`,图片字节数据 | |||
| > 本质上仅支持`String/Long/[]byte`这三种数据类型,`JSON/XML/Image/Timestamp`等起标识作用,用于扩展差异化数据展示等场景需求 | |||
| `setText("key1", "value1", -1)`中第三个参数即为数据版本,需要传入`JD Chain`网络中当前`key1`的最高数据版本,首次写入时传入`-1`。 | |||
| @@ -0,0 +1,185 @@ | |||
| ## 事件 | |||
| `JD Chain`账本中设计了事件数据集,用以存储事件账户,事件数据。 | |||
| `事件账户`与`用户`,`数据账户`,`合约账户`是相互独立的,所承载的数据也是相互隔离的。 | |||
| `JD Chain`事件分为两类:[系统事件](#系统事件),[用户事件](#用户事件) | |||
| `JD Chain SDK` 针对事件数据集开发了[事件发布](#事件发布),[事件监听](#事件监听)实现。 | |||
| ### 1. 系统事件 | |||
| 系统运行期间产生的事件,目前仅定义了`新区块产生`这一个: | |||
| ```java | |||
| /** | |||
| * 系统事件类型 | |||
| */ | |||
| public enum SystemEvent { | |||
| // 新区块 | |||
| NEW_BLOCK_CREATED("new_block_created"); | |||
| } | |||
| ``` | |||
| ### 2. 用户事件 | |||
| 用户自定义事件类型,需要创建`事件账户`,用户自定义事件名 | |||
| `事件账户`数量没有限制,一个事件账户内`Topic`(事件名)数量没有限制 | |||
| 同一个`事件账户`的同一个`Topic`所指向的`Content`(事件内容)有`Sequence`(事件序号)的概念,以`Topic`作为唯一标识,同一个`Topic`的更新发布需要提供当前该`Topic`的最高序号,`Topic`不存在时为`-1`。 | |||
| ### 3. 事件结构 | |||
| ```java | |||
| /** | |||
| * 事件; | |||
| * | |||
| */ | |||
| @DataContract(code = DataCodes.EVENT_MESSAGE) | |||
| public interface Event { | |||
| /** | |||
| * 事件名; | |||
| * | |||
| * @return | |||
| */ | |||
| @DataField(order = 1, primitiveType = PrimitiveType.TEXT) | |||
| String getName(); | |||
| /** | |||
| * 事件序号; | |||
| * | |||
| * @return | |||
| */ | |||
| @DataField(order = 2, primitiveType = PrimitiveType.INT64) | |||
| long getSequence(); | |||
| /** | |||
| * 事件内容; | |||
| * | |||
| * @return | |||
| */ | |||
| @DataField(order=3, refContract = true) | |||
| BytesValue getContent(); | |||
| /** | |||
| * 产生事件的交易哈希; | |||
| * | |||
| * @return | |||
| */ | |||
| @DataField(order = 4, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getTransactionSource(); | |||
| /** | |||
| * 产生事件的合约地址; | |||
| * | |||
| * @return | |||
| */ | |||
| @DataField(order = 5, primitiveType = PrimitiveType.TEXT) | |||
| String getContractSource(); | |||
| /** | |||
| * 产生事件的区块高度 | |||
| * | |||
| * @return | |||
| */ | |||
| @DataField(order = 6, primitiveType = PrimitiveType.INT64) | |||
| long getBlockHeight(); | |||
| /** | |||
| * 事件账户地址,系统事件此字段为空 | |||
| * | |||
| * @return | |||
| */ | |||
| @DataField(order = 7, primitiveType = PrimitiveType.BYTES) | |||
| Bytes getEventAccount(); | |||
| } | |||
| ``` | |||
| ### 4. SDK | |||
| `JD Chain`事件监听是`SDK`端以`拉`的方式实现,消息可重复消费,需要使用者自行保存消费位置。 | |||
| 以下只描述主要步骤,完整示例代码可参照[JD Chain Samples](samples.md)事件相关部分。 | |||
| #### 4.1 生成事件账户 | |||
| > 用户事件才有事件账户 | |||
| ```java | |||
| // 新建交易 | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 生成事件账户 | |||
| BlockchainKeypair eventAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
| System.out.println("事件账户地址:" + eventAccount.getAddress()); | |||
| // 注册事件账户 | |||
| txTemp.eventAccounts().register(eventAccount.getIdentity()); | |||
| ``` | |||
| #### 4.2 事件发布 | |||
| > 系统事件由系统运行期间自动产生,用户事件可通过SDK发布 | |||
| ```java | |||
| // 新建交易 | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| txTemp.eventAccount(Bytes.fromBase58("LdeNr7H1CUbqe3kWjwPwiqHcmd86zEQz2VRye")) | |||
| .publish("topic1", "content1", -1) | |||
| .publish("topic1", "content2", 0) | |||
| .publish("topic1", "content3", 1) | |||
| .publish("topic2", "content", -1) | |||
| .publish("topic3", 1, -1) | |||
| .publish("topic4", Bytes.fromInt(1), -1); | |||
| ``` | |||
| 支持发布数据类型: | |||
| - `Text`,字符类型 | |||
| - `Int64`,长整型 | |||
| - `JSON`,`JSON`串 | |||
| - `Bytes`,字节数组 | |||
| - `Timestamp`,时间戳,长整型 | |||
| - `XML`,`XML`文本 | |||
| - `Image`,图片字节数据 | |||
| > 本质上仅支持`String/Long/[]byte`这三种数据类型,`JSON/XML/Image/Timestamp`等起标识作用,用于扩展差异化数据展示等场景需求 | |||
| `publish("topic1", "content1", -1)`中第三个参数即为事件序号,需要传入`JD Chain`网络中当前`topic1`的最高序号,首次写入时传入`-1`。 | |||
| #### 4.3 事件监听 | |||
| - 监听系统事件 | |||
| ```java | |||
| // 目前仅有新区快产生事件 | |||
| blockchainService.monitorSystemEvent(ledger, | |||
| SystemEvent.NEW_BLOCK_CREATED, 0, (eventMessages, eventContext) -> { | |||
| for (Event eventMessage : eventMessages) { | |||
| // content中存放的是当前链上最新高度 | |||
| System.out.println("New block:" + eventMessage.getSequence() + ":" + BytesUtils.toLong(eventMessage.getContent().getBytes().toBytes())); | |||
| } | |||
| }); | |||
| ``` | |||
| - 监听用户事件: | |||
| ```java | |||
| blockchainService.monitorUserEvent(ledger, "LdeNr7H1CUbqe3kWjwPwiqHcmd86zEQz2VRye", "sample-event", 0, (eventMessage, eventContext) -> { | |||
| BytesValue content = eventMessage.getContent(); | |||
| switch (content.getType()) { | |||
| case TEXT: | |||
| case XML: | |||
| case JSON: | |||
| System.out.println(eventMessage.getName() + ":" + eventMessage.getSequence() + ":" + content.getBytes().toUTF8String()); | |||
| break; | |||
| case INT64: | |||
| case TIMESTAMP: | |||
| System.out.println(eventMessage.getName() + ":" + eventMessage.getSequence() + ":" + BytesUtils.toLong(content.getBytes().toBytes())); | |||
| break; | |||
| default: // byte[], Bytes | |||
| System.out.println(eventMessage.getName() + ":" + eventMessage.getSequence() + ":" + new String(content.getBytes().toBytes())); | |||
| break; | |||
| } | |||
| }); | |||
| ``` | |||
| @@ -0,0 +1,53 @@ | |||
| ## 网关服务 | |||
| `JD Chain`的网关服务是应用的接入层。 | |||
| 终端接入是`JD Chain`网关的基本功能,在确认终端身份的同时提供连接节点、转发消息和隔离共识节点与客户端等服务。网关确认客户端的合法身份,接收并验证交易;网关根据初始配置文件与对应的共识节点建立连接,并转发交易数据。 | |||
| ### 1. 配置 | |||
| 网关配置在文件`gateway.conf`中: | |||
| ```properties | |||
| #网关的HTTP服务地址; | |||
| http.host=0.0.0.0 | |||
| #网关的HTTP服务端口; | |||
| http.port=8080 | |||
| #网关的HTTP服务上下文路径,可选; | |||
| #http.context-path= | |||
| #共识节点的服务地址(与该网关节点连接的Peer节点的IP地址); | |||
| peer.host=127.0.0.1 | |||
| #共识节点的服务端口(与该网关节点连接的Peer节点的端口,即在Peer节点的peer-startup.sh中定义的端口); | |||
| peer.port=7080 | |||
| #共识节点的服务是否启用安全证书; | |||
| peer.secure=false | |||
| #是否存储共识拓扑信息,网关重启后若存储存在有效的拓扑信息可快速建立与拓扑中所有节点的连接 | |||
| topology.store=false | |||
| #共识节点的服务提供解析器 | |||
| #BftSmart共识Provider:com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider | |||
| #简单消息共识Provider:com.jd.blockchain.consensus.mq.MsgQueueConsensusProvider | |||
| peer.providers=com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider | |||
| #数据检索服务对应URL,格式:http://{ip}:{port},例如:http://127.0.0.1:10001 | |||
| #若该值不配置或配置不正确,则浏览器模糊查询部分无法正常显示 | |||
| data.retrieval.url= | |||
| schema.retrieval.url= | |||
| #默认公钥的内容(Base58编码数据); | |||
| keys.default.pubkey= | |||
| #默认私钥的路径;在 pk-path 和 pk 之间必须设置其一; | |||
| keys.default.privkey-path= | |||
| #默认私钥的内容(加密的Base58编码数据);在 pk-path 和 pk 之间必须设置其一; | |||
| keys.default.privkey= | |||
| #默认私钥的解码密码; | |||
| keys.default.privkey-password= | |||
| ``` | |||
| 其中: | |||
| - `data.retrieval.url`与`schema.retrieval.url`分别与[Argus(高级检索)](https://github.com/blockchain-jd-com/jdchain-indexer)中的[区块链基础数据检索服务](https://github.com/blockchain-jd-com/jdchain-indexer#%E5%90%AF%E5%8A%A8%E5%8C%BA%E5%9D%97%E9%93%BE%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E7%B4%A2%E5%BC%95%E6%A3%80%E7%B4%A2%E6%9C%8D%E5%8A%A1)和[`Schema`服务](https://github.com/blockchain-jd-com/jdchain-indexer#%E5%90%AF%E5%8A%A8value%E7%B4%A2%E5%BC%95%E6%9C%8D%E5%8A%A1)相对应,只有启动并正确配置了`Argus`,`JD Chain`浏览器才能正常使用搜索功能。 | |||
| - `keys.default`几个参数代表接入`JD Chain`网络的身份信息,只有处于非停用(`DEACTIVATED`)状态的参与方身份才可作为此处的配置。 | |||
| @@ -0,0 +1,15 @@ | |||
| # 高级检索 | |||
| ## Argus | |||
| 穿透式检索Argus提供JD Chain区块链基础数据索引,自定义键值索引服务 | |||
| 源码已开源,请参照[Argus](https://github.com/blockchain-jd-com/jdchain-indexer)首页说明安装使用。 | |||
| ## JD Chain + Argus | |||
| 修改JD Chain网关配置文件`gateway.conf`中: | |||
| - `data.retrieval.url` 对应`Argus`中区块链基础数据检索服务 | |||
| - `schema.retrieval.url` 对应`Argus`中`Schema`服务 | |||
| 重启网关后可在区块连浏览器中使用搜索功能 | |||
| @@ -0,0 +1,30 @@ | |||
| ## JD Chain Cli | |||
| JD Chain 命令行工具集,提供密钥管理,实时交易,链上信息查询,离线交易,共识节点变更等操作。 | |||
| ```bash | |||
| :bin$ ./jdchain-cli.sh -h | |||
| Usage: jdchain-cli [-hV] [--pretty] [--home=<path>] [COMMAND] | |||
| JDChain Cli is a convenient tool to manage jdchain keys, sign and send | |||
| transactions to jdchain network, query data from jdchain network. | |||
| -h, --help Show this help message and exit. | |||
| --home=<path> Set the home directory. | |||
| --pretty Pretty json print | |||
| -V, --version Print version information and exit. | |||
| Commands: | |||
| The most commonly used git commands are: | |||
| keys List, create, update or delete keypairs. | |||
| tx Build, sign or send transaction. | |||
| query Query commands. | |||
| participant Add, update or delete participant. | |||
| help Displays help information about the specified command | |||
| See 'jdchain-cli help <command>' to read about a specific subcommand or concept. | |||
| ``` | |||
| - `keys` [密钥管理](cli/keys.md) | |||
| - `tx` [交易](cli/tx.md) | |||
| - `query` [链上信息查询](cli/query.md) | |||
| - `participant` [共识节点变更](cli/participant.md) | |||
| @@ -0,0 +1,365 @@ | |||
| ## RocksDB as a server | |||
| ### 1.简介 | |||
| `KVDB`是一个简单的`NoSQL`数据库,支持简单的“键值”读写操作。 | |||
| `KVDB`包装了`RocksDB`作为数据库引擎,实现了单机部署和集群部署。 | |||
| `KVDB`的集群是一个分布式的分片服务集群,每个分片节点是一个`KVDB`的数据库服务实例,采用对等模式部署,没有主节点。 | |||
| ### 2.安装 | |||
| > java 版本>= 1.8 | |||
| 下载源代码,执行: | |||
| ```bash | |||
| mvn clean package | |||
| ``` | |||
| `kvdb-server`模块`target`下会生成`kvdb-***.zip`,解压缩安装包,结构如下: | |||
| ```bash | |||
| bin # 脚本文件目录 | |||
| start # 启动本机的数据库服务的脚本文件 | |||
| stop # 停止本机的数据库服务的脚本文件 | |||
| kvdb-cli # 连接本机的数据库服务控制台的脚本文件 | |||
| kvdb-benchmark # 基准测试 | |||
| conf # 配置文件目录 | |||
| kvdb.conf # 本机的数据库服务配置 | |||
| cluster.conf # 数据库集群的配置 | |||
| libs # 数据库服务的代码库目录 | |||
| system # 系统数据目录 | |||
| dblist # 文件记录了本数据库服务实例装载的数据库列表 | |||
| pid # 文件记录了本数据库服务实例的运行进程ID;当服务实例运行时创建,服务实例退出后删除 | |||
| ``` | |||
| ### 3.部署配置 | |||
| #### 3.1 `kvdb.conf` | |||
| ```bash | |||
| # 数据库服务的本机监听地址; | |||
| server.host=0.0.0.0 | |||
| # 数据库服务的本机监听端口; | |||
| server.port=7078 | |||
| # 管理控制台的端口; | |||
| # 注:管理控制台总是绑定到环回地址 127.0.0.1,只允许本机访问; | |||
| manager.port=7060 | |||
| # 数据库实例默认的根目录 | |||
| dbs.rootdir=/usr/kvdb/dbs | |||
| # 数据库实例默认的本地分区数 | |||
| dbs.partitions=4 | |||
| # 是否禁用WAL | |||
| wal.disable=false | |||
| # WAL刷新机制,-1跟随系统,0实时刷新,n(n>0)秒刷新一次 | |||
| wal.flush=1 | |||
| ``` | |||
| > `WAL`写入成功,但`RocksDB`写入失败,服务器将在后续所有数据库读写操作前根据`WAL`进行重试操作,直至成功。 | |||
| #### 3.2 `cluster.conf` | |||
| ```bash | |||
| # 数据库集群的分片数,每一个分片都赋予唯一的编号,分片编号最小为 0,所有分片的编号必须连续递增 | |||
| # ‘<name>’表示集群名称,只允许用字母、数字、下划线组成; | |||
| cluster.<name>.partitions=3 | |||
| # 数据库集群 ‘<name>’ 的第 1 个分片的数据库实例地址(URL格式); | |||
| cluster.<name>.0=kvdb://<host>:<port>/<dbname> | |||
| # 数据库集群 ‘<name>’ 的第 2 个分片的数据库实例地址(URL格式); | |||
| cluster.<name>.1=kvdb://<host>:<port>/<dbname> | |||
| # 数据库集群 ‘<name>’ 的第 3 个分片的数据库实例地址(URL格式); | |||
| cluster.<name>.2=kvdb://<host>:<port>/<dbname> | |||
| # 指定多个不同的集群 | |||
| #cluster.<name1>.partitions=3 | |||
| ... | |||
| ``` | |||
| > 一个数据库实例只能加入唯一的集群;一旦加入集群,则不能再被客户端以单库连接方式访问,对该库实例的连接自动转为集群连接。集群中配置的数据库必须在`dblist`中已存在且设置为`true` | |||
| > 所有`host`使用明确的可通畅连接的地址,不同服务器节点中同一集群配置必须完全一致 | |||
| #### 3.3 `dblist` | |||
| ```bash | |||
| # 是否激活数据库 ‘<name>’ ;如果为`true`,则创建或加载该数据库并提供访问; | |||
| # ‘<name>’表示数据库名称,只允许用字母、数字、下划线组成; | |||
| # db.<name>.enable=true | |||
| # 数据库 <name> 的根目录;如果未配置,则从默认目录加载(由`conf/kvdb.conf`指定了默认配置${dbs.rootdir}/<name>); | |||
| # db.test.rootdir= | |||
| # 数据库 <name> 的本地分区数;如果未配置,则采用`conf/kvdb.conf`中指定的默认配置${dbs.partitions} | |||
| # db.test.partitions= | |||
| ``` | |||
| > `dblist`中配置的数据库会在`kvdb-server`启动时自动创建。在`kvdb-server`启动完成后由管理工具执行`create database <name>`创建数据库,创建成功的数据库会追加到`dblist`中。使用`kvdb-cli`命令行工具执行数据库实例更新操作。 | |||
| #### 3.4 日志 | |||
| 1. `kvdb-server` | |||
| 修改`bin`目录下,`start.sh`文件: | |||
| ```bash | |||
| LOG_SET="-Dlogging.path="$HOME/logs" -Dlogging.level=ERROR" | |||
| ``` | |||
| 默认日志路径:程序解压缩后主目录下`logs`目录 | |||
| 默认日志等级:`ERROR` | |||
| 可配置日志等级:`ALL`,`TRACE`,`DEBUG`,`INFO`,`WARN`,`ERROR`,`FATAL`,`OFF` | |||
| 2. `kvdb-cli` | |||
| 修改`bin`目录下,`kvdb-cli.sh`文件: | |||
| ```bash | |||
| LOG_SET="-Dlogging.path="$HOME/logs" -Dlogging.level.root=error" | |||
| ``` | |||
| 默认日志路径:程序解压缩后主目录下`logs`目录 | |||
| 默认日志等级:`error` | |||
| #### 3.5 启动 | |||
| ```bash | |||
| ./start.sh | |||
| ``` | |||
| #### 停止 | |||
| ```bash | |||
| ./stop.sh | |||
| ``` | |||
| ### 4.SDK | |||
| 1. 依赖 | |||
| ```maven | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>kvdb-client</artifactId> | |||
| <version>${project.version}</version> | |||
| </dependency> | |||
| ``` | |||
| 2. 创建客户端连接 | |||
| ```java | |||
| // `host`、`port`为服务器地址和端口,`database`为数据库名称 | |||
| KVDBClient client = new KVDBClient("kvdb://<host>:<port>/<database>"); | |||
| # KVDBClient client = new KVDBClient(new KVDBURI("kvdb://<host>:<port>/<database>")); | |||
| # KVDBClient client = new KVDBClient(new ClientConfig(host, port, db)); | |||
| ``` | |||
| > 对于集群的连接,客户端将开启`WAL`,在`WAL`写入成功,部分服务器失败的情况下,客户端在执行后续所有操作前都将根据`WAL`日志执行重试操作,直至成功。 | |||
| 3. 操作 | |||
| ```java | |||
| /** | |||
| * 存在性查询 | |||
| * | |||
| * @param key | |||
| * @return | |||
| * @throws KVDBException | |||
| */ | |||
| boolean exists(Bytes key) throws KVDBException; | |||
| /** | |||
| * 存在性查询,支持多个键值 | |||
| * | |||
| * @param keys | |||
| * @return | |||
| * @throws KVDBException | |||
| */ | |||
| boolean[] exists(Bytes... keys) throws KVDBException; | |||
| /** | |||
| * 键值获取 | |||
| * | |||
| * @param key | |||
| * @return | |||
| * @throws KVDBException | |||
| */ | |||
| Bytes get(Bytes key) throws KVDBException; | |||
| /** | |||
| * 键值获取,支持多个键值 | |||
| * | |||
| * @param keys | |||
| * @return | |||
| * @throws KVDBException | |||
| */ | |||
| Bytes[] get(Bytes... keys) throws KVDBException; | |||
| /** | |||
| * 设置键值对 | |||
| * | |||
| * @param key | |||
| * @param value | |||
| * @return | |||
| * @throws KVDBException | |||
| */ | |||
| boolean put(Bytes key, Bytes value) throws KVDBException; | |||
| /** | |||
| * 设置键值对 | |||
| * | |||
| * @param key | |||
| * @param value | |||
| * @param aSync | |||
| * @return | |||
| * @throws KVDBException | |||
| */ | |||
| boolean put(Bytes key, Bytes value, boolean aSync) throws KVDBException; | |||
| /** | |||
| * 开启批处理 | |||
| * | |||
| * @return | |||
| * @throws KVDBException | |||
| */ | |||
| boolean batchBegin() throws KVDBException; | |||
| /** | |||
| * 取消批处理 | |||
| * | |||
| * @return | |||
| * @throws KVDBException | |||
| */ | |||
| boolean batchAbort() throws KVDBException; | |||
| /** | |||
| * 提交批处理,服务器掉线重连后会丢失未提交批处理数据 | |||
| * <p> | |||
| * 未提交的`batch`对其他客户端连接不可见。 | |||
| * | |||
| * @return | |||
| * @throws KVDBException | |||
| */ | |||
| boolean batchCommit() throws KVDBException; | |||
| /** | |||
| * 提交批处理 | |||
| * | |||
| * @param size 此次批处理操作去重后的key数量 | |||
| * @return | |||
| * @throws KVDBException | |||
| */ | |||
| boolean batchCommit(long size) throws KVDBException; | |||
| /** | |||
| * 关闭 | |||
| */ | |||
| void close(); | |||
| ``` | |||
| ### 5.管理工具 | |||
| `kvdb-cli`是基于[`SDK`](#SDK)的命令行工具实现: | |||
| ```bash | |||
| ./kvdb-cli.sh -h <kvdb server host> -p <kvdb server port> -db <database> -t <time out in milliseconds> -bs <buffer size> -k <keep alive> | |||
| ``` | |||
| 参数说明: | |||
| - `-h` 服务器地址。选填,默认`localhost` | |||
| - `-p` 管理端口。选填,默认`7060` | |||
| - `-db` 数据库。选填 | |||
| - `-t` 超时时间,毫秒。选填,默认`60000 ms` | |||
| - `-bs` 发送/接收缓冲区大小。选填,默认`1048576` | |||
| - `-k` 保持连接。选填,默认`true` | |||
| 所有支持指令操作: | |||
| ```bash | |||
| localhost:7060>help | |||
| AVAILABLE COMMANDS | |||
| Built-In Commands | |||
| clear: Clear the shell screen. | |||
| exit, quit: Exit the shell. | |||
| help: Display help about available commands. | |||
| stacktrace: Display the full stacktrace of the last error. | |||
| KVDB Commands | |||
| batch abort: 取消批处理 | |||
| batch begin: 开启批处理 | |||
| batch commit: 提交批处理 | |||
| cluster info: 服务器集群配置信息 | |||
| create database: 创建数据库实例,仅在当前连接的`kvdb`服务器创建数据库实例,集群数据库创建只能通过修改`cluster.conf`进行配置。 | |||
| disable database: 关闭数据库实例,加入集群的实例不可修改 | |||
| drop database: 删除数据库实例,加入集群的实例不可修改 | |||
| enable database: 开放数据库实例,加入集群的实例不可修改 | |||
| exists: 检查存在性 | |||
| get: 获取键值 | |||
| put, set: 设置键值 | |||
| show databases: 展示数据库实例列表 | |||
| status: 当前数据库信息 | |||
| use: 切换数据库 | |||
| ``` | |||
| 指令帮助(以`put`为例): | |||
| ```bash | |||
| localhost:7060>help put | |||
| NAME | |||
| put - Set a key-value | |||
| SYNOPSYS | |||
| put [--key] string [--value] string | |||
| OPTIONS | |||
| --key string | |||
| [Mandatory] | |||
| --value string | |||
| [Mandatory] | |||
| ALSO KNOWN AS | |||
| set | |||
| ``` | |||
| 示例(可执行如下指令插入数据): | |||
| ```bash | |||
| localhost:7060>put k v | |||
| localhost:7060>set k v | |||
| localhost:7060>put --key k --value v | |||
| localhost:7060>set --key k --value v | |||
| ``` | |||
| ### 6.Benchmark | |||
| `kvdb-sever`性能测试工具,简单的数据插入测试。 | |||
| ```bash | |||
| ./kvdb-benchmark.sh -h <kvdb server host> -p <kvdb server port> -db <database> -c <time out in milliseconds> -n <request times> -b <buffer size> -k <keep alive> | |||
| ``` | |||
| 参数说明: | |||
| - `-h` 服务器地址。选填,默认`localhost` | |||
| - `-p` 端口。选填,默认`7078` | |||
| - `-db` 数据库。必填 | |||
| - `-c` 客户端数量。选填,默认`20` | |||
| - `-n` 请求数量。选填,默认`100000` | |||
| - `-ds` 键/值字节数。选填,默认`16` | |||
| - `-b` 是否使用批处理。选填,默认`false` | |||
| - `-bs` 一次批处理键值对数。选填,默认`100` | |||
| - `-k` 保持连接。选填,默认`true` | |||
| 示例: | |||
| ```bash | |||
| ./kvdb-benchmark.sh -db test1 -c 1 -n 4000000 -b true -bs 1000 -ds 8 | |||
| requests:4000000, clients:1, batch:true, batch_size:1000, kv_data_size:8bytes, times:54014ms, tps:74054.874662 | |||
| ``` | |||
| 其中: | |||
| - `requests` 请求数量 | |||
| - `clients` 并发数 | |||
| - `batch` 是否开启批量模式 | |||
| - `batch_size` 一次批处理键值对数 | |||
| - `kv_data_size` 键/值字节数 | |||
| - `times` 总耗时 | |||
| - `tps` TPS | |||
| 可根据`tps`值,调整`kvdb.conf`中`dbs.partitions`数值,以寻找最优服务器配置。 | |||
| @@ -0,0 +1,19 @@ | |||
| ## 日志 | |||
| `JD Chain`使用`Log4j2`配置日志输出 | |||
| ### Gateway | |||
| 网关日志默认配置文件为`config/log4j2-gw.xml`,可修改启动脚本(`bin/startup.sh`)中`-Djdchain.log=$APP_HOME/logs`和`-Dlogging.config=file:$APP_HOME/config/log4j2-gw.xml`两个选项修改日志配置。 | |||
| 启动过程中会有少量控制台输出`bin/peer.out`,注意观察启动时是否有错误信息。 | |||
| 运行中日志根据默认配置会输出到`logs`目录中。 | |||
| ### Peer | |||
| 节点日志默认配置文件为`config/log4j2-peer.xml`,可修改启动脚本(`bin/peer-startup.sh`)中`-Djdchain.log=$APP_HOME/logs`和`-Dlogging.config=file:$APP_HOME/config/log4j2-peer.xml`两个选项修改日志配置。 | |||
| 启动过程中会有少量控制台输出`bin/peer.out`,注意观察启动时是否有错误信息。 | |||
| 运行中日志根据默认配置会输出到`logs`目录中。 | |||
| @@ -0,0 +1,253 @@ | |||
| ## 共识节点变更 | |||
| 借助`BFT-SMaRT`共识提供的`Reconfig`操作元语,`JD Chain`实现了在不停机的情况下快速更新共识网络拓扑,实现[添加共识节点](#1添加共识节点),[移除共识节点](#2移除共识节点),[更新共识信息](#3更新共识信息) 等功能。 | |||
| **共识节点相关操作错误极容易导致整个网络不可用,甚至无法恢复,操作前请做好数据备份,务必谨慎操作,确保所有环境和指令正确** | |||
| 以下操作说明均以在部署好的如下单机四节点环境操作为例: | |||
| - `ledger` `j5m4yF1uyxaMwwBWKaqJqyHkKViXs8LGe9ChWvPs1CqdjP` | |||
| - `peer0` 启动端口 `7080`,共识端口`10080` | |||
| - `peer1` 启动端口 `7081`,共识端口`10081` | |||
| - `peer2` 启动端口 `7082`,共识端口`10082` | |||
| - `peer3` 启动端口 `7083`,共识端口`10083` | |||
| - `网关` 服务端口 `8080` | |||
| ### 1.添加共识节点 | |||
| #### 1.1 生成身份信息 | |||
| 解压`peer`的`zip`包作为新的参与方节点`peer4`。使用`bin`目录下`keygen.sh`脚本生成公私钥信息: | |||
| ```bash | |||
| $ ./keygen.sh -n new-node | |||
| # 输入私钥密码 | |||
| Input password: | |||
| # 是否保存Base58编码后的私钥密码信息 | |||
| Do you want to save encode password to file? Please input y or n ...y | |||
| ``` | |||
| 执行完成后会在`peer4`中`config/keys`目录下生成`new-node.priv`/`new-node.pub`/`new-node.pwd`文件,分别保存公钥/私钥/私钥密码信息,用作新增节点的身份信息。 | |||
| #### 1.2 注册新节点 | |||
| `peer4`中`bin`目录下提供了`reg-parti.sh`注册参与方脚本: | |||
| ```bash | |||
| ./reg-parti.sh -ledger <账本HASH> -pub <新节点公钥> -priv <新节点私钥> -pass <新节点私钥密码> -name <新节点名称> -existpub <链上已存在用户公钥> -existpriv <链上已存在用户私钥> -existpass <链上已存在用户私钥密码> -host <网关IP> -port <网关端口> | |||
| ``` | |||
| 参数: | |||
| - `ledger`,指定要注册新参与方的 用户手册账本,`Base58`编码的字符串,必填 | |||
| - `pub`,步骤1中产生的用户公钥信息,`Base58`编码的字符串,必填 | |||
| - `priv`,步骤1中产生的用户私钥信息,`Base58`编码的字符串,必填 | |||
| - `pass`,步骤1中产生的用户私钥密码, `Base58`编码的字符串,必填 | |||
| - `name`,新参与方的名字,字符串,必填 | |||
| - `existpub`,账本中已注册的任意用户的公钥信息,`Base58`编码的字符串,必填 | |||
| - `existpriv`,账本中已注册的任意用户的私钥信息,`Base58`编码的字符串,用于对客户端提交的交易进行终端签名,必填 | |||
| - `existpass`,账本中已注册的任意用户的私钥密码信息,`Base58`编码的字符串,必填 | |||
| - `host`,客户端接入网关`IP`,必填 | |||
| - `port`,客户端接入网关`Port`,必填 | |||
| - `debug`,开启`DEBUG`模式 ,默认为`false`,可选 | |||
| 示例: | |||
| ```bash | |||
| ./reg-parti.sh -ledger j5m4yF1uyxaMwwBWKaqJqyHkKViXs8LGe9ChWvPs1CqdjP -pub 3snPdw7i7PjLAfhTx22uU4C9oGRFpHAcFkt3wHNVSuQgodE616H45H -priv 177gjyyCaYGiTvjRQbxAZwUDc8SjriRwgWwAwZLuzCvuixJ9AbcZx2FWTMP1XoUigRqUJmD -pass 8EjkXVSTxMFjCvNNsTo8RBMDEVQmk7gYkW4SCDuvdsBG -name 4 -existpub 3snPdw7i7PjWHy3hV5yw7JqMKfNqZdotSNzqJJHpnKmLCeJMd383jd -existpriv 177gjzjdwHqADjfC6yLqECZcEK8HPM8GAvjsQZrDiPNyLUGgqQPQ8zqjFBcV6TsKitmfFFV -existpass 8EjkXVSTxMFjCvNNsTo8RBMDEVQmk7gYkW4SCDuvdsBG -host 127.0.0.1 -port 8080 | |||
| ``` | |||
| 执行成功后将打印成功信息,并显示`peer4`的用户地址信息。 | |||
| #### 1.3 确定复制节点 | |||
| 查询每个共识节点的账本信息: | |||
| ```bash | |||
| curl http://<ip>:<port>/ledgers/<ledgerHash> | |||
| ``` | |||
| > 其中`ip`和`port`为各个`peer`的`IP`地址和启动端口 | |||
| 选出具有最新区块数据的共识节点作为复制节点。 | |||
| #### 1.4 构建新节点 | |||
| 修改`peer4`目录下以下文件: | |||
| 1. `config/ledger-binding.conf` | |||
| ```bash | |||
| # Base58编码的账本哈希 | |||
| ledger.bindings=<账本hash> | |||
| # 账本名字,与账本相关的其他其他peer保持一致 | |||
| binding.<账本HASH>.name=<节点名称> | |||
| # peer4的名名称,与[向现有共识网络注册新的参与方]操作中保持一致 | |||
| binding.<账本hash>.parti.name=<节点名称> | |||
| # peer4的用户地址 | |||
| binding.<账本hash>.parti.address=<peer4的用户地址> | |||
| # 新参与方base58编码的私钥,与[向现有共识网络注册新的参与方]操作中保持一致 | |||
| binding.<账本hash>.parti.pk=<新参与方base58编码的私钥> | |||
| # 新参与方base58编码的私钥读取密码,与[向现有共识网络注册新的参与方]操作中保持一致 | |||
| binding.<账本hash>.parti.pwd=<新参与方base58编码的私钥读取密码> | |||
| # 新参与方对应的账本数据库连接uri,即peer4的账本数据库存放位置,参照其他peer修改,不可与其他peer混用 | |||
| binding.<账本hash>.db.uri=<账本数据库连接> | |||
| ``` | |||
| 2. `bin/peer-startup.sh` | |||
| 对于脚本中`PROC_INFO`变量的`-p` 参数修改为`peer4`的`http`启动端口 | |||
| 3. 账本数据复制 | |||
| 步骤[确定复制节点](#确定复制节点)中选出了复制节点,以`peer0`为例,从`peer0`的`ledger-binding.conf`文件可知`peer0`的账本数据库存放地址,如:`rocksdb:///home/jdchain/peer0/rocksdb`,复制此目录所有数据到`peer4` `config/ledger-binding.conf`中`binding.<账本hash>.db.uri`指定的位置 | |||
| > **除了数据库目录,切不可将其他节点所有内容直接复制使用**,因为像`runtime`等目录中会保存节点差异化的相关运行数据。 | |||
| #### 1.5 启动新节点 | |||
| > **一定注意在启动新参与方节点进程之前确保完成了账本数据库的复制工作** | |||
| 执行`peer4`中`bin`目录下`peer-startup.sh`脚本启动启动新参与方`peer4`节点进程: | |||
| ```bash | |||
| ./peer-startup.sh | |||
| ``` | |||
| #### 1.6 激活新节点 | |||
| > **在进行新参与方节点的激活操作时,要求暂停向共识网络中发起新的业务数据** | |||
| `peer`包中`bin`目录下提供了`active-parti.sh`激活参与方脚本: | |||
| ```bash | |||
| ./active-parti.sh -ledger <账本哈希> -httphost <新节点IP> -httpport <新节点启动端口> -consensushost <新节点共识IP> -consensusport <新节点共识端口> -synchost <数据同步节点IP> -syncport <数据同步节点端口> -debug -shutdown | |||
| ``` | |||
| 参数: | |||
| - `ledger`,指定要注册新参与方的账本,`Base58`编码的字符串,必填 | |||
| - `httphost`,新参与方`IP`地址,必填 | |||
| - `httpport`,新参与方启动端口,必填 | |||
| - `consensushost`,新参与方的共识`IP`地址,必填(**务必确保该端口未被占用**) | |||
| - `synchost`,新参与方进行账本数据复制的节点`IP`地址,必填 | |||
| - `syncport`, 新参与方进行账本数据复制的节点的启动端口,,必填 | |||
| - `debug`,开启`DEBUG`模式 ,默认为`false`,可选 | |||
| 示例: | |||
| ```bash | |||
| ./active-parti.sh -ledger j5m4yF1uyxaMwwBWKaqJqyHkKViXs8LGe9ChWvPs1CqdjP -httphost 127.0.0.1 -httpport 7084 -consensushost 127.0.0.1 -consensusport 10088 -synchost 127.0.0.1 -syncport 7080 | |||
| ``` | |||
| #### 1.7 查询节点状态 | |||
| 查询网关接口,获取节点状态信息: | |||
| ```bash | |||
| curl http://<网关ip>:<网关port>/ledgers/<账本hash>/participants | |||
| ``` | |||
| 节点状态为`CONSENSUS`,说明添加成功。 | |||
| ### 2.移除共识节点 | |||
| > **在多于4个共识节点的共识网络中,才允许进行节点的移除操作** | |||
| > **在进行节点的移除操作时,要求暂停向共识网络中发起新的业务数据上链请求** | |||
| #### 2.1 确定复制节点 | |||
| 查询每个共识节点的账本信息: | |||
| ```bash | |||
| curl http://<ip>:<port>/ledgers/<ledgerHash> | |||
| ``` | |||
| > 其中`ip`和`port`为各个`peer`的`IP`地址和启动端口 | |||
| 选出具有最新区块数据的共识节点作为复制节点。 | |||
| #### 2.2 移除节点 | |||
| `peer`包中`bin`目录下提供了`deactive-parti.sh`移除节点脚本: | |||
| ```bash | |||
| ./reg-parti.sh -ledger <账本HASH> -participantAddress <待移除节点地址> -httphost <待移除节点的http启动IP地址> -httpport <待移除节点的http启动Port> -synchost <数据同步节点IP> -syncport <数据同步节点端口> -debug | |||
| ``` | |||
| 参数: | |||
| - `ledger`,指定要注册新参与方的账本,`Base58`编码的字符串,必填 | |||
| - `participantAddress`, 要移除的参与方用户地址,`Base58`编码的字符串,必填; | |||
| - `httphost`,待移除参与方`IP`地址,必填 | |||
| - `httpport`,待移除参与方启动端口,必填 | |||
| - `synchost`,待移除参与方进行账本数据复制的节点`IP`地址,必填 | |||
| - `syncport`,待移除参与方进行账本数据复制的节点的启动端口, 必填 | |||
| - `debug`,开启`DEBUG`模式 ,默认为`false`,可选 | |||
| 示例: | |||
| ```bash | |||
| ./deactive-parti.sh -ledger j5m4yF1uyxaMwwBWKaqJqyHkKViXs8LGe9ChWvPs1CqdjP -participantAddress LdeNnjhSDx9nxE5hoyzzbU7paWQhP4MAfpYiC -httphost 127.0.0.1 -httpport 7084 -synchost 127.0.0.1 -syncport 7080 | |||
| ``` | |||
| 执行此操作可将`peer4`从该共识网络中移除,不再参与共识服务。 | |||
| #### 2.3 查询节点状态 | |||
| 查询网关接口,获取参与方状态信息: | |||
| ```bash | |||
| curl http://<网关ip>:<网关port>/ledgers/<账本hash>/participants | |||
| ``` | |||
| 参与方状态为非`CONSENSUS`,说明操作成功。 | |||
| ### 3.更新共识信息 | |||
| 通过[激活节点](#6. 激活新节点)操作除了激活新增的节点外,还可以动态修改已经处于激活状态的共识节点的`IP`和`共识端口`信息,从而实现本机的共识端口变更,不同机器之间进行`账本迁移`。 | |||
| > **在进行节点信息变更时,要求暂停向共识网络中发起新的业务数据上链请求** | |||
| #### 3.1 变更共识端口 | |||
| > **操作前请确保变更到的端口未被占用** | |||
| 如将`peer1`共识端口由`10082`修改为`10182`,操作指令如下: | |||
| ```bash | |||
| ./active-parti.sh -ledger j5m4yF1uyxaMwwBWKaqJqyHkKViXs8LGe9ChWvPs1CqdjP -httphost 127.0.0.1 -httpport 7081 -consensushost 127.0.0.1 -consensusport 10182 -synchost 127.0.0.1 -syncport 7080 | |||
| ``` | |||
| 指令成功执行后,`peer1`的共识端口将自动变更为`10182`。 | |||
| #### 3.2 账本迁移 | |||
| 账本迁移指将一台机器(`IP`)上的共识节点迁移到另一台机器(`IP`)上,主要操作流程如下: | |||
| > **操作前请确保变更到的端口未被占用** | |||
| 1. 修改共识信息 | |||
| 如将`peer2`中账本`j5m4yF1uyxaMwwBWKaqJqyHkKViXs8LGe9ChWvPs1CqdjP`的共识`IP`由`127.0.0.1`修改为`192.168.1.100`(另一台机器),操作指令如下: | |||
| ```bash | |||
| ./active-parti.sh -ledger j5m4yF1uyxaMwwBWKaqJqyHkKViXs8LGe9ChWvPs1CqdjP -httphost 127.0.0.1 -httpport 7082 -consensushost 192.168.1.100 -consensusport 10084 -synchost 127.0.0.1 -syncport 7080 -shutdown | |||
| ``` | |||
| **特别注意**:`-shutdown`为必填选项,否则将导致整个网络需要重启。 | |||
| 指令成功执行后,`127.0.0.1`上账本`j5m4yF1uyxaMwwBWKaqJqyHkKViXs8LGe9ChWvPs1CqdjP`节点将不再参与此账本的共识服务。 | |||
| 2. 迁移节点数据 | |||
| 拷贝步骤1中`127.0.0.1`上与移出账本相关的所有数据(包括`rocksdb`,`runtime`等等)到`191.168.1.100`上一致的部署目录。 | |||
| 请修改`127.0.0.1`上`peer2/config/ledger-binding.conf`文件,去除移出的账本配置。若此节点上仅此一个账本,可关闭本节点进程。 | |||
| 修改`191.168.1.100`上`peer2/config/ledger-binding.conf`文件,保留移出的账本配置。 | |||
| 3. 启动节点 | |||
| 在`191.168.1.100`上执行`peer2/bin`目录下`peer-startup.sh`脚本启动节点。 | |||
| 查询网关接口: | |||
| ```bash | |||
| curl http://<网关ip>:<网关port>/ledgers/<账本hash>/settings | |||
| ``` | |||
| 查看账本各共识节点的相关信息。 | |||
| @@ -0,0 +1,56 @@ | |||
| ### JD Chain 源代码库 | |||
| #### project | |||
| - URL:git@github.com:blockchain-jd-com/jdchain-project.git | |||
| - 说明:公共的父项目,定义公共的依赖 | |||
| #### framework | |||
| - URL:git@github.com:blockchain-jd-com/jdchain-framework.git | |||
| - 说明:框架源码库,定义公共数据类型、框架、模块组件接口、`SDK`、`SPI`、工具 | |||
| #### core | |||
| - URL:git@github.com:blockchain-jd-com/jdchain-core.git | |||
| - 说明:模块组件实现的源码库 | |||
| #### explorer | |||
| - URL:git@github.com:blockchain-jd-com/explorer.git | |||
| - 说明:相关产品的前端模块的源码库 | |||
| #### bft-smart | |||
| - URL:git@github.com:blockchain-jd-com/bftsmart.git | |||
| - 说明:`BFT-SMaRt` 共识算法的源码库 | |||
| #### explorer | |||
| - URL:git@github.com:blockchain-jd-com/explorer.git | |||
| - 说明:区块链浏览器/管理工具前端代码 | |||
| #### binary-proto | |||
| - URL:git@github.com:blockchain-jd-com/binary-proto.git | |||
| - 说明:自研序列化/反序列化框架 | |||
| #### utils | |||
| - URL:git@github.com:blockchain-jd-com/utils.git | |||
| - 说明:工具类库 | |||
| #### httpservice | |||
| - URL:git@github.com:blockchain-jd-com/httpservice.git | |||
| - 说明:`HTTP` `RPC` 服务框架 | |||
| #### kvdb | |||
| - URL:git@github.com:blockchain-jd-com/kvdb.git | |||
| - 说明:基于`RocksDB` `JNI`方式实现可独立部署,支持`KV`读写的数据库服务 | |||
| #### test | |||
| - URL:git@github.com:blockchain-jd-com/jdchain-test.git | |||
| - 说明:集成测试用例的源码库 | |||
| @@ -0,0 +1,52 @@ | |||
| 以下四种方式均可初始化/运行`JD Chain`网络,组网过程有难易,需要开发者细心操作,操作过程中遇到问题,可随时与我们联系。 | |||
| ### 1. 官方完整步骤 | |||
| `JD Chain`官网提供了[安装部署](http://ledger.jd.com/setup.html)详细介绍,较为繁琐,但是其他便捷组网方法的基础。 | |||
| ### 2. 管理工具 | |||
| `JD Chain`提供了基于界面操作的网络初始化启动工具,相关脚本为`manager-startup.sh`和`manager-shutdown.sh`。 | |||
| 送上操作视频:http://storage.jd.com/jd.block.chain/init-jdchain-by-manager-tool.mp4 | |||
| ### 3. 基于内存的四节点网络 | |||
| 1. 克隆[ JD Chain主项目](https://github.com/blockchain-jd-com/jdchain)源码,并切换到对应版本分支 | |||
| 请查阅**主项目首页介绍**,里面有子项目代码拉取,项目编译打包的介绍。代码根路径下执行: | |||
| ```bash | |||
| build/build.sh --update --skipTests | |||
| ``` | |||
| 即可完成所有子项目代码拉取,完成编译打包 | |||
| 2. 运行[Samples](https://github.com/blockchain-jd-com/jdchain/tree/master/samples)模块下代码 | |||
| 参照[Samples介绍](https://github.com/blockchain-jd-com/jdchain/tree/master/samples) | |||
| 运行`sdk-samples`里的`TestNet`类`main`方法即可启动基于内存的四节点+单网关区块链网络环境,浏览器地址为`http://localhost:11000`。 | |||
| `sdk-samples`中测试用例默认基于`TestNet`启动的网络环境配置,都可直接运行。覆盖绝大多数交易类型提交,交易查询。 | |||
| ### 4. 基于安装包和部署脚本 | |||
| 1. 下载`JD Chain`安装包 | |||
| 安装包获取途径: | |||
| - 下载编译[`JD Chain`源码](https://github.com/blockchain-jd-com/jdchain),参照首页说明进行编译打包。 | |||
| - 访问[JD Chain官网](http://ledger.jd.com/downloadapps.html)下载,版本更新可能不及源码快。 | |||
| 2. 脚本初始化 | |||
| 复制[testnet.sh](scripts/testnet.sh)脚本,保存到本地,设置可运行权限 | |||
| > 脚本仅在特定的`linux`环境下测试通过,不同系统环境可能存在`shell`语句或者依赖差异,请酌情修改 | |||
| > 此脚本可一键生成多节点,多账本,目前还相当粗糙,仅当抛砖引玉~ | |||
| 将`jdchain-peer-*.RELEASE.zip`,`jdchain-gateway-*.RELEASE.zip`压缩包以及`testnet.sh`脚本放置同一目录下。 | |||
| 直接运行`testnet.sh`便可自动初始化默认四节点+单网关的环境,同时生成一键启动(`start.sh`)和关闭(`shutdown.sh`)的脚本。 | |||
| 运行`start.sh`便可启动测试网络,参照[JD Chain Samples](https://github.com/blockchain-jd-com/jdchain/tree/master/samples)介绍,配置好网络环境参数,即可快速上手`JD Chain SDK`使用。 | |||
| @@ -0,0 +1,45 @@ | |||
| ## 示例代码 | |||
| 克隆[JD Chain主项目](https://github.com/blockchain-jd-com/jdchain),切换到指定分支。 | |||
| [Samples](https://github.com/blockchain-jd-com/jdchain/tree/master/samples) 中提供了 `用户`,`数据账户`,`合约`,`事件`,`查询API`相关使用。 | |||
| ### 依赖 TestNet | |||
| `Samples`项目中提供了基于内存的四节点加单网关的网络环境初始化和启动方式(`TestNet`类),若需要此运行环境,请执行: | |||
| ```bash | |||
| build/build.sh --update --skipTests | |||
| ``` | |||
| 将项目子项目及依赖都更新到指定分支对应版本,并完成编译打包。 | |||
| 执行`TestNet`类`main`即可启动测试网络,网络成功启动后可执行`sdk-samples`中所有测试用例。 | |||
| ### 不依赖 TestNet | |||
| 对于已有`JD Chain`测试网络,不使用`TestNet`的情况,开发者可以只导入`Samples`项目,并删除`TestNet`相关的包和类,去除`pom.xml`中以下依赖: | |||
| ```xml | |||
| <!--以下依赖用于 com.jdchain.samples.Network 中四节点网路环境初始化和启动 --> | |||
| <dependency> | |||
| <groupId>org.reflections</groupId> | |||
| <artifactId>reflections</artifactId> | |||
| <version>0.9.12</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>tools-initializer</artifactId> | |||
| <version>${framework.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>peer</artifactId> | |||
| <version>${framework.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.jd.blockchain</groupId> | |||
| <artifactId>gateway</artifactId> | |||
| <version>${framework.version}</version> | |||
| </dependency> | |||
| ``` | |||
| 修改`resources`中关于网络的相关参数,即可运行所有测试用例。 | |||
| @@ -0,0 +1,278 @@ | |||
| ```bash | |||
| #!/bin/bash | |||
| HOME=$(cd `dirname $0`; pwd) | |||
| echo "" > nohup.out | |||
| ps -ef|grep '$HOME'|grep -v grep|cut -c 9-15|xargs kill -9 | |||
| echo "Start jdchain Initialing in $HOME" | |||
| ## parameters | |||
| # 节点数量 | |||
| NODE_SIZE=4 | |||
| # 允许错误节点个数 | |||
| FAULT_SIZE=1 | |||
| # 数据库URI | |||
| DB_URI="" | |||
| # peer API server start port | |||
| PEER_PORT=7080 | |||
| # consus start port | |||
| CONSUS_PORT=10080 | |||
| # DB password | |||
| DB_PWD="" | |||
| # ledger size | |||
| LEDGER_SIZE=1 | |||
| while getopts ":N:f:d:P:p:w:L:" opt | |||
| do | |||
| case $opt in | |||
| N) | |||
| NODE_SIZE=$OPTARG | |||
| ;; | |||
| f) | |||
| FAULT_SIZE=$OPTARG | |||
| ;; | |||
| d) | |||
| DB_URI=$OPTARG | |||
| ;; | |||
| P) | |||
| PEER_PORT=$OPTARG | |||
| ;; | |||
| p) | |||
| CONSUS_PORT=$OPTARG | |||
| ;; | |||
| w) | |||
| DB_PWD=$OPTARG | |||
| ;; | |||
| L) | |||
| LEDGER_SIZE=$OPTARG | |||
| ;; | |||
| ?) | |||
| echo "unknow parameter" | |||
| esac | |||
| done | |||
| # clear old data | |||
| rm -rf peer* | |||
| rm -rf gw* | |||
| ####################################### init peers files | |||
| KEY_NAME=`date '+%s'` | |||
| i=0 | |||
| while(( $i<$NODE_SIZE )) | |||
| do | |||
| # uzip peer and gateway files | |||
| unzip -oq jdchain-peer-* -d peer$i | |||
| chmod 777 peer$i/bin/* | |||
| # generate key | |||
| mkdir peer$i/config/keys | |||
| if [ -f "keygen" ];then | |||
| chmod +x keygen | |||
| ./keygen $KEY_NAME peer$i/config/keys "WprD3WiAi5S6Z1BSDlvUkhBVhcBiaxf"$i | |||
| else | |||
| cd peer$i/bin/ | |||
| expect -c " | |||
| set timeout 10 | |||
| spawn ./keygen.sh -n $KEY_NAME | |||
| expect \"*Input password:\" | |||
| send \"1\r\" | |||
| expect \"*input y or n *\" | |||
| send \"y\r\" | |||
| expect eof | |||
| " | |||
| cd $HOME | |||
| fi | |||
| IDs[$i]=$i | |||
| PUBKs[$i]=$(cat peer$i/config/keys/$KEY_NAME.pub) | |||
| PRIVs[$i]=$(cat peer$i/config/keys/$KEY_NAME.priv) | |||
| PWDs[$i]=$(cat peer$i/config/keys/$KEY_NAME.pwd) | |||
| let "i++" | |||
| done | |||
| # init peer-startup.sh | |||
| i=0 | |||
| while(( $i<$NODE_SIZE )) | |||
| do | |||
| sed -ri "s#7080#$PEER_PORT#g" peer$i/bin/peer-startup.sh | |||
| let "PEER_PORT++" | |||
| let "i++" | |||
| done | |||
| ####################################### init gateway | |||
| unzip -oq jdchain-gateway-* -d gw | |||
| chmod 777 gw/bin/* | |||
| sed -ri "s#peer.port=7080#peer.port=$((PEER_PORT-NODE_SIZE))#g" gw/config/gateway.conf | |||
| sed -ri "s#keys.default.pubkey=#keys.default.pubkey=${PUBKs[0]}#g" gw/config/gateway.conf | |||
| sed -ri "s#keys.default.privkey=#keys.default.privkey=${PRIVs[0]}#g" gw/config/gateway.conf | |||
| sed -ri "s#keys.default.privkey-password=#keys.default.privkey-password=${PWDs[0]}#g" gw/config/gateway.conf | |||
| ###################################### generate start and shutdown files | |||
| echo "#!/bin/bash" > start.sh | |||
| i=0 | |||
| while(( $i<$NODE_SIZE )) | |||
| do | |||
| echo " | |||
| nohup ./peer$i/bin/peer-startup.sh & " >> start.sh | |||
| chmod 777 start.sh | |||
| let "i++" | |||
| done | |||
| echo " | |||
| sleep 20 | |||
| nohup ./gw/bin/startup.sh &" >> start.sh | |||
| echo "#!/bin/bash | |||
| ps -ef|grep '$HOME'|grep -v grep|cut -c 9-15|xargs kill -9 | |||
| " > shutdown.sh | |||
| chmod 777 shutdown.sh | |||
| ###################################### ledger init | |||
| k=0 | |||
| while(( $k<$LEDGER_SIZE )) | |||
| do | |||
| echo "初始化账本 "$k | |||
| seed=`date +%s%N | md5sum |cut -c 1-32` | |||
| i=0 | |||
| while(( $i<$NODE_SIZE )) | |||
| do | |||
| #### init local.conf | |||
| sed -ri "s/local.parti.id(.*)/local.parti.id=$i/g" peer$i/config/init/local.conf | |||
| sed -ri "s/local.parti.pubkey(.*)/local.parti.pubkey=${PUBKs[$i]}/g" peer$i/config/init/local.conf | |||
| sed -ri "s/local.parti.privkey(.*)/local.parti.privkey=${PRIVs[$i]}/g" peer$i/config/init/local.conf | |||
| sed -ri "s/local.parti.pwd(.*)/local.parti.pwd=${PWDs[$i]}/g" peer$i/config/init/local.conf | |||
| if [ -z $DB_URI ] | |||
| then | |||
| sed -ri "s#ledger.db.uri(.*)#ledger.db.uri=rocksdb://$HOME/peer$i/rocksdb$k#g" peer$i/config/init/local.conf | |||
| else | |||
| sed -ri "s#ledger.db.uri(.*)#ledger.db.uri=$DB_URI/$i#g" peer$i/config/init/local.conf | |||
| fi | |||
| sed -ri "s#ledger.db.pwd(.*)#ledger.db.pwd=$DB_PWD#g" peer$i/config/init/local.conf | |||
| #### init ledger.init | |||
| echo " | |||
| ledger.seed=$seed | |||
| ledger.name=ledger$k | |||
| created-time=$(date +"%Y-%m-%d %H:%M:%S").000+0800 | |||
| consensus.service-provider=com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider | |||
| consensus.conf=$HOME/peer$i/config/init/bftsmart.config | |||
| crypto.service-providers=com.jd.blockchain.crypto.service.classic.ClassicCryptoService, com.jd.blockchain.crypto.service.sm.SMCryptoService | |||
| crypto.verify-hash=true | |||
| crypto.hash-algorithm=SHA256 | |||
| cons_parti.count=$NODE_SIZE" > peer$i/config/init/ledger.init | |||
| j=0 | |||
| port=11010 | |||
| while(( $j<$NODE_SIZE )) | |||
| do | |||
| echo " | |||
| cons_parti.$j.name=$j | |||
| cons_parti.$j.pubkey=${PUBKs[$j]} | |||
| cons_parti.$j.initializer.host=127.0.0.1 | |||
| cons_parti.$j.initializer.port=$port | |||
| cons_parti.$j.initializer.secure=false" >> peer$i/config/init/ledger.init | |||
| let "j++" | |||
| let "port++" | |||
| done | |||
| #### init bftsmart.config | |||
| echo "" > peer$i/config/init/bftsmart.config | |||
| j=0 | |||
| port=$((CONSUS_PORT+k*100)) | |||
| while(( $j<$NODE_SIZE )) | |||
| do | |||
| echo " | |||
| system.server.$j.network.host=127.0.0.1 | |||
| system.server.$j.network.port=$port | |||
| system.server.$j.network.secure=false" >> peer$i/config/init/bftsmart.config | |||
| let "j++" | |||
| let "port++" | |||
| let "port++" | |||
| done | |||
| echo " | |||
| system.communication.useSenderThread = true | |||
| system.communication.defaultkeys = true | |||
| system.servers.num = $NODE_SIZE | |||
| system.servers.f = $FAULT_SIZE | |||
| system.totalordermulticast.timeout = 60000 | |||
| system.totalordermulticast.timeTolerance = 3000000 | |||
| system.totalordermulticast.maxbatchsize = 2000 | |||
| system.totalordermulticast.nonces = 10 | |||
| system.totalordermulticast.verifyTimestamps = false | |||
| system.communication.inQueueSize = 500000 | |||
| system.communication.outQueueSize = 500000 | |||
| system.communication.send.retryInterval = 2000 | |||
| system.communication.send.retryCount = 100 | |||
| system.communication.useSignatures = 0 | |||
| system.communication.useMACs = 1 | |||
| system.debug = 0 | |||
| system.shutdownhook = true | |||
| system.totalordermulticast.state_transfer = true | |||
| system.totalordermulticast.highMark = 10000 | |||
| system.totalordermulticast.revival_highMark = 10 | |||
| system.totalordermulticast.timeout_highMark = 200 | |||
| system.totalordermulticast.log = true | |||
| system.totalordermulticast.log_parallel = false | |||
| system.totalordermulticast.log_to_disk = true | |||
| system.totalordermulticast.sync_log = false | |||
| system.totalordermulticast.checkpoint_period = 1000 | |||
| system.totalordermulticast.global_checkpoint_period = 120000 | |||
| system.totalordermulticast.checkpoint_to_disk = false | |||
| system.totalordermulticast.sync_ckp = false | |||
| system.initial.view = $( IFS=$','; echo "${IDs[*]}" ) | |||
| system.ttp.id = 2001 | |||
| system.bft = true" >> peer$i/config/init/bftsmart.config | |||
| let "i++" | |||
| done | |||
| echo "" > nohup.out | |||
| ### start ledger init | |||
| i=0 | |||
| while(( $i<$NODE_SIZE )) | |||
| do | |||
| nohup expect -c " | |||
| set timeout 180 | |||
| spawn peer$i/bin/ledger-init.sh | |||
| expect \"*Any key to continue...*\" | |||
| send \"1\r\" | |||
| expect \"*Press any key to quit. *\" | |||
| send \"quit\r\" | |||
| expect eof | |||
| " & | |||
| let "i++" | |||
| done | |||
| tail -f nohup.out| while read line | |||
| nSize=0 | |||
| do | |||
| if [[ $line =~ "Update Ledger binding configuration success!" ]] | |||
| then | |||
| let "nSize++" | |||
| if [[ $line == $NODE_SIZE ]] | |||
| then | |||
| echo -e ".\c" | |||
| else | |||
| echo "" | |||
| echo "账本 "$k" 初始化完成" | |||
| break | |||
| fi | |||
| else | |||
| echo -e ".\c" | |||
| fi | |||
| done | |||
| let "k++" | |||
| sleep 1 | |||
| done | |||
| ``` | |||
| @@ -0,0 +1,295 @@ | |||
| ## SDK | |||
| `JD Chain`提供了`Java`和`Go`版本的`SDK`,此处以`Java`为例,`Go`版本参照[framework-go](https://github.com/blockchain-jd-com/framework-go) | |||
| `SDK`可执行示例代码参照[JD Chain Samples](https://github.com/blockchain-jd-com/jdchain/tree/master/samples) | |||
| ### 1. 连接网关 | |||
| 通过`GatewayServiceFactory`中静态方法连接网关: | |||
| ```java | |||
| static GatewayServiceFactory connect(NetworkAddress gatewayAddress) | |||
| static GatewayServiceFactory connect(NetworkAddress gatewayAddress, BlockchainKeypair userKey) | |||
| static GatewayServiceFactory connect(String gatewayHost, int gatewayPort, boolean secure) | |||
| static GatewayServiceFactory connect(String gatewayHost, int gatewayPort, boolean secure, BlockchainKeypair userKey) | |||
| ``` | |||
| 其中`BlockchainKeypair userKey`参数用于自动签署终端用户签名,可不传,不传`userKey`时需要在提交交易前调用签名操作,添加终端账户签名信息。 | |||
| 通过: | |||
| ```java | |||
| BlockchainService blockchainService = GatewayServiceFactory.connect(gatewayHost, gatewayPort, false).getBlockchainService(); | |||
| ``` | |||
| 创建区块连服务,常规情况`BlockchainService`**单例**即可。 | |||
| ### 2. 调用过程 | |||
| #### 2.1 操作 | |||
| 1. 新建交易: | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| ``` | |||
| 2. 操作: | |||
| ```java | |||
| BlockchainKeypair user = BlockchainKeyGenerator.getInstance().generate(); | |||
| // 注册用户 | |||
| txTemp.users().register(user.getIdentity()); | |||
| ``` | |||
| > 一笔交易中可以包含多个操作,所有操作类型参照[交易](transaction.md)文档。 | |||
| 3. 准备交易 | |||
| ```java | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ``` | |||
| 4. 终端用户签名 | |||
| ```java | |||
| ptx.sign(userKey); | |||
| ``` | |||
| > 若[连接网关](#1-连接网关)中传入了用户身份信息,且用户身份具备交易中包含的所有操作[权限](user.md),此处签名操作可省略。 | |||
| 5. 提交交易 | |||
| ```java | |||
| TransactionResponse response = ptx.commit(); | |||
| ``` | |||
| #### 2.2 查询 | |||
| `BlockchainService`中包含了所有链上数据的查询方法,直接使用即可: | |||
| ```java | |||
| LedgerInfo ledgerInfo = blockchainService.getLedger(ledger); | |||
| ``` | |||
| ### 3. 操作类型 | |||
| 按功能模块划分:`用户操作`,`数据账户操作`,`事件操作`,`合约操作`,`查询`。 | |||
| #### 3.1 用户操作 | |||
| 用户/角色/权限相关说明参照[用户](user.md)文档说明。 | |||
| 1. 注册用户 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 生成用户 | |||
| BlockchainKeypair user = BlockchainKeyGenerator.getInstance().generate(); | |||
| System.out.println("用户地址:" + user.getAddress()); | |||
| // 注册用户 | |||
| txTemp.users().register(user.getIdentity()); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| ``` | |||
| 2. 角色赋权 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 创建角色 MANAGER ,并设置可以写数据账户,能执行交易 | |||
| txTemp.security().roles().configure("MANAGER") | |||
| .enable(LedgerPermission.WRITE_DATA_ACCOUNT) | |||
| .enable(TransactionPermission.DIRECT_OPERATION); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| ``` | |||
| 3. 用户赋权 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 赋予用户 user MANAGER 角色,取消 ADMIN 角色,设置多角色策略策略为合并策略 | |||
| txTemp.security().authorziations().forUser(user.getAddress()).authorize("MANAGER").unauthorize("ADMIN").setPolicy(RolesPolicy.UNION); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| ``` | |||
| #### 3.2 数据账户操作 | |||
| 1. 创建数据账户 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 生成数据账户 | |||
| BlockchainKeypair dataAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
| System.out.println("数据账户地址:" + dataAccount.getAddress()); | |||
| // 注册数据账户 | |||
| txTemp.dataAccounts().register(dataAccount.getIdentity()); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| ``` | |||
| 2. 写KV | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // expVersion是针对此key的插入更新操作次数严格递增,初始为-1 | |||
| txTemp.dataAccount(Bytes.fromBase58("LdeNscE3MP9a1vgyVUg9LgxQx6yzkUEUS65Rn")).setInt64("key2", 1024, -1); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| ``` | |||
| #### 3.3 事件操作 | |||
| 1. 创建事件账户 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 生成事件账户 | |||
| BlockchainKeypair eventAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
| System.out.println("事件账户地址:" + eventAccount.getAddress()); | |||
| // 注册事件账户 | |||
| txTemp.eventAccounts().register(eventAccount.getIdentity()); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| ``` | |||
| 2. 发布事件 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // sequence是针对此消息name的序列严格递增,初始为-1,可通过查询事件名下最新事件获取序列参数 | |||
| txTemp.eventAccount(Bytes.fromBase58("LdeNiAPuZ5tpYZVrrbELJNjqdvB51PBpNd8QA")).publish("topic", "content", -1); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| ``` | |||
| 3. 监听事件 | |||
| ```java | |||
| // 监听系统事件,目前仅有新区快产生事件 | |||
| blockchainService.monitorSystemEvent(ledger, | |||
| SystemEvent.NEW_BLOCK_CREATED, 0, (eventMessages, eventContext) -> { | |||
| for (Event eventMessage : eventMessages) { | |||
| // content中存放的是当前链上最新高度 | |||
| System.out.println("New block:" + eventMessage.getSequence() + ":" + BytesUtils.toLong(eventMessage.getContent().getBytes().toBytes())); | |||
| } | |||
| }); | |||
| // 监听用户自定义事件 | |||
| blockchainService.monitorUserEvent(ledger, "LdeNr7H1CUbqe3kWjwPwiqHcmd86zEQz2VRye", "sample-event", 0, (eventMessage, eventContext) -> { | |||
| BytesValue content = eventMessage.getContent(); | |||
| switch (content.getType()) { | |||
| case TEXT: | |||
| case XML: | |||
| case JSON: | |||
| System.out.println(eventMessage.getName() + ":" + eventMessage.getSequence() + ":" + content.getBytes().toUTF8String()); | |||
| break; | |||
| case INT64: | |||
| case TIMESTAMP: | |||
| System.out.println(eventMessage.getName() + ":" + eventMessage.getSequence() + ":" + BytesUtils.toLong(content.getBytes().toBytes())); | |||
| break; | |||
| default: // byte[], Bytes | |||
| System.out.println(eventMessage.getName() + ":" + eventMessage.getSequence() + ":" + content.getBytes().toBase58()); | |||
| break; | |||
| } | |||
| }); | |||
| ``` | |||
| #### 3.4 合约操作 | |||
| 1. 部署合约 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 生成合约账户 | |||
| BlockchainKeypair contractAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
| System.out.println("合约地址:" + contractAccount.getAddress()); | |||
| // 部署合约 | |||
| txTemp.contracts().deploy(contractAccount.getIdentity(), FileUtils.readBytes("src/main/resources/contract-samples-1.4.2.RELEASE.car")); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| ``` | |||
| 2. 升级合约 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 解析已存在的合约身份信息 | |||
| BlockchainIdentity contractIdentity = new BlockchainIdentityData(KeyGenUtils.decodePubKey("7VeRCfSaoBW3uRuvTqVb26PYTNwvQ1iZ5HBY92YKpEVN7Qht")); | |||
| System.out.println("合约地址:" + contractIdentity.getAddress()); | |||
| // 部署合约 | |||
| txTemp.contracts().deploy(contractIdentity, FileUtils.readBytes("src/main/resources/contract-samples-1.4.2.RELEASE.car")); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| ``` | |||
| 3. 调用合约 | |||
| 基于动态代理方式合约调用,需要依赖合约接口: | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 一次交易中可调用多个(多次调用)合约方法 | |||
| // 调用合约的 registerUser 方法 | |||
| SampleContract sampleContract = txTemp.contract("LdeNr7H1CUbqe3kWjwPwiqHcmd86zEQz2VRye", SampleContract.class); | |||
| GenericValueHolder<String> userAddress = ContractReturnValue.decode(sampleContract.registerUser(UUID.randomUUID().toString())); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| Assert.assertTrue(response.isSuccess()); | |||
| // 获取返回值 | |||
| System.out.println(userAddress.get()); | |||
| ``` | |||
| 非动态代理方式合约调用,不需要依赖合约接口及实现: | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| ContractEventSendOperationBuilder builder = txTemp.contract(); | |||
| // 一次交易中可调用多个(多次调用)合约方法 | |||
| // 调用合约的 registerUser 方法,传入合约地址,合约方法名,合约方法参数列表 | |||
| builder.send("LdeNr7H1CUbqe3kWjwPwiqHcmd86zEQz2VRye", "registerUser", | |||
| new BytesDataList(new TypedValue[]{ | |||
| TypedValue.fromText(UUID.randomUUID().toString()) | |||
| }) | |||
| ); | |||
| PreparedTransaction ptx = txTemp.prepare(); | |||
| ptx.sign(adminKey); | |||
| TransactionResponse response = ptx.commit(); | |||
| Assert.assertTrue(response.isSuccess()); | |||
| // 解析合约方法调用返回值 | |||
| for (int i = 0; i < response.getOperationResults().length; i++) { | |||
| BytesValue content = response.getOperationResults()[i].getResult(); | |||
| switch (content.getType()) { | |||
| case TEXT: | |||
| System.out.println(content.getBytes().toUTF8String()); | |||
| break; | |||
| case INT64: | |||
| System.out.println(BytesUtils.toLong(content.getBytes().toBytes())); | |||
| break; | |||
| case BOOLEAN: | |||
| System.out.println(BytesUtils.toBoolean(content.getBytes().toBytes()[0])); | |||
| break; | |||
| default: // byte[], Bytes | |||
| System.out.println(content.getBytes().toBase58()); | |||
| break; | |||
| } | |||
| } | |||
| ``` | |||
| #### 3.5 查询 | |||
| 与[网关 API](api.md)所提供查询一致,参照[Query Samples](https://github.com/blockchain-jd-com/jdchain/blob/master/samples/sdk-samples/src/test/java/com/jdchain/samples/sdk/QuerySample.java) | |||
| @@ -0,0 +1,200 @@ | |||
| ## 架构设计 | |||
| ### 总体目标 | |||
| 区块链是一种新型分布式架构,以密码学和分布式技术为核心,无需借助“第三方” 就能在多个业务方之间进行安全、可信、直接的信息和价值交换。在这种点对点的信息和价值的交换中,区块链起到了“协议”的作用。基于这一视角,`JD Chain`的目标是实现一个面向企业应用场景的通用区块链框架系统,能够作为企业级基础设施,为业务创新提供高效、灵活和安全的解决方案。 | |||
| ### 设计原则 | |||
| 设计原则是系统设计和实现的第一价值观,从根本上指导技术产品的发展方向。京东区块链在技术规划和系统架构设计上,遵循以下设计原则: | |||
|  | |||
| 1. 面向业务 | |||
| “企业级区块链”的目标定位决定了系统的功能设计必须要从实际的业务场景出发,分析抽象不同业务领域的共性需求。京东的区块链应用实践案例涉及包括金融、供应链、电子存证、医疗、政务、公益慈善等众多领域,从中获得丰富的应用实践经验,这能够为京东区块链获得良好通用性提供设计输入和业务验证。 | |||
| 2. 模块化 | |||
| 企业应用场景的多样性和复杂性要求系统有良好的扩展性,遵循模块化的设计原则,可以在确保系统核心逻辑稳定的同时,对外提供最小的扩展边界,实现系统的高内聚低耦合。 | |||
| 3. 安全可审计 | |||
| 区块链的可信任需要在系统设计和实现上遵循安全原则,数据可审计原则,以及满足不同地区和场景的标准与合规要求,保障信息处理可满足机密性、完整性、可控性、可用性和不可否认性等要求。 | |||
| 4. 简洁与效率 | |||
| 简洁即高效,从设计到编码都力求遵循这一原则。采用简洁的系统模型可以提升易用性并降低分布式系统的实现风险。此外,在追求提升系统性能的同时,也注重提升应用开发和方案落地的效能。 | |||
| 5. 标准化 | |||
| 区块链作为一种点对点的信息和价值交换的“桥梁”,通过定义一套标准的操作接口和数据结构,能够提升多方业务对接的效率,降低应用落地的复杂度。遵循标准化原则,要求在系统设计时数据模型及操作模型独立于系统实现,让数据“系于链却独于链”,可在链下被独立地验证和运用,更好地支持企业进行数据治理,提升区块链系统的灵活性和通用性。 | |||
| ### 设计核心 | |||
| 区块链的核心可以归结为两点:运用密码算法保障信息的完整性与不可否认性;在上述基础上,运用共识协议使信息复制保存到不同业务方,实现多方对信息共同背书。基于这两点可定义出区块链的5个核心部分:密码算法、共识协议、数据账本模型、数据存储、`API`(应用编程接口Application Programing Interface,以下简称`API`)。围绕总体设计原则,`JD Chain`的设计思路如下: | |||
| 1. 密码算法 | |||
| 密码算法的选择需要满足安全和合规的要求,同时面临源自实际业务场景的多样性要求。`JD Chain`在密码方面的关键任务是设计可插拔的密码框架,定义标准的`SPI`(服务提供者接口Service Provider Interface, 以下简称`SPI`)。系统默认支持国密算法以满足合规要求。基于密码SPI可以快速适配其它的密码算法实现,支持多密码体系。`JD Chain`将提供具有隐私保护功能密码算法和安全协议,来满足具体应用与业务的需求。 | |||
| 2. 共识协议 | |||
| 共识协议的核心任务是保障区块链网络中有效节点的状态一致性。另外在选择共识协议时,还需要考虑业务场景中的安全性要求、时效性要求和节点规模等诸多因素。`JD Chain`在共识协议方面的关键任务是设计可插拔的共识框架,解耦共识协议与数据账本模型,定义标准的共识协议`SPI`,以满足业务场景的多样化需求。 | |||
| 3. 数据账本模型 | |||
| 数据账本的核心任务是对数据进行有效地组织和管理,因此,需要定义数据的结构和数据处理的操作模型。`JD Chain`的数据账本模型以“键值”结构来组织业务数据,定义标准的读写操作,记录数据变更历史,维护数据完整性与不可否认性,管理数据的存在性证明。 | |||
| 4. 数据存储 | |||
| 数据存储的核心任务是把数据账本高效地读写到持久化介质中。`JD Chain`把数据账本模型映射为“键值”结构,为数据的存储提供更好的伸缩性。另外,还定义了标准的持久化服务`SPI`,能够适配不同的数据库引擎,更好地复用企业现有的IT基础设施,满足企业的多样化需求。 | |||
| 5. API | |||
| `JD Chain`的`API`设计需要提供标准化的操作接口,考虑通讯协议和编程语言的广泛性,支持端到端的离线密码计算,向企业提供更安全可信和易用的编程接口。 | |||
| ### 功能模块 | |||
| JD Chain按功能层次分为4个部分:网关服务、共识服务、数据账本和工具包。 | |||
| 1. 网关服务 | |||
| `JD Chain`的网关服务是应用的接入层,提供终端接入、私钥托管、安全隐私和协议转换等功能。 | |||
| 终端接入是`JD Chain`网关的基本功能,在确认终端身份的同时提供连接节点、转发消息和隔离共识节点与客户端等服务。网关确认客户端的合法身份,接收并验证交易;网关根据初始配置文件与对应的共识节点建立连接,并转发交易数据。 | |||
| 私钥托管功能使共识节点可以将私钥等秘密信息以密文的形式托管在网关内,为有权限的共识节点提供私钥恢复、签名生成等服务。 | |||
| 安全隐私,一方面是网关借助具有隐私保护功能的密码算法和协议,来进行隐藏端到端身份信息,脱敏处理数据信息,防止无权限客户端访问数据信息等操作;另一方面,网关的隔离作用使外部实体无法干预内部共识过程,保证共识和业务之间的独立性。 | |||
| 网关中的协议转换功能提供了轻量化的HTTP Restful Service,能够适配区块链节点的`API`,实现各节点在不同协议之间的互操作。 | |||
| 数据浏览功能提供对链上数据可视化展示的能力。 | |||
| 2. 共识服务 | |||
| 共识服务是`JD Chain`的核心实现层,包括共识网络、身份管理、安全权限、交易处理、智能合约和数据检索等功能,来保证各节点间账本信息的一致性。 | |||
| `JD Chain`的共识网络采用多种可插拔共识协议,并加以优化,来提供确定性交易执行、拜占庭容错和动态调整节点等功能,进而满足企业级应用场景需求。按照模块化的设计思路,将共识协议的各阶段进行封装,抽象出可扩展的接口,方便节点调用。共识节点之间使用`P2P`网络作为传输通道来执行共识协议。 | |||
| 身份管理功能使`JD Chain`网络能够通过公钥信息来辨识并认证节点,为访问控制、权限管理提供基础身份服务。 | |||
| 安全权限指的是,根据具体应用和业务场景,为节点设置多种权限形式,实现指定的安全管理,契合应用和业务场景。 | |||
| 交易处理是共识节点根据具体的协议来对交易信息进行排序、验证、共识和结块等处理操作,使全局共享相同的账本信息的功能。 | |||
| 智能合约是`JD Chain`中一种能够自动执行的链上编码逻辑,来更改账本和账户的状态信息。合约内容包括业务逻辑、节点的准入退出和系统配置的变更等。此外,`JD Chain`采用相应的合约引擎来保证智能合约能够安全高效地执行,降低开发难度并增加扩展性。开发者可以使用该合约引擎进行开发和测试,并通过接口进行部署和调用。 | |||
| 数据检索能够为协助节点检索接口,来查询区块、交易、合约、账本等相关信息。 | |||
| 3. 数据账本 | |||
| 数据账本为各参与方提供区块链底层服务功能,包括区块、账户、配置和存储等。 | |||
| 区块是`JD Chain`账本主要组成部分,包含交易信息和交易执行状态的数据快照哈希值,但不存储具体的交易操作和状态数据。`JD Chain`将账本状态和合约进行分离,并约束合约对账本状态的访问,来实现数据与逻辑分离,提供无状态逻辑抽象。 | |||
| `JD Chain`通过细化账户分类、分级分类授权的方式,对区块链系统中的账户进行管理,达到逻辑清晰化、隔离业务和保护相关数据内容的目的。 | |||
| 配置文件包括密钥信息,存储信息以及共享的参与者身份信息等内容,使`JD Chain`系统中各节点能够执行诸如连接其他节点、验证信息、存储并更新账本等操作。 | |||
| 存储格式采用简洁的`KV`数据类型, 使用较为成熟的`NoSQL`数据库来实现账本的持久化存储,使区块链系统能够支持海量交易。 | |||
| 4. 工具包 | |||
| 节点可以使用`JD Chain`中提供的工具包获取上述三个层级的功能服务,并响应相关应用和业务。工具包贯穿整个区块链系统,使用者只需调用特定的接口即可使用对应工具。工具包包括数据管理、开发包(`SDK`)、安装部署和服务监控等。 | |||
| 上述三个功能层级都有对应的开发包,以接口形式提供给使用者,这些开发包包括密码算法、智能合约、数据检索的`SPI`等。 | |||
| 数据管理是对数据信息进行管理操作的工具包,这些管理操作包括备份、转移、导出、校验、回溯,以及多链情况下的数据合并、拆分等操作。 | |||
| 安装部署类工具包括密钥生成、数据存储等辅助功能,帮助各节点执行区块链系统。 | |||
| 服务监控工具能够帮组使用者获取即时吞吐量、节点状态、数据内容等系统运行信息,实现运维管理和实时监控。 | |||
| ### 部署模型 | |||
| 在企业的实际使用过程中,应用场景随着业务的不同往往是千差万别,不同的场景下如何选择部署模型,如何进行部署,往往是每个企业都会面临的实际问题。面对复杂多样的应用场景,`JD Chain`从易用性方面考虑,为企业应用提供了一套行之有效的部署模型解决方案。 | |||
|  | |||
| `JD Chain`通过节点实现信息之间的交互,不同类型的节点可以在同一物理服务器上部署运行。`JD Chain`中定义了三种不同类型的节点: | |||
| 客户端节点(`Client`):通过`JD Chain`的`SDK`进行区块链操作的上层应用; | |||
| 网关节点(`Gateway`):提供网关服务的节点,用于连接客户端和共识节点; | |||
| 共识节点(`Peer`):共识协议参与方,会产生一致性账本。 | |||
| 不同企业规模的应用,部署方案会有较大区别,`JD Chain`根据实际应用的不同规模,提供了面向中小型企业和大型企业的两种不同部署模型。 | |||
| 1. 中小企业应用部署模型 | |||
| `JD Chain`在不同的应用环境中可采用不同的部署模型。它的最简部署模型是`JD Chain`可正常运行的最低配置,在硬件条件满足的情况下,可以支持亿级交易,通常用于`Demo`实验或小型应用。 | |||
| 最简部署模型需要部署一个客户端节点、一个网关节点和多个共识节点(共识节点数量依赖于共识算法),如下所示: | |||
|  | |||
| 此外,`JD Chain`提供了数据服务功能作为可选项,通过安装数据服务组件可以进行数据的检索、汇总等功能。数据服务组件与共识节点部署在相同或不同服务器均可,由此最简部署可演化为如下图所示模型: | |||
|  | |||
| 随着应用级别的提升,对数据存储需求越来越大,每个共识节点可采用数据库集群的存储方式实现存储的平行化扩展。在这种方式下,可支持交易级别达到十亿(乃至更多),如下图所示: | |||
|  | |||
| 在某些中型(乃至大型)实际应用中,共识节点会由不同的业务方安装部署,从安全性和扩展性角度考虑,更推荐使用共识节点集群: | |||
|  | |||
| 2. 大型企业应用部署模型 | |||
| 在一些大型的企业应用中,实际的业务方关系、应用场景将非常复杂。在这类应用中,可能由很多不同类型的参与方、不同类型的终端接入区块链网络,甚至这些终端可以从任意授权的网关节点接入,它们之间也会存在各种复杂的逻辑关系。 下图是一个比较常见的大型企业应用部署模型,在这个模型中涉及到多种类型的参与方,不同的终端,不同的接入方式。 | |||
|  | |||
| ### 交易流程 | |||
| 本文档描述了溯源数据状态转换及共享的交易机制。该方案包括四个参与方,`A`、`B`、`C`、`D`。他们每个参与方都有一部分基础溯源数据,换句话说,每个参与方对应了溯源数据的不同流程,但是有序的流程。基本的流程可以暂时认为包括:生产、加工、运输、收货。 | |||
|  | |||
| #### 准备工作 | |||
| 1. 用户准备 | |||
| 每个参与方在实际的`JD Chain`使用过程中都对应了一个用户,参与方之间实际是通过对应用户进行交易处理。 | |||
| `JD Chain`利用用户的公私钥来标识用户,使用者可通过公私钥工具(`keygen.sh`) 生成一个用户: | |||
|  | |||
| 2. 账本初始化 | |||
| 所有参与方根据安装部署脚本达成共识,建立一条专门用户溯源数据的状态转换和数据共享的链(一个账本)。 | |||
| 四个参与方分别对应了整个溯源的四个环节,分别按照`A -> B -> C -> D`顺序进行数据状态变更及数据更新,每个参与方依赖于前一个参与方的数据状态对其进行更新和共享。 | |||
| #### 交易过程 | |||
| 每个参与方在实际处理过程都需要使用`Client`作为交易处理的载体,每个`Client`中都有该参与方在该链对应的用户。 | |||
| 以参与方A为示例对交流过程进行说明。每个参与方都是在其他参与方交易基础上进行数据共享和状态转换,这得益于`K-V-Version`的存储方式,可以将状态以`Version`的方式标记。 | |||
| 1. 启动交易 | |||
| 参与方A通过`Client`发送A所持有的生产数据。`Client`为实际业务系统,通过调用`JD Chain`提供的`SDK`(目前支持`Java`)将某溯源产品的生产数据封装为交易。该交易附有业务系统的签名信息,`SDK`将该交易封装成正确的请求格式,通过`Http`将其发送至`Gateway`节点。 | |||
|  | |||
| 2. 验证交易 | |||
| `Gateway`节点收到`Client`发送的交易后,首先对交易进行验证(包括交易的格式及签名信息),验证通过后`Gateway`节点会在原交易基础上附加自己的签名信息,然后将交易发送至`Peer`节点群进行共识。 | |||
|  | |||
| 3. 交易共识 | |||
| Peer节点群使用BFTSmart共识算法对提交的交易进行共识,该算法需要节点数为:`N = 3f + 1`(其中`N`为节点总数,`f`为可支持作恶节点数),因此共识过程至少需要`4`个节点参与。 | |||
| `Peer`节点群对交易共识成功后会输出排序后的交易列表,该交易列表对于每个`Peer`节点而言是平等的,换句话说,每个`Peer`节点都会接收到完全一致的按顺序的交易列表。 | |||
|  | |||
| 4. 交易应答 | |||
| `Peer`节点收到排序后交易后对其进行检查(包括合法性验证、验签等),检查通过后会根据实际结块规则生成区块并更新账本。然后将结块的信息作为应答发送至`Gateway`节点,进而发送至`Client`节点。 | |||
|  | |||
| #### 时序图 | |||
| 一种事务流程如下: | |||
| 1. `Client`调用`SDK`生成对应的交易请求,并通过`SDK`将该交易发送至`Gateway`(`HTTP`协议)。 | |||
| 2. `Gateway`会对交易进行合法性验证,并进行验签,然后使用自身节点的用户进行签名,再将交易以自定义协议(`TCP`)的方式发送至`Peer`节点群进行共识。 | |||
| 3. `Peer`节点群接收连续的交易并进行共识,共识通过后会输出排序后的交易列表至每个`Peer`节点。 | |||
| 4. 每个`Peer`节点对接收的交易列表进行合法性检查、验签,验证通过后会执行对应的交易并根据结块规则生成区块,更新账本。完成后会将应答(含结块信息)发送至对应`Gateway`,进而发送至`Client`。 | |||
|  | |||
| @@ -0,0 +1,478 @@ | |||
| ## 交易 | |||
| ### 1. 请求 | |||
| > `JD Chain`限制单笔交易数据大小不得超过 `4M` | |||
| ```java | |||
| /** | |||
| * 交易请求; | |||
| */ | |||
| @DataContract(code= DataCodes.TX_REQUEST) | |||
| public interface TransactionRequest { | |||
| /** | |||
| * 交易哈希; | |||
| */ | |||
| @DataField(order = 1, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getTransactionHash(); | |||
| /** | |||
| * 交易内容; | |||
| */ | |||
| @DataField(order = 2, refContract = true) | |||
| TransactionContent getTransactionContent(); | |||
| /** | |||
| * 终端用户的签名列表; | |||
| */ | |||
| @DataField(order = 3, list = true, refContract = true) | |||
| DigitalSignature[] getEndpointSignatures(); | |||
| /** | |||
| * 接入交易的节点的签名; | |||
| */ | |||
| @DataField(order=4, list=true, refContract=true) | |||
| DigitalSignature[] getNodeSignatures(); | |||
| } | |||
| ``` | |||
| 交易内容: | |||
| ```java | |||
| /** | |||
| * 交易内容; | |||
| */ | |||
| @DataContract(code = DataCodes.TX_CONTENT) | |||
| public interface TransactionContent { | |||
| /** | |||
| * 执行交易的账本地址; | |||
| * | |||
| * 注:除了账本的创世交易之外,任何交易的账本地址都不允许为 null; | |||
| */ | |||
| @DataField(order = 1, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getLedgerHash(); | |||
| /** | |||
| * 操作列表; | |||
| */ | |||
| @DataField(order = 2, list = true, refContract = true, genericContract = true) | |||
| Operation[] getOperations(); | |||
| /** | |||
| * 生成交易的时间;<br> | |||
| * 以毫秒为单位,表示距离 1970-1-1 00:00:00 (UTC) 的毫秒数;<br> | |||
| */ | |||
| @DataField(order = 3, primitiveType = PrimitiveType.INT64) | |||
| long getTimestamp(); | |||
| } | |||
| ``` | |||
| ### 2. 操作 | |||
| `Operation`接口实现类均为`JD Chain`交易中支持的操作类型 | |||
| #### 2.1 共识信息变更 | |||
| ```java | |||
| @DataContract(code= DataCodes.TX_OP_CONSENSUS_SETTINGS_UPDATE) | |||
| public interface ConsensusSettingsUpdateOperation extends Operation{ | |||
| /** | |||
| * 配置列表 | |||
| */ | |||
| @DataField(order = 0, primitiveType = PrimitiveType.BYTES, list = true) | |||
| Property[] getProperties(); | |||
| } | |||
| ``` | |||
| #### 2.2 合约部署 | |||
| ```java | |||
| @DataContract(code= DataCodes.TX_OP_CONTRACT_DEPLOY) | |||
| public interface ContractCodeDeployOperation extends Operation { | |||
| /** | |||
| * 合约账户信息 | |||
| */ | |||
| @DataField(order=2, refContract = true) | |||
| BlockchainIdentity getContractID(); | |||
| /** | |||
| * 合约代码字节 | |||
| */ | |||
| @DataField(order=3, primitiveType=PrimitiveType.BYTES) | |||
| byte[] getChainCode(); | |||
| /** | |||
| * 合约版本 | |||
| */ | |||
| @DataField(order=5, primitiveType=PrimitiveType.INT64) | |||
| long getChainCodeVersion(); | |||
| } | |||
| ``` | |||
| #### 2.3 合约调用 | |||
| ```java | |||
| @DataContract(code = DataCodes.TX_OP_CONTRACT_EVENT_SEND) | |||
| public interface ContractEventSendOperation extends Operation { | |||
| /** | |||
| * 合约地址; | |||
| */ | |||
| @DataField(order = 2, primitiveType = PrimitiveType.BYTES) | |||
| Bytes getContractAddress(); | |||
| /** | |||
| * 合约方法名; | |||
| * | |||
| * @return | |||
| */ | |||
| @DataField(order = 3, primitiveType = PrimitiveType.TEXT) | |||
| String getEvent(); | |||
| /** | |||
| * 合约方法调用参数; | |||
| * | |||
| * @return | |||
| */ | |||
| @DataField(order = 4, refContract = true) | |||
| BytesValueList getArgs(); | |||
| /** | |||
| * 合约版本; | |||
| */ | |||
| @DataField(order = 5, primitiveType = PrimitiveType.INT64) | |||
| long getVersion(); | |||
| } | |||
| ``` | |||
| #### 2.4 注册数据账户 | |||
| ```java | |||
| @DataContract(code= DataCodes.TX_OP_DATA_ACC_REG) | |||
| public interface DataAccountRegisterOperation extends Operation { | |||
| /** | |||
| * 数据账户信息; | |||
| */ | |||
| @DataField(order=1, refContract = true) | |||
| BlockchainIdentity getAccountID(); | |||
| } | |||
| ``` | |||
| #### 2.5 写KV操作 | |||
| ```java | |||
| @DataContract(code= DataCodes.TX_OP_DATA_ACC_SET) | |||
| public interface DataAccountKVSetOperation extends Operation { | |||
| /** | |||
| * 数据账户 | |||
| */ | |||
| @DataField(order=2, primitiveType=PrimitiveType.BYTES) | |||
| Bytes getAccountAddress(); | |||
| /** | |||
| * KV列表 | |||
| */ | |||
| @DataField(order=3, list=true, refContract=true) | |||
| KVWriteEntry[] getWriteSet(); | |||
| @DataContract(code=DataCodes.TX_OP_DATA_ACC_SET_KV) | |||
| public static interface KVWriteEntry{ | |||
| @DataField(order=1, primitiveType=PrimitiveType.TEXT) | |||
| String getKey(); | |||
| @DataField(order=2, refContract = true) | |||
| BytesValue getValue(); | |||
| @DataField(order=3, primitiveType=PrimitiveType.INT64) | |||
| long getExpectedVersion(); | |||
| } | |||
| } | |||
| ``` | |||
| #### 2.6 注册事件账户 | |||
| ```java | |||
| @DataContract(code = DataCodes.TX_OP_EVENT_ACC_REG) | |||
| public interface EventAccountRegisterOperation extends Operation { | |||
| @DataField(order = 2, refContract = true) | |||
| BlockchainIdentity getEventAccountID(); | |||
| } | |||
| ``` | |||
| #### 2.7 发布事件 | |||
| ```java | |||
| @DataContract(code = DataCodes.TX_OP_EVENT_PUBLISH) | |||
| public interface EventPublishOperation extends Operation { | |||
| /** | |||
| * 事件地址 | |||
| */ | |||
| @DataField(order = 1, primitiveType = PrimitiveType.BYTES) | |||
| Bytes getEventAddress(); | |||
| /** | |||
| * 事件列表 | |||
| */ | |||
| @DataField(order = 2, list = true, refContract = true) | |||
| EventEntry[] getEvents(); | |||
| @DataContract(code = DataCodes.TX_OP_EVENT_PUBLISH_ENTITY) | |||
| interface EventEntry { | |||
| @DataField(order = 1, primitiveType = PrimitiveType.TEXT) | |||
| String getName(); | |||
| @DataField(order = 2, refContract = true) | |||
| BytesValue getContent(); | |||
| @DataField(order = 3, primitiveType = PrimitiveType.INT64) | |||
| long getSequence(); | |||
| } | |||
| } | |||
| ``` | |||
| #### 2.8 账本初始化 | |||
| ```java | |||
| @DataContract(code= DataCodes.TX_OP_LEDGER_INIT) | |||
| public interface LedgerInitOperation extends Operation{ | |||
| /** | |||
| * 账本初始化配置 | |||
| */ | |||
| @DataField(order=1, refContract=true) | |||
| LedgerInitSetting getInitSetting(); | |||
| } | |||
| ``` | |||
| #### 2.9 参与方状态变更 | |||
| ```java | |||
| @DataContract(code= DataCodes.TX_OP_PARTICIPANT_STATE_UPDATE) | |||
| public interface ParticipantStateUpdateOperation extends Operation { | |||
| /** | |||
| * 参与方身份 | |||
| */ | |||
| @DataField(order = 0, refContract = true) | |||
| BlockchainIdentity getParticipantID(); | |||
| /** | |||
| * 新状态 | |||
| */ | |||
| @DataField(order = 1, refEnum = true) | |||
| ParticipantNodeState getState(); | |||
| } | |||
| ``` | |||
| #### 2.10 角色赋权 | |||
| ```java | |||
| @DataContract(code = DataCodes.TX_OP_ROLE_CONFIGURE) | |||
| public interface RolesConfigureOperation extends Operation { | |||
| /** | |||
| * 角色权限列表 | |||
| */ | |||
| @DataField(order = 2, refContract = true, list = true) | |||
| RolePrivilegeEntry[] getRoles(); | |||
| @DataContract(code = DataCodes.TX_OP_ROLE_CONFIGURE_ENTRY) | |||
| public static interface RolePrivilegeEntry { | |||
| /** | |||
| * 角色名 | |||
| */ | |||
| @DataField(order = 1, primitiveType = PrimitiveType.TEXT) | |||
| String getRoleName(); | |||
| /** | |||
| * 开启的账本权限列表 | |||
| */ | |||
| @DataField(order = 2, refEnum = true, list = true) | |||
| LedgerPermission[] getEnableLedgerPermissions(); | |||
| /** | |||
| * 关闭的账本权限列表 | |||
| */ | |||
| @DataField(order = 3, refEnum = true, list = true) | |||
| LedgerPermission[] getDisableLedgerPermissions(); | |||
| /** | |||
| * 开启的交易权限列表 | |||
| */ | |||
| @DataField(order = 4, refEnum = true, list = true) | |||
| TransactionPermission[] getEnableTransactionPermissions(); | |||
| /** | |||
| * 关闭的交易权限列表 | |||
| */ | |||
| @DataField(order = 5, refEnum = true, list = true) | |||
| TransactionPermission[] getDisableTransactionPermissions(); | |||
| } | |||
| } | |||
| ``` | |||
| #### 2.11 用户赋权 | |||
| ```java | |||
| @DataContract(code = DataCodes.TX_OP_USER_ROLES_AUTHORIZE) | |||
| public interface UserAuthorizeOperation extends Operation { | |||
| /** | |||
| * 用户角色列表 | |||
| */ | |||
| @DataField(order = 2, refContract = true, list = true) | |||
| UserRolesEntry[] getUserRolesAuthorizations(); | |||
| @DataContract(code = DataCodes.TX_OP_USER_ROLE_AUTHORIZE_ENTRY) | |||
| public static interface UserRolesEntry { | |||
| /** | |||
| * 用户地址; | |||
| */ | |||
| @DataField(order = 0, primitiveType = PrimitiveType.BYTES, list = true) | |||
| Bytes[] getUserAddresses(); | |||
| /** | |||
| * 要更新的多角色权限策略; | |||
| */ | |||
| @DataField(order = 2, refEnum = true) | |||
| RolesPolicy getPolicy(); | |||
| /** | |||
| * 授权的角色清单; | |||
| */ | |||
| @DataField(order = 3, primitiveType = PrimitiveType.TEXT, list = true) | |||
| String[] getAuthorizedRoles(); | |||
| /** | |||
| * 取消授权的角色清单; | |||
| */ | |||
| @DataField(order = 4, primitiveType = PrimitiveType.TEXT, list = true) | |||
| String[] getUnauthorizedRoles(); | |||
| } | |||
| } | |||
| ``` | |||
| #### 2.12 注册用户 | |||
| ```java | |||
| @DataContract(code = DataCodes.TX_OP_USER_REG) | |||
| public interface UserRegisterOperation extends Operation { | |||
| /** | |||
| * 用户身份信息 | |||
| */ | |||
| @DataField(order = 2, refContract = true) | |||
| BlockchainIdentity getUserID(); | |||
| } | |||
| ``` | |||
| ### 3. 结果 | |||
| 交易执行结果数据结构如下: | |||
| ```java | |||
| @DataContract(code = DataCodes.TX_RESULT) | |||
| public interface TransactionResult { | |||
| /** | |||
| * 交易哈希; | |||
| */ | |||
| @DataField(order = 1, primitiveType = PrimitiveType.BYTES) | |||
| HashDigest getTransactionHash(); | |||
| /** | |||
| * 交易被包含的区块高度; | |||
| */ | |||
| @DataField(order = 2, primitiveType = PrimitiveType.INT64) | |||
| long getBlockHeight(); | |||
| /** | |||
| * 交易的执行结果; | |||
| */ | |||
| @DataField(order = 3, refEnum = true) | |||
| TransactionState getExecutionState(); | |||
| /** | |||
| * 交易中操作的返回结果;顺序与操作列表的顺序一致; | |||
| */ | |||
| @DataField(order = 4, list = true, refContract = true) | |||
| OperationResult[] getOperationResults(); | |||
| /** | |||
| * 账本数据快照; | |||
| */ | |||
| @DataField(order = 5, refContract = true) | |||
| LedgerDataSnapshot getDataSnapshot(); | |||
| } | |||
| ``` | |||
| ### 4. 查询 | |||
| `SDK`查询交易详情数据使用`LedgerTransaction`接口实现 | |||
| #### 4.1 结构 | |||
| ```java | |||
| @DataContract(code = DataCodes.TX_RECORD) | |||
| public interface LedgerTransaction { | |||
| /** | |||
| * 交易请求; | |||
| */ | |||
| @DataField(order = 1, refContract = true) | |||
| TransactionRequest getRequest(); | |||
| /** | |||
| * 交易结果; | |||
| */ | |||
| @DataField(order = 2, refContract = true) | |||
| TransactionResult getResult(); | |||
| } | |||
| ``` | |||
| #### 4.2 解析 | |||
| - 成功/失败: | |||
| ```java | |||
| getResult().getExecutionState(); | |||
| ``` | |||
| `TransactionState.SUCCESS`为成功,其他失败。 | |||
| - 操作解析 | |||
| ```java | |||
| for(Operation operation : tx.getRequest().getTransactionContent().getOperations()) { | |||
| // 注册用户 | |||
| if(operation instanceof UserRegisterOperation) { | |||
| UserRegisterOperation userRegisterOperation = (UserRegisterOperation) operation; | |||
| // ... | |||
| // 注册数据账户 | |||
| } else if(operation instanceof DataAccountRegisterOperation) { | |||
| DataAccountRegisterOperation dataAccountRegisterOperation = (DataAccountRegisterOperation) operation; | |||
| // ... | |||
| } // ... | |||
| } | |||
| ``` | |||
| 上诉仅以注册用户/注册数据账户为例,其他操作类型嗯参照[所有操作类型](#2-操作)进行解析。 | |||
| @@ -0,0 +1,129 @@ | |||
| ## 用户 | |||
| `JD Chain`实现了基于角色和权限的用户账户管理体系。 | |||
| ### 1. 用户 | |||
| 可类比传统数据库的用户概念,`JD Chain`用户是接入`JD Chain`网络的必要身份,本质上由一对公私钥对标识,公钥和地址信息记录在账本用户数据集中。 | |||
| ### 2. 角色 | |||
| 角色名称不区分大小写,最长不超过20个字符,多个角色名称之间用半角的逗点`,`分隔 | |||
| 系统会预置一个默认角色`DEFAULT`,所有未指定角色的用户都以赋予该角色的权限,若初始化时未配置默认角色的权限,则为默认角色分配所有权限; | |||
| #### 2.1 多角色策略 | |||
| 表示如何处理一个对象被赋予多个角色时的综合权限,在`RolesPolicy`枚举中定义: | |||
| ```java | |||
| public enum RolesPolicy { | |||
| // 合并权限,综合权限是所有角色权限的并集,即任何一个角色的权限都被继承 | |||
| UNION((byte) 0), | |||
| // 交叉权限,综合权限是所有角色权限的交集,即只有全部角色共同拥有的权限才会被继承 | |||
| INTERSECT((byte) 1); | |||
| } | |||
| ``` | |||
| ### 3. 权限 | |||
| `JD Chain`权限设计分为两类:账本权限,交易权限。 | |||
| #### 3.1 账本权限 | |||
| 账本相关的权限,这些权限属于全局性的 | |||
| - `CONFIGURE_ROLES`配置角色 | |||
| - `AUTHORIZE_USER_ROLES`授权用户角色 | |||
| - SET_CONSENSUS 设置共识协议 | |||
| - SET_CRYPTO 设置密码体系 | |||
| - `APPROVE_TX`参与方核准交易,如果不具备此项权限,则无法作为节点签署由终端提交的交易 | |||
| - `CONSENSUS_TX`参与方共识交易 | |||
| - `REGISTER_PARTICIPANT`注册参与方 | |||
| - SET_USER_ATTRIBUTES 设置用户属性 | |||
| - `REGISTER_USER`注册用户 | |||
| - `REGISTER_EVENT_ACCOUNT`注册事件账户 | |||
| - `WRITE_EVENT_ACCOUNT`发布事件 | |||
| - `REGISTER_DATA_ACCOUNT`注册数据账户 | |||
| - `WRITE_DATA_ACCOUNT`写入数据账户 | |||
| - `REGISTER_CONTRACT`注册合约 | |||
| - `UPGRADE_CONTRACT`升级合约 | |||
| #### 3.2 交易权限 | |||
| 一个用户可以发起的交易类型 | |||
| - `DIRECT_OPERATION`交易中包含指令操作 | |||
| - `CONTRACT_OPERATION`交易中包含合约操作 | |||
| ### 4. 控制逻辑 | |||
| `JD Chain`[交易](transaction.md)执行前会验证交易的签名信息,签名主要包含**节点签名**和**终端用户签名**。 | |||
| #### 4.1 节点身份验证 | |||
| > 网关提交交易前,会使用网关配置文件中配置的公私钥信息所代表的节点用户,自动添加签名到节点签名列表中。 | |||
| `JD Chain`运行时网络执行交易前,要求节点签名用户至少有一个具有`LedgerPermission.APPROVE_TX`权限。 | |||
| #### 4.2 终端用户验证 | |||
| 提交交易前,要求添加终端用户签名信息。 | |||
| 执行到具体操作前会校验相应账本/交易权限,策略都是至少有一个终端用户包含操作权限。 | |||
| 例如:创建用户账户操作执行前会校验所有终端用户签名中的用户**至少有一个**包含`LedgerPermission.REGISTER_USER`权限。 | |||
| ### 5. SDK | |||
| 可在`ledger.init`中配置好角色权限,在组网成功后所配置的角色和权限信息会写入到相关账本中。 | |||
| 也可以通过以下相关`SDK`代码向运行中`JD Chain`网络执行用户相关操作: | |||
| #### 5.1 注册用户 | |||
| 默认使用`ed25519`编码 | |||
| 创建公私钥对: | |||
| ```java | |||
| BlockchainKeypair user = BlockchainKeyGenerator.getInstance().generate(); | |||
| ``` | |||
| 从已存在的公私钥恢复: | |||
| ```java | |||
| PubKey pubKey = KeyGenUtils.decodePubKey("7VeRLdGtSz1Y91gjLTqEdnkotzUfaAqdap3xw6fQ1yKHkvVq"); | |||
| PrivKey privKey = KeyGenUtils.decodePrivKey("177gjzHTznYdPgWqZrH43W3yp37onm74wYXT4v9FukpCHBrhRysBBZh7Pzdo5AMRyQGJD7x", "DYu3G8aGTMBW1WrTw76zxQJQU4DHLw9MLyy7peG4LKkY"); | |||
| BlockchainKeypair user = new BlockchainKeypair(pubKey, privKey); | |||
| ``` | |||
| 注册用户: | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| txTemp.users().register(user.getIdentity()); | |||
| ``` | |||
| #### 5.2 创建角色 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 创建角色 MANAGER ,并设置可以写数据账户,能执行交易 | |||
| txTemp.security().roles().configure("MANAGER") | |||
| .enable(LedgerPermission.WRITE_DATA_ACCOUNT) | |||
| .disable(LedgerPermission.REGISTER_USER) | |||
| .enable(TransactionPermission.DIRECT_OPERATION); | |||
| ``` | |||
| #### 5.3 用户赋权 | |||
| ```java | |||
| TransactionTemplate txTemp = blockchainService.newTransaction(ledger); | |||
| // 给用户设置 MANAGER 角色权限 | |||
| txTemp.security().authorziations().forUser(Bytes.fromBase58("LdeNr7H1CUbqe3kWjwPwiqHcmd86zEQz2VRye")).authorize("MANAGER").unauthorize("DEFAULT").setPolicy(RolesPolicy.UNION); | |||
| // 或者 | |||
| txTemp.security().authorziations().forUser(user.getIdentity()).authorize("MANAGER").unauthorize("DEFAULT").setPolicy(RolesPolicy.UNION); | |||
| ``` | |||