Tutorials
- Conflux入门心得汇总
- ConfluxScan 验证合约
- Conflux 区块链数据同步指南
- PersonalMessage & CIP23
- 如何连 MetaMask至 Conflux eSpace
- Conflux 网络 v2.1.0 升级详解
- Conflux Core 合约设置代付
- 将以太坊合约迁移至Conflux
Conflux入门心得汇总
概念
参考资料:
- https://developer.confluxnetwork.org 开发者文档
- https://docs.confluxnetwork.org/go-conflux-sdk sdk文档
- https://zhuanlan.zhihu.com/p/400583419 https://zhuanlan.zhihu.com/p/393935101 NFT部署教程
树图结构
由开发者文档所示, 与传统的以太坊的链式结构不同, 该结构是类似于IoTA的有向无环图(DAG)结构的, 具体结构可自行查阅.
基本概念
CFX
CFX为conflux中的token, 对应于以太坊中的Ether. conflux中有多种代币单位, 包括Drip,GDrip, uCFX, CFX. 这几种代币之间的进率如下图所示:
CFX用处
- 参与链治理 -> 拥有CFX的用户可以参与链治理, 发起投票
- 发送交易: 包括转账, 部署与调用合约等
- 抵押(staking mechanism):
CFX获取
-
挖矿: 当挖到相应的区块后可以获得分红. 分红包括:
- 固定奖励2CFX
- 执行交易获得的费用
- 所有抵押代币所获得的年化率4%的回报(需要加入PoS)
-
抵押: 将自己的代币抵押给内置合约, 从而使得该账户参与链治理的权力变大. 内置合约根据规则支付年化4%的回报
-
转账: 通过从其他账户转账从而获得代币
ChainID 与 NetworkID
顾名思义, 对链与网络的一个标识
注意点
- ChainID在主网为1029, 在测试网为1
- ChainID可以用来对抗交易重放攻击(transaction replay attacks)
- NetworkID与ChainID相同
地址
conflux地址为base32形式, 满足cip317(一种编码方式, 提高安全性且增加了可读性), 与以太坊相对
例子如下:
16 进制地址:0x1386b4185a223ef49592233b69291bbe5a80c527
base32地址:cfx:aak2rra2njvd77ezwjvx04kkds9fzagfe6ku8scz91
在以太坊的common.Address
是以16进制的方式存储, 可调用base32地址的GetHexString
获得, 也就是说, conflux地址与以太坊地址间存在一对一映射关系
账户
存储与以太坊相同, 以 地址-> 状态的键值对以kvstore存储在MPT树当中
与以太坊账户相同的部分: nonce
, balance
, codeHash
(合约账户)
CONFLUX特有的部分: stakingBalance
, storageCollateral
, accumulatedInterestReturn
, admin
, sponsorInfo
具体解释可以查看(https://developer.confluxnetwork.org/introduction/en/conflux_basics)
交易
大致的field与以太坊相同, 主要有from
, to
, nonce
, gasPrice
, gas
, value
, chainId
, data
, v, r, s
这些field的意义也与以太坊相同
conflux特殊的是有epochHeight
字段与storageLimit
, 跟区块高度相对, conflux的共识是以epoch去表征区块深度的
交易的生命周期
- 准备发送者的私钥和地址
- 准备交易的元数据,组装成交易, 并将其encode(keccak256), 私钥进行签名(ECDSA), 再用rlp进行encode -> hexString -> rawtransaction
- 通过rpc方法, 将rawtransaction发送至全节点的交易池
- 交易被矿工打包进区块
- 等待5个epoch -> 状态变为executed, 执行交易 (本地状态进行更新?)
- 等待50个epoch -> 状态变为confirmed, 交易被承认 (若未被承认, 则状态回滚) 承认机制, 比较epoch大小
- 等待被区块链引用至链上 -> 状态变为Finalized, 完结 (全局状态进行更新?)
与ETH的区别
https://developer.confluxnetwork.org/sending-tx/en/transaction_explain#:~:text=no%20details%20provided.-,Differences%20between%20Conflux%20and%20Ethereum,-%23
存储
需要锁一定数量的资金作为质押, 来作为执行交易时所使用storage需要付出的token, 账户这部分的token会被锁; 计算公式为
X B/64 *(1/16)
# 64字节为一个storage entry
当存储空间的所有权转移时, 新拥有者的token会被锁, 旧拥有者的token会被解除锁定, 并被加回旧拥有者的账户(不发生转账交易, 无法查询)
内置合约
主要有三个合约: AdminControl contract
, SponsorWhitelistControl contract
, Staking Contract
AdminControl contract
创建合约账户的账户成为该合约的admin, 拥有包括destroy, 白名单等多项权力, 该admin权限可以转移
例子:
- A在创建了合约B, 并在合约构建阶段将权力给了C -> C是admin
- A调用合约B, 合约B创建了合约C, 且合约C在合约构建阶段将D设为admin. -> 设为admin操作失败, A是C的管理员, B只是消息传递者
- 若在例子2中, D地址为零, 则可以成功, 则C没有admin
SponsorWhitelistControl contract
使得余额为零的账户能够调用合约, 该费用由sponsor来支付. 条件:
- 调用小于sponsor的上限(该上限可以由sponsor调整)
- 调用账户在白名单当中(白名单只有admin与合约本身可以改, sponsor不能改)
- sponsor关系可以转移
Staking Contract
账户可以将token质押, 从而获取相应的storage和链治理当中的投票权
注意: 质押操作会被更强大的承诺所覆盖, 如, 明年质押1CFX, 与明年质押2CFX相比. (更强大的比较只是token数量?)
实践
建议在Linux与OSX环境下执行·, Windows需要vs编译器过于不友好
安装环境
根据开发者文档步骤, 安装环境 https://developer.confluxnetwork.org/conflux-doc/docs/installation
启动私有链
修改配置文件
在项目目录下进入run
目录
cd run
复制主网toml文件为开发toml文件
cp hydra.toml develpoment.toml
修改development.toml
mode = "dev"
genesis_secrets = "key.txt" ##这个后面需要用
dev_block_interval_ms = 250
jsonrpc_http_port=12537 ##用jsonrpc_local_http_port=12539也可以, 钱包的localhost是12537
从钱包网站上按照步骤获得keystore, 或是从github项目中获取keystore
https://github.com/conflux-fans/conflux-abigen-example/tree/main/keystore
https://wallet.confluxscan.io/login
拉取go-sdk代码
go get github.com/Conflux-Chain/go-conflux-sdk
调用sdk在keystore中创建账户, 并导出账户私钥
func createAcc(client *conflux.Client){
client, err := conflux.NewClient(local_url, conflux.ClientOption{
KeystorePath: ".keystore"}) //local_url = "http://127.0.0.1:12537"
if err != nil {
panic(err)
}
defer client.Close()
acc1,err := client.AccountManager.Create("test")
if err != nil {
log.Fatalln(err)
}
fmt.Println(acc1.String())
acc2,err := client.AccountManager.Create("test")
if err != nil {
log.Fatalln(err)
}
fmt.Println(acc2.String())
}
func exportPriKeys(client *conflux.Client){
list := client.GetAccountManager().List()
priv1, err := client.GetAccountManager().Export(list[0], "test")
if err != nil {
panic(err)
}
priv2, err := client.GetAccountManager().Export(list[1], "test")
if err != nil {
panic(err)
}
fmt.Println(priv2)
fmt.Println(priv1)
}
在run目录下将输出的私钥写入key.txt, 一行一串私钥, 由此对应的账户会被分配1000cfx
vim key.txt
清除链上数据, 并重启
sh clear_state.sh
sh test.sh
test.sh :
export RUST_BACKTRACE=1
export RUST_BACKTRACE=full
../target/release/conflux --config development.toml
发送交易
发送交易的sdk如下
func sendTx(client *conflux.Client){
list := client.GetAccountManager().List()
var utx types.UnsignedTransaction
utx.From = &list[0] //use default account if not set
utx.To = &list[1]
// unlock account
err = client.AccountManager.Unlock(acc1, "test")
if err != nil {
log.Fatal(err)
}
txhash, err := client.SendTransaction(utx)
if err != nil {
log.Fatal(err)
}
fmt.Println(txhash)
}
部署与调用合约
部署合约与调用合约基于cfxabigen, 参考https://docs.confluxnetwork.org/go-conflux-sdk/cfxabigen
下载代码
git clone https://github.com/Conflux-Chain/go-conflux-sdk.git
安装工具
go install ./cmd/cfxabigen
ERC20
根据示例生成go文件
cfxabigen --sol token.sol --pkg main --out token.go >> token.go
接着部署与调用合约可完全参考https://github.com/conflux-fans/conflux-abigen-example/blob/main/token/main.go
ERC721
在理解了上述ERC20步骤后, 可参考 https://zhuanlan.zhihu.com/p/400583419 和https://zhuanlan.zhihu.com/p/393935101 进行ERC721合约的部署与调用
首先, 编译合约需要依赖Openzeppelin
需要安装npm
sudo apt install npm
安装Openzeppelin库
npm install @openzeppelin/contracts
在remix中进行编译从而得到字节码与abi, 将其分别写入nft.bin与nft.abi中(注:其中ipfs中的url可以为自己生成的, 也可随意)
用cfxabigen工具进行编译
cfxabigen --abi nft.abi --bin nft.bin --pkg main --out nft.go >> nft.go
根据nft.go进行如下sdk编写
部署合约
func deployNFT(client *conflux.Client){
err := client.AccountManager.UnlockDefault("test1")
if err != nil {
log.Fatal(err)
}
//tx, hash, t, err := DeployMain(nil, client)
tx, hash, _, err := DeployMain(nil, client)
if err != nil {
panic(err)
}
fmt.Println(tx)
fmt.Println(hash)
receipt, err := client.WaitForTransationReceipt(*hash, time.Second)
if err != nil {
panic(err)
}
logrus.WithFields(logrus.Fields{
"tx": tx,
"hash": hash,
"contract address": receipt.ContractCreated,
}).Info("deploy token done")
}
调用铸造NFT
func mintNFT(client *conflux.Client) {
acc, _ := client.GetAccountManager().GetDefault()
err := client.AccountManager.UnlockDefault("test")
if err != nil {
panic(err)
}
contractAddr := cfxaddress.MustNew("cfx:acdujjy8mrjm3913cnztf0zbgpx5h3fbby7jcwp2pc")
instance, err := NewMain(contractAddr, client)
if err != nil {
panic(err)
}
err = client.AccountManager.UnlockDefault("test")
if err != nil {
panic(err)
}
//to := cfxaddress.MustNew("cfx:aap05tb7b9bsxdn5rn365y3peta8pr6mxefp7m682a")
comacc, _, _ := acc.ToCommon()
tx, hash, err := instance.Mint(nil, comacc)
if err != nil {
panic(err)
}
logrus.WithField("tx", tx).WithField("hash", hash).Info("transfer")
receipt, err := client.WaitForTransationReceipt(*hash, time.Second)
if err != nil {
panic(err)
}
logrus.WithField("transfer receipt", receipt).Info()
}
查询账户拥有的nft
func queryNFT(client *conflux.Client){
acc, _ := client.GetAccountManager().GetDefault()
err := client.AccountManager.UnlockDefault("test1")
if err != nil {
panic(err)
}
contractAddr := cfxaddress.MustNew("cfx:acdujjy8mrjm3913cnztf0zbgpx5h3fbby7jcwp2pc")
instance, err := NewMain(contractAddr, client)
if err != nil {
panic(err)
}
comacc, _, _ := acc.ToCommon()
res, err := instance.MainCaller.BalanceOf(nil, comacc)
if err != nil {
panic(err)
}
res1, _ := instance.MainCaller.Symbol(nil)
fmt.Println(res)
fmt.Println(res1)
}
调用后可以获得账户对应的nft和nft对应的symbol
转移nft
func transferNFT(client *conflux.Client){
acc, _ := client.GetAccountManager().GetDefault()
err := client.AccountManager.UnlockDefault("test1")
if err != nil {
panic(err)
}
contractAddr := cfxaddress.MustNew("cfx:acdujjy8mrjm3913cnztf0zbgpx5h3fbby7jcwp2pc")
instance, err := NewMain(contractAddr, client)
if err != nil {
panic(err)
}
comacc, _, _ := acc.ToCommon()
list := client.GetAccountManager().List()
toacc, _, _ := list[len(list)-1].ToCommon()
err = client.AccountManager.Unlock(list[len(list)-1], "test")
if err != nil {
panic(err)
}
id := big.NewInt(0)
tx, hash, err := instance.TransferFrom(nil, comacc, toacc, id)
if err != nil {
panic(err)
}
receipt, err := client.WaitForTransationReceipt(*hash, time.Second)
if err != nil {
panic(err)
}
logrus.WithFields(logrus.Fields{
"tx": tx,
"hash": hash,
"contract address": receipt.ContractCreated,
}).Info("deploy token done")
res, err := instance.MainCaller.BalanceOf(nil, comacc)
if err != nil {
panic(err)
}
res1, err := instance.MainCaller.BalanceOf(nil, toacc)
if err != nil {
panic(err)
}
fmt.Println(res)
fmt.Println(res1)
}
查询后可以发现一个为0, 一个为1, 则转移成功
ConfluxScan 验证合约
ConfluxScan 提供了合约验证的功能,本文将介绍如何在 Scan 验证一个合约。
为什么要进行合约验证?
合约验证之后会有如下几个好处:
- 合约验证之后可在 Scan 上看到 Solidity 源码及 ABI,更加透明,人人可审查合约逻辑。
- 验证之后的合约可直接在 Scan 合约详情页,调用合约方法读取合约状态,或通过链接 Fluent 更新合约状态。
- 合约验证之后,Scan 会显示绿色✅标识,用户更放心。
如何在 Scan 进行合约验证?
合约详情页,如果合约未验证的话, Contract Tab 会展示合约验证的跳转链接,点击之后可以进入到验证页面
合约验证需要开发者提供合约一些原始信息,Scan 验证服务会用来进行验证操作:
- 合约地址: Base32 格式地址,目前只支持主网,测试网合约地址
- 主合约名: 被验证主合约的名字,提交源码中可能包含多个合约,需要正确的指定需要验证合约的名字。
- License: 合约所采用的许可证类型
- Solidity 编译器版本
- 编译优化配置: 编译时是否打开了优化选项,选项是多少
- 合约源码: 被验证合约对应的源码,需要注意的一点时,如果合约中引入了其他文件中的合约,需要将所有文件代码 flatten 到单个文件中。
Flatten Solidity 代码
因为目前 ConfluxScan 验证只支持单文件,所以在验证前需要进行 flatten 操作,从而获取主合约及所有依赖的完整代码。目前主流的 Solidity 开发工具均提供 flatten 功能,比如:
- truffle-flattener
- npx hardhat flatten
- chainIDE flatten 插件
npx hardhat flatten
假如开发者的合约项目使用 hardhat,则可以使用如下方式获取 merge 后的合约.
$ npx hardhat flatten ./contracts/ERC20Token.sol > ERC20TokenMerged.sol
该命令执行完之后,项目根目录下会有一个完整的合约代码的 ERC20TokenMerged.sol 文件。可直使用 ConfluxScan 的验证页面的合约文件上传功能上传代码,或手动复制文件内容到源代码编辑框中。
关于 npx hardhat flatten
使用也可参看 How to Flatten a Solidity file using Hardhat.
chainIDE flatten 插件
ChainIDE 是一个在线 Solidity 开发工具,通过插件,可以非常方便的扩展功能, @chainide/solidity-flattener
模块即是用于合并合约代码的插件,点击激活,即会在右侧增加一个 Flattener 功能模块。
选择需要 Flatten 的合约,点击 Flatten 按钮,完成即可看到完整代码下载按钮,点击即可下载完成合约文件。
使用 hardhat-conflux 进行合约验证
EtherScan 跟 ConfluxScan 都对外提供 OpenAPI 接口,其中也包含合约验证的接口。从而衍生了一些工具可用于直接验证合约,比如 hardhat-etherscan
插件可用于在 EtherScan 直接验证以太坊合约。
hardhat-conflux 是一个 Hardhat 插件,不仅可以用于 Conflux 合约部署与交互,同样也提供了合约验证功能,使用 npm 安装之后,在 hardhat.config.js 文件引入之后,hardhat 将会增加一个 verifyCfxContract
命令,可用于验证合约
# npx hardhat verifyCfxContract CONTRACT_NAME DEPLOYED_CONTRACT_ADDRESS
$ npx hardhat verifyCfxContract Greeter cfxtest:acba7cvb1k6bhctzsfshybg5zgch39gnpuc8teem53
FAQs
为什么合约验证不通过?
合约验证不通过,大概率是因为某个参数设置不正确,比如 主合约名,编译器版本,是否开启优化等。
Conflux eSpace 如何验证合约?
整体验证方式相同,也可以使用 hardhat-etherscan 插件验证,但需要自行配置 OpenAPI 服务配置
Conflux 区块链数据同步指南
在某些应用场景下如区块链浏览器,需要将链上的区块信息重新写入链下数据库,便于快速查询索引以及聚合分析等操作。通常获取区块链数据的做法是搭一个全节点,然后调用全节点的 RPC 方法获取数据。当然也有别的方法,比如直接解析全节点数据库,这些方法技术难度更大,需要对区块链原理以及区块链数据的存储结构有深入理解,这里不做讨论。
Conflux 具有比 Ethereum 更复杂的特性,如出块时间短,可达到秒级出块;挖矿交易延迟执行等,关于更多 Conflux 特性可参考开发者文档或者白皮书。这对如何保证原子性地同步获取最新的区块提出了更多的挑战。以下是我们结合 Confura 数据同步实践过程中总结的一些经验教训,罗列的一些技术要点,希望能对社区开发者有所帮助。
Target 同步高度
因为 latest mined epoch 打包的交易并不会被立即执行,所以合理的数据同步方式是按照 latest state epoch 或者 latest confirmed epoch 高度进行同步。由于 latest state epoch 发生 pivot chain reorg 的概率比较大,建议使用 latest confirmed epoch 做为同步高度获取数据,减少频繁删除数据产生的性能损耗及实现复杂度。
获取频率
为了最大程度获取最新区块及减少无效 RPC 请求,可根据当前同步的 epoch 高度,动态设置 RPC 请求抓取频率:
- 如果当前同步 epoch 高度 < target 同步高度 , 当前为 catch-up mode, 抓取频率可设置为毫秒级(可根据实际抓取效果设置)。
- 如果当前同步数据已追平 target 同步高度,抓取频率可设置为出块时间如 1s。
获取过程
以下详细描述抓取单个 epoch 区块数据的流程步骤及调用的 RPC 方法。
a. 获取 epoch 下 block hash 列表
通过 cfx_getBlocksByEpoch 获取 epoch 下的 block hash 数组,数组中最后一个元素即为 pivot block hash。
b. 根据 block hash 获取 block/transaction 数据
通过 cfx_getBlockByHashWithPivotAssumption 获取区块的详细数据,为了防止数据获取过程中发生 reorg 导致数据不正确,该接口支持传入 pivot block hash 用于检测 pivot chain switch(如有则返回错误),保证操作原子性和数据准确性。
*为了保证数据的准确性,建议对获取的 block 进行数据有效性检查,如 epoch 高度和 block hash 是否匹配。
c. 获取 epoch 下的 transaction receipts
有如下这两种方式获取 transaction receipts:
- 通过提取 block 中的执行交易 txn hash, 调用 cfx_getTransactionReceipt 获取每个交易的 receipts。
- 通过传入 pivot block hash 调用 cfx_getEpochReceipts 批量获取 epoch 下的所有 receipts, 注意该 rpc 方法属于 debug 命名空间,默认不对外开放,需要额外配置 fullnode
public_rpc_apis
选项才会开启。该接口支持传入 pivot block hash 用于检测 pivot chain switch(如有则返回错误),保证操作原子性和数据准确性。建议使用这种方式获取 receipts 数据,即高效又能保证操作原子性。 *为了保证数据的准确性,建议对获取的 receipt 进行数据有效性检查,如 epoch 高度、 block hash 和 txn hash 是否匹配。
d. 存储数据
将上述爬取的数据写入数据库等存储系统中,最好能使用事务保证数据写入的原子性。
Reorg 检测
为了保证数据的正确性,当发生 pivot chain switch 时需要将发生 reorg 高度的 epoch 数据从存储中删除,并从删除的 epoch 高度重新同步数据。有以下两种方式用于检测 pivot chain switch:
- 使用 Pubsub 订阅 epochs 主题监听 latest state epoch 出块高度,当推送的 epoch 高度小于等于前续推送的 epoch 高度时表示在当前推送 epoch 高度由于 pivot chain switch 发生了 reorg,需要将该 epoch 高度以后的数据从存储中删除,并从该高度开始进行重新同步。
- 同步抓取过程中,检查当前获取 epoch 数据的 parent hash 和上一条 epoch 数据的 pivot hash 是否匹配及 epoch number 是否连续,如不匹配的话,需删除上一条 epoch 数据并重新获取。
PersonalMessage & CIP23
简介
conflux-sdk及fluent支持脱胎于以太坊EIP712
的CIP23
。 CIP23
的提出是为了提高签名的安全性。在支持CIP23
前,若签名的消息是一串字符串,可以看到具体的内容,但若签名的消息是一个结构化的数据(typedData),则用户在钱包中看到的消息是一串处理过的哈希字串,没法看到具体的信息;
在支持CIP23
后,用户可以在钱包中看到自己签名的内容(如图1-2支持EIP712
前后metamask的签名所示)。
本文在此基础上介绍CIP23
的基本概念,并以java-conflux-sdk
为例,说明如何实现CIP23
协议下的签名与验签。
基本概念
CIP23
的基本概念与和EIP712
之间的异同可以参考:https://github.com/Conflux-Chain/CIPs/blob/master/CIPs/cip-23.md
CIP23
中主要用于签名的两个方法为personal_sign
与cfx_signTypedData_v4
。前者为实现普通的字符串的签名,如"v0G9u7huK4mJb2K1", 后者为实现结构体的签名,如
{
"types": {
"CIP23Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "wallet",
"type": "address"
}
],
"Mail": [
{
"name": "from",
"type": "Person"
},
{
"name": "to",
"type": "Person"
},
{
"name": "contents",
"type": "string"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
具体的,typed消息的签名理论可以参考:
普通的字符串消息msg
的签名为将消息加上前缀\x19Conflux Signed Message:\n
,再对其进行hash操作。
fluent签名
fluent钱包是一款为web3研发的简单而安全的conflux钱包,目前已支持的rpc功能可参考: https://conflux-chain.github.io/fluent-wallet-doc/docs/provider-rpc/
在安装了fluent钱包后,就可以去调用fluent的rpc功能。在浏览器页面进入https://fluent-wallet.zendesk.com/hc/zh-cn。此时,fluent钱包会连接上该网站。
在此页面打开console
,输入
> conflux
> conflux.isFluent
本章节将对如何用fluent对消息进行签名进行具体的说明。
personal_sign
在console
中输入以下命令
conflux
.request({
method: 'personal_sign',
params: [
'v0G9u7huK4mJb2K1', '<your_address>']})
此时页面会弹出来自fluent的签名消息
cfx_signTypedData_v4
在console
中输入以下命令
conflux
.request({
method: 'cfx_signTypedData_v4',
params: [
'<your_address>',
`{
"types": {
"CIP23Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "wallet",
"type": "address"
}
],
"Mail": [
{
"name": "from",
"type": "Person"
},
{
"name": "to",
"type": "Person"
},
{
"name": "contents",
"type": "string"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}`]
})
利用Java-conflux-sdk进行签名与验签
签名
java-conflux-sdk
支持了personal_sign
与cfx_signTypedData_v4
。具体的实现如下:
package conflux.web3j.crypto;
import org.junit.jupiter.api.Test;
import org.web3j.utils.Numeric;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class SignDataTests {
//cfx_signTypedData_v4
@Test
public void testSignValidStructure() throws IOException {
StructuredDataTests t = new StructuredDataTests();
//获取TypedData
String msg = t.getResource(
"build/resources/test/"
+ "structured_data_json_files/ValidStructuredData.json");
//根据msg创建编码器
StructuredDataEncoder dataEncoder = new StructuredDataEncoder(msg);
//将数据进行编码后得到msghash,再进行签名
org.web3j.crypto.Sign.SignatureData sign = org.web3j.crypto.Sign.signMessage(dataEncoder.hashStructuredData(), SampleKeys.KEY_PAIR, false);
assertEquals(
"0x371ef48d63082d3875fee13b392c5b6a7449aa638921cb9f3d419f5b6a817ba754d085965fb3a041c3b178d3ae3798ea322ae74cb687dd699b5f6045c7fe47a91c",
Numeric.toHexString(sign.getR()) + Numeric.toHexStringNoPrefix(sign.getS()) + Numeric.toHexStringNoPrefix(sign.getV()));
}
//personal_sign
@Test
public void testSignAnyMessage() throws IOException {
String message = "v0G9u7huK4mJb2K1";
//将msg加上前缀后进行编码后得到msghash,再进行签名
org.web3j.crypto.Sign.SignatureData sign = Sign.signPrefixedMessage(message.getBytes(), SampleKeys.KEY_PAIR);
assertEquals(
"0xbb0ee8492623f2ef6ed461ea638f8b5060b191a1c8830c93d84245f3fb27e20a755e24ff60fe76482dd4377a0aef036937ef88537b2d0fdd834a54e76ecafadc1c",
Numeric.toHexString(sign.getR()) + Numeric.toHexStringNoPrefix(sign.getS()) + Numeric.toHexStringNoPrefix(sign.getV()));
}
}
验签
sdk验签
java-conflux-sdk
为验签提供了ecrecover
方法与测试例程。具体的实现如下:
package conflux.web3j.crypto;
import org.junit.jupiter.api.Test;
import org.web3j.crypto.*;
import org.web3j.crypto.Sign;
import org.web3j.utils.Numeric;
import java.io.IOException;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ECRecoverTest {
private String getAddress() {
return Numeric.prependHexPrefix(Keys.getAddress(getPubKey()));
}
private String getPubKey() {
return SampleKeys.KEY_PAIR.getPublicKey().toString();
}
@Test
public void testSignAndRecoverMessage() {
String message = "v0G9u7huK4mJb2K1";
byte[] msgHash = conflux.web3j.crypto.Sign.getConfluxMessageHash(message.getBytes());
Sign.SignatureData sign = conflux.web3j.crypto.Sign.signPrefixedMessage(message.getBytes(), SampleKeys.KEY_PAIR);
//根据签名与地址进行recover,得到签名的地址
String recoverAddress = conflux.web3j.crypto.Sign.recoverSignature(sign, msgHash, getAddress());
assertEquals(recoverAddress, getAddress());
}
//在fluent中进行签名得到签名后,可以参考以下例程来进行recover
@Test
public void testRecoverTyped() throws IOException {
StructuredDataTests t = new StructuredDataTests();
String msg = t.getResource(
"build/resources/test/"
+ "structured_data_json_files/ValidStructuredData.json");
StructuredDataEncoder dataEncoder = new StructuredDataEncoder(msg);
//以下的签名可以根据上文中的fluent签名来获取
String signature =
"0x371ef48d63082d3875fee13b392c5b6a7449aa638921cb9f3d419f5b6a817ba754d085965fb3a041c3b178d3ae3798ea322ae74cb687dd699b5f6045c7fe47a91c";
//根据签名创建签名实例
byte[] signatureBytes = Numeric.hexStringToByteArray(signature);
byte v = signatureBytes[64];
if (v < 27) {
v += 27;
}
Sign.SignatureData sd =
new Sign.SignatureData(
v,
(byte[]) Arrays.copyOfRange(signatureBytes, 0, 32),
(byte[]) Arrays.copyOfRange(signatureBytes, 32, 64));
//getAddress()中的私钥应换为在fluent钱包中的账户私钥
String recoverAddress = conflux.web3j.crypto.Sign.recoverSignature(sd, dataEncoder.hashStructuredData(), getAddress());
assertEquals(recoverAddress, getAddress());
}
//以下实现与testSignAndRecoverMessage()一样,只是签名方式有区别
@Test
public void testSignAndRecoverTyped() throws IOException {
StructuredDataTests t = new StructuredDataTests();
String msg = t.getResource(
"build/resources/test/"
+ "structured_data_json_files/ValidStructuredData.json");
StructuredDataEncoder dataEncoder = new StructuredDataEncoder(msg);
Sign.SignatureData sign = Sign.signMessage(dataEncoder.hashStructuredData(), SampleKeys.KEY_PAIR, false);
String recoverAddress = conflux.web3j.crypto.Sign.recoverSignature(sign, dataEncoder.hashStructuredData(), getAddress());
assertEquals(recoverAddress, getAddress());
}
}
合约验签
合约验签的参考示例如下:
// file: CIP23DomainExample.sol
pragma solidity ^0.4.24;
contract Example {
struct Person {
string name;
address wallet;
}
struct Mail {
Person from;
Person to;
string contents;
}
bytes32 constant PERSON_TYPEHASH = keccak256(
"Person(string name,address wallet)"
);
bytes32 constant MAIL_TYPEHASH = keccak256(
"Mail(Person from,Person to,string contents)Person(string name,address wallet)"
);
struct CIP23Domain {
string name;
string version;
uint256 chainId;
}
struct VerifyClaim{
address userAddress;
uint256 randNo;
uint256 amount;
}
bytes32 constant CIP23DOMAIN_TYPEHASH = keccak256(
"CIP23Domain(string name,string version,uint256 chainId)"
);
bytes32 constant VERIFYCLAIM_TYPEHASH = keccak256(
"VerifyClaim(address userAddress,uint256 randNo,uint256 amount)"
);
bytes32 DOMAIN_SEPARATOR;
constructor () public {
DOMAIN_SEPARATOR = hash(CIP23Domain({
name: "VerifyClaim",
version: '1',
chainId: 97
}));
}
function hash(Person person) internal pure returns (bytes32) {
return keccak256(abi.encode(
PERSON_TYPEHASH,
keccak256(bytes(person.name)),
person.wallet
));
}
function hash(Mail mail) internal pure returns (bytes32) {
return keccak256(abi.encode(
MAIL_TYPEHASH,
hash(mail.from),
hash(mail.to),
keccak256(bytes(mail.contents))
));
}
function hash(CIP23Domain cip23Domain) internal pure returns (bytes32) {
return keccak256(abi.encode(
CIP23DOMAIN_TYPEHASH,
keccak256(bytes(cip23Domain.name)),
keccak256(bytes(cip23Domain.version)),
cip23Domain.chainId
));
}
function hash(VerifyClaim verifyclaim) internal pure returns (bytes32) {
return keccak256(abi.encode(
VERIFYCLAIM_TYPEHASH,
verifyclaim.userAddress,
verifyclaim.randNo,
verifyclaim.amount
));
}
function verify(VerifyClaim verifyclaim, uint8 v, bytes32 r, bytes32 s) internal view returns (bool) {
// Note: we need to use `encodePacked` here instead of `encode`.
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
hash(verifyclaim)
));
return ecrecover(digest, v, r, s) == 0x53dE6A872435F5286BEFd0b6fB3bC06742aF8C8F;
}
function test(address _userAddress, uint256 _randNO, uint256 _amount, uint8 _v, bytes32 _r, bytes32 _s) public view returns (bool) {
// Example signed message
VerifyClaim memory verifyclaim = VerifyClaim({
userAddress: _userAddress,
randNo: _randNO,
amount: _amount
});
assert(verify(verifyclaim, _v, _r, _s));
return true;
}
}
通过sdk部署该合约,再通过sdk去调用该合约。可以参考上述合约,在设计的合约当中加入验签功能,从而实现对操作是否真的由某用户进行的。
常见问题汇总
解决方案: 进入国内网站进行调用
解决方案:进入https://fluent-wallet.zendesk.com/hc/zh-cn, 再进行命令行调用。
参考代码
如何连 MetaMask至 Conflux eSpace
Conflux eSpace 是一个与以太坊完全兼容的网络,因此所有以太坊的钱包也可以在 Conflux eSpace 使用,比如 Metamask,你需要做的只是将 eSpace 添加至钱包网络中,其他体验保持一致。
注:Conflux Core 空间用户请使用 FluentWallet
连接 MetaMask
可以按照如下步骤为 MetaMask 添加 eSpace 网络:
- 打开 MetaMask 网络切换下拉框
- 选择自定义网络
- 然后按如下配置填写配置项:
eSpace 主网按如下配置填写
- Network Name: Conflux eSpace
- New RPC URL: https://evm.confluxrpc.com
- Chain ID: 1030
- Currency Symbol: CFX
- Block Explorer URL: https://evm.confluxscan.net
- For the eSpace testnet, please use the following configuration values:
eSpace 测试网网按如下配置填写
- Network Name: Conflux eSpace (Testnet)
- New RPC URL: https://evmtestnet.confluxrpc.com
- Chain ID: 71
- Currency Symbol: CFX
- Block Explorer URL: https://evmtestnet.confluxscan.net
注:eSpace 水龙头地址: https://efaucet.confluxnetwork.org/
自动添加服务或工具
许多工具或网站提供了一键为 MetaMask 添加网络的功能,比如 chainlist 以及 eSpace 浏览器的底部栏功能。
以 chainlist 为例,按照如下步骤可完成网络添加:
- 打开网站 https://chainlist.org.
- 搜索 "Conflux eSpace".
- 点击 "Conflux eSpace" 下的 "Connect Wallet" 按钮,该网页会向 MetaMask 发送添加请求
- 点击 "Conflux eSpace" 下的 "Add to Metamask" 按钮.
- 当 MetaMask 弹窗提示 "Allow this site to add a network?" 时,点击 "Approve".
- 当 MetaMask 弹窗提示 "Allow this site to switch the network?" 时,点击 "Approve".
至此你的 MetaMask 就完成了 Conflux eSpace 网络配置,你可以随时将网络切换至其他网络来使用其他链。
配置其他钱包
其他手机钱包比如 TokenPocket 等均提供添加网络功能,只要使用上述网络信息添加配置切换即可。
Conflux 网络 v2.1.0 升级详解
Conflux 网络将在近期进行一次小的 hardfork 升级 -- v2.1.0
,测试网已升级完成 具体参看 For English check here
本次升级包含四个 CIP,其中CIP94, CIP99影响大一些,其他是修复 bug 及优化. 本文对这次升级做一个详解。
CIP-94 详解
CIP-94 为 Conflux 链引入了一个 DAO 投票机制。允许社区通过质押CFX 并投票的方式调整链共识参数。目前支持投票的两个参数:
- PoW base reward
- PoS interest rate
投票每隔两个月会进行一次,任何人都可以参与,在一轮投票结束之后,会根据投票的结果调整参数。该升级可让 Conflux 社区成员充分参与治理,同时此两个参数的调整更加灵活,无需硬分叉即可调整。
具体的 CIP 内容和机制可参看这篇介绍。
另外官方也提供 DAO 投票 Dapp,用户可直接使用 Fluent 钱包,连接参与 DAO 投票。
CIP-99 详解
CIP-99 主要是调整了 PoS 节点进委员会后,不参与投票被强制退休的检测时间长短。CIP-99 之前,如果一个委员会成员节点,超过 1 小时不正常工作,那么该节点状态会被强制设为退休,所有的票自动 unlock,节点的退休状态会持续 7 天,unlock 的票经过 7-14 天的时间方可取出。
为了降低 PoS 节点的运行成本和复杂度,CIP99将参数做了一些调整:
- 强制退休检测时间由一小时改为三小时,即如果一个委员会节点超过三小时不持续投票,才会被认为 down 掉,并进行强制退休操作。
- 节点强制退休状态持续时间从 7 天改为 1 天。
- PoS 票 unlock 锁定时间由 7 天改为 1 天。但锁定时间由 7 天改为 13 天。
这些参数的调整可以大大降低节点被强制退休的概率,并且强制退休节点恢复的时间也被大大缩短。
FullState
v2.1.0 版本另外一大更新是优化了节点 FullState 的实现。所谓的 FullState 即是支持区块链全历史状态查询。比如查询某账户地址在任意区块高度时的 balance, 任意高度时的 nonce 等。大部分情况下只需查询最新状态,但一些特殊应用会有此需求,比如 TheGraph。
在本次升级之前,Conflux-Rust 是通过节点 snapshot 机制来实现 FullState 查询的,随着数据的增大,单个 snapshot 所占用的存储空间越来越大(几十G),一个 snapshot 只能支持 2000 epoch 状态查询,如果想通过 snapshot 支持全量状态查询从空间占用来说是不可能的。
本次升级后,采用了新的 FullState 存储实现。使得全历史状态查询成为可能,如果想运行一个支持 FullState 查询的节点需要做一些额外的配置,并且机器需要额外的空间存储 FullState 数据。
-
enable_single_mpt_storage
以存储所有的历史状态 -
single_mpt_space = "evm"
以仅存储 eSpace 状态。
注意,你需要重新
同步一个归档节点
来重建所有的历史状态。如果只需要 eSpace 的 fullstate 则只需要从 eSpace hardfork 区块开始重新同步即可。
另外开启 FullState 会给节点增加额外的内存消耗,目前 FullState 节点内存用量为 21-25G 左右
。
Conflux Core 合约设置代付
Conflux 中的代付机制详解
将以太坊合约迁移至Conflux
概述
本文简单归纳了开发者将合约迁移至Conflux需要注意的事项。除本文外,读者还可以阅读以下文档了解相关细节
地址
迁移应用时首先需要注意的是地址。Conflux中地址有两种编码方式:
-
0x
开头的十六进制地址。如0x0888000000000000000000000000000000000000
。 - 以
cfx:
或cfxtest:
开头的Base32地址。如cfx:aaejuaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2mhjju8k
。
其中Base32地址编码后的信息包含了十六进制地址信息与该地址所在的网络信息,如cfx:...
代表该地址为主网地址,cfxtest:
代表该地址为测试网地址。这两种编码格式的地址使用场景如下:
- 十六进制地址:仅在
.sol
合约中出现。 - Base32地址:除了
.sol
文件的基本所有场合。
本文章主要讨论迁移合约的注意事项,因此主要关注于十六进制地址。但需要注意的是,以太坊与Conflux计算十六进制地址的方式并不相同,这也是迁移应用时需要注意的。大体而言
- 同样的私钥对应的EOA账户的十六进制地址很可能不同;
- 同样的方式部署合约,合约账户的十六进制地址很可能不相同。
如果开发者不想了解地址计算的细节区别,请按照如下规则检查合约文件中的地址:
替换合约文件(.sol
)中硬编码的地址
由于以太坊与Conflux计算十六进制地址的方式并不相同(包括EOA地址),硬编码的地址一般不可用,同时还可能引起合约执行失败。如果合约中硬编码的地址为EOA地址,则需要使用Conflux提供的工具获得对应的EOA地址,并进行替换。例如python中可以用以下的方法获得以太坊EOA地址对应的Conflux地址:
from cfx_address.utils import eth_eoa_address_to_cfx_hex
eth_address = "0xcfffde169afbd51f081d2e82acca0f19cadcbbe1"
print(eth_eoa_address_to_cfx_hex(eth_address))
硬编码的合约地址则需要替换为Conflux上部署的合约地址。特别需要注意的是,部分合约模版中也会包含硬编码的地址,如ERC777合约模版中可能硬编码了ERC1820合约的地址。
Conflux 中 1820 合约的地址为
0x88887eD889e776bCBe2f0f9932EcFaBcDfCd1820
在替换后,可以在ConfluxScan上查询对应地址是否合法。
检查合约中是否使用了计算合约地址的合约模版
一些以太坊合约会在合约内通过参数计算合约地址,如oz的Create2
合约与Clone
合约。如果项目中使用了这些合约,可能会造成运行结果不符预期或运行出错。conflux-contract 仓库中提供了对应合约的conflux适配版,开发者可以视需要使用。
opcode(NUMBER
与 BLOCKHASH
)
Conflux 中,部分 Opcode 的实现与以太坊中存在区别。
NUMBER
Conflux 中,NUMBER
opcode 返回的为 epoch number,而非block number。
BLOCKHASH
合约中如果使用了BLOCKHASH
opcode,则需要注意兼容性问题。以太坊中BLOCKHASH
opcode参数支持(BLOCK-1
至BLOCK-256
),Conflux中,BLOCKHASH
仅支持参数 NUMBER-1
。
EIP-712 签名
部分合约中可能采用了 EIP-712: Typed structured data hashing and signing 的签名方案。在 Conflux 中,我们提倡(不要求)采用 CIP-23进行签名。CIP-23 与 EIP-712 的主要区别在于:
- 签名字符串时,前缀由
\x19Ethereum Signed Message:\n
改为\x19Conflux Signed Message:\n
。 - 签名 typed structured data 时,将
EIP712domain
替换为CIP23domain
。 - 在
CIP23domain
字段中,chainId
为必选字段。