附录

    CentOS环境安装Java

    Ubuntu环境安装Java

    1. sudo apt install -y default-jdk
    2. # 查询Java版本
    3. java -version

    1.2 Gradle部署

    此处给出简单步骤,供快速查阅。更详细的步骤,请参考。

    (1)从官网下载对应版本的Gradle安装包,并解压到相应目录

    1. mkdir /software/
    2. unzip -d /software/ gradleXXX.zip

    (2)配置环境变量

    1. export GRADLE_HOME=/software/gradle-4.10
    2. export PATH=$GRADLE_HOME/bin:$PATH

    (3)查看版本

    1. gradle -version
    • 1:执行shell脚本报错误”permission denied”或格式错误

      1. 赋权限:chmod + *.sh
      2. 转格式:dos2unix *.sh
    • 2:eclipse环境编译源码失败,错误提示如下:

    1. ...
    2. /data/temp/WeBASE-Front/src/main/java/com/webank/webase/front/performance/PerformanceService.java:167: error: cannot find symbol
    3. log.info("begin sync performance");
    4. ^
    5. symbol: variable log
    6. location: class PerformanceService
    7. Note: /data/temp/WeBASE-Front/src/main/java/com/webank/webase/front/contract/CommonContract.java uses or overrides a deprecated API.
    8. Note: Recompile with -Xlint:deprecation for details.
    9. Note: Some input files use unchecked or unsafe operations.
    10. Note: Recompile with -Xlint:unchecked for details.
    11. 100 errors
    12. > Task :compileJava FAILED
    13. FAILURE: Build failed with an exception.
    14. ...

    答:问题是不能编译Lombok注解 ,修改build.gradle文件,将以下代码的注释加上

    1. //annotationProcessor 'org.projectlombok:lombok:1.18.6'
    • 3:节点运行一段时间后新增了一个群组,前置查不到新群组的信息。

      答:调用 方法,即可手动更新。

    • 4:升级1.0.2版本时,数据库报错:

      答:将H2数据库删除(在h2目录下),或者配置新数据库名,在 application.yml 文件中的配置如下:

      1. spring:
      2. url: jdbc:h2:file:./h2/webasefront;DB_CLOSE_ON_EXIT=FALSE // 默认H2库为webasefront
      3. ...
    • 5:日志报以下错误信息:

      1. 2019-08-08 17:29:05.505 [pool-11-thread-1] ERROR TaskUtils$LoggingErrorHandler() - Unexpected error occurred in scheduled task.
      2. org.hyperic.sigar.SigarFileNotFoundException: 没有那个文件或目录
      3. at org.hyperic.sigar.FileSystemUsage.gather(Native Method) ~[sigar-1.6.4.jar:?]
      4. at org.hyperic.sigar.FileSystemUsage.fetch(FileSystemUsage.java:30) ~[sigar-1.6.4.jar:?]
      5. at org.hyperic.sigar.Sigar.getFileSystemUsage(Sigar.java:667) ~[sigar-1.6.4.jar:?]

      答:监控目录不存在,需配置节点所在磁盘目录,在 application.yml 文件中的配置如下:

      1. ...
      2. constant:
      3. monitorDisk: / // 要监控的磁盘目录,配置节点所在目录(如:/home
      4. ...
    • 6:启动报错“nested exception is javax.net.ssl.SSLException”:

    1. ...
    2. nested exception is javax.net.ssl.SSLException: Failed to initialize the client-side SSLContext: Input stream not contain valid certificates.

    答:CentOS的yum仓库的OpenJDK缺少JCE(Java Cryptography Extension),导致Web3SDK/Java-SDK无法正常连接区块链节点,因此在使用CentOS操作系统时,推荐使用OracleJDK

    • 7:启动报错“Processing bcos message timeout”
    1. ...
    2. [main] ERROR SpringApplication() - Application startup failed
    3. org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'contractController': Unsatisfied dependency expressed through field 'contractService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'contractService': Unsatisfied dependency expressed through field 'web3jMap'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'web3j' defined in class path resource [com/webank/webase/front/config/Web3Config.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.HashMap]: Factory method 'web3j' threw exception; nested exception is java.io.IOException: Processing bcos message timeout
    4. ...

    答:一些OpenJDK版本缺少相关包,导致节点连接异常。推荐使用。

    • 8:启动失败,日志却没有异常
    1. ===============================================================================================
    2. Starting Server com.webank.webase.front.Application Port 5002 ................................[Failed]. Please view log file (default path:./log/).
    3. Because port 5002 not up in 20 seconds.Script finally killed the process.
    4. ===============================================================================================

    答:确认机器是否满足硬件要求。机器性能过低会导致服务端口一定时间内没起来,脚本会自动杀掉进程。可以尝试手动修改dist目录下的start.sh脚本,将启动等待时间设置久一点(默认600,单位:秒),然后启动。

    1. ...
    2. ...
    • 9:启动报错SSLContext: null

    答:确保conf/目录下包含sdk证书; 若使用的是v1.5.0以前的版本,则需要保证ca.crt, node.crt, node.key;其中node.crt, node.key为sdk.crt, sdk.key复制并重命名得到;若使用v1.5.0及以上版本,则需要复制链的sdk目录下的所有文件(ca.crt, sdk.crt, sdk.key及gm文件夹)到前置服务的conf目录

    3.1. 导入私钥

    支持txt文件和pem文件导入测试用户的私钥信息

    导入.txt私钥内容格式示例:

    其中用户类型为0代表用户为WeBASE-Front的本地私钥用户,导入的私钥均为该类型;

    1. -----BEGIN PRIVATE KEY-----
    2. MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgC8TbvFSMA9y3CghFt51/
    3. XmExewlioX99veYHOV7dTvOhRANCAASZtMhCTcaedNP+H7iljbTIqXOFM6qm5aVs
    4. fM/yuDBK2MRfFbfnOYVTNKyOSnmkY+xBfCR8Q86wcsQm9NZpkmFK
    5. -----END PRIVATE KEY-----

    其中pem文件开头的-----BEGIN PRIVATE KEY-----\n和结尾的\n-----END PRIVATE KEY-----\n格式不可更改,否则将读取pem文件失败

    3.2. 导出私钥

    目前仅支持导出测试用户的txt格式私钥

    Java中如何使用导出的私钥

    以上文中的私钥加载:

    基于javasdk的私钥加载:

    1. @Test
    2. public void testCrypto() {
    3. // 1-国密,0-ECDSA
    4. CryptoSuite cryptoSuite = new CryptoSuite(1);
    5. CryptoKeyPair keyPair = cryptoSuite.createKeyPair("e843a542a7a8240f9c9e418b9517c2c8f4dc041a11a44e614a3b026c3588c188");
    6. System.out.println("privateKey: " + keyPair.getHexPrivateKey());
    7. System.out.println("address: " + keyPair.getAddress());
    8. System.out.println("publicKey: " + keyPair.getHexPublicKey());
    9. }

    基于web3sdk的私钥加载:

    1. @Test
    2. public void loadPrivateKeyTest() {
    3. CryptoSuite
    4. String privateKey = "71f1479d9051e8d6b141a3b3ef9c01a7756da823a0af280c6bf62d18ee0cc978";
    5. Credentials credentials = GenCredential.create(privateKey);
    6. // private key 实例
    7. BigInteger privateKeyInstance = credentials.getEcKeyPair().getPrivateKey();
    8. System.out.println(Numeric.toHexStringNoPrefix(privateKeyInstance));
    9. // public key 实例
    10. BigInteger publicKeyInstance = credentials.getEcKeyPair().getPublicKey();
    11. System.out.println(Numeric.toHexString(publicKeyInstance));
    12. // address 地址
    13. String address = credentials.getAddress();
    14. }

    在IDE中开发WeBASE-Front

    IDE配置

    • 由于项目依赖了lombok,需要在settings-build-compiler的Enable Annotation Processing设置中打钩
    • 本项目使用gradle进行构建,可以在settings-build-build tools-gradle中设置本地的gradle环境

    证书与项目配置

    • 需要在资源目录中创建conf目录
    • 将sdk中的所有证书文件拷贝到resources/conf目录
    • 修改application.yml中的sdk.ipsdk.channelPort

    WeBASE-Front采用 JPA + H2数据库 的方式保存数据

    • 源码查看各个数据表的内容:需要通过查看WeBASE-Front源码的各个包中带有@Entity注解的entity实体类;如,查看私钥数据表KeyStoreInfo则查看该文件com.webank.webase.front.keystore.entity.KeyStoreInfo.java
    • 通过H2控制台连接H2数据库

    • 同机H2访问:可以通过浏览器打开localhost:5002/WeBASE-Front/console,以默认配置为例填入连接参数
      • JDBC URL应填入file:../h2/webasefront;,与前置服务的application.yml中配置的spring.datasource.url对应
      • 若未设置用户名与密码,则默认用户名为sa,密码为空
    • 服务端H2访问:
      • 修改前置服务的application.yml中的spring.h2.console.settings.web-allow-others设为true,允许远端访问H2控制台
      • 重启前置服务
      • 访问{ip}:{port}/WeBASE-Front/console,参数填入方法同上

    使用swagger

    节点前置搭配了swagger,可用于直接调试接口,通过访问 即可访问前置的swagger页面

    ../../_images/swagger3.png

    在swagger页面中选中一个接口后,点击“Try it out”既可以开始调用了,输入框将提示入参的格式

    在v1.5.2后,WeBASE-Front中丰富了组装交易的接口,包括了本地签名组装交易接口/trans/convertRawTxStr/local和通过/trans/convertRawTxStr/withSign,下面以本地签名举例(接口的具体入参可参考对应接口文档)。

    本地签名组装交易需要填入的参数包含合约地址、函数名及函数入参、群组ID和WeBASE-Front的私钥用户地址等,如下所示

    1. {
    2. "user":"0x2db346f9d24324a4b0eac7fb7f3379a2422704db",
    3. "contractName":"HelloWorld",
    4. "contractAddress":"dasdfav23rf213vbcdvadf3bcdf2fc23rqde",
    5. "funcName":"set",
    6. "contractAbi":[{"constant":true,"inputs":[],"name":"getVersion","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStorageCell","outputs":[{"name":"","type":"string"},{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"n","type":"string"}],"name":"setVersion","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"storageHash","type":"string"},{"name":"storageInfo","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}],
    7. "funcParam":["Hi,Welcome!"],
    8. "groupId" :"1",
    9. "useCns": false
    10. }

    调用组装交易接口后,接口将返回签名后的交易体编码值(在测试接口时,可以通过节点前置的Swagger直接发起请求):

    1. 0xf9012da001071041dddc1b3c553b48c0fbefecc07f3812f5ce4004d47708f1c3342844db018405f5e10082029d94e10441d9179cf0424aae808b51bc85dcbbfe144780b8643590b49f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000033333330000000000000000000000000000000000000000000000000000000000010180b84083bb5313e3dd7825b8b3e32d73aa8aedf9f9a8fcf435e5c37edfe4645c1af4211c12e1368024336a576f26ed624407da0b94e0bc5760514543c0b7a38fa03a7da0972843d0879ffdbdae733e8707896a532e5e1a3c7262cb84db657dd34f09111ba0786106465fe0fd2383588693cafef8934df62b188c6bb5a74eb6b9f23adaba32

    通过已签名交易发送接口/trans/signed-transaction将交易编码值发到链上,接口将返回交易回执,可根据交易回执的status判断交易是否成功

    **注:**若发起的是查询交易,除了上述接口的两阶段调用方法之外,还可以使用/trans/encodeFunction获取encodedFunction值。 根据入参要求调用已编码查询交易发送接口/trans/query-transaction,即可发送查询交易,接口将返回查询的返回值。

    在某些业务场景中,应用层需要实时获取链上的事件,如出块事件、合约Event事件等。应用层通过WeBASE连接节点后,由于无法和节点直接建立长连接,难以实时获取链上的消息。

    支持通过消息队列(Message Queue)来获取WeBASE-Front(v1.2.3+)的链上事件的消息推送

    1. WeBASE-Front连接到MQ-Server(目前支持RabbitMQ-Server);
    2. WeBASE-Front接收节点的事件Push后,如出块通知,WeBASE-Front将出块消息发送到消息队列中;
    3. 区块链应用连接MQ-Server,获取消息队列中待消费的消息,即可获得事件通知;

    下面介绍如何搭建RabbitMQ的消息队列服务与WeBASE-Front的配置方法

    4.1 RabbitMQ消息队列事件通知

    安装RabbitMQ服务并启用管理功能

    启用消息队列的事件推送服务,需要

    • 安装RabbitMQ Server

    参考RabbitMQ官网的,安装并启动RabbitMQ-Server服务

    注:RabbitMQ依赖Erlang环境,可根据官网教程安装Erlang

    • 启动mq服务,并确保RabbitMQ Server服务所在主机的5672, 15672端口可访问;
    • 启用RabbitMQ的rabbitmq_managerment功能

    启用该功能可通过访问localhost:15672页面,可视化管理MQ的队列与用户;否则,需要通过rabbitmqctl命令行工具管理;

    启用方法:服务启动后,在mq所在主机运行以下命令,命令行显示启用成功即可:

    1. rabbitmq-plugins enable rabbitmq_management

    添加RabbitMQ管理员账户

    若启用了rabbitmq_managerment的功能,可在浏览器访问mq服务所在主机的ip:15672端口,如访问本机localhost:15672

    通过默认用户guest(密码也为guest)登录管理页,在Web页面上方的Admin项中,选择add user,新增tag为Administrator的管理员用户

    注: 可通过RabbitMQ的命令行工具,添加管理员账户(Administrator),具体可参考

    guest用户不支持远程登录Web管理页,如需远程登录管理页面,需要通过rabbitmqctl新增一个管理员用户

    WeBASE-Front的配置

    通过配置applcation.yml中spring-rabbitmq项,WeBASE-Front即可连接到RabbitMQ-Server,将出块通知与合约Event通知推送到消息队列中:

    需要配置mq服务所在主机与管理员账户密码

    1. spring:
    2. datasource:
    3. ...
    4. jpa:
    5. ...
    6. h2:
    7. ...
    8. rabbitmq:
    9. host: 127.0.0.1 # rabbitmq部署所在主机的ip
    10. port: 5672 # rabbitmq默认连接端口
    11. username: defaultAccount # 要求具有Administrator权限的用户,本地连接rabbitmq可用guest账户
    12. password: defaultPassword
    13. virtual-host: defaultVirtualHost # 消息队列和Exchange所在虚拟节点,默认为空或"/"
    14. publisher-confirm: true # 消息发布确认开启
    15. ssl:
    16. enabled: false # 是否启用ssl连接,默认false

    客户端(区块链应用/消息消费者)使用说明

    客户端开发流程
    • 申请账号:客户端用户提供自己客户端应用编号appId,向mq-server运维管理员申请MQ服务的账号(可设置账户名和密码、virtual host)。
    • 创建队列与赋予权限:运维管理员创建账号后,管理员以用户提供的客户端应用的appId为名字,创建一个该账户专属的队列,然后赋予该账户read其专属队列的权限( permission-read中设置)。
    • 客户端连接到MQ:用户根据运维管理员提供的MQ账户名和密码、virtual host、消息交换机名(exchangeName),将自己的区块链应用连接到相应队列中,获取消息推送。

    下面简单展示运维管理员通过RabbitMQ的Web工具管理MQ服务:

    创建MQ账户:

    ../../_images/add_user.png创建MQ账户

    赋予MQ账户访问appId队列的read权限: 赋予read权限

    创建以appId命名的队列

    ../../_images/add_queue.png创建appId队列

    客户端订阅事件推送流程:
    • 客户端调用WeBASE-Front节点前置服务接口(/event/newBlockEvent和),注册事件监听;接口内容请查看接口文档-事件通知

    用户调用注册事件接口之后,实际上WeBASE-Front将以appId+事件名+{randomString}的routingKey,将用户所拥有的的队列Queue绑定到对应的Exchange中:

    appId队列绑定到群组Exchange

    • 用户在客户端以用户名密码连接到对应的virtual host,监听自己队列的消息,接收到消息后解析处理;

    客户端获取事件通知过程需如上进行配置,可参考的消费者客户端的代码实现(Dev分支)

      1. 配置文件解析