Skip to content

如何基于Embedded BCDNS实现跨链(v1)

zouxyan edited this page Jan 10, 2025 · 1 revision

1. 介绍🔎

本文档旨在介绍如何搭建一个本地环境,其中涵盖了AntChain Bridge Relayer、Embedded BCDNS、PluginServer、CommitteePTC以及以太坊私有链的启动流程。Embedded BCDNS是AntChain Bridge框架下定义并实现的一种区块链域名服务(BCDNS),它不仅全面实现了BCDNS的核心功能,还为跨链身份认证、凭证签发与查询提供了强大支持。此外,它还支持区块链域名证书的认证,以及跨链路由服务的注册和查询功能。CommitteePTC支持跨链消息合法性验证以及对跨链消息背书的功能,它支持多个节点组成委员会的形式对跨链消息完成验证和背书。在这个基础上,文档将进一步阐述如何注册跨链身份和域名证书。

scan dingding

上图大体介绍了整体操作的脉络。

  • 操作者需要部署中继(Relayer)、Embedded BCDNS服务将会运行在Relayer、插件服务、单节点CommitteePTC以及一条以太坊2.0的私有链。
  • 使用CLI工具生成BCDNS的密钥、自签根证书。
  • 使用CLI工具生成Relayer公私钥,向BCDNS申请中继证书,会配置到Relayer中。
  • 操作过程中,涉及很多证书,主要是中继证书,用于验证Relayer本身的权限和身份,然后是域名证书,区块链注册进中继之前,需要向BCDNS申请域名证书,目前可以通过中继申请,其余则是自签名的X.509证书,用于TLS连接。
  • 私有链采用ethPandaOps的工具,部署完毕后会有很多预置的私钥,这里默认操作者熟悉相关操作。
  • 整个操作,用到很多工具,比如Remix、MetaMask、Docker等,这里默认操作者熟悉相关操作。

2. 环境准备🔎

2.0 下载

  • Docker

    可以参考GetDocker

    wget -qO- https://get.docker.com/ | bash
    
  • JDK

    这里需要分别下载JDK8和JDK21,建议使用sdkman进行下载和管理。

    [!IMPORTANT]
    除此之外,Ethereum2.0插件和CommitteePTC均使用Java21开发,组件PluginServer、CommitteePTC Node和SupervisorCLI需要使用JDK21运行,建议使用sdkman进行JDK下载和管理,或者自行下载JDK8和JDK21备用,未来我们会逐步转移到Java21技术栈开发🫰。

  • 代码

    下载AntChain Bridge代码。

    git clone https://github.com/AntChainOpenLabs/AntChainBridge.git
    

2.1 中间件

目前系统使用了MySQL和Redis,这里建议使用docker快速安装依赖。

首先通过脚本安装docker,或者在官网下载。

wget -qO- https://get.docker.com/ | bash

然后下载MySQL镜像并启动容器,注意这里指定了时区为+08:00,请修改为您的时区。

docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD='YOUR_PWD' mysql:8 --mysql-native-password=ON --default_time_zone +08:00

然后下载Redis镜像并启动容器:

docker run -itd --name redis-test -p 6379:6379 redis --requirepass 'YOUR_PWD' --maxmemory 500MB

2.2 启动内置了BCDNS功能的Relayer服务

  • 启动Relayer

首先,可以按照Relayer的README快速开始完成【编译】、【配置】、【跨链身份】,执行generate-relayer-account 之后先不用申请Relayer跨链证书,先完成下面的配置工作。

在Relayer的安装目录,找到config/application.yml,将下面配置relayer.network.node.crosschain_cert_path注释掉,也就是先不配置Relayer跨链证书,不要忘记将generate-relayer-account 的私钥文件路径填入配置relayer.network.node.private_key_path

relayer:
  network:
    node:
      sig_algo: Ed25519
#      crosschain_cert_path: classpath:cc_certs/relayer.crt
      private_key_path: file: cc_certs/private_key.pem

之后按照【启动Embedded BCDNS】小节配置内置BCDNS功能,别忘记为BCDNS功能在relayer数据库中创建表,SQL脚本在

随后按照【运行】的步骤启动Relayer。

  • 准备Relayer证书CSR

下面从内置的BCDNS完成Relayer跨链证书的申请,首先准备证书的证书签名请求(Certificate Signing Request, CSR)。

用法可以参考Relayer CLI文档,具体使用如下:

使用generate-relayer-csr生成CSR,指定--pubkeyFile到relayer account使用的公钥,即跨链身份小节中generate-relayer-account生成的公钥,拷贝生成的Base64字符串,在下一步中使用。

relayer:> generate-relayer-csr --pubkeyFile /path/to/public_key.pem
your CSR is 
AACXAAAAAA...KRMzgQAAAAAAA==
  • 签署Relayer证书

在当前版本中,Relayer无法申请自己的证书,需要通过向Embedded BCDNS获取。

这里需要使用grpcurl或者postman来发送grpc请求,这里给出grpcurl的示例。

将上一步中构建的CSR填入certSigningRequest请求中:

Note

-import-path 指定了存放embedded-bcdns-service.proto的文件夹路径,这里可以在这里下载proto文件,然后保存到本地。

-d @ 指定了Embedded BCDNS的服务地址,默认是localhost:8090。

grpcurl -plaintext \
    -import-path /path/to/acb-sdk/bcdns-services/embedded-bcdns/embedded-bcdns-core/src/main/proto/ \
    -proto embedded-bcdns-service.proto \
    -d @ localhost:8090 \
    com.alipay.antchain.bridge.bcdns.embedded.grpc.EmbeddedBcdnsService/applyRelayerCertificate <<EOM
    {
                "certSigningRequest": "AACXAAAAAA...KRMzgQAAAAAAA=="
    }
EOM

运行之后,将返回类似下面的json数据,applyReceipt是用来查询证书签署结果的收据ID。

{
  "applyRelayerCertificateResp": {
    "applyReceipt": "16b363c1b73b0a2e73a394b69cd592542144dff96e16e0b2a9f07c0f6842cb91"
  }
}

运行下面命令,使用你的收据值替换applyReceipt参数,向Embedded BCDNS发送请求,查询证书。

grpcurl -plaintext \
    -import-path /path/to/acb-sdk/bcdns-services/embedded-bcdns/embedded-bcdns-core/src/main/proto/ \
    -proto embedded-bcdns-service.proto \
    -d @ localhost:8090 \
    com.alipay.antchain.bridge.bcdns.embedded.grpc.EmbeddedBcdnsService/queryRelayerCertificateApplicationResult <<EOM
    {
                "applyReceipt": "16b363c1b73b0a2e73a394b69cd592542144dff96e16e0b2a9f07c0f6842cb91"
    }
EOM

成功发送的话,可以拿到类似下面的返回,这里的Base64字段certificate是指Relayer README中提到的跨链证书。

{
  "applicationResult": {
    "isFinalResult": true,
    "certificate": "AADzAQAAAAABAAAAMQEAQAAA...tAzoHKIeYVth+W6bjXjAQ=="
  }
}

可以使用CLI工具将Base64格式的证书转换成PEM格式,以用于Relayer。

relayer:> convert-cross-chain-cert-to-pem --base64Input AADzAQAAAAABAAAAMQEAQAAA...tAzoHKIeYVth+W6bjXjAQ==
-----BEGIN RELAYER CERTIFICATE-----
AAAIAgAAAAABAAAAMQEAKAAAAGRpZDpiaWQ6ZWY5OVJ6OFRpN3g0aTZ6eUNyUHlG
aXk5dXRzV0JKVVcCAAEAAAADAwA7AAAAAAA1AAAAAAABAAAAAQEAKAAAAGRpZDpi
...
4QlxLUp70uRK43ECAAcAAABFZDI1NTE5AwBAAAAAbA8zkKXCI4Iwp6KBERXOqKln
JT/qn36in7+iU6SsNEz0rsJpmEvVRT6adNVY7zS/ni35JwWf/zi60DKnQ7xaCA==
-----END RELAYER CERTIFICATE-----

参考Relayer README,在获得PEM格式的中继证书和密钥之后,将其配置到文件中,这里假设将证书和密钥分别放在cc_certs/relayer.crtcc_certs/private_key.pem

relayer:
  network:
    node:
      sig_algo: Ed25519
      crosschain_cert_path: file:cc_certs/relayer.crt
      private_key_path: file:cc_certs/private_key.pem

最后重启Relayer服务即可。

bin/stop.sh && bin/start.sh

2.3 PluginServer

  • 启动插件服务

    请按照插件服务的README配置和启动插件服务(v1.0.0),这中间需要用到中继的TLS证书,这往往在中继安装目录的tls_certs/relayer.crt

  • 安装以太坊2.0插件

    首先,进入路径acb-sdk/pluginset/ethereum2/offchain-plugin,执行下面命令,编译合约AB,这里要注意使用JDK21:

    mvn web3j:generate-sources
    

    然后,编译整个项目:

    mvn package -DskipTests 
    

    可以在项目target下面找到插件:ethereum2-acb-plugin-1.0.0-plugin.jar

    或者从release下载,重启插件服务可以重新加载插件,或者使用CLI工具。

    [!NOTE]

    注意编译ethereum2.0插件之前,需要先安装AntChain Bridge SDK。

  • 注册PluginServer到Relayer

    使用Relayer的CLI工具,注册PluginServer,这里用到插件服务的TLS证书certs/server.crt和URL,配置项按照本地环境填入即可,pluginServerId不要重复即可,后续注册区块链需要用到该ID。

    relayer:> register-plugin-server --address localhost:9090 --pluginServerId myps.id --pluginServerCAPath /path/to/certs/server.crt
    

2.4 以太坊私有链

  • ethPandaOps

    ethPandaOps为社区提供了很多开发工具,我们要使用他们的ethereum-package

    按照他们的README,执行Quickstart部分。

    [!IMPORTANT]
    这里要注意,AntChain Bridge要求beacon客户端提供lightclient的接口,而lighthouse的beacon客户端存在支持问题,所以需要通过启动ethereum2.0环境的时候指定network_params.yaml,比如下面配置,将会使用GEth和nimbus构筑区块链,并启动beaconchain浏览器Dora。

    participants:
        # CL(Consensus Layer) Specific flags
        # The type of CL client that should be started
        # Valid values are nimbus, lighthouse, lodestar, teku, prysm, and grandine
      - cl_type: nimbus
    additional_services:
      - dora
    

    成功启动以太坊私链之后,在其输出内容中包含了很多预置的私钥,比如:

    ......
    "pre_funded_accounts": [
    		{
    			"address": "0x8943545177806ED17B9F23F0a21ee5948eCaa776",
    			"private_key": "bcdf20249abf0ed6d944c0288fad489e33f66b3960d9e6229c1cd214ed3bbe31"
    		},
    ......
    

    我们后面将使用这些私钥完成合约部署、发交易等工作。

  • MetaMask

    首先,按照教程安装MetaMask,这里假设开发者已经熟悉MetaMask的使用。

    在MetaMask上,添加以太坊2.0私链网络、添加区块链的账户信息。

  • 生成以太坊插件配置

    填写配置json文件,并保存下来,后面该配置文件,将会用在注册区块链操作中。

    • bcdnsRootCertPem:BCDNS根证书,可以在Embedded BCDNS的配置acb.bcdns.embedded.root-cert-file中找到。
    • beaconApiUrl:这里配置Beacon客户端的RPC地址。
    • eth2ChainConfig:这里先空置,需要请求beacon rpc接口来获取,这里当ethNetwork为private-net的时候,需要明确一些链的具体配置,比如EPOCH包含多少SLOTS、SYNC COMMITTEE SIZE之类的信息。
    • gasLimitPolicy:这里指定发送交易的时候,如何填入交易的gas limit,这里ESTIMATE是指发送之前通过EL JSON RPC估计一下gas消耗并作为交易的gas limit;
    • gasPricePolicy:这里指定发送交易的时候,如何填入交易的gas price,这里是通过调用特定服务的API来获得gas price值;
    • gasPriceProviderSupplier:这里指定提供获取gas price的服务的类型,ethereum是指通过EL JSON RPC来获取;
    • privateKey:这里填入Relayer发送跨链交易的账户私钥,该私钥仅作为跨链账户使用,不要在其他地方用于发送交易;
    • url:以太坊执行节点的RPC地址;
    {
        "bcdnsRootCertPem": "-----BEGIN BCDNS TRUST ROOT CERTIFICATE-----\nAAAVAgAAAA...npp1tvNQJKwumjAw=\n-----END BCDNS TRUST ROOT CERTIFICATE-----",
        "beaconApiUrl": "http://localhost:33001",
        "eth2ChainConfig": {},
        "ethNetwork": "private-net",
        "gasLimitPolicy": "ESTIMATE",
        "gasPricePolicy": "FROM_API",
        "gasPriceProviderSupplier": "ethereum",
        "privateKey": "5f5510cf48668a08e3...1d5d47b163",
        "url": "http://localhost:32002"
    }

    下面介绍如果获取eth2ChainConfig,使用Java21执行下面命令,注意jar包路径位置:

    /zulu21/bin/java -cp plugins/ethereum2-acb-plugin-1.0.0-plugin.jar com.alipay.antchain.bridge.plugins.ethereum2.tools.EthBbcTools getEth2Config http://localhost:33001 private-net
    

    可以获得一段json作为返回,拷贝到eth2ChainConfig的value位置,数据类似:

    {
    	"forks":[
    		{
    			"epoch":0,
    			"name":"GENESIS",
    			"version":"EAAAOA=="
    		},
    		{
    			"epoch":0,
    			"name":"ALTAIR",
    			"version":"IAAAOA=="
    		},
    		{
    			"epoch":0,
    			"name":"BELLATRIX",
    			"version":"MAAAOA=="
    		},
    		{
    			"epoch":0,
    			"name":"CAPELLA",
    			"version":"QAAAOA=="
    		},
    		{
    			"epoch":0,
    			"name":"DENEB",
    			"version":"UAAAOA=="
    		}
    	],
    	"genesis_time":1735124997,
    	"genesis_validators_root":"1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=",
    	"network_name":"private-net",
    	"remote_spec_config":{
    		"SLOTS_PER_EPOCH":"32",
    		"BLS_WITHDRAWAL_PREFIX":"0x00"
    	}
    }
    

2.5 委员会(Committee)PTC

在AntChain Bridge项目中,找到acb-committeeptc下面的README,按照“快速开始”部分执行【编译】、【安装】、【启动服务】三部分。

Note

在README里面,会启动四个Committee Node,也可以只启动一个Node,这样可以减少操作难度,之后注意PTC Trust Root构造之类涉及节点数量的操作。

在启动服务时,README用到了星火链网BCDNS,但是本教程使用了Embedded BCDNS,操作类似,这里给出过程:

紧接着“第三步”通过BCDNS申请PTC证书这里,需要拿着generate-ptc-csr之后的Base64结果,去向Embedded BCDNS申请PTC证书。

grpcurl -plaintext \
    -import-path /path/to/acb-sdk/bcdns-services/embedded-bcdns/embedded-bcdns-core/src/main/proto/ \
    -proto embedded-bcdns-service.proto \
    -d @ localhost:8090 \
    com.alipay.antchain.bridge.bcdns.embedded.grpc.EmbeddedBcdnsService/applyPTCCertificate <<EOM
    {
                "certSigningRequest": "AACXAAAAAA...KRMzgQAAAAAAA=="
    }
EOM

类似Relayer证书申请过程,BCDNS会返回收据信息,比如:

{
  "applyPTCCertificateResp": {
    "applyReceipt": "d6c4cfb9505fd6badc90c037b4bcb5d196a68c9b1c911d108493abc8ba62995c"
  }
}

然后查询PTC证书:

grpcurl -plaintext \
    -import-path /path/to/acb-sdk/bcdns-services/embedded-bcdns/embedded-bcdns-core/src/main/proto/ \
    -proto embedded-bcdns-service.proto \
    -d @ localhost:8090 \
    com.alipay.antchain.bridge.bcdns.embedded.grpc.EmbeddedBcdnsService/queryPTCCertificateApplicationResult <<EOM
    {
                "applyReceipt": "d6c4cfb9505fd6badc90c037b4bcb5d196a68c9b1c911d108493abc8ba62995c"
    }
EOM

可以得到类似下面结果:

{
  "applicationResult": {
    "isFinalResult": true,
    "certificate": "AAAiAgAAAAABAAAA...kPXjFCuwkcb2/89m0QA"
  }
}

拷贝certificate的Base64内容,通过下面方式转换为PEM格式,并保存到特定文件,比如ptc.crt:

supervisor:> convert-cross-chain-cert-to-pem --base64Input AAAiAgAAAAABAAAA...kPXjFCuwkcb2/89m0QA
-----BEGIN RELAYER CERTIFICATE-----
AAAIAgAAAAABAAAAMQEAKAAAAGRpZDpiaWQ6ZWY5OVJ6OFRpN3g0aTZ6eUNyUHlG
aXk5dXRzV0JKVVcCAAEAAAADAwA7AAAAAAA1AAAAAAABAAAAAQEAKAAAAGRpZDpi
...
4QlxLUp70uRK43ECAAcAAABFZDI1NTE5AwBAAAAAbA8zkKXCI4Iwp6KBERXOqKln
JT/qn36in7+iU6SsNEz0rsJpmEvVRT6adNVY7zS/ni35JwWf/zi60DKnQ7xaCA==
-----END RELAYER CERTIFICATE-----

把上面的PEM格式PTC跨链证书保存到文件,打开Supervisor CLI配置文件./conf/config.json,找到ptc_certificate字段,把PTC证书文件的路径填到这个字段,要求重启Supervisor CLI。

第四步,准备Embedded BCDNS的客户端配置文件。

填写下面Embedded BCDNS客户端的配置,server_address为BCDNS服务的地址:

{
    "server_address": "grpc://0.0.0.0:8090"
}

准备完毕后,保存到文件,比如bcdns.json备用。

在Supervisor CLI运行,启动BIF BCDNS客户端。

start-bcdns-client --bcdnsType BIF --bcdnsClientConfigPath ./bcdns.json

后续步骤按照README执行即可。

在“第七步,安装HCDVS插件”时,使用之前编译的ethereum2-acb-plugin-1.0.0-plugin.jar

完成Committee Node的启动之后,需要将Committee PTC注册到Relayer

准备Committee PTC客户端的配置文件,比如committee-client.json,共两个参数tls_client_pem_pkcs8_key和tls_client_pem_cert,是用来和Committee Node建立链接用的,tls_client_pem_pkcs8_key为私钥,使用Relayer的tls_certs/relayer.key,类似地,tls_client_pem_cert使用Relayer的tls_certs/relayer.crt:

{
  "tls_client_pem_pkcs8_key": "-----BEGIN PRIVATE KEY-----\nMIIEvg...bFMRGr\n-----END PRIVATE KEY-----\n",
  "tls_client_pem_cert": "-----BEGIN CERTIFICATE-----\nMIIDmzCCA...KRgefoZTOpgk\n-----END CERTIFICATE-----\n"
}

启动r-cli,执行命令,ptcCertFile指向PTC的跨链证书,比如上面提到的ptc.crt,注意修改configFile和ptcCertFile的路径:

register-ptc-service --ptcServiceId myptc --configFile /path/to/committee-client.json --ptcCertFile /path/to/ptc.crt

3. 注册区块链🔎

域名申请

区块链想要加入AntChainBridge跨链网络,需要向BCDNS申请域名和域名证书

  • 注册BCDNS服务

首先要构造Embedded BCDNS客户端的配置文件,这里给出模版,保存到本地,假设是config/embedded_root_bcdns.json

{
  "server_address":"grpc://0.0.0.0:8090"
}

使用register-bcdnsservice注册BCDNS服务:

relayer:> register-bcdnsservice --bcdnsType embedded --propFile /path/to/config/embedded_root_bcdns.json
success
  • BCDNS签发域名证书

apply-domain-name-cert用于申请域名证书,oidFilePath指向generate-relayer-account生成的public_key.pem,参数domain则代表要申请的区块链域名:

relayer:> apply-domain-name-cert --applicantOidType X509_PUBLIC_KEY_INFO --oidFilePath /path/to/public_key.pem --domain testdomain1
your receipt is 6165148aa797eb97416fcdf8fbb2a648704d0343ea2ad0d8826837c3bda3781a
  • 查询域名证书申请结果

query-domain-cert-application-state用于查询指定区块链域名的证书申请状态:

relayer:> query-domain-cert-application-state --domain testdomain1
application finished: apply_success
  • 查询域名证书

query-domain-name-cert-from-bcdns用于从BCDNS查询指定域名的证书信息:

relayer:> query-domain-name-cert-from-bcdns --domain testdomain1

同样地,申请域名testdomain2备用。

注册链

通过CLI配置区块链到Relayer,这里首先指定域名domain为刚申请的testdomain1,区块链类型product为以太坊插件指定的simple-ethereumblockchainId则随意指定不要重复即可,pluginServerId为上面注册的PluginServer的ID,confFile为以太坊配置文件。

add-blockchain-anchor用于向系统添加一个指定的区块链配置:

relayer:> add-blockchain-anchor --domain testdomain1 --product ethereum2 --blockchainId eth01.id --pluginServerId myps.id --confFile /path/to/eth.json

部署合约

注册V1版本链,也就是支持安全跨链的异构链,要求先手动部署BBC相关合约,这是为了方便将AM合约地址填入区块链信任锚(BTA)中,作为后续验证跨链消息的依据。

setup-bbccontracts --product ethereum2 --blockchainId eth01.id

Note

这一步会执行比较久的时间,因为链出块时间默认是12s,因此这里请耐心等待⌛️

执行完成之后可以通过CLI查询合约地址:

relayer:> get-blockchain-contracts --product ethereum2 --blockchainId eth01.id
{"am_contract":"0xb2eb5555c7782d1e4da78f92e0891ea24a755743","ptc_contract":"0xa462b73154baf8ceaacce8a0425716f702634f7f","sdp_contract":"0xe47f9aca10db807f5253c29ee9512318f5fa4f09","state":"DEPLOY_FINISHED"}

构造PTC背书配置

首先运行下面命令,获取到Committee PTC的信任根:

get-ptc-trust-root --ptcServiceId myptc --showNetworkInfo true  --showVerifyAnchors true

得到类似下面结果:

network info: {
	"committee_id":"default",
	"nodes":[
		{
			"endpoint":"grpcs://172.16.0.50:10080",
			"node_id":"node1",
			"tls_cert":"-----BEGIN CERTIFICATE-----\nMIIDoTCCAomgA...6o9JYouD2E\n-----END CERTIFICATE-----\n"
		},
		{
			"endpoint":"grpcs://172.16.0.50:10180",
			"node_id":"node2",
			"tls_cert":"-----BEGIN CERTIFICATE-----\nMIIDoTCCAo...3JQSgx6o9JYouD2E\n-----END CERTIFICATE-----\n"
		},
		{
			"endpoint":"grpcs://172.16.0.50:10280",
			"node_id":"node3",
			"tls_cert":"-----BEGIN CERTIFICATE-----\nMIIDoTCCAo...JYouD2E\n-----END CERTIFICATE-----\n"
		},
		{
			"endpoint":"grpcs://172.16.0.50:10380",
			"node_id":"node4",
			"tls_cert":"-----BEGIN CERTIFICATE-----\nMIIDoTCCAo...6o9JYouD2E\n-----END CERTIFICATE-----\n"
		}
	]
}
verify-anchors info: {
	"0":{
		"anchors":[
			{
				"node_id":"node1",
				"node_public_keys":[
					{
						"key_id":"default",
						"public_key":"-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZ...BrLWB/sQ==\n-----END PUBLIC KEY-----\n",
						"rawPublicKey":"MFYwEAYHKoZ...c16BrLWB/sQ=="
					}
				]
			},
			{
				"node_id":"node2",
				"node_public_keys":[
					{
						"key_id":"default",
						"public_key":"-----BEGIN PUBLIC KEY-----\nMFYwEAYHKo...AzqYoesSuw==\n-----END PUBLIC KEY-----\n",
						"rawPublicKey":"MFYwEAYHKoZIzj0CAQ...qQeR/6t1XVfb0qAzqYoesSuw=="
					}
				]
			},
			{
				"node_id":"node3",
				"node_public_keys":[
					{
						"key_id":"default",
						"public_key":"-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj...NWOXQ==\n-----END PUBLIC KEY-----\n",
						"rawPublicKey":"MFYwEAYHKoZIzj0CA...HNWENWOXQ=="
					}
				]
			},
			{
				"node_id":"node4",
				"node_public_keys":[
					{
						"key_id":"default",
						"public_key":"-----BEGIN PUBLIC KEY-----\nMFYwEAYHKo...jhuV4rvF6zaHcgTa+w==\n-----END PUBLIC KEY-----\n",
						"rawPublicKey":"MFYwEAYHKoZI...F6zaHcgTa+w=="
					}
				]
			}
		],
		"committee_id":"default"
	}
}

构造下面这样的配置文件endorse_root.json,⚠️node_id、key_id都需要和上面json一致,public_key则是上面的PEM格式公钥,也就是验证Committee签名的公钥,required为true代表该节点是否必须包含在签名集合中,否则证明非法,required为false则代表该节点为可选的,threshold则代表要求可选节点在签名集合中至少包含大于几个。

Important

如果之前部署CommitteePTC时,仅启动了一个节点,那么这里结果只有一个,比如node1,假设下面的node1的required为true,那么policy.threshold可以配置为>=0,请酌情配置。

{
  "committee_id": "default",
  "endorsers": [
    {
      "node_id": "node1",
      "node_public_key": {
        "key_id": "default",
        "public_key": "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQY...FYbXcc16BrLWB/sQ==\n-----END PUBLIC KEY-----\n"
      },
      "required": true
    },
    {
      "node_id": "node2",
      "node_public_key": {
        "key_id": "default",
        "public_key": "-----BEGIN PUBLIC KEY-----\nMFYwEAYHK...oesSuw==\n-----END PUBLIC KEY-----\n"
      },
      "required": false
    },
    {
      "node_id": "node3",
      "node_public_key": {
        "key_id": "default",
        "public_key": "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZ...HNWENWOXQ==\n-----END PUBLIC KEY-----\n"
      },
      "required": false
    },
    {
      "node_id": "node4",
      "node_public_key": {
        "key_id": "default",
        "public_key": "-----BEGIN PUBLIC KEY-----\nMFYwEAYHK...HcgTa+w==\n-----END PUBLIC KEY-----\n"
      },
      "required": false
    }
  ],
  "policy": {
    "threshold": ">=2"
  }
}

构造BTA拓展信息

Relayer CLI运行下面命令,注意修改endorseRootFile路径:

construct-extension-in-bta-for-committee-ptc --senderDomain testdomain1 --endorseRootFile /path/to/endorse_root.json

会得到一个base64 结构(1),拷贝下来备用。

构造BTA

首先通过运行ETH2插件提供的小工具,来生成区块链要填入BTA的锚定信息,这里需要使用JDK21运行,注意ethereum2-acb-plugin-1.0.0-plugin.jar的路径,这里一共有三个参数,依次为:

  • Beacon 节点RPC地址,这里改为自己私有链的地址;
  • private-net,接入Ethereum的类型,这里是private-net,我们也支持mainnet、holesky等;
  • 最后是获取配置的slot,这里一般是用最新的即可,可以通过dora或者beacon rpc查询,这里不赘述了;
/zulu21/bin/java -cp plugins/ethereum2-acb-plugin-1.0.0-plugin.jar com.alipay.antchain.bridge.plugins.ethereum2.tools.EthBbcTools buildEthSubjectIdentity http://localhost:33001 private-net 62217 > output.txt

这里会保存一个Base64到output.txt,这个Base64会被填在BTA的subject identity字段。

再运行下面命令,构造BTA,subjectIdentityBase64填入上面output.txt的内容,注意subjectIdentityBase64的内容会比较长,extensionBase64填入之前的base64 (1),domainOwnerPrivateKeyFile、domainOwnerPublicKeyFile则对应relayer cc_certs下面的密钥,注意修改路径:

build-btav1 --blockchainDomain testdomain1 --subjectProduct ethereum2 --signAlgo ED25519 --subjectIdentityBase64 ewoJImN1cnJl...TNaV0l6In0= --extensionBase64 eyJlb...z09In0= --ptcServiceId myptc --domainOwnerPrivateKeyFile /path/to/acb-relayer/cc_certs/private_key.pem --domainOwnerPublicKeyFile /path/to/acb-relayer/cc_certs/public_key.pem

运行之后拿到Base64格式的BTA,保存待用。

注册TpBTA

使用上面最后BTA的base64结果,作为下面命令的参数rawBase64Bta:

register-tp-bta --ptcServiceId myptc --rawBase64Bta Kg8gZZ...AHsAr3CA==

上传TpBTA到BCDNS

执行下面命令,将TpBTA公开到BCDNS,这样其他Relayer也都可以获取到该TpBTA,用于接收链合约中的验证过程。

upload-tp-bta --senderDomain testdomain1

启动区块链跨链服务

通过CLI工具启动Relayer对于该链的监听、转发等服务,统一称为Anchor服务。

start-blockchain-anchor --product ethereum2 --blockchainId eth01.id

启动的区块链,Relayer会对其启动Anchor服务,扫描注册之后的每个高度,可以通过CLI工具查看当前扫描的高度。

relayer:> get-blockchain-heights --product ethereum2 --blockchainId eth01.id
{
	"crosschainTaskBlockHeight":{
		"gmtModified":"2023-12-27 19:20:23",
		"height":2096510
	},
	"latestBlockHeight":{
		"gmtModified":"2023-12-27 19:20:23",
		"height":2096511
	}
}

同样地,用域名testdomain2注册eth02.id。在注册时,可以将eth.json中的账户私钥替换成另一个私钥,绝不可以重复使用私钥,以防链上nonce冲突。虽然注册用了同一条链,但是会部署两套系统合约,在跨链系统中,会被作为两条链处理,即在AntChainBridge跨链中,参与的区块链都是以域名为ID的逻辑链。

4. 执行Demo🔎

下面将要尝试从testdomain1的智能合约发送一段消息到testdomain2的智能合约。

准备合约

首先,给出消息发送合约和消息接收合约的代码。

  • 发送合约

    pragma solidity ^0.8.0;
    
    interface ProtocolInterface {
        function sendMessage(
            string calldata _destination_domain,
            bytes32 _receiver,
            bytes calldata _message
        ) external;
    
        function sendUnorderedMessage(
            string calldata _destination_domain,
            bytes32 _receiver,
            bytes calldata _message
        ) external;
    }
    
    contract SenderContract {
        address sdp_address;
    
        function setSdpMSGAddress(address _sdp_address) public {
            sdp_address = _sdp_address;
        }
    
        function send(
            bytes32 receiver,
            string memory domain,
            bytes memory _msg
        ) public {
            ProtocolInterface sdp = ProtocolInterface(sdp_address);
            sdp.sendMessage(domain, receiver, _msg);
        }
    
        function sendUnordered(
            bytes32 receiver,
            string memory domain,
            bytes memory _msg
        ) public {
            ProtocolInterface sdp = ProtocolInterface(sdp_address);
            sdp.sendUnorderedMessage(domain, receiver, _msg);
        }
    }
  • 接收合约

    pragma solidity ^0.8.0;
    
    contract ReceiverContract {
        bytes last_msg;
        bytes last_uo_msg;
    
        event amNotify(string key, bytes32 value, string enterprise);
    
        function recvMessage(
            string memory domain_name,
            bytes32 author,
            bytes memory message
        ) public {
            require(message.length != 32, "32B");
            last_msg = message;
            emit amNotify(domain_name, author, string(message));
        }
    
        function getLastMsg() public view returns (bytes memory) {
            return last_msg;
        }
    
        function recvUnorderedMessage(
            string memory domain_name,
            bytes32 author,
            bytes memory message
        ) public {
            require(message.length != 32, "32B");
            last_uo_msg = message;
            emit amNotify(domain_name, author, string(message));
        }
    
        function getLastUnorderedMsg() public view returns (bytes memory) {
            return last_uo_msg;
        }
    }

    打开Remix,关于Remix的使用,请参考官方教程,这里不对基本操作做过多的介绍。

    将上述代码分别创建一个文件比如SenderContract.solReceiverContract.sol并拷贝进去。使用Remix分别编译两个合约、并选择“Injected Provider - MetaMask”作为ENVIRONMENT,部署合约到私有链,注意不要使用注册区块链的私钥

    部署完成后,对于SenderContract.sol,需要完成一次配置操作:

    在Remix调用SenderContract.sol实例的setSdpMSGAddress方法,将testdomain1通过get-blockchain-contracts拿到的sdp_contract地址作为参数即可,比如上面例子的0xe47f9aca10db807f5253c29ee9512318f5fa4f09

配置授权

Relayer的Anchor服务会对发送的跨链消息进行监听,并发送给接收链,这里会校验发送链的权限,如果无权限,则会失败。

通过CLI,完成合约-合约级别授权:

relayer:> add-cross-chain-msg-acl --grantDomain testdomain1 --grantIdentity 0x3202821beaC4F58be60bb465d1ae4b1899Cbc79a --ownerDomain testdomain2 --ownerIdentity 0x6f0E88921360dD375CA309d6e96B44C81FB25979

在上面命令中,grantDomain为发送链域名,grantIdentity为发送合约地址,这里替换成你部署的合约地址,ownerDomain为接收链域名,ownerIdentity为接收合约地址,这里替换成你部署的合约地址。

发送消息

SenderContract.sol有接口senderUnordered,用来发送无序的跨链消息。

发送之前你需要准备三个参数:

  • receiver:接收合约的地址,这里为bytes32,因为在跨链中,所有账户地址都需要表达为32Bytes,像以太坊地址,这里是前缀加12Bytes的0x00,拓展到32Bytes,比如0x0000000000000000000000006f0E88921360dD375CA309d6e96B44C81FB25979,这里替换成自己的接收合约地址(后缀20Bytes)。

  • domain:接收域名,这里是eth02.local.bif

  • _msg:发送的消息,这里是bytes类型,需要填入Hex格式,比如下面一个hex,解出来是:I'm sending a 🦊 to a 🐶 by antchain bridge.

    0x49276d2073656e64696e67206120f09fa68a20746f206120f09f90b620627920616e74636861696e206272696467652e
    

然后,使用Remix调用发送合约的senderUnordered接口,发送跨链消息。

查看接收结果

接收合约的recvUnorderedMessage方法会被接收链的SDP合约调用,以传递跨链消息。

调用接收合约的getLastUnorderedMsg,可以看到接收到的信息,依然是hex值,解出来后,可以看到消息字符串。

如果需要交易hash信息,或者没有接收到,可以查询数据库的sdp_msg_poolsdp_msg_archive,可以看到字段tx_hashtx_fail_reason

5. Q&A🔎

1. Ethereum2.0插件验证BLS签名要求GLIBCXX_3.4.21,需要升级GCC并更新glibstdc++.6的软链接。

如果CommitteePTC运行出现类似下述日志,那么请升级您环境的GCC版本,推荐gcc8.x。

2025-01-03 15:07:38.392 [grpc-default-executor-1] ERROR c.a.a.b.p.c.node.server.CommitteeNodeServiceImpl -
                commit anchor state with height 62272 and hash 54863d4b4fe7f585bbf806693b01f41017715f75df9f8159992c99f97e9e4888 for domain eth2-1.web3 failed with unexpected error:
java.lang.UnsatisfiedLinkError: /tmp/blst@15533889795096216357/libblst.so: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by /tmp/blst@15533889795096216357/libblst.so)
        at java.base/jdk.internal.loader.NativeLibraries.load(Native Method)
        at java.base/jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open(NativeLibraries.java:331)
        at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:197)
        at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:139)
        at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2418)
        at java.base/java.lang.Runtime.load0(Runtime.java:852)
        at java.base/java.lang.System.load(System.java:2025)
        at supranational.blst.blstJNI.<clinit>(blstJNI.java:42)
        at java.base/java.lang.Class.forName0(Native Method)
        at java.base/java.lang.Class.forName(Class.java:421)
        at java.base/java.lang.Class.forName(Class.java:412)
        at tech.pegasys.teku.bls.impl.blst.BlstLoader.loadBlst(BlstLoader.java:37)
        at tech.pegasys.teku.bls.impl.blst.BlstLoader.<clinit>(BlstLoader.java:32)
        at tech.pegasys.teku.bls.BLS.resetBlsImplementation(BLS.java:56)
        at tech.pegasys.teku.bls.BLS.<clinit>(BLS.java:48)
        at tech.pegasys.teku.bls.BLSSignatureVerifier$1.verify(BLSSignatureVerifier.java:34)
        at com.alipay.antchain.bridge.plugins.ethereum2.core.EthConsensusStateData.validate(EthConsensusStateData.java:135)
        at com.alipay.antchain.bridge.plugins.ethereum2.EthereumHcdvsService.verifyAnchorConsensusState(EthereumHcdvsService.java:56)
        at com.alipay.antchain.bridge.ptc.committee.node.service.impl.EndorserServiceImpl.commitAnchorState(EndorserServiceImpl.java:203)
        at com.alipay.antchain.bridge.ptc.committee.node.server.CommitteeNodeServiceImpl.commitAnchorState(CommitteeNodeServiceImpl.java:219)
        at com.alipay.antchain.bridge.ptc.committee.grpc.CommitteeNodeServiceGrpc$MethodHandlers.invoke(CommitteeNodeServiceGrpc.java:718)
        at io.grpc.stub.ServerCalls$UnaryServerCallHandler$UnaryServerCallListener.onHalfClose(ServerCalls.java:182)
        at io.grpc.PartialForwardingServerCallListener.onHalfClose(PartialForwardingServerCallListener.java:35)
        at io.grpc.ForwardingServerCallListener.onHalfClose(ForwardingServerCallListener.java:23)
        at io.grpc.ForwardingServerCallListener$SimpleForwardingServerCallListener.onHalfClose(ForwardingServerCallListener.java:40)
        at io.grpc.Contexts$ContextualizedServerCallListener.onHalfClose(Contexts.java:86)
        at io.grpc.internal.ServerCallImpl$ServerStreamListenerImpl.halfClosed(ServerCallImpl.java:356)
        at io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener$1HalfClosed.runInContext(ServerImpl.java:861)
        at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)