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-rc4'
  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. /** 创建Driver对象 */
  3. public Driver newDriver();
  4. /** 解析Connection配置stub.toml,创建Connection对象 */
  5. public Connection newConnection(String path);
  6. /** 解析Account配置account.toml,创建Account对象 */
  7. public Account newAccount(String name, String path);
  8. }

BCOS Stub示例:

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

Account

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

接口定义

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

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. private final String name;
  4. /** 类型 */
  5. private final String type;
  6. /** 公钥 */
  7. private final String publicKey;
  8. /** BCOS私钥对象,交易签名,加载配置的私钥文件生成 */
  9. private final Credentials credentials;
  10. public Credentials getCredentials() {
  11. return credentials;
  12. }
  13. /** 其他接口 */
  14. }

Connection

Connection用于

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

接口定义

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

接口列表

  • getResources

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

  • send

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

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

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. public class BCOSConnection implements Connection {
  15. /** BCOS SDK对象,调用区块链接口 */
  16. private final Web3jWrapper web3jWrapper;
  17. /** 为Driver提供统一发送接口,根据不同请求类型分别与区块链完成交互 */
  18. @Override
  19. public Response send(Request request) {
  20. switch (request.getType()) {
  21. /** call请求 */
  22. case BCOSRequestType.CALL:
  23. /** handle call request */
  24. /** sendTransaction请求 */
  25. case BCOSRequestType.SEND_TRANSACTION:
  26. /** handle sendTransaction request */
  27. /** 获取区块头 */
  28. case BCOSRequestType.GET_BLOCK_HEADER:
  29. /** handle getBlockeHeader request */
  30. /** 获取块高 */
  31. case BCOSRequestType.GET_BLOCK_NUMBER:
  32. /** handle getBlockNumber request */
  33. /** 获取交易Merkle证明 */
  34. case BCOSRequestType.GET_TRANSACTION_PROOF:
  35. /** handle getTransactionProof request */
  36. default:
  37. /** unrecognized request type */
  38. }
  39. }
  40. /** 获取资源列表 */
  41. @Override
  42. public List<ResourceInfo> getResources() {
  43. return resourceInfoList;
  44. }
  45. }

BCOS stub.toml示例

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

Driver

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

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

接口定义

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

接口列表

  • 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. public class TransactionResponse {
        10. // 返回状态,0表示成功,其他表示错误码
        11. private Integer errorCode;
        12. // 错误信息
        13. private String errorMessage;
        14. // 交易hash,sendTransaction时有效
        15. private String hash;
        16. // 区块高度,交易所在的区块的块高,sendTransaction时有效
        17. private long blockNumber;
        18. // 返回结果
        19. private String[] result;
        20. }
        21. // 交易请求上下文参数,获取构造交易需要的参数
        22. public class TransactionContext<TransactionRequest> {
        23. // 交易请求
        24. private TransactionRequest data;
        25. // 账户,用于交易签名
        26. private Account account;
        27. // 请求资源,用于获取资源相关信息
        28. private ResourceInfo resourceInfo;
        29. // 区块头管理器,获取区块头信息
        30. private BlockHeaderManager blockHeaderManager;
        31. }
        32. // 区块头管理器
        33. public interface BlockHeaderManager {
        34. // 获取当前块高
        35. public long getBlockNumber();
        36. // 获取区块头,阻塞操作
        37. public byte[] getBlockHeader(long blockNumber);
        38. }
  • 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<Object, Object> 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. @Override
  5. public Response send(Request request) {
  6. switch (request.getType()) {
  7. case BCOSRequestType.SEND_TRANSACTION:
  8. /** type: SEND_TRANSACTION */
  9. return handleTransactionRequest(request);
  10. /** 其他case场景 */
  11. }
  12. }
  13. /** 发送交易请求处理 */
  14. public Response handleTransactionRequest(Request request) {
  15. Response response = new Response();
  16. try {
  17. /** 参数JSON序列化,反序列化得到请求参数*/
  18. TransactionParams transaction =
  19. objectMapper.readValue(request.getData(), TransactionParams.class);
  20. /** 签名交易 */
  21. String signTx = transaction.getData();
  22. /** 调用BCOS RPC发送交易接口,获取回执以及Merkle证明 */
  23. TransactionReceipt receipt = web3jWrapper.sendTransactionAndGetProof(signTx);
  24. /** 交易回执不存在 */
  25. if (Objects.isNull(receipt)
  26. || Objects.isNull(receipt.getTransactionHash())
  27. || "".equals(receipt.getTransactionHash())) {
  28. throw new BCOSStubException(
  29. BCOSStatusCode.TransactionReceiptNotExist,
  30. BCOSStatusCode.getStatusMessage(BCOSStatusCode.TransactionReceiptNotExist));
  31. }
  32. /** 交易执行失败 */
  33. if (!receipt.isStatusOK()) {
  34. throw new BCOSStubException(
  35. BCOSStatusCode.SendTransactionNotSuccessStatus,
  36. StatusCode.getStatusMessage(receipt.getStatus()));
  37. }
  38. /** 交易正确执行 */
  39. response.setErrorCode(BCOSStatusCode.Success);
  40. response.setErrorMessage(BCOSStatusCode.getStatusMessage(BCOSStatusCode.Success));
  41. /** 返回交易回执,回执JSON方式序列化 */
  42. response.setData(objectMapper.writeValueAsBytes(receipt));
  43. } catch (Exception e) {
  44. /** 异常情况 */
  45. response.setErrorCode(BCOSStatusCode.HandleSendTransactionFailed);
  46. response.setErrorMessage(" errorMessage: " + e.getMessage());
  47. }
  48. return response;
  49. }
  50. }

生成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. shadowJar {
  4. destinationDir file('dist/apps')
  5. archiveName project.name + '.jar'
  6. // 其他打包逻辑
  7. }
  • 执行build操作
  1. bash gradlew build

dist/apps目录生成jar文件

参考链接

WeCross-BCOS-Stub

WeCross-Fabric-Stub

WeCross文档