跳转至

跨链管理合约

合约介绍

package rep.sc.tpl.cross

import CrossManage.{chainInfoPrefix, contractInfoPrefix, crossTranPrefix, crossTranReceiptPrefix, routeInfoPrefix, userPermitRoutePrefix}
import org.json4s.DefaultFormats
import org.json4s.jackson.JsonMethods.parse
import org.json4s.jackson.Serialization.{read, write}
import org.slf4j.Logger
import rep.proto.rc2.ActionResult
import rep.sc.scalax.{ContractContext, ContractException, IContract}


/**
 *
 * @param chainHash            链或适配器ID
 * @param chainType            链类型 fabric/repchain/fisco-bcos
 * @param consensusNodeUrlList 节点的ip:port
 * @param adapterName          适配器名
 */
case class ChainInfo(chainHash: String, chainType: Int, configPath: String, consensusNodeUrlList: Option[String], adapterName: String)

/**
 *
 * @param contractHash 合约ID
 * @param chainHash    链或适配器ID
 * @param contractName 合约描述
 * @param contractAddr 合约地址
 */
case class ContractInfo(contractHash: String, chainHash: String, contractName: String, contractAddr: String)

/**
 *
 * @param routeHash        路由ID
 * @param routeName        路由描述
 * @param contractHashFrom 原合约ID
 * @param contractHashTo   目标合约ID
 */
case class RouteInfo(routeHash: String, routeName: String, contractHashFrom: String, contractHashTo: String)

/**
 *
 * @param creditCode 用户ID
 * @param routeSeq   路由序列
 */
case class PermitRoute(creditCode: String, routeSeq: Seq[String])

/**
 *
 * @param chainHash  链ID
 * @param routeHash  路由ID
 * @param accountId  账户ID
 * @param methodName 方法名
 * @param methodArgs 方法参数
 */
case class CrossTransaction(chainHash: String, routeHash: String, accountId: String, methodName: String, methodArgs: String)

/**
 *
 * @param chainHash        链ID
 * @param crossTranHashSrc 代理交易的hash
 * @param crossTranHash    目标链交易Hash
 */
case class CrossTransactionReceipt(chainHash: String, crossTranHashSrc: String, crossTranHash: String)

/**
 * @author zyf
 */
class CrossChainManage extends IContract {

  implicit val formats: DefaultFormats.type = DefaultFormats
  var logger: Logger = _

  def init(ctx: ContractContext): Unit = {
    this.logger = ctx.api.getLogger
    logger.info(s"init | 初始化跨链管理合约:${ctx.t.cid.get.chaincodeName}, 交易ID为:${ctx.t.id}")
  }

  /**
   * 登记chainInfo
   *
   * @param ctx
   * @param sdata
   * @return
   */
  def registerChainInfo(ctx: ContractContext, sdata: String): ActionResult = {
    val chainInfo = parse(sdata).extract[ChainInfo]
    logger.info("registerChainInfo | chainInfo: {}", sdata)
    val chainInfoKey = chainInfoPrefix + chainInfo.chainHash
    if (ctx.api.getVal(chainInfoKey) != null) {
      logger.error(s"registerChainInfo | 已经有该id ${chainInfo.chainHash} 了,请使用updateChainInfo")
      throw ContractException(s"registerChainInfo | 已经有该id ${chainInfo.chainHash} 了,请使用updateChainInfo")
    }
    ctx.api.setVal(chainInfoKey, sdata)
    ActionResult()
  }

  /**
   * 更新chainInfo
   *
   * @param ctx
   * @param sdata
   * @return
   */
  def updateChainInfo(ctx: ContractContext, sdata: String): ActionResult = {
    val chainInfo = parse(sdata).extract[ChainInfo]
    logger.info("updateChainInfo | chainInfo: {}", sdata)
    val chainInfoKey = chainInfoPrefix + chainInfo.chainHash
    if (ctx.api.getVal(chainInfoKey) == null) {
      logger.error(s"updateChainInfo | 不存在该id ${chainInfo.chainHash} 了,请使用registerChainInfo")
      throw ContractException(s"updateChainInfo | 不存在该id ${chainInfo.chainHash} 了,请使用registerChainInfo")
    }
    ctx.api.setVal(chainInfoKey, sdata)
    ActionResult()
  }

  /**
   *
   * @param ctx
   * @param sdata
   * @return
   */
  def registerContractInfo(ctx: ContractContext, sdata: String): ActionResult = {
    val contractInfo = parse(sdata).extract[ContractInfo]
    logger.info("registerContractInfo | contractInfo: {}", sdata)
    val contractInfoKey = contractInfoPrefix + contractInfo.contractHash
    if (ctx.api.getVal(contractInfoKey) != null) {
      logger.error(s"registerContractInfo | 已经有该id ${contractInfo.contractHash} 了,暂不支持重复登记或更新")
      throw ContractException(s"registerContractInfo | 已经有该id ${contractInfo.contractHash} 了,暂不支持重复登记或更新")
    }
    ctx.api.setVal(contractInfoKey, sdata)
    ActionResult()
  }

  /**
   *
   * @param ctx
   * @param sdata
   * @return
   */
  def registerRouterInfo(ctx: ContractContext, sdata: String): ActionResult = {
    val routeInfo = parse(sdata).extract[RouteInfo]
    logger.info("registerRouterInfo | routeInfo: {}", sdata)
    val routeInfoKey = routeInfoPrefix + routeInfo.routeHash
    if (ctx.api.getVal(routeInfoKey) != null) {
      logger.error(s"registerRouterInfo | 已经有该id ${routeInfo.routeHash} 了,暂不支持重复登记或更新")
      throw ContractException(s"registerRouterInfo | 已经有该id ${routeInfo.routeHash} 了,暂不支持重复登记或更新")
    }
    ctx.api.setVal(routeInfoKey, sdata)
    ActionResult()
  }

  /**
   *
   * @param ctx
   * @param sdata
   * @return
   */
  def registerPermitRoute(ctx: ContractContext, sdata: String): ActionResult = {
    val permitInfo = parse(sdata).extract[PermitRoute]
    logger.info("registerPermitRoute | permitInfo: {}", sdata)
    val permitInfoKey = userPermitRoutePrefix + permitInfo.creditCode
    ctx.api.setVal(permitInfoKey, sdata)
    ActionResult()
  }

  /**
   *
   * @param ctx
   * @param sdata
   * @return
   */
  def updatePermitRoute(ctx: ContractContext, sdata: String): ActionResult = {
    val permitInfo = parse(sdata).extract[PermitRoute]
    logger.info("updatePermitChain | permitInfo: {}", sdata)
    val permitInfoKey = userPermitRoutePrefix + permitInfo.creditCode
    ctx.api.setVal(permitInfoKey, sdata)
    ActionResult()
  }

  /**
   *
   * @param ctx
   * @param sdata
   * @return
   */
  def submitCrossProxyTran(ctx: ContractContext, sdata: String): ActionResult = {
    val permitInfoKey = userPermitRoutePrefix + ctx.t.getSignature.getCertId.creditCode
    val permitInfo = parse(ctx.api.getVal(permitInfoKey).asInstanceOf[String]).extract[PermitRoute]
    val crossTran = parse(sdata).extract[CrossTransaction]
    logger.info(s"submitCrossProxyTran | crossTran: $sdata, permitInfo: ${write(permitInfo)}")
    if (permitInfo.routeSeq.contains(crossTran.routeHash)) {
      val routeInfo = parse(ctx.api.getVal(routeInfoPrefix + crossTran.routeHash).asInstanceOf[String]).extract[RouteInfo]
      val contractHashTo = routeInfo.contractHashTo
      logger.info(s"submitCrossProxyTran | 有向路由 ${write(routeInfo)} 合约 $contractHashTo 提交跨链交易的权限")
      ctx.api.setVal(crossTranPrefix + ctx.t.id, sdata)
    } else {
      logger.error(s"submitCrossProxyTran | 不具有向链 ${crossTran.chainHash} 提交跨链交易的权限")
      throw ContractException(s"submitCrossProxyTran | 不具有向链 ${crossTran.chainHash} 提交跨链交易的权限")
    }
    ActionResult()
  }

  /**
   *
   * @param ctx
   * @param sdata
   * @return
   */
  def submitCrossProxyReceipt(ctx: ContractContext, sdata: String): ActionResult = {
    val crossTranReceipt = parse(sdata).extract[CrossTransactionReceipt]
    logger.info("submitCrossProxyReceipt | receipt: {}", sdata)
    ctx.api.setVal(crossTranReceiptPrefix + crossTranReceipt.crossTranHash, write(parse(sdata) merge parse(s"""{"receiptTranHash":"${ctx.t.id}"}""")))
    ActionResult()
  }

  def onAction(ctx: ContractContext, action: String, sdata: String): ActionResult = {
    action match {
      case "registerChainInfo" => registerChainInfo(ctx, sdata)
      case "updateChainInfo" => updateChainInfo(ctx, sdata)
      case "registerContractInfo" => registerContractInfo(ctx, sdata)
      case "registerRouterInfo" => registerRouterInfo(ctx, sdata)
      case "registerPermitRoute" => registerPermitRoute(ctx, sdata)
      case "updatePermitRoute" => updatePermitRoute(ctx, sdata)
      case "submitCrossProxyTran" => submitCrossProxyTran(ctx, sdata)
      case "submitCrossProxyReceipt" => submitCrossProxyReceipt(ctx, sdata)
      case _ => throw ContractException("该合约没有改方法")
    }
  }
}

合约方法介绍

  1. registerChainInfo:注册chainInfo,方便后续和网关对接,来源链和目标链都注册(包括配置文件地址),一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着调用的Storage模块(cross-storage)进行存储分析,若Storage模块监测到包含该交易的区块信息时,会将配置文件提交到网关。
  2. updateChainInfo:更新chainInfo,一般用来更新跨链网关中对应目标链的配置文件,一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,会将配置文件提交到网关。
  3. registerContractInfo:注册contractInfo,注册目标链合约(要跨的合约信息),一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,将对应信息写到数据库里。
  4. registerRouterInfo:注册routeInfo,路由信息,原链的合约到目标链合约,一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,将对应信息写到数据库里。
  5. registerPermitRoute:注册permitRoute,管理员为用户注册允许的路由信息,一个用户名下都有哪些routeinfo,在用户提交跨链交易时,合约里会校验,该用户有没有向目标链提交交易的权限,一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,将对应信息写到数据库里。
  6. updatePermitRoute:更新permitRoute,更新用户的路由权限,一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,将对应信息写到数据库里。
  7. submitCrossProxyTran:提交跨链交易crossProxyTran,用户想repchain提交代理交易,一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,会组织交易参数,然后提交到网关,由网关构造代理签名交易提交到对应的目标链。
  8. submitCrossProxyReceipt:提交对应的回执crossProxyReceipt,该方法由Storage模块调用,同步目标链区块时,如果同步存储模块监测到上一步的交易出块了,那么向repchain提交一个回执信息,当SyncInfo模块同步到目标链的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,将提交一个回执交易到来源链RepChain上。

测试介绍

代码在repchain-cross-storage/src/main/test目录下

跨链网关必需的两个请求参数:chain_hash:链id,对应于adapterId;chain_type:链类型名

其中测试例中涉及到的zip文件,在repchain-cross-gateway/conf下自行打包

  1. 启动跨链网关

  2. 启动GateWayManagement

  3. 启动GateWay

  4. 执行测试例initRepChain(),上传RepChain配置文件

  5. 执行测试例testRepSyncService(),启动同步RepChain的服务,注意高度和hash的设置(可以查看数据库)

  6. 执行测试例testRegisterRepChainChaintInfo(),向RepChain提交注册RepChain自身链信息

  7. 执行测试例testRegisterFsicoChaintInfo(),向RepChain提交注册Fisco链信息

  8. 执行测试例testFiscoSyncService(),启动同步FiscoBcos的服务,注意高度和hash的设置(可以查看数据库)

  9. 执行测试例testUpdateFiscoChaintInfo(),向RepChain提交更新Fisco链信息,一般是更新配置文件,才更新chainInfo

  10. 执行测试例testRegisterContractInfo(),向RepChain提交注册合约信息,这里以FiscoBcos上部署的HelloWorld为例

  11. 执行测试例testRegisterRouteInfo(),向RepChain提交注册路由信息,这里主要是说从哪个合约跨到哪个合约

  12. 执行测试例testRegisterPermitRoute(),向RepChain提交路由权限登记,这里主要说是允许某个用户某个路由

  13. 执行测试例testRegisterFiscoSubmitCross(),向FiscoBcos提交跨链交易,RepChain存储模块会解析区块,然后根据对应的参数向目标链提交代理交易;Fisco存储模块也会解析对应的区块,提交回执到RepChain上

    1. 根据chainHash来明确是向来源链还是目标链提交交易

    2. 根据accountId来选择使用网关处的哪一个账户提交交易

  14. 执行测试例testRegisterFiscoSubmitCross_1(),向FiscoBcos提交跨链交易(使用和11不同的私钥,通过accountId来指定),RepChain存储模块会解析区块,然后根据对应的参数向目标链提交代理交易;Fisco存储模块也会解析对应的区块,提交回执到RepChain上

    1. 根据chainHash来明确是向来源链还是目标链提交交易

    2. 根据accountId来选择使用网关处的哪一个账户提交交易

  15. 执行测试例testRegisterRepChainSubmitCross_1(),向RepChain提交跨链交易,RepChain存储模块会解析区块,然后根据对应的参数向来源链提交代理交易;RepChain存储模块也会解析对应的区块,提交回执到RepChain上

    1. 根据chainHash来明确是向来源链还是目标链提交交易

    2. 根据accountId来选择使用网关处的哪一个账户提交交易

回到页面顶部