[TOC] #JD区块链 0.5.0-SNAPSHOT ------------------------------------------------------------------------ ### 版本修订历史
版本号 作 者 修改日期 备 注
0.0.6 黄海泉 2017-11-10 定义JD区块链项目的目标与关键能力;
定义JD区块链的核心对象模型;
定义“账户”的生成算法和“区块/交易/操作/账户”的关键属性;
描述了编程接口的示例代码;
0.0.7 黄海泉 2017-11-17 丰富了对“节点共识”、“节点分区”两项关键能力的详细描述;
0.0.8 黄海泉 2018-07-17 增加部署图;增加智能合约开发的示例;
------------------------------------------------------------------------ ## 一、概述 JD区块链项目的目标是提供一个面向广泛的应用场景、满足企业核心需求的灵活和易用的区块链系统。 以下是 JD 区块链用以满足企业核心需求的关键能力,也是显著区别于其它区块链的重要特征: - 快速共识 - 节点分区 - 并行多账本 - 大数据伸缩存储 - 条件检索 - 面向对象的合约代码编程模型 - 节点快速部署 - 多终端灵活接入 - 分布式自治的账户权限管理模型 JD区块链对于关键能力的定义是建立在深入理解和抽象各种多样化需求的基础上的。 - 快速共识(Efficient Consensus) 我们认为,“快速”不仅仅体现在“用更短时间”达成共识,还要体现在交易(Transaction)要得到可靠地执行。 需要在算法和实现层面做出保障,确保所有提交的合法的交易会被系统在一个“确定性”和“足够短”的时间之内被严格地执行,系统不主动作出随机性地丢弃(不包括系统故障因素)。注:POW类算法产生的链分叉处理是一种系统随机性地丢弃交易的行为。 从使用者的视角来看,这种能力就是区块链系统的“可靠性”,这对于企业、金融场景而言尤其重要。 - 节点分区(Peer Partition) “分区”是一种分布式系统架构原则,通过将大范围目标按照某种相似特征分隔为一个个小范围目标,分别进行更高效地处理,这能从整体上提升整个系统的处理能力。 区块链系统也是一种分布式系统,沿用“分区”的思想这点来自以往的系统架构实践的经验,是有可能让区块链系统获得可媲美现有系统的处理能力。这种能力将可以无障碍地把区块链在应用于广泛的企业场景中。 在此,我们所说的“节点分区(Peer Partition)” 是共识过程中的物理通讯层面的分区,在共识过程中只有相关的物理节点才会建立通讯链路并复制和存储共识的状态数据。在一个区块链系统中,可以从节点全集中选择一个或多个节点子集,分别组成一个或多个节点分区(Peer Partition) - 并行多账本 账本(Ledger) - 大数据伸缩存储 - 条件检索 - 面向对象的合约代码编程模型 - 节点快速部署 - 多终端灵活接入 - 分布式自治的账户权限管理模型 ## 二、对象模型 JD区块链的核心对象包括: - 账本(Ledger) 一份账本(Ledger)实质上是由两部分组成: - 一组以“账户(Account)”表示的状态数据; - 以“区块的链条(Block-chain)”表示的状态数据的变更历史; JD区块链的“账本(Ledger)”是一个最顶层的管理数据的逻辑单元。在一个区块链节点构成的共识网络中,可以维护多套并行的“账本(Ledger)”。 - 账户(Account) - 在JD区块链中,账户(Account)被设计为包含“身份(Identity)”、“权限(Privilege)”、“状态(State)”、“控制规则(Control Rule)” 这4种属性的对象。 - 其中,“身份(Identity)”、“权限(Privilege)”这两种属性是一个面向应用的系统的基本功能; - “状态(State)”、“控制规则(Control Rule)” 这两种属性这是一个具备“图灵完备性”的系统的基本属性,使系统可以处理任意多样化的任务; - 在这里,“身份(Identity)”是一个抽象表述,其形式上就是一个“区块链地址(Address)”和相应的“非对称秘钥钥对(KeyPair)”/证书。 简单来说,一个“账户(Account)”就是一个区块链地址、公私钥对以及一套的权限配置. - 一个“账户(Account)”的“状态(State)”、“控制规则(Control Rule)” 这2种属性则是可选的,这提供了不同于其它区块链的账户/合约的一种新的使用方式,即一种数据和逻辑分离的应用设计思路(这在传统的web应用编程中被称为“贫血模型”)。同时,也意味着一个特定“账户”中的数据状态是可以跨账户/合约代码进行共享访问的。 - 从应用的视角来看,对“账户”的使用方式可以有几种模式: - 用于表示业务角色/用户: - 用于表示业务数据:仅有“状态(State)”没有“控制规则(Control Rule)”的账户,可称为数据账户,就有些类似于关系数据库中的“表(Table)”的作用,不同业务类别的数据则使用不同的账户来管理。 - 用于表示业务逻辑:仅有“控制规则(Control Rule)”没有“状态(State)”的账户,即所谓的合约账户,可表示某种用于处理数据的通用逻辑,可被授权访问特定的数据账户。 - 区块(Block) 在概念上,与通常所说的区块的概念是一致的。 在实现上,一个区块的主要内容是包含了某一时刻提交的所有交易以及交易执行之后的状态数据的快照的hash,而不存储具体的交易操作和状态数据。 - 交易(Transaction) 在概念上,与通常所说的Transaction的概念是一致的,表示一组需要原子执行的操作。 - 操作(Operation) 操作是针对“账户(Account)”的“写”指令,包括以下几类: - 注册账户 - 更新账户的状态数据 - 更新账户的合约代码定义 - 调用账户的合约代码 - 合约代码(Contract Code) 合约代码是一段用于对“账户(Account)”的状态数据执行操作的代码程序。 ## 三、部署模型 1. 总体部署 ![deployment architecture](docs/images/deployment.png) 2. 系统组件 - 共识节点 - 复制节点 - SDK - 网关 - 终端 3. 配置和管理 ## 四、账本结构 ### 1. 账户生成算法 - 公私钥对 - 算法:默认ED25519 ,支持 SM2、CA; - 公钥存储格式:版本 + 算法标识 + 公私钥原始内容 + 校验码 - 字符编码方式:Base64 - 地址 - 算法 - 给定公钥 P (或由私钥 R 算出公钥 P) - 中间值 H1 = SHA256( P ) - 中间值 H2 = RIPEMD-160( H1 ) - 中间值 X = 版本 + 公钥算法标识 + H2 - 校验和 C = 前4字节( SHA256( SHA256( X )) ) - 地址 Address = Base58( X + C ) ### 2. 区块
属性 名称 说明
BlockHash 当前区块 hash 对区块中除此之外的其它所有属性一起进行哈希运算生成
BlockVersion 区块版本 表示区块-交易的属性结构的版本号;
PreviousBlockHash 上一区块 hash
BlockNumber 区块高度 区块高度是一个区块在链中的序号;
创始区块的高度为 0,每个新区块的高度依次递增;
AccountHash 账户树hash 账户的 Merkle Tree 根的 hash
AccountCount 账户数量 区块生成时账本中的全部账户的总数
TxTreeHash 交易树 hash 本区块的交易集合的 Merkle Tree 根的 hash
TxCount 区块交易数量 当前区块包含的交易的数量;
TxTotalCount 账本交易总数 截止到当前区块为止当前账本的所有交易的总数量;
CloseTime 区块关闭时间 生成当前区块时的区块链节点的网络时间;
### 3. 交易
属性 名称 说明
Hash 当前交易 hash 对交易中除此之外的其它所有属性一起进行哈希运算生成
LedgerNumber 区块高度 交易被包含的区块高度
BlobHash 交易数据块hash 交易的数据块是交易的原始数据,包含客户端提交的交易的全部操作及其参数;
交易的参与者需要使用私钥对交易数据块进行签名;
Operations 操作列表 交易的操作列表;
Sponsor 交易发起人 交易发起人的账户地址;
SequenceNumber 交易序号 交易序号记录了一个特定的发起人的交易的顺序号,等同于该发起人历史上发起的交易的总数;
Signatures 签名列表 由交易发起人和其它参与者对交易数据块的签名的列表;
Result 交易结果 0 - 表示执行成功;非零表示执行失败;
注:最终的账本只包含成功的交易;
### 4. 操作
属性 名称 说明
OpType 操作类型 一级操作类型包括:注册账户、配置权限、写入键值数据、写入对象数据、定义合约代码、调用合约代码;
“键值数据写入”操作的子操作类型包括:填入键值、移除键、数值增加、数值减少;
“对象数据写入”操作的自操作类型包括:插入对象、更新对象、移除对象;
Args 参数列表 与操作类型相对应的参数列表;
SubOps 子操作列表 “子操作”是“操作”的递归定义,由“操作类型”来标识;
### 5. 账户
属性 名称 说明
Address 地址 账户的唯一标识
RegNumber 注册号 账户被注册到区块链的区块高度;
TxSquenceNumber 交易序列号 由账户发起的交易的序列号,初始为 0,账户每发起一个交易则增加1;
ModelVersion 账户模型版本 表示构成一个账户结构的属性模型的程序版本号;
Version 账户版本 初始为 0,对账户的每一次变更(包括对权限设置、状态和合约代码的变更)都会使账户状态版本增加 1 ;
注:交易序号的改变不会导致账户版本的增加;
PrivilegeHash 权限 hash 权限树的根hash;
PrivilegeVersion 权限版本 初始为 0, 每次对权限的变更都导致版本号加 1;
StateType 状态类型 账户的状态类型有3种:空类型(NIL);键值类型;对象类型;
StateVersion 状态版本 账户的状态类型有3种:空类型(NIL);键值类型;对象类型;
StateHash 状态哈希 数据状态的 merkle tree 的根hash;
CodeHash 合约代码哈希 由“账户地址+合约代码版本号+合约代码内容”生成的哈希;
CodeVersion 代码版本 初始为 0,每次对代码的变更都使版本加 1 ;
## 五、编程接口 ### 1. 服务连接 // 区块链共识域; String realm = "SUPPLY_CHAIN_ALLIANCE"; // 节点地址列表; String[] peerIPs = { "192.168.10.10", "192.168.10.11", "192.168.10.12", "192.168.10.13" }; // 客户端的认证账户; String clientAddress = "kkjsafieweqEkadsfaslkdslkae998232jojf=="; String privKey = "safefsd32q34vdsvs"; // 创建服务代理; BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); ### 2. 账户注册 // 创建服务代理; BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); // 在本地定义注册账号的 TX; String sponsorAddress = "kFGeafiafEeqEkadsfaslkdslkae99ds66jf=="; String sponsorPrivKey = "privKkjwlkejflkjdsfoiajfij329323=="; TransactionTemplate txTemp = service.newTransaction(sponsorAddress); //-------------------------------------- // 区块链秘钥生成器;用于在客户端生成账户的公私钥和地址; BlockchainKeyGenerator generator = BlockchainKeyGenerator.getInstance(); BlockchainKeyPair bcKey1 = generator.generate(KeyType.ED25519); BlockchainKeyPair bcKey2 = generator.generate(KeyType.ED25519); String exchangeContractScript = "function(){}"; // 注册账户; txTemp.registerAccount() .register(bcKey1, AccountStateType.MAP, exchangeContractScript) .register(bcKey2, AccountStateType.OBJECT, null); //-------------------------------------- // TX 准备就绪; PreparedTransaction prepTx = txTemp.prepare(); // 使用私钥进行签名; prepTx.sign(sponsorAddress, sponsorPrivKey); // 提交交易; prepTx.commit(); ### 3. 权限设置 // 创建服务代理; BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); // 在本地定义注册账号的 TX; String sponsorAddress = "kFGeafiafEeqEkadsfaslkdslkae99ds66jf=="; String sponsorPrivKey = "privKkjwlkejflkjdsfoiajfij329323=="; TransactionTemplate txTemp = service.newTransaction(sponsorAddress); //-------------------------------------- // 配置账户的权限; String walletAccount = "Kjfe8832hfa9jjjJJDkshrFjksjdlkfj93F=="; String user1 = "MMMEy902jkjjJJDkshreGeasdfassdfajjf=="; String user2 = "Kjfe8832hfa9jjjJJDkshrFjksjdlkfj93F=="; // 配置: // “状态数据的写入权限”的阈值为 100; // 需要 user1、user2 两个账户的联合签名才能写入; // 当前账户仅用于表示一个业务钱包,禁止自身的写入权限,只能由业务角色的账户才能操作; txTemp.configPrivilege(walletAccount) .setThreshhold(PrivilegeType.STATE_WRITE, 100) .enable(PrivilegeType.STATE_WRITE, user1, 50) .enable(PrivilegeType.STATE_WRITE, user2, 50) .disable(PrivilegeType.STATE_WRITE, walletAccount); //-------------------------------------- // TX 准备就绪; PreparedTransaction prepTx = txTemp.prepare(); // 使用私钥进行签名; prepTx.sign(sponsorAddress, sponsorPrivKey); // 提交交易; prepTx.commit(); ### 4. 写入数据 // 创建服务代理; BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); // 在本地定义注册账号的 TX; String sponsorAddress = "kFGeafiafEeqEkadsfaslkdslkae99ds66jf=="; String sponsorPrivKey = "privKkjwlkejflkjdsfoiajfij329323=="; TransactionTemplate txTemp = service.newTransaction(sponsorAddress); // -------------------------------------- // 将商品信息写入到指定的账户中; // 对象将被序列化为 JSON 形式存储,并基于 JSON 结构建立查询索引; String commodityDataAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; Commodity commodity1 = new Commodity(); Commodity commodity2 = new Commodity(); txTemp.updateObjects(commodityDataAccount) .insert(commodity1.getCode(), commodity1) .update(commodity2.getCode(), commodity2, true); // 在钱包账户以 KEY “RMB-ASSET” 表示一种数字资产,通过在一个 TX // 对两个账户的同一个资产数值分别增加和减少,实现转账的功能; String walletAccount1 = "MMMEy902jkjjJJDkshreGeasdfassdfajjf=="; String walletAccount2 = "Kjfe8832hfa9jjjJJDkshrFjksjdlkfj93F=="; txTemp.updateMap(walletAccount1).decreaseInt("RMB-ASSET", 1000); txTemp.updateMap(walletAccount2).increaseInt("RMB-ASSET", 1000); // -------------------------------------- // TX 准备就绪; PreparedTransaction prepTx = txTemp.prepare(); String txHash = prepTx.getHash(); // 使用私钥进行签名; prepTx.sign(sponsorAddress, sponsorPrivKey); // 提交交易; prepTx.commit(); ### 5. 查询数据 // 创建服务代理; BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); // 查询区块信息; // 区块高度; long ledgerNumber = service.getLedgerNumber(); // 最新区块; Block latestBlock = service.getBlock(ledgerNumber); // 区块中的交易的数量; int txCount = latestBlock.getTxCount(); // 获取交易列表; Transaction[] txList = service.getTransactions(ledgerNumber, 0, 100); // 根据交易的 hash 获得交易;注:客户端生成 PrepareTransaction 时得到交易hash; String txHash = "iikjeqke98321rjoijsdfa"; Transaction tx = service.getTransaction(txHash); // 获取数据; String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; Set objKeys = ArrayUtils.asSet(new String[] { "x001", "x002" }); PayloadMap payloadData = service.getPayload(commerceAccount, objKeys); AccountStateType payloadType = payloadData.getPayloadType(); long payloadVersion = payloadData.getPayloadVersion(); boolean exist = service.containPayload(commerceAccount, "x003"); // 按条件查询; // 1、从保存会员信息的账户地址查询; String condition = "female = true AND age > 18 AND address.city = 'beijing'"; String memberInfoAccountAddress = "kkf2io39823jfIjfiIRWKQj30203fx=="; PayloadMap memberInfo = service.queryObject(memberInfoAccountAddress, condition); // 2、从保存会员信息的账户地址查询; Map memberInfoWithAccounts = service.queryObject(condition); ### 6. 合约调用 // 创建服务代理; BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); // 发起交易; String sponsorAddress = "kFGeafiafEeqEkadsfaslkdslkae99ds66jf=="; String sponsorPrivKey = "privKkjwlkejflkjdsfoiajfij329323=="; TransactionTemplate txTemp = service.newTransaction(sponsorAddress); // -------------------------------------- // 一个贸易账户,贸易结算后的利润将通过一个合约账户来执行利润分配; // 合约账户被设置为通用的账户,不具备对贸易结算账户的直接权限; // 只有当前交易发起人具备对贸易账户的直接权限,当交易发起人对交易进行签名之后,权限被间接传递给合约账户; String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; // 处理利润分成的通用业务逻辑的合约账户; String profitDistributionContract = "AAdfe4346fHhefe34fwf343kaeER4678RT=="; //收益人账户; String receiptorAccount1 = "MMMEy902jkjjJJDkshreGeasdfassdfajjf=="; String receiptorAccount2 = "Kjfe8832hfa9jjjJJDkshrFjksjdlkfj93F=="; //资产编码; String assetKey = "RMB-ASSET"; //此次待分配利润; long profit = 1000000; //备注信息; Remark remark = new Remark(); String remarkJSON = SerializeUtils.serializeToJSON(remark); // 合约代码的参数表; String[] args = { commerceAccount, assetKey, profit+"", receiptorAccount1, receiptorAccount2, remarkJSON }; // 调用合约代码的分配操作; txTemp.executeScript(commerceAccount) .invoke("DISTRIBUTE", args); // -------------------------------------- // TX 准备就绪; PreparedTransaction prepTx = txTemp.prepare(); String txHash = prepTx.getHash(); // 使用私钥进行签名; prepTx.sign(sponsorAddress, sponsorPrivKey); // 提交交易; prepTx.commit(); ### 7. 事件监听 // 创建服务代理; BlockchainService service = BlockchainServiceFactory.createServiceProxy(realm, peerIPs, clientAddress, privKey); //监听账户变动; String walletAccount = "MMMEy902jkjjJJDkshreGeasdfassdfajjf=="; service.addBlockchainEventListener(BlockchainEventType.PAYLOAD_UPDATED.CODE, null, walletAccount, new BlockchainEventListener() { @Override public void onEvent(BlockchainEventMessage eventMessage, BlockchainEventHandle eventHandle) { //钱包余额; PayloadMap balancePayload = service.getPayload(walletAccount, "RMB-ASSET"); Long balance =(Long) balancePayload.get("RMB-ASSET"); if (balance != null) { //notify balance change; }else{ //wallet is empty and isn't listened any more; eventHandle.cancel(); } } }); //销毁服务代理; service.dispose(); ### 8. 合约开发 /** * 示例:一个“资产管理”智能合约的实现; * * 注: 1、实现 EventProcessingAwire 接口以便合约实例在运行时可以从上下文获得合约生命周期事件的通知; 2、实现 * AssetContract 接口定义的合约方法; * * @author huanghaiquan * */ public class AssetContractImpl implements EventProcessingAwire, AssetContract { // 资产管理账户的地址; private static final String ASSET_ADDRESS = "2njZBNbFQcmKd385DxVejwSjy4driRzf9Pk"; // 保存资产总数的键; private static final String KEY_TOTAL = "TOTAL"; // 合约事件上下文; private ContractEventContext eventContext; /** * ------------------- 定义可以由外部用户通过提交“交易”触发的调用方法 ------------------ */ @Override public void issue(long amount, String assetHolderAddress) { checkAllOwnersAgreementPermission(); // 新发行的资产数量; if (amount < 0) { throw new ContractError("The amount is negative!"); } if (amount == 0) { return; } // 校验持有者账户的有效性; BlockchainAccount holderAccount = eventContext.getLedger().getAccount(currentLedgerHash(), assetHolderAddress); if (holderAccount == null) { throw new ContractError("The holder is not exist!"); } // 查询当前值; Set keys = new HashSet<>(); keys.add(KEY_TOTAL); keys.add(assetHolderAddress); StateMap currStates = eventContext.getLedger().getStates(currentLedgerHash(), ASSET_ADDRESS, keys); // 计算资产的发行总数; StateEntry currTotal = currStates.get(KEY_TOTAL); StateEntry newTotal = currTotal.newLong(currTotal.longValue() + amount); // 分配到持有者账户; StateEntry holderAmount = currStates.get(assetHolderAddress); StateEntry newHodlerAmount = holderAmount.newLong(holderAmount.longValue() + amount); // 把数据的更改写入到账本; SimpleStateMap newStates = new SimpleStateMap(currStates.getAccount(), currStates.getAccountVersion(), currStates.getStateVersion()); newStates.setValue(newTotal); newStates.setValue(newHodlerAmount); eventContext.getLedger().updateState(ASSET_ADDRESS).setStates(currStates); } @Override public void transfer(String fromAddress, String toAddress, long amount) { if (amount < 0) { throw new ContractError("The amount is negative!"); } if (amount == 0) { return; } //校验“转出账户”是否已签名; checkSignerPermission(fromAddress); // 查询现有的余额; Set keys = new HashSet<>(); keys.add(fromAddress); keys.add(toAddress); StateMap origBalances = eventContext.getLedger().getStates(currentLedgerHash(), ASSET_ADDRESS, keys); StateEntry fromBalance = origBalances.get(fromAddress); StateEntry toBalance = origBalances.get(toAddress); //检查是否余额不足; if ((fromBalance.longValue() - amount) < 0) { throw new ContractError("Insufficient balance!"); } // 把数据的更改写入到账本; SimpleStateMap newBalances = new SimpleStateMap(origBalances.getAccount(), origBalances.getAccountVersion(), origBalances.getStateVersion()); StateEntry newFromBalance = fromBalance.newLong(fromBalance.longValue() - amount); StateEntry newToBalance = toBalance.newLong(toBalance.longValue() + amount); newBalances.setValue(newFromBalance); newBalances.setValue(newToBalance); eventContext.getLedger().updateState(ASSET_ADDRESS).setStates(newBalances); } }