跨链管理合约
合约介绍
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("该合约没有改方法")
}
}
}
合约方法介绍
- registerChainInfo:注册chainInfo,方便后续和网关对接,来源链和目标链都注册(包括配置文件地址),一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着调用的Storage模块(cross-storage)进行存储分析,若Storage模块监测到包含该交易的区块信息时,会将配置文件提交到网关。
- updateChainInfo:更新chainInfo,一般用来更新跨链网关中对应目标链的配置文件,一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,会将配置文件提交到网关。
- registerContractInfo:注册contractInfo,注册目标链合约(要跨的合约信息),一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,将对应信息写到数据库里。
- registerRouterInfo:注册routeInfo,路由信息,原链的合约到目标链合约,一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,将对应信息写到数据库里。
- registerPermitRoute:注册permitRoute,管理员为用户注册允许的路由信息,一个用户名下都有哪些routeinfo,在用户提交跨链交易时,合约里会校验,该用户有没有向目标链提交交易的权限,一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,将对应信息写到数据库里。
- updatePermitRoute:更新permitRoute,更新用户的路由权限,一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,将对应信息写到数据库里。
- submitCrossProxyTran:提交跨链交易crossProxyTran,用户想repchain提交代理交易,一般由跨链服务(cross-server)后台来调用该方法,当SyncInfo模块同步到原链RepChain的区块时,接着Storage模块(cross-storage)监测到包含该交易的区块信息时,会组织交易参数,然后提交到网关,由网关构造代理签名交易提交到对应的目标链。
- 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下自行打包
-
启动跨链网关
-
启动GateWayManagement
-
启动GateWay
-
执行测试例initRepChain(),上传RepChain配置文件
-
执行测试例testRepSyncService(),启动同步RepChain的服务,注意高度和hash的设置(可以查看数据库)
-
执行测试例testRegisterRepChainChaintInfo(),向RepChain提交注册RepChain自身链信息
-
执行测试例testRegisterFsicoChaintInfo(),向RepChain提交注册Fisco链信息
-
执行测试例testFiscoSyncService(),启动同步FiscoBcos的服务,注意高度和hash的设置(可以查看数据库)
-
执行测试例testUpdateFiscoChaintInfo(),向RepChain提交更新Fisco链信息,一般是更新配置文件,才更新chainInfo
-
执行测试例testRegisterContractInfo(),向RepChain提交注册合约信息,这里以FiscoBcos上部署的HelloWorld为例
-
执行测试例testRegisterRouteInfo(),向RepChain提交注册路由信息,这里主要是说从哪个合约跨到哪个合约
-
执行测试例testRegisterPermitRoute(),向RepChain提交路由权限登记,这里主要说是允许某个用户某个路由
-
执行测试例testRegisterFiscoSubmitCross(),向FiscoBcos提交跨链交易,RepChain存储模块会解析区块,然后根据对应的参数向目标链提交代理交易;Fisco存储模块也会解析对应的区块,提交回执到RepChain上
-
根据chainHash来明确是向来源链还是目标链提交交易
-
根据accountId来选择使用网关处的哪一个账户提交交易
-
-
执行测试例testRegisterFiscoSubmitCross_1(),向FiscoBcos提交跨链交易(使用和11不同的私钥,通过accountId来指定),RepChain存储模块会解析区块,然后根据对应的参数向目标链提交代理交易;Fisco存储模块也会解析对应的区块,提交回执到RepChain上
-
根据chainHash来明确是向来源链还是目标链提交交易
-
根据accountId来选择使用网关处的哪一个账户提交交易
-
-
执行测试例testRegisterRepChainSubmitCross_1(),向RepChain提交跨链交易,RepChain存储模块会解析区块,然后根据对应的参数向来源链提交代理交易;RepChain存储模块也会解析对应的区块,提交回执到RepChain上
-
根据chainHash来明确是向来源链还是目标链提交交易
-
根据accountId来选择使用网关处的哪一个账户提交交易
-