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区块链对于关键能力的定义是建立在深入理解和抽象各种多样化需求的基础上的。

二、对象模型

JD区块链的核心对象包括:

三、部署模型

  1. 总体部署

    deployment architecture

  2. 系统组件

    • 共识节点
    • 复制节点
    • SDK
    • 网关
    • 终端
  3. 配置和管理

四、账本结构

1. 账户生成算法

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<String> 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<String, PayloadMap> 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<String> 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<String> 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);
	}
}