JD Chain私有区块链最小化布署

介绍

JDChain 是专为企业应用设计的区块链框架系统,适用多种通用业务场景,秉承简单易用、灵活高效的设计理念,满足企业积木化按需定制,让企业快速接入区块链世界,重塑商业未来。本例我们采用WEB管理工具进行布署,平台为CentOS 7.6

基本概念

系统结构

交易执行过程

SDK整体介绍

概念体系

下载

http://ledger.jd.com/developer.html

peer-1.5.0.RELEASE

gateway-1.5.0.RELEASE

peer-1.4.2.RELEASE

gateway-1.4.2.RELEASE

kvdb-1.1.2.RELEASE

用winscp上传到服务器/home/jdchain/

安装

1、解压缩

unzip jdchain-peer-1.5.0.RELEASE.zip -d /home/jdchain/peer1
unzip jdchain-gateway-1.5.0.RELEASE.zip -d /home/jdchain/gateway

2、将peer复制4份,分别为,并配置权限

cp /home/jdchain/peer1 /home/jdchain/peer2 -rf
cp /home/jdchain/peer1 /home/jdchain/peer3 -rf
cp /home/jdchain/peer1 /home/jdchain/peer4 -rf

chmod 775 /home/jdchain/peer1/bin/*.sh
chmod 775 /home/jdchain/peer2/bin/*.sh
chmod 775 /home/jdchain/peer3/bin/*.sh
chmod 775 /home/jdchain/peer4/bin/*.sh

3、修改节点端口为10011~10014

vim /home/jdchain/peer1/bin/manager-startup.sh
vim /home/jdchain/peer2/bin/manager-startup.sh
vim /home/jdchain/peer3/bin/manager-startup.sh
vim /home/jdchain/peer4/bin/manager-startup.sh

4、修改peer启动端口为10021~10024

vim /home/jdchain/peer1/bin/peer-startup.sh
vim /home/jdchain/peer2/bin/peer-startup.sh
vim /home/jdchain/peer3/bin/peer-startup.sh
vim /home/jdchain/peer4/bin/peer-startup.sh

5、开放防火墙,启动管理工具

firewall-cmd --add-port=10011-10014/tcp
firewall-cmd --add-port=10021-10024/tcp
firewall-cmd --add-port=10550/tcp
sh /home/jdchain/peer1/bin/manager-startup.sh
sh /home/jdchain/peer2/bin/manager-startup.sh
sh /home/jdchain/peer3/bin/manager-startup.sh
sh /home/jdchain/peer4/bin/manager-startup.sh

6、依次打开各节点WEB界面

http://192.168.10.8:10011/
http://192.168.10.8:10012/
http://192.168.10.8:10013/
http://192.168.10.8:10014/

7、生成公私钥

公私钥管理->生成公私钥

名称
jcpeer1
密码
1
随机数

(1)peer1

生成公钥为:
7VeRHRFgPzVG6azVoXNuft6EpdGPw8WCT32wQfZ9acutytCV
私钥为:
177gjxqZf53RWh8Vc3eoYURCtLqkf5XKvzEx5czmzyBfDVVjFre9d2JERDLGCMbG1QsQ7fK

(2)peer2

生成公钥为:
7VeR7Fb5dXJS3udx7PVwzbUErczaWH6HQmDoQRjye5LKx39U
私钥为:
177gjxRHSC7u7NVc3wD9HYtnwjF5DHdfY425hZY2HhrgGUn11tZDq5NnH2ftDPN4sziV1f1

(3)peer3

生成公钥为:
7VeRCSJqmm56ZfPFtV1EuAZzmamXfcRhwL93f5D2P3GYY6yP
私钥为:
177gjzsWQPeuFCsk16WhZPoBgpTQQRvWipCiN2VBGAwyo9jNotPM5nSu4fMozsCw5dUCfbD

(4)peer4

生成公钥为:
7VeRE4kN4HNeTpk5X1mZVNuFnPeNbZ8oEx7cW6h9RY8Ya13L
私钥为:
177gk1goAukdVgJCX4KV7RJmD3N1Te2WvcQ8ki4jFvQLd2Mi6R6LtKKpW3ihxnRwyoz92HF

8、生成帐本

我们选择peer1为协调方,其它为参与方,回到http://192.168.10.8:10011/

账本 -> 初始化账本

初始化账本操作类型:协调方
账本邀请码: 45421406
帐本名称:jdchain.ledger
共识协议:Bftsmart
密码算法:默认
参与方数量:4
共识节点信息:端口范围规划在10110-10140   (注意,不能设置为连续端口)
peer节点名称:a0.com - a3.com
初始化共识地址:端口范围规划在10041-10044  (注意,不能设置为连续端口)
数据库名称:rocksdb1-rocksdb4

暂时不要保存,切换到参与方节点,填写好信息暂不保存

账本邀请码: 45421406 (必须使用协调方的邀请码)
协调方地址:127.0.0.1:10011

四个节点全部填写完成后,先保存协调方节点,然后快速保存参与方节点。

此时页面下方会显示所有参与方信息


快速点击四个节点的开始按钮进行账本创建

静待初始化完成

9、启动节点

点击各节点 -> 账本 -> 查看账本 中的 “启动节点”按钮

10、安装网关

chmod 775 /home/jdchain/gateway/bin/*.sh
vim cd /home/jdchain/gateway/config/gateway.conf

查询peer1的公钥、私钥和解密密钥,写到下面的配置项

more /home/jdchain/peer1/config/keys/*
#网关的HTTP服务端口;
http.port=10550

#共识节点的服务地址(与该网关节点连接的Peer节点的IP地址);
peer.host=127.0.0.1
#共识节点的服务端口(与该网关节点连接的Peer节点的端口,即在Peer节点的peer-startup.sh中定义的端口);
peer.port=10011
#默认公钥的内容(Base58编码数据);
keys.default.pubkey=7VeRHvvx9E7FvnPRJko33YZvBgLrfP2dAhM5KA4vBkkhqQRU
#默认私钥的内容(加密的Base58编码数据);在 pk-path 和 pk 之间必须设置其一;
keys.default.privkey=177gjzf6e4g21xs7pJSGS4ScMrCRfnHVq63Rtu8T9FryQwiW667bXcW33Xr48w73gZWWnm5
#默认私钥的解码密码;
keys.default.privkey-password=8EjkXVSTxMFjCvNNsTo8RBMDEVQmk7gYkW4SCDuvdsBG

启动网关后,即可打开区块链浏览器查看相关信息。

sh /home/jdchain/gateway/bin/startup.sh

通过SDK操作

1、打开IDEA,新建一个Maven Quickstart项目

2、修改pom.xml引入区块链SDK

<?xml version="1.0" encoding="UTF-8"?>

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>jdchainconsole</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>jdchainconsole</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    <framework.version>1.5.0.RELEASE</framework.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.jd.blockchain</groupId>
      <artifactId>sdk-client</artifactId>
      <version>${framework.version}</version>
    </dependency>

    <dependency>
      <groupId>com.jd.blockchain</groupId>
      <artifactId>sdk-rpc</artifactId>
      <version>${framework.version}</version>
    </dependency>

    <dependency>
      <groupId>com.jd.blockchain</groupId>
      <artifactId>crypto-classic</artifactId>
      <version>${framework.version}</version>
    </dependency>

    <dependency>
      <groupId>com.jd.blockchain</groupId>
      <artifactId>crypto-sm</artifactId>
      <version>${framework.version}</version>
    </dependency>

    <dependency>
      <groupId>com.jd.blockchain</groupId>
      <artifactId>ledger-model</artifactId>
      <version>${framework.version}</version>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

3、COPY 示例代码到程序中,测试建立数据账本、向账本中写入值等SDK

package org.example;

import com.jd.blockchain.contract.ContractEventContext;
import com.jd.blockchain.contract.EventProcessingAware;
import com.jd.blockchain.contract.LedgerContext;
import com.jd.blockchain.crypto.*;
import com.jd.blockchain.ledger.*;
import com.jd.blockchain.sdk.BlockchainService;
import com.jd.blockchain.sdk.client.GatewayServiceFactory;
import utils.Bytes;
import utils.codec.Base58Utils;

/**
 * Hello world!
 *
 */
public class App 
{
    // 账本Hash
    protected static HashDigest ledger;
    protected static BlockchainService blockchainService;
    protected static BlockchainKeypair adminKey;
    public static void main( String[] args )
    {
        // 初始配置交易签名用户信息
        PubKey pubKey = KeyGenUtils.decodePubKey("7VeR8q2oLa5dBgcSKi8AZ8ojpyDcVPP3pnaa4BCC7Kom5xhC");
        PrivKey privKey = KeyGenUtils.decodePrivKey("177gjvw6ePEW5GhThsQFUNXwu16MkaPEh8TPiEXjmNMj25UJUycaaZCYQ8rkucmYTqrJQ6H", "8EjkXVSTxMFjCvNNsTo8RBMDEVQmk7gYkW4SCDuvdsBG");
        adminKey = new BlockchainKeypair(pubKey, privKey);
        // 读取网关配置
        String gatewayHost = "192.168.10.8";
        Integer gatewayPort = 10550;
        blockchainService = GatewayServiceFactory.connect(gatewayHost, gatewayPort, false, adminKey).getBlockchainService();
        String ledgerHash = "j5tos1z5oFXagUdJUuYjjjYC3rXYWn2kiDs4F4qVGBLz4N";
        ledger = Crypto.resolveAsHashDigest(Base58Utils.decode(ledgerHash));

        testSetKV();

        System.out.println( "Hello World!4444" );
    }
    public void getTransactionByContentHash(HashDigest sampleHash) {
        LedgerTransaction tx = blockchainService.getTransactionByContentHash(ledger, sampleHash);

    }
    // 数据上链
    public static void testSetKV() {
        // 新建交易
        TransactionTemplate txTemp = blockchainService.newTransaction(ledger);

        // 请正确填写数据账户地址
        // expVersion是针对此key的插入更新操作次数严格递增,初始为-1,再次运行本测试用例请修改该值,否则服务端将报版本冲突异常。
        txTemp.dataAccount(Bytes.fromBase58("LdeNve1fHLWCfRCm9J77pbWGDnR56xskKT3Mz"))
                .setText("key1", "文本值", -1)
                .setInt64("key2", 1024, -1)
                .setJSON("key3", "{\"Name\":\"JSON值\"}", -1)
                .setBytes("key4", Bytes.fromInt(2048), -1);
        // 交易准备
        PreparedTransaction ptx = txTemp.prepare();
        // 交易签名
        ptx.sign(adminKey);
        // 提交交易
        TransactionResponse response = ptx.commit();
        if (response.isSuccess()) {
            System.out.println(String.format("height=%d, ###OK#, contentHash=%s, executionState=%s",
                    response.getBlockHeight(),
                    response.getContentHash(), response.getExecutionState().toString()));
        } else {
            System.out.println(String.format("height=%d, ###exception#, contentHash=%s, executionState=%s",
                    response.getBlockHeight(),
                    response.getContentHash(), response.getExecutionState().toString()));
        }

    }
    // 注册数据帐户
    public static void testRegisterDataAccount() {
        // 新建交易
        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);
        System.out.println( "Hello World!2222" );
        // 提交交易
        TransactionResponse response = ptx.commit();
        if (response.isSuccess()) {
            System.out.println(String.format("height=%d, ###OK#, contentHash=%s, executionState=%s",
                    response.getBlockHeight(),
                    response.getContentHash(), response.getExecutionState().toString()));
        } else {
            System.out.println(String.format("height=%d, ###exception#, contentHash=%s, executionState=%s",
                    response.getBlockHeight(),
                    response.getContentHash(), response.getExecutionState().toString()));
        }
    }
    public static void testRegisterUserAndConfigRole() {
        // 新建交易
        TransactionTemplate txTemp = blockchainService.newTransaction(ledger);
        // 生成用户
        BlockchainKeypair user = BlockchainKeyGenerator.getInstance().generate();
        System.out.println("用户地址:" + user.getAddress());
        // 注册用户
        txTemp.users().register(user.getIdentity());

        // 创建角色 MANAGER
        txTemp.security().roles().configure("MANAGER")
                .enable(LedgerPermission.WRITE_DATA_ACCOUNT)
                .enable(TransactionPermission.DIRECT_OPERATION);

        // 设置用户角色权限
        txTemp.security().authorziations().forUser(user.getAddress()).authorize("MANAGER");
        System.out.println("完成11111111111");
        // 交易主恩贝
        PreparedTransaction ptx = txTemp.prepare();
        System.out.println("222222");
        // 交易签名
        ptx.sign(adminKey);
        System.out.println("333333");
        // 提交交易
        TransactionResponse response = ptx.commit();
        if (response.isSuccess()) {
            System.out.println(String.format("height=%d, ###OK#, contentHash=%s, executionState=%s",
                    response.getBlockHeight(),
                    response.getContentHash(), response.getExecutionState().toString()));
        } else {
            System.out.println(String.format("height=%d, ###exception#, contentHash=%s, executionState=%s",
                    response.getBlockHeight(),
                    response.getContentHash(), response.getExecutionState().toString()));
        }
    }
    public static boolean registerUser() {
        // 新建交易
        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();
        return response.isSuccess();
    }
    public static void createRole() {
        // 新建交易
        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();
        System.out.println("完成");
    }
    public static void configUserRole() {
        // 新建交易
        TransactionTemplate txTemp = blockchainService.newTransaction(ledger);

        // 给用户设置 MANAGER 角色权限
        txTemp.security().authorziations().forUser(Bytes.fromBase58("LdeNv7JLbcQidY98y2QYXK1dGmi589zjFrf4v")).authorize("MANAGER");
        // 交易准备
        PreparedTransaction ptx = txTemp.prepare();
        // 交易签名
        ptx.sign(adminKey);
        // 提交交易
        TransactionResponse response = ptx.commit();
        System.out.println("完成2");
    }
}

FAQ

查看账本的时候报错:java.lang.NoClassDefFoundError: sun/jvmstat/monitor/MonitoredHost

如果采用OracleJDK,需要把JDK中的tools.jar拷贝到各peer节点下的libs中。 OpenJDK不存在此问题。

此操作需要重启管理工具

sh /home/jdchain/peer1/bin/manager-shutdown.sh
sh /home/jdchain/peer2/bin/manager-shutdown.sh
sh /home/jdchain/peer3/bin/manager-shutdown.sh
sh /home/jdchain/peer4/bin/manager-shutdown.sh

sh /home/jdchain/peer1/bin/manager-startup.sh
sh /home/jdchain/peer2/bin/manager-startup.sh
sh /home/jdchain/peer3/bin/manager-startup.sh
sh /home/jdchain/peer4/bin/manager-startup.sh

账本初始化时间过长

要以尽量短的时间内点击“开始”按钮,否则需要清理配置后重新安装

SDK调用无响应

因为节点间通讯我们配置了一个共识服务端口,但服务会开启多一个端口去通讯,故不能把单机多节点的端口设置连续值。(例如上面我们间隔了10进行设置)

sh /home/jdchain/peer1/bin/peer-shutdown.sh
sh /home/jdchain/peer2/bin/peer-shutdown.sh
sh /home/jdchain/peer3/bin/peer-shutdown.sh
sh /home/jdchain/peer4/bin/peer-shutdown.sh

sh /home/jdchain/peer1/bin/peer-startup.sh
sh /home/jdchain/peer2/bin/peer-startup.sh
sh /home/jdchain/peer3/bin/peer-startup.sh
sh /home/jdchain/peer4/bin/peer-startup.sh