WeCross Stub插件开发

本章内容介绍区块链接入WeCross的完整开发流程,用户可以根据本教程实现一个WeCross区块链Stub插件,通过该插件接入WeCross。

注解

  • Java编程语言

WeCross使用Java实现,接入的区块链需要支持Java版本的SDK,要求开发人员具备Java开发能力。

  • Gradle构建工具

WeCross组件目前使用Gradle进行构建,因此假定用户能够使用Gradle,maven能达到相同的效果,用户可以自行探索。

创建Gradle项目

Gradle依赖分类

  • Stub API定义
  • Java SDK,区块链Java版的SDK
  • 其他依赖,例如:toml解析工具
  1. // Gradle 依赖示例
  2. dependencies {
  3. // Stub接口定义Jar
  4. implementation 'com.webank:wecross-java-stub:1.0.0-rc2'
  5. // BCOS JavaSDK
  6. implementation 'org.fisco-bcos:web3sdk:2.4.0'
  7. // toml文件解析
  8. implementation
  9. 'com.moandjiezana.toml:toml4j:0.7.2'
  10. // 其他依赖
  11. }

新建Gradle工程,并且在build.gradle中添加依赖。

Gradle配置参考: WeCross-BCOS-Stub build.gradle

核心组件

Stub插件需要实现的组件接口:

  • StubFactory: 创建其他组件
  • Account: 账户,用于交易签名
  • Connection: 与区块链交互
  • Driver: 区块链数据的编解码

StubFactory

StubFactory功能

  • 添加@Stub注解,指定插件类型
  • 提供Account、Connection、Driver实例化入口

只有添加@Stub注解的插件才能被Wecross Router识别加载!

StubFactory接口定义

  1. public interface StubFactory {
  2.  
  3. /** 创建Driver对象 */
  4. public Driver newDriver();
  5.  
  6. /** 解析Connection配置stub.toml,创建Connection对象 */
  7. public Connection newConnection(String path);
  8.  
  9. /** 解析Account配置account.toml,创建Account对象 */
  10. public Account newAccount(String name, String path);
  11. }

BCOS Stub示例:

  1. /** @Stub注解,插件类型: BCOS2.0 */
  2. @Stub("BCOS2.0")
  3. public class BCOSStubFactory implements StubFactory {
  4.  
  5. @Override
  6. public Driver newDriver() {
  7. // 创建返回BCOSDriver对象
  8. }
  9.  
  10. @Override
  11. public Connection newConnection(String path) {
  12. // 解析stub.toml配置, 创建返回BCOSConnection对象
  13. }
  14.  
  15. @Override
  16. public Account newAccount(String name, String path) {
  17. // 解析account.toml账户配置,创建返回BCOSAccount对象
  18. }
  19. }

Account

Account包含账户私钥,用于交易签名。

接口定义

  1. public interface Account {
  2.  
  3. /** 账户名称 */
  4. String getName();
  5.  
  6. /** 账户类型 */
  7. String getType();
  8.  
  9. /** 账户公钥 */
  10. String getIdentity();
  11. }

Account由StubFactory对象newAccount接口创建,用户需要解析配置生成Account对象

  • Account newAccount(String name, String path)
    • name: 账户名称
    • path: 配置文件account.toml所在目录路径

账户配置文件位于conf/accounts/目录,可以配置多个账户,每个账户置于单独的子目录。

  1. # 目录结构, conf/accounts/账户名称/
  2. conf/accounts
  3. └── bcos # 账户名称: bcos
  4. ├── 0x4c9e341a015ce8200060a028ce45dfea8bf33e15.pem # BCOS私钥文件
  5. └── account.toml # account.toml配置文件
  1. # account.toml内容
  2. [account]
  3. type = "BCOS2.0" # 必须项,账户类型,与插件@Stub注解定义的类型保持一致
  4. accountFile = '0x4c9e341a015ce8200060a028ce45dfea8bf33e15.pem' # 配置的私钥文件名称

account.toml解析流程可以参考BCOS Stub account.toml解析

BCOS Stub示例

  1. public class BCOSAccount implements Account {
  2.  
  3. /** 名称 */
  4. private final String name;
  5.  
  6. /** 类型 */
  7. private final String type;
  8.  
  9. /** 公钥 */
  10. private final String publicKey;
  11.  
  12. /** BCOS私钥对象,交易签名,加载配置的私钥文件生成 */
  13. private final Credentials credentials;
  14.  
  15. public Credentials getCredentials() {
  16. return credentials;
  17. }
  18. /** 其他接口 */
  19. }

Connection

Connection用于

  • 为Driver提供统一发送接口,与区块链交互
  • 获取配置的资源列表,资源在Connection初始化时从配置文件加载

接口定义

  1. public interface Connection {
  2.  
  3. /** 发送接口请求给区块链 */
  4. Response send(Request request);
  5.  
  6. /** 获取资源列表,资源列表在stub.toml文件配置 */
  7. List<ResourceInfo> getResources();
  8. }

接口列表

  • getResources

获取资源列表,资源在Connection初始化时加载,表示Stub可以访问的区块链资源。

  • send

提供统一的发送请求接口给Driver使用,Driver设置请求类型并将参数序列化生成Request,调用send接口,返回Response对象,Response包含返回值、错误信息以及返回内容。

  1. /** Request请求对象,包含请求类型与请求内容 */
  2. public class Request {
  3.  
  4. /** 请求类型,用户自定义,区分不同的区块链请求 */
  5. private int type;
  6.  
  7. /** 请求内容,序列化的请求参数,用户自定义序列化方式 */
  8.  
  9. private byte[] data;
  10.  
  11. /** 请求资源 */
  12. private ResourceInfo resourceInfo;
  13. }
  14.  
  15. /** Response返回值对象,包含错误码、错误描述以及返回内容 */
  16. public class Response {
  17.  
  18. /** 返回状态 */
  19. private int errorCode;
  20.  
  21. /** 返回错误描述 */
  22. private String errorMessage;
  23.  
  24. /** 返回内容,序列化的返回参数,可以反序列化为返回对象 */
  25. private byte[] data;
  26. }
  27.  
  28. /** 资源对象 */
  29. public class ResourceInfo {
  30.  
  31. /** 资源名称 */
  32. private String name;
  33.  
  34. /** 资源类型,用户自定义 */
  35. private String stubType;
  36.  
  37. /** 额外属性,用户自定义 */
  38. private Map<Object, Object> properties = new HashMap<Object, Object>();
  39. }

Connection由StubFactory对象newConnection接口创建,解析配置生成Connection对象

  • Connnection newConnection(String path)
    • path: 配置文件stub.toml所在目录路径

插件配置stub.toml

  • 通用配置:插件名称、类型
  • SDK配置:初始化JavaSDK,与区块链交互
  • 资源列表:区块链可以访问的资源列表

插件配置默认位于chains/目录,可以配置多个stub,每个stub位于单独的子目录。

  1. # 目录结构, conf/chains/stub名称/
  2. conf/chains/
  3. └── bcos # stub名称: bcos
  4. └── stub.toml # stub.toml配置文件
  5. # 其他文件列表,比如:证书文件

stub.toml解析流程可以参考BCOS Stub stub.toml解析

BCOS示例

  1. /** Request type定义,用户自定义 */
  2. public class BCOSRequestType {
  3. // call
  4. public static final int CALL = 1000;
  5. // sendTransaction
  6. public static final int SEND_TRANSACTION = 1001;
  7. // 获取区块高度
  8. public static final int GET_BLOCK_NUMBER = 1002;
  9. // 获取BlockHeader
  10. public static final int GET_BLOCK_HEADER = 1003;
  11. // 获取交易Merkle证明
  12. public static final int GET_TRANSACTION_PROOF = 1004;
  13. }
  14.  
  15. public class BCOSConnection implements Connection {
  16. /** BCOS SDK对象,调用区块链接口 */
  17. private final Web3jWrapper web3jWrapper;
  18. /** 为Driver提供统一发送接口,根据不同请求类型分别与区块链完成交互 */
  19. @Override
  20. public Response send(Request request) {
  21. switch (request.getType()) {
  22. /** call请求 */
  23. case BCOSRequestType.CALL:
  24. /** handle call request */
  25. /** sendTransaction请求 */
  26. case BCOSRequestType.SEND_TRANSACTION:
  27. /** handle sendTransaction request */
  28. /** 获取区块头 */
  29. case BCOSRequestType.GET_BLOCK_HEADER:
  30. /** handle getBlockeHeader request */
  31. /** 获取块高 */
  32. case BCOSRequestType.GET_BLOCK_NUMBER:
  33. /** handle getBlockNumber request */
  34. /** 获取交易Merkle证明 */
  35. case BCOSRequestType.GET_TRANSACTION_PROOF:
  36. /** handle getTransactionProof request */
  37. default:
  38. /** unrecognized request type */
  39. }
  40. }
  41.  
  42. /** 获取资源列表 */
  43. @Override
  44. public List<ResourceInfo> getResources() {
  45. return resourceInfoList;
  46. }
  47. }

BCOS stub.toml示例

  1. [common] # 通用配置
  2. name = 'bcos' # 名称,必须项
  3. type = 'BCOS2.0' # 必须项,插件类型,与插件@Stub注解定义的类型保持一致
  4.  
  5. [chain] # BCOS链属性配置
  6. groupId = 1 # default 1
  7. chainId = 1 # default 1
  8.  
  9. [channelService] # BCOS JavaSDK配置
  10. caCert = 'ca.crt'
  11. sslCert = 'sdk.crt'
  12. sslKey = 'sdk.key'
  13. timeout = 300000 # ms, default 60000ms
  14. connectionsStr = ['127.0.0.1:20200', '127.0.0.1:20201', '127.0.0.1:20202']
  15.  
  16. [[resources]] # 资源配置列表
  17. name = 'HelloWeCross' # 资源名称
  18. type = 'BCOS_CONTRACT' # 资源类型,BCOS合约
  19. contractAddress = '0x8827cca7f0f38b861b62dae6d711efe92a1e3602' # 合约地址

Driver

Driver是Stub与WeCross Router交互的入口,用途包括:

  • 发送交易
  • 编解码交易
  • 编解码区块
  • 验证交易
  • 其他功能

接口定义

  1. public interface Driver {
  2.  
  3. /** call或者sendTransaction请求,返回为true,其他类型返回false */
  4. public boolean isTransaction(Request request);
  5.  
  6. /** 解码BlockHeader数据 */
  7. public BlockHeader decodeBlockHeader(byte[] data);
  8.  
  9. /** 获取区块链当前块高 */
  10. public long getBlockNumber(Connection connection);
  11.  
  12. /** 获取Block Header */
  13. public byte[] getBlockHeader(long blockNumber, Connection connection);
  14.  
  15. /** 解析交易请求,请求可能为call或者sendTransaction */
  16. public TransactionContext<TransactionRequest> decodeTransactionRequest(byte[] data);
  17.  
  18. /** 调用合约,查询请求 */
  19. public TransactionResponse call(
  20. TransactionContext<TransactionRequest> request, Connection connection);
  21.  
  22. /** 调用合约,交易请求 */
  23. public TransactionResponse sendTransaction(
  24. TransactionContext<TransactionRequest> request, Connection connection);
  25.  
  26. /** 获取交易,并且对交易进行验证 */
  27. public VerifiedTransaction getVerifiedTransaction(
  28. String transactionHash,
  29. long blockNumber,
  30. BlockHeaderManager blockHeaderManager,
  31. Connection connection);
  32. }

接口列表

  • isTransaction 是否为请求交易

    • 参数列表:
      • Request request: 请求对象
    • 返回值:
      • request为call或者sendTransaction请求返回true,否则返回false
  • getBlockNumber 获取当前区块高度

    • 参数列表:
      • Connection connection: Connection对象,发送请求
    • 返回值
      • 区块高度,负值表示获取区块高度失败
  • getBlockHeader 获取区块头数据,区块头为序列化的二进制数据

    • 参数列表:
      • long blockNumber: 块高
      • Connection connection: Connection对象,发送请求
    • 返回值
      • 序列化的区块头数据,返回null表示获取区块头数据失败,可以使用decodeBlockHeader获取区块头对象
  • decodeBlockHeader 解析区块头数据,返回区块头对象

    • 参数列表:

      • byte[] data: 序列化的区块头数据
    • 返回值

      • 区块头BlockHeader对象,返回null表示解析区块头数据失败

        1. 区块头对象
        2. public class BlockHeader {
        3. /** 区块高度 */
        4. private long number;
        5. /** 上一个区块hash */
        6. private String prevHash;
        7. /** 区块hash */
        8. private String hash;
        9. /** 状态根,验证状态 */
        10. private String stateRoot;
        11. /** 交易根,验证区块交易 */
        12. private String transactionRoot;
        13. /** 交易回执根,验证区块交易回执 */
        14. private String receiptRoot;
        15. }
  • call、sendTransaction callsendTransaction接口类似,前者用于查询状态,后者发送交易,修改区块链状态

    • 参数列表

      • TransactionContext request: 请求上下文,获取构造交易需要的数据,构造交易
      • Connection connection: Connection对象,发送请求
    • 返回值

      • TransactionResponse: 返回对象
    • 注意:

      • sendTransaction接口要对返回交易进行验证,各个区块链的验证方式有所不同,BCOS采用Merkle证明的方式对交易及交易回执进行验证

        1. // 请求对象
        2. public class TransactionRequest {
        3. /** 接口 */
        4. private String method;
        5. /** 参数 */
        6. private String[] args;
        7. }
        8.  
        9. // 返回对象
        10. public class TransactionResponse {
        11. // 返回状态,0表示成功,其他表示错误码
        12. private Integer errorCode;
        13. // 错误信息
        14. private String errorMessage;
        15. // 交易hash,sendTransaction时有效
        16. private String hash;
        17. // 区块高度,交易所在的区块的块高,sendTransaction时有效
        18. private long blockNumber;
        19. // 返回结果
        20. private String[] result;
        21. }
        22.  
        23. // 交易请求上下文参数,获取构造交易需要的参数
        24. public class TransactionContext<TransactionRequest> {
        25. // 交易请求
        26. private TransactionRequest data;
        27. // 账户,用于交易签名
        28. private Account account;
        29. // 请求资源,用于获取资源相关信息
        30. private ResourceInfo resourceInfo;
        31. // 区块头管理器,获取区块头信息
        32. private BlockHeaderManager blockHeaderManager;
        33. }
        34.  
        35. // 区块头管理器
        36. public interface BlockHeaderManager {
        37. // 获取当前块高
        38. public long getBlockNumber();
        39. // 获取区块头,阻塞操作
        40. public byte[] getBlockHeader(long blockNumber);
        41. }
  • getVerifiedTransaction 根据哈希和块高查询交易并校验交易,返回交易的请求与返回对象,校验交易方式与sendTransaction接口校验交易方式保持一致。

    • 参数列表

      • String transactionHash: 交易hash
      • long blockNumber: 交易所在区块高度
      • BlockHeaderManager blockHeaderManager: 区块头管理器,获取区块头
      • Connection connection: 发送请求
    • 返回值

      • VerifiedTransaction对象

        1. public class VerifiedTransaction {
        2. /** 交易所在块高 */
        3. private long blockNumber;
        4. /** 交易hash */
        5. private String transactionHash;
        6. /** 交易调用的合约地址 */
        7. private String realAddress;
        8. /** 交易请求参数 */
        9. private TransactionRequest transactionRequest;
        10. /** 交易返回 */
        11. private TransactionResponse transactionResponse;
        12. }

BCOS示例

这里给个完整的BCOS Stub发送交易的处理流程,说明Driver与Connection的协作,以及在BCOS中如何进行交易验证。

  • BCOSDriver

  1. @Override
  2. public TransactionResponse sendTransaction(
  3. TransactionContext<TransactionRequest> request, Connection connection) {

  4. TransactionResponse response = new TransactionResponse();
  5. try {
  6.     ResourceInfo resourceInfo = request.getResourceInfo();
  7.     /** 合约的额外属性,BCOS构造交易需要这些参数,参考BCOS Stub.toml配置 */
  8.     Map&lt;Object, Object&gt; properties = resourceInfo.getProperties();
  9.     /** 获取合约地址 */
  10.     String contractAddress = (String) properties.get(resourceInfo.getName());
  11.     /** 获取群组Id */
  12.     Integer groupId = (Integer) properties.get(BCOSConstant.BCOS_RESOURCEINFO_GROUP_ID);
  13.     /** 获取链Id */
  14.     Integer chainId = (Integer) properties.get(BCOSConstant.BCOS_RESOURCEINFO_CHAIN_ID);
  15.     /** 获取块高 */
  16.     long blockNumber = request.getBlockHeaderManager().getBlockNumber();
  17.     BCOSAccount bcosAccount = (BCOSAccount) request.getAccount();
  18.     /** 获取私钥 参考account.toml配置 */
  19.     Credentials credentials = bcosAccount.getCredentials();
  20.     /** 交易签名,使用credentials对构造的交易进行签名 */
  21.     String signTx =
  22.             SignTransaction.sign(
  23.                     credentials,
  24.                     contractAddress,
  25.                     BigInteger.valueOf(groupId),
  26.                     BigInteger.valueOf(chainId),
  27.                     BigInteger.valueOf(blockNumber),
  28.                     FunctionEncoder.encode(function));
  29.     // 构造Request参数
  30.     TransactionParams transaction = new TransactionParams(request.getData(), signTx);
  31.     Request req = new Request();
  32.     /** Request类型 SEND_TRANSACTION */
  33.     req.setType(BCOSRequestType.SEND_TRANSACTION);
  34.     /** 参数JSON序列化 */
  35.     req.setData(objectMapper.writeValueAsBytes(transaction));
  36.     /** Connection send发送请求 */
  37.     Response resp = connection.send(req);
  38.     if (resp.getErrorCode() != BCOSStatusCode.Success) {
  39.         /** Connection返回异常 */
  40.         throw new BCOSStubException(resp.getErrorCode(), resp.getErrorMessage());
  41.     }
  42.     /** 获取返回的交易回执,回执被序列化为byte[] */
  43.     TransactionReceipt receipt =
  44.             objectMapper.readValue(resp.getData(), TransactionReceipt.class);
  45.     // Merkle证明,校验交易hash及交易回执,失败抛出异常
  46.     verifyTransactionProof(
  47.             receipt.getBlockNumber().longValue(),
  48.             receipt.getTransactionHash(),
  49.             request.getBlockHeaderManager(),
  50.             receipt);
  51.     /** 其他逻辑,构造返回 */
  52. } catch (Exception e) {
  53.     /** 异常场景 */
  54.     response.setErrorCode(BCOSStatusCode.UnclassifiedError);
  55.     response.setErrorMessage(" errorMessage: " + e.getMessage());
  56. }
  57. return response;
  58. }

  • BCOSConnection
  1. public class BCOSConnection implements Connection {
  2. /** BCOS JavaSDK 实例句柄 */
  3. private final Web3jWrapper web3jWrapper;
  4.  
  5. @Override
  6. public Response send(Request request) {
  7. switch (request.getType()) {
  8. case BCOSRequestType.SEND_TRANSACTION:
  9. /** type: SEND_TRANSACTION */
  10. return handleTransactionRequest(request);
  11. /** 其他case场景 */
  12. }
  13. }
  14.  
  15. /** 发送交易请求处理 */
  16. public Response handleTransactionRequest(Request request) {
  17. Response response = new Response();
  18. try {
  19. /** 参数JSON序列化,反序列化得到请求参数*/
  20. TransactionParams transaction =
  21. objectMapper.readValue(request.getData(), TransactionParams.class);
  22. /** 签名交易 */
  23. String signTx = transaction.getData();
  24. /** 调用BCOS RPC发送交易接口,获取回执以及Merkle证明 */
  25. TransactionReceipt receipt = web3jWrapper.sendTransactionAndGetProof(signTx);
  26.  
  27. /** 交易回执不存在 */
  28. if (Objects.isNull(receipt)
  29. || Objects.isNull(receipt.getTransactionHash())
  30. || "".equals(receipt.getTransactionHash())) {
  31. throw new BCOSStubException(
  32. BCOSStatusCode.TransactionReceiptNotExist,
  33. BCOSStatusCode.getStatusMessage(BCOSStatusCode.TransactionReceiptNotExist));
  34. }
  35.  
  36. /** 交易执行失败 */
  37. if (!receipt.isStatusOK()) {
  38. throw new BCOSStubException(
  39. BCOSStatusCode.SendTransactionNotSuccessStatus,
  40. StatusCode.getStatusMessage(receipt.getStatus()));
  41. }
  42.  
  43. /** 交易正确执行 */
  44. response.setErrorCode(BCOSStatusCode.Success);
  45. response.setErrorMessage(BCOSStatusCode.getStatusMessage(BCOSStatusCode.Success));
  46. /** 返回交易回执,回执JSON方式序列化 */
  47. response.setData(objectMapper.writeValueAsBytes(receipt));
  48. } catch (Exception e) {
  49. /** 异常情况 */
  50. response.setErrorCode(BCOSStatusCode.HandleSendTransactionFailed);
  51. response.setErrorMessage(" errorMessage: " + e.getMessage());
  52. }
  53. return response;
  54. }
  55. }

生成Jar

Stub插件需要打包生成shadow jar才可以被WeCross Router加载使用,在Gradle中引入shadow插件。

shadow插件使用: Gradle Shadow Plugin

  • 引入shadow插件
  1. plugins {
  2. // 其他插件列表
  3. id 'com.github.johnrengelman.shadow' version '5.2.0'
  4. }
  • 添加打包task
  1. jar.enabled = false
  2. project.tasks.assemble.dependsOn project.tasks.shadowJar
  3.  
  4. shadowJar {
  5. destinationDir file('dist/apps')
  6. archiveName project.name + '.jar'
  7. // 其他打包逻辑
  8. }
  • 执行build操作
  1. bash gradlew build

dist/apps目录生成jar文件

参考链接

WeCross-BCOS-Stub

WeCross-Fabric-Stub

WeCross文档