Conflux101

Overview

Conflux Network 是一个基于自主创新树图共识机制的高性能区块链网络,在完全去中心化的前提之下,可达到 3000 TPS,远超比特币和以太坊。同时实现虚拟机 EVM 兼容, 大大减少项目移植工作量

Fluent 钱包

基本概念

生态应用

如何运行节点

如果开发 Defi 或 NFT 应用

常见问题

开发工具

SDKs

Other Libs

Tools

Glossary

TreeGraph

树图账本结构

Epoch

树图 Pivot chain 上的每个 block 定义一个 Epoch,一个 Epoch 可能会包含多个 block ,EpochNumber 从 0 自增。

Base32 地址

采用 base32 编码格式的地址,具体参看 CIP-37

Era

每 20000 个 Epoch 是一个 Era

Checkpoint

在 era 的分界点上会创建 Checkpoint,checkpoint 之前的 consensus 相关数据会删掉(fullnode),共识算法有关

Snapshot

Snapshot是 state 相关的,每 2000 个 epoch 重新 merge 一个 state 的 snapshot

内置合约

内置合约

AdminControl

总览

AdminControl 合约是一款用于合约开发的调试工具。 在交易过程中创建合约时,当前交易的发送者会自动成为合约的管理者(admin)。

管理者 admin 的地址可以通过调用接口 setAdmin(address contractAddr, address newAdmin) 将管理权益转交给其他的普通用户地址零地址。而一个合约不可成为管理者。

合约的管理者具备多个管理权限。管理者可以调用 destroy(address contractAddr) 接口以销毁合约,该操作就像通过合约调用 suicide() 函数那样。而SponsorWhitelist内部合约提供了一些管理员专用的函数。这些函数可以更新赞助者机制中的白名单。我们将在随后进行介绍。

注意:对于所有和管理者权限相关的接口,不论调用成功与否都不会在执行时触发任何错误或异常。 例如,如果一个非管理者地址尝试将管理者地址转移给其自己,该交易会成功但不会造成任何改动。

如果合约拥有非零的管理者地址,ConfluxScan会将合约标记为调试模式。因此请记住,如果你认为你的合约已经准备好进入实际生产环境,你应当将管理者地址设置为零地址。

AdminControl 合约同时也提供了一个可以被任何人调用的查询接口 getAdmin(address contractAddr)

需要注意的细节:

  1. 默认管理者(交易发送者)是在合约开始创建时设置的。因此,如果发送者 A 创建合约 B 并在合约构建时设置管理者为 C ,在合约部署后合约的管理者为 C
  2. 然而,如果发送者 A 调用合约 B ,随后合约 B 创建合约 C 并在合约创建时将管理者设置为 D ,则该设置会失败,原因是: C 合约的管理者是 A ,但创建合约 C 的发起者是 B
  3. Conflux会引入一种特别的规则。在案例2中,如果 D 是零地址,则管理者设置成功。这意味着合约创建时可以显示地声明其不需要管理者。

样例

考虑到您可以已经部署了一个地址为 contract_addr 的合约。管理者可以通过调用AdminControl.setAdmin(contract_addr, new_admin) 以变更管理员以及通过调用AdminControl.destroy(contract_addr) 来销毁合约。

const PRIVATE_KEY = '0xxxxxxx';
const cfx = new Conflux({
  url: 'http://test.confluxrpc.org',
  logger: console,
});
const account = cfx.wallet.addPrivateKey(PRIVATE_KEY); // create account instance

const admin_contract = cfx.InternalContract('AdminControl')
// to change administrator
admin_contract.setAdmin(contract_addr, new_admin).sendTransaction({
  from: account,
}).confirmed();

// to kill the contract
admin_contract.destroy(contract_addr).sendTransaction({
  from: account,
}).confirmed();
内置合约

SponsorWhitelistControl

总览

Conflux实现了一种赞助机制来补贴用户对智能合约的使用。 因此,只要对合约调用的交易被赞助(通常由Dapps的运营商赞助),使用余额为零的新帐户也能够调用智能合约。 通过引入内置的SponsorControl合约能够记录和管理智能合约的赞助信息。

在进行子调用(Message Call)时,Conflux不会再次检查赞助情况。例如,如果一个普通用户地址 A 调用合约 B ,然后合约 B 调用合约 C, Conflux仅仅会检查 A 是否被合约 B 赞助。如果 A 被赞助,B 会在交易执行过程中负担全部的燃料费用和/或存储抵押费用,包含 B 调用 C 的费用。换句话说,只有交易的发送者才能被赞助,B 不可能被赞助。

SponsorControl合约为每一个用户建立的合约保留了如下的信息:

有两种资源能够被赞助:燃料费用和存储抵押物。

当一个合约被创建的时候,它的 sponsor_for_gassponsor_for_collateral 会被置为零地址,相应的燃料补贴余额也是零。 提供燃料补贴的账户和存储押金补贴的账户都可以通过与 SponsorControl 合约交互完成。合约当前的赞助账户可以直接追加补贴余额,也可以在满足一定条件下提高 sponsor_limit_for_gas_fee。其他普通用户账户如果提供高于当前余额的资金,可以将原先的赞助者取而代之。更具体的细节如下。

赞助者替换

为了替换合约的 sponsor_for_gas ,新的赞助者需要调用函数 setSponsorForGas(address contractAddr, uint upperBound) 并向内置合约转移一笔资金。只有在满足下述条件时才能完成燃料费用赞助者的替换:

  1. 转移的资金应当比合约当前的 sponsor_balance_for_gas 高。
  2. sponsor_limit_for_gas_fee 的新值(被指定为 upperBound 参数)应当不小于原赞助者的限制,除非原本的 sponsor_balance_for_gas 无法负担原赞助者的限制。
  3. 转移的资金应为新限额的1000倍以上,以便足以补贴至少 1000 次调用合约的交易。

如果上述条件满足,剩余的 sponsor_balance_for_gas 会返还给原赞助人 sponsor_for_gas ,随后转给内置合约的资金被注入 sponsor_balance_for_gassponsor_for_gas 以及 sponsor_limit_for_gas_fee 会根据新赞助人指定的值进行更新。如果上述条件没有满足,会触发异常。

sponsor_for_collateral 的替换与替换燃料赞助者的方式类似,处理没有燃料限额的逻辑。方法是 setSponsorForCollateral(address contractAddr) 。新的赞助者需要向合约转移一笔比目前余额更多的资金。随后当前 sponsor_for_collateral 赞助的金额会被全部退还,即 sponsor_balance_for_collateral 和合约当前所有存储抵押金之和,两个与存储押金赞助相关的字段将按照新赞助者的要求进行相应的变更。

一个合约账户也被允许成为赞助者。

提高赞助额度

赞助者可以在无需更换赞助者的情况下提供额外的赞助资金。在该情况下,赞助人需要调用函数 setSponsorForGas(address contractAddr, uint upperBound)setSponsorForCollateral(address contractAddr) 并满足赞助者替换的三条要求中的条件 2,3. 如果满足相关需求,交易的赞助资金会被加入赞助余额中且 sponsor_limit_for_gas_fee 也会相应地被更新。

白名单列表维护

只有合约本身或合约管理者可以更新合约白名单列表。赞助者则无权变更白名单列表。

合约可以通过调用 addPrivilege(address[] memory) 将任何地址加入到白名单列表中。这意味着如果 sponsor_for_gas 被设置,合约会为白名单列表中的账户支付存储押金。全零地址是一个特殊的地址 0x0000000000000000000000000000000000000000 。如果该地址被加入白名单列表,则所有调用该合约的交易都会被赞助。合约还可以调用方法 removePrivilege(address[] memory) 将部分正常账户从白名单列表中移除。移除一个不存在的地址不会导致错误或异常。

需要注意的细节:

  1. 一个合约地址也可被加入到白名单列表中,但该操作无任何意义,因为只有交易的发送者可以被赞助。

合约的管理者可以使用 addPrivilegeByAdmin(address contractAddr, address[] memory addresses)removePrivilegeByAdmin(address contractAddr, address[] memory addresses) 以维护白名单列表。

样例

假定你有一个如下所示的简单合约。

pragma solidity >=0.4.15;

import "https://github.com/Conflux-Chain/conflux-rust/blob/master/internal_contract/contracts/SponsorWhitelistControl.sol";

contract CommissionPrivilegeTest {
    mapping(uint => uint) public ss;

    function add(address account) public payable {
        SponsorWhitelistControl cpc = SponsorWhitelistControl(0x0888000000000000000000000000000000000001);
        address[] memory a = new address[](1);
        a[0] = account;
        cpc.addPrivilege(a);
    }

    function remove(address account) public payable {
        SponsorWhitelistControl cpc = SponsorWhitelistControl(0x0888000000000000000000000000000000000001);
        address[] memory a = new address[](1);
        a[0] = account;
        cpc.removePrivilege(a);
    }

    function foo() public payable {
    }

    function par_add(uint start, uint end) public payable {
        for (uint i = start; i < end; i++) {
            ss[i] = 1;
        }
    }
}

部署合约且地址为 contract_addr 如果有些人希望赞助燃料费用,他/她可以发送如下的交易:

const PRIVATE_KEY = '0xxxxxxx';
const cfx = new Conflux({
  url: 'http://test.confluxrpc.org',
  logger: console,
});
const account = cfx.wallet.addPrivateKey(PRIVATE_KEY); // create account instance

const sponsor_contract = cfx.InternalContract('SponsorWhitelistControl');
sponsor_contract.setSponsorForGas(contract_addr, your_upper_bound).sendTransaction({
  from: account,
  value: your_sponsor_value
}).confirmed();

如果需要赞助存储抵押物,可以简单的将 setSponsorForGas(contract_addr, your_upper_bound) 替代为 setSponsorForCollateral(contract_addr) 即可。

之后你可以使用 addPrivilegeremovePrivilege 为你的合约维护 whitelist 。特殊的全零地址 0x0000000000000000000000000000000000000000 则表示 所有人都处于 whitelist 中。需要谨慎使用。

you_contract.add(white_list_addr).sendTransaction({
  from: account,
})

you_contract.remove(white_list_addr).sendTransaction({
  from: account,
})

随后在 whitelist 中的账户在调用 you_contract.foo()you_contract.par_add(1, 10) 时,不会支付任何费用。

内置合约

Staking

总览

Conflux引入权益质押机制的原因有两个:一、权益机制提供了一种对占用存储空间更好的收费方式(相比于“一次付费,永久占用”)。二、该机制还有助于定义分散治理中的投票权。

在顶层,Conflux实现了一个内置的Staking合约,以记录所有账户的权益信息。通过向该合约发送交易,用户(包括外部用户和智能合约)可以存入/提取资金,也被称为合约内的权益。

用户可以通过调用 deposit(uint amount) 来存入用于抵押的金额,随后 amount 数量的资金将从其 balance 移至 stakingBalance. 需要注意的是该函数不是 payable 的,用户只需要指定抵押的金额而无需将资金转入到内部合约中。

用户还可以通过调用 withdraw(uint amount) 来提取余额。调用者可以调用该函数从Conflux质押合约提取部分代币。抵押资金将会及时的转入用户余额中。所有提款的顺序将按照先到先服务的方式进行处理。

锁定与投票权

通过锁定质押余额,用户可获取投票权以进一步进行链上治理。通过调用 voteLock(uint amount, uint unlock_block_number) 函数,一个帐户可以做出如下承诺:“我的 stakingBalance 在未来 unlock_block_number 中将始终具有至少 amount 的资金”。单个账户可以做出多个承诺,比如说“今年我将至少持有10CFX,并且在明年至少持有5CFX。” **一旦做出承诺,无法取消!**但是该账户可通过锁定更多的金额覆盖原有的承诺。每当账户尝试提取 stakingBalance 时,内部合约会检查剩余余额是否与锁定承诺吻合。

在此处我们将通过几个样例介绍锁定余额的逻辑细节。假设Conflux在今年的剩余时间将产生 x 个区块,在明年的产生 y 个区块。由于Conflux网络每秒能生成两个区块,因此 y 近似等于 2 * 60 * 60 * 24 * 365 。而 x 取决于您何时阅读到该文章。

  1. 假设一个账户的 stakingBalance 中有10CFX,如果其调用 voteLock(100 * 10^18, x) ,说明账户尝试锁定 100CFX. 但由于其缺少足够的 stakingBalance,交易执行失败。
  2. 然而,如果该账户调用 voteLock(8 * 10^18, x) ,则交易会成功。
  3. 随后,如果该账户调用 voteLock(6 * 10^18, x+y),交易同样会成功。这意味着交易执行后 2CFX 在今年结束时解锁,而另外 6CFX 会被锁定直到明年结束。
  4. 如果账户再调用 voteLock(0, x),没有任何事情会发生。在交易执行过程中交易不会触发错误。内置合约会将此调用视为无意义的承诺:该帐户在步骤 2、3 做出的旧承诺有效的前提下,再次承诺在今年结束前至少锁定 0 CFX.
  5. 如果该账户调用 voteLock(9 * 10^18, x+y),则两个较老的承诺将会因为“锁定 9CFX 直到明年结束是一个更强的承诺”被覆盖。

锁定对利息无任何影响。当账户成功取出抵押余额时,利息会将照常计算。

在任何时间,每一个锁定的金额将根据其解锁时间被分配0到1的表决权。锁定期超过1年的部分将拥有全额的投票权利。查看 Conflux Protocol Specification 章节 8.3.2 获取更多信息。

样例

const PRIVATE_KEY = '0xxxxxxx';
const cfx = new Conflux({
  url: 'http://test.confluxrpc.org',
  logger: console,
});
const account = cfx.wallet.addPrivateKey(PRIVATE_KEY); // create account instance

const staking_contract = cfx.InternalContract('Staking');
// deposit some amount of tokens
staking_contract.deposit(your_number_of_tokens).sendTransaction({
  from: account,
}).confirmed();

// withdraw some amount of tokens
staking_contract.withdraw(your_number_of_tokens).sendTransaction({
  from: account,
}).confirmed();

// lock some tokens until some block number
staking_contract.voteLock(your_number_of_tokens, your_unlock_block_number).sendTransaction({
  from: account,
}).confirmed();
内置合约

ConfluxContext

ConfluxContext 内置合约可用于在 Solidity 中获取当前网络信息,目前包含:epochNumber, posHeight, finalizedEpochNumber

pragma solidity >=0.4.15;

contract ConfluxContext {
    /*** Query Functions ***/
    /**
     * @dev get the current epoch number
     * @return the current epoch number
     */
    function epochNumber() public view returns (uint256) {}
    /**
     * @dev get the height of the referred PoS block in the last epoch
`    * @return the current PoS block height
     */
    function posHeight() public view returns (uint256) {}
    /**
     * @dev get the epoch number of the finalized pivot block.
     * @return the finalized epoch number
     */
    function finalizedEpochNumber() public view returns (uint256) {}
}
内置合约

PoSRegister

简介

PoSRegister合约主要为PoW用户在PoS上注册提供了相应的通道。通过质押与锁仓一定数量的代币后调用合约中的方法从而使得PoW账户与PoS账户进行绑定。绑定后在锁仓代币数量足够的前提下实现增加与减少投票权重的功能。该合约主要实现的功能有:注册(绑定)PoS全节点;增加,减少投票权重;查询PoS节点当前投票权重;PoS地址与PoW地址间的互相转换

前提概念

  1. conflux有两条链,一条PoW链,主要处理事务;一条PoS链,对PoW的区块进行验证
  2. 质押并锁仓的CFX的个数最少为1000
  3. 1 PoS 投票权重 = 1000 CFX
  4. 增加,减少投票权重的单位是 vote power
  5. 锁定到提现有一个时间限制,在staking合约中调用voteLock()时需要指定解锁时间

前置条件

方法阐述

register

概念

PoW账户与PoS账户进行绑定的通道, 当PoW账户的锁仓金额达到1000CFX时,可以调用该方法来为PoS节点与PoW建立连接,目前1000CFX可以换取1 vote power

前置条件

输入参数

输出参数

increaseStake

概念

在PoW账户与PoS账户建立连接, 若用户有提高自己投票权重的需要, 可以调用该方法将锁仓后的CFX置换为投票权重

前置条件

输入参数

输出参数

retire

概念

在PoW账户与PoS账户建立连接, 若PoW账户有解除和PoS账户相绑定的需要,可调用该方法通过部分或全部解除锁仓来实现绑定的解除功能

前置条件

输入参数

输出参数

getVotes

概念

通过调用该方法来获取质押的token的votes和解除锁仓后的token的votes

前置条件

输入参数

输出参数

identifierToAddress

概念

根据PoS账户地址获取对应的PoW地址

前置条件

输入参数

输出参数

addressToIdentifier

概念

根据PoW账户地址获取对应的PoS地址

前置条件

输入参数

输出参数

代码

// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;

interface PoSRegister {
    /**
     * @dev Register PoS account
     * @param indentifier - PoS account address to register
     * @param votePower - votes count
     * @param blsPubKey - BLS public key
     * @param vrfPubKey - VRF public key
     * @param blsPubKeyProof - BLS public key's proof of legality, used to against some attack, generated by conflux-rust fullnode
     */
    function register(
        bytes32 indentifier,
        uint64 votePower,
        bytes calldata blsPubKey,
        bytes calldata vrfPubKey,
        bytes[2] calldata blsPubKeyProof
    ) external;

    /**
     * @dev Increase specified number votes for msg.sender
     * @param votePower - count of votes to increase
     */
    function increaseStake(uint64 votePower) external;

    /**
     * @dev Retire specified number votes for msg.sender
     * @param votePower - count of votes to retire
     */
    function retire(uint64 votePower) external;

    /**
     * @dev Query PoS account's lock info. Include "totalStakedVotes" and "totalUnlockedVotes"
     * @param identifier - PoS address
     */
    function getVotes(bytes32 identifier) external view returns (uint256, uint256);

    /**
     * @dev Query the PoW address binding with specified PoS address
     * @param identifier - PoS address
     */
    function identifierToAddress(bytes32 identifier) external view returns (address);

    /**
     * @dev Query the PoS address binding with specified PoW address
     * @param addr - PoW address
     */
    function addressToIdentifier(address addr) external view returns (bytes32);

    /**
     * @dev Emitted when register method executed successfully
     */
    event Register(bytes32 indexed identifier, bytes blsPubKey, bytes vrfPubKey);

    /**
     * @dev Emitted when increaseStake method executed successfully
     */
    event IncreaseStake(bytes32 indexed identifier, uint64 votePower);

    /**
     * @dev Emitted when retire method executed successfully
     */
    event Retire(bytes32 indexed identifier, uint64 votePower);
}
内置合约

CrossSpaceCall

简介

CrossSpaceCall 合约为conflux中coreSpace与ESpace沟通的桥梁。该合约能够实现在coreSpace中的账户在ESpace中互相转账,部署合约,调用合约及查询与CoreSpace中账户相对应的ESpace中账户的的nonce与balance

前提概念

  1. 在conlux中有CoreSpace与ESpace两个域,对应着conflux与以太坊两条链;
  2. CoreSpace中的账户与ESpace中的账户有一一对应关系;

前置条件

方法阐述

createEVM

概念

通过调用该方法,在espace中创建合约(合约地址可通过事件获得,也可通过解析call方法调用合约返回的数据来获得相应的合约地址)

注: 由于call方法不改变状态,且是将当前账户的nonce+1来作为生成合约地址的数据,因此需要先调用call方法获得合约地址,再去调用createEVM来获得相应的合约地址

输入参数

输出参数

transferEVM

概念

向PoW账户映射的Espace账户中转帐

前置条件

输入参数

输出参数

callEVM

概念

通过该方法调用ESpace下的合约(合约地址可通过事件获得,也可通过解析call方法调用合约返回的数据来获得相应的合约地址)

输入参数

输出参数

staticCallEVM

概念

通过该方法静态调用ESpace下的合约

输入参数

输出参数

withdrawFromMapped

概念

从ESpace账户中收回相应的代币至其映射的CoreSpace账户

输入参数

输出参数

mappedBalance

概念

查询CoreSpace账户映射的CoreSpace账户的余额

输入参数

输出参数

mappedNonce

概念

查询CoreSpace账户映射的CoreSpace账户的nonce

输入参数

输出参数

代码

// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;

interface CrossSpaceCall {

    event Call(bytes20 indexed sender, bytes20 indexed receiver, uint256 value, uint256 nonce, bytes data);

    event Create(bytes20 indexed sender, bytes20 indexed contract_address, uint256 value, uint256 nonce, bytes init);

    event Withdraw(bytes20 indexed sender, address indexed receiver, uint256 value, uint256 nonce);

    event Outcome(bool success);

    function createEVM(bytes calldata init) external payable returns (bytes20);
    
    function transferEVM(bytes20 to) external payable returns (bytes memory output);

    function callEVM(bytes20 to, bytes calldata data) external payable returns (bytes memory output);

    function staticCallEVM(bytes20 to, bytes calldata data) external view returns (bytes memory output);

    function withdrawFromMapped(uint256 value) external;

    function mappedBalance(address addr) external view returns (uint256);

    function mappedNonce(address addr) external view returns (uint256);
}
内置合约

ParamsControl

一般而言,区块链需要通过硬分叉(hardfork)才能更改区块奖励等全局参数。Conflux引入ParamsControl内置合约,使得DAO能够在链上投票来调整Conflux的链参数,如PoW的每个区块的出块奖励,或PoS链上的利率。ParamsControl内置合约由CIP-94引入,CIP-94 介绍页面进行了一定的介绍。

合约接口

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface ParamsControl {
    struct Vote {
        uint16 topic_index;
        uint256[3] votes;
    }

    /*** Query Functions ***/
    /**
     * @dev cast vote for parameters
     * @param vote_round The round to vote for
     * @param vote_data The list of votes to cast
     */
    function castVote(uint64 vote_round, Vote[] calldata vote_data) external;

    /**
     * @dev read the vote data of an account
     * @param addr The address of the account to read
     */
    function readVote(address addr) external view returns (Vote[] memory);

    /**
     * @dev Current vote round
     */
    function currentRound() external view returns (uint64);

    /**
     * @dev read the total votes of given round
     * @param vote_round The vote number
     */
    function totalVotes(uint64 vote_round) external view returns (Vote[] memory);

    /**
     * @dev read the PoS stake for the round.
     */
    function posStakeForVotes(uint64) external view returns (uint256);

    event CastVote(uint64 indexed vote_round, address indexed addr, uint16 indexed topic_index, uint256[3] votes);
    event RevokeVote(uint64 indexed vote_round, address indexed addr, uint16 indexed topic_index, uint256[3] votes);
}

Misc

Misc

Tx Pool GC Rule

After receiving a valid new transaction from an account:

  1. If there is an old transaction from the same sender with the same nonce, replace the old transaction if any of the conditions holds:
    • the epoch_height of the new transaction is 200000 more than the old one
    • the new transaction has a higher gas price
    • the new transaction has the same gas price and a higher epoch_height
  2. If the transaction pool is full, try to garbage collect an old transaction:
    1. Try to garbage collect a transaction that has been packed (packed but not executed transactions will be inserted back to the tx pool later). This is the normal case when we receive a new transaction.
    2. If all transactions have not been packed, try to garbage a transaction that is not ready to be packed (with a non-consecutive nonce or not enough balance).
    3. If all transactions can be packed, choose a transaction (if an account has many ready transaction with consecutive nonces, only the one with the least nonce is considered here) with the least gas price. If the gas price is less than that of the newly received transaction, garbage collect this chosen old transaction.
  3. If the transaction pool is not full now, insert the new transaction. Otherwise, discard the new transaction.
Misc

cfx_estimate 接口行为

参数缺省行为

当输入缺少一些域时,一些检查会被跳过。

  1. 没有指定 from 时,跳过所有和 balance 相关的检查。
  2. 没有指定 gas_price 时,忽略对交易费检查,忽略对 Sponsor Gas Upper Bound 的检查。
  3. 没有指定 nonce 时,自动填入当前正确的 nonce. (指定 nonce 时,按指定 nonce 执行,但交易不会因 nonce 失败)
  4. 没有指定 value 时,默认为 0
  5. 没有指定 data 时,默认为空
  6. 没有指定 to 时,默认为创建合约。

冗余参数

Estimate 本身是用来估计 gas 和 storage collateral, 但接口同时允许用户输入 gas 和 storage collateral. 这是一种“带着答案问问题”的请求,estimate 执行如下逻辑。

指定 gas

  1. 使用指定的 gas 进行交易执行,这是除 gas 消耗超出 15 million 上限外,唯一可能出现 OutOfGas 的情形。
  2. 如同时指定 fromgas_price ,会在估算交易执行时扣交易费。其余情形只会在交易执行结束后复检交易费。

指定 storage limit

  1. 估算交易执行时全程忽略传入的 storage_limit,在全部估算结束后,判断给定的 storage limit 是否足够。

其他

  1. 当 Storage 被 Sponsor 时,执行是不检查 storage_limit 的,这是 Conflux 自身的逻辑。

实现思路

在模拟执行时尽量删减逻辑检查,将逻辑检查推后至估出 gas 与 storage 消耗后。

  1. Gas 消耗不受是否被 sponsor 影响,因此直接给足够大的 gas limit (不收交易费) 执行
  2. Storage 消耗与是否被 sponsor 影响很大,因此跑两遍,分别是被 sponsor 和不被 sponsor.
Misc

PoS 介绍

Conflux PoS Finality 文档目录

EVM 开发常用工具

合约开发常用工具

  1. Truffle
  2. hardhat
  3. Foundry
  4. Remix
  5. https://tenderly.co/

DevTips

Conflux 开发小窍门,每日一 Tip

DevTips

Week 1 - 7.4

Day1 - CFX 的单位

CFX 是 Conflux 网络的原生货币,也是该货币的基本单位,除此之外还有另外两个单位也比较常用 DripGDrip

另外还有一个单位 uCFX 1 uCFX = 10^12 Drip(参看黄皮书) uCFX 使用比较少。

GDrip 在发送交易时设置燃气费价格较为常见,一般设置为 1-100 GDrip

Day2 - EpochNumber Tag

在访问 Epoch 相关的 RPC 方法时,通常可以指定一个具体的 hex number 作为参数,除此之外还有一些特殊的 EpochNumber,被称作 Epoch Tag。他们分别有不同的含义:

Conflux 有一个延迟执行机制,区块被打包之后,不会立刻执行,需要延迟 5 个 epoch 才会执行。

通常 latest_state - latest_confirmed 间隔 40-50 Epoch. latest_state - latest_finalized 间隔 400-600 个 Epoch.

Day3 区块大小

Conflux 一个区块的容量为 3000w gas, 意味着一个区块可以包含 1420 多笔普通 CFX 转账交易。

Day4 Transaction 大小

Conflux 网络一笔交易所能指定的最大 gas 为 1500w,也就是区块 gas 容量的一半。 一笔交易 data 的上限为 不到 200k

Day5 Conflux 出块速度,区块时间

Conflux 主网 Core 空间平均一个区块 0.5s,平均一个 Epoch 1s 多。 正常情况下交易从发出到被执行 5-10s。交易确认时间大概为 40-50s

DevTips

Week2-7.11

Day1 Conflux Core 交易与以太坊 155 交易的区别

Conflux Core 空间的交易,相比于以太坊 155 格式交易多了两个字段:

另外构造交易原始数据时,数据的组装方式(RLP 编码)有一些区别,具体参看

最后交易的 status 值所表示的含义也不同,在 Conflux Core 空间,交易或 receipt 的 status 含义如下:

Day2 Conflux Core 空间地址的类型

Conflux 空间地址是区分类型的,一共有三种:

base32 verbose 格式的地址一般使用大写字母表示,最重要的是包含地址类型:

CFX:TYPE.USER:AATD0WZV4F7F6J33KH5E182Z4NSCSP59VYE32H4YZ6

具体参看 CIP-37

Day3 Gas Explained

什么是 Gas,他的作用是什么 https://ethgas.io/cn/index.html

Day4 Conflux Gas 详解

Conflux Gas 详解

Day5 Web3 图书馆

Library of web3 是 Alex Phan 整理的 Web3 相关概念介绍,而且内容按分类进行了划分,方便查看。

DevTips

Week3-7.18

Day1 - Java-solidity数据类型转换

java-conflux-sdk继承于web3j. 当需要做与合约有关的操作时, 需要将java中的数据类型转为solidity合约中的数据类型. 这里的数据类型的对应关系可以查看: https://github.com/web3j/web3j/blob/master/abi/src/main/java/org/web3j/abi/datatypes/AbiTypes.java#:~:text=public%20static%20Class%3C%3F%20extends%20Type%3E%20getType(String%20type%2C%20boolean%20primitives)

Day2 - Java-conflux-sdk hexstring与byte[]的转换

java-conflux-sdk中在调用call方法时会涉及到hexstring和byte[]的转换。 web3j为我们提供了一个方便的Numeric类来帮助我们实现转换功能。具体的可见https://github.com/web3j/web3j/blob/master/utils/src/main/java/org/web3j/utils/Numeric.java#:~:text=public%20final%20class%20Numeric

示例代码:

        String hexstring = "xxxxxx"; //hexstring
        byte[] test = Numeric.hexStringToByteArray(hexstring);
        
        String tmp = Numeric.toHexString(test);

Day3 - account.call与client.call的区别

java-conflux-sdk中在通过client调用call方法去读取合约的某个方法,并返回合约的返回值时不会改变合约状态,账户的nonce不会增加。在通过account.call方法去调用合约时,会带来合约状态的改变。

Day4 - 调用sdk时的rpc_url

在用sdk创建client时,需要用到rpc_url。目前rpc_url的汇总如下:

URL 场景 备注
http://127.0.0.1:12539 本地节点 port在toml文件里可以修改
https://test.confluxrpc.com 香港测试网节点
https://main.confluxrpc.com 香港主网节点
https://test.confluxrpc.org 美东测试网节点
https://main.confluxrpc.org 美东主网节点

Day5 - pos节点的注册

conflux目前支持了pos共识,调用pos内置合约需要将本地节点注册为pos节点。java-conflux-sdk提供了相应的内置合约接口去调用内置合约中的注册方法,只需要用户提供相应的raw data. raw data的获取可见:

https://wiki.conflux123.xyz/link/32#bkmrk-%E6%9C%89%E4%B8%80%E4%B8%AA%E6%AD%A3%E5%9C%A8%E8%BF%90%E8%A1%8C%E7%9A%84pos%E6%9C%AC%E5%9C%B0%E8%8A%82%E7%82%B9-%E5%9C%A8%E6%9C%AC%E5%9C%B0%E8%B0%83

DevTips

Week4 - 7.25

Day1 - PoW 链中区块的确认

攻击者利用算力攻击网络时,可能使 Conflux 的 PoW 链中的 pivot 区块发生变更,进而导致已上链区块中交易的顺序与执行结果发生改变。我们可以通过RPC cfx_getConfirmationRiskByHash 获知持有全网 15% 算力的攻击者对指定区块发起攻击的成功概率。当该值小于 1e-8,即 0.000001% 时,该区块可被视作已确认。区块从上链到确认一般需要40-50秒。

广为人知的比特币的确认时间为6个区块(1个小时)。这意味着持有全网10%算力的攻击者对6个区块之前的区块发起攻击的成功概率为0.1%。

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"cfx_getConfirmationRiskByHash","params":["0x3912275cf09f8982a69735a876c14584dae95078762090c5d32fdf0dbec0647c"],"id":1}' -H "Content-Type: application/json" localhost:12539

// fix64 格式的返回值,使用该值除以2**256-1可以得到概率为1e-8
{
  "jsonrpc": "2.0",
  "result": "0x2af31dc4611873bf3f70834acdae9f0f4f534f5d60585a5f1c1a3ced1b", 
  "id": 1
}

Day2 - 进行重要交易时的区块确认方法

一般情况下,RPC cfx_getConfirmationRiskByHash 的返回值足以满足日常交易的安全性需求。但在特定情况下,例如在发送某些重要交易时,我们可能有更高级别的安全需求,这时可以借助 PoS 链为 PoW 链提供的 Finality(最终性)来确认交易。简单来说,PoS 链会指定 PoW 链中的 pivot 区块,在这之后,即使 51% 攻击者尝试逆转这个区块,也不会被 PoW 节点认可。

通过RPC cfx_getStatus 可以得到当前的节点状态,其中的 latestFinalized 字段就包括当前已被 PoS 链确认的最新 Epoch。通过比较该值与区块的 Epoch,就能知道区块是否已被最终确认。

这种确认方式要更加安全,但同时也会更加耗时。从区块上链到确认需要花费400秒左右。可以在Conflux Scan中查看最新状态到 PoS 最终确认状态的间隔

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"cfx_getStatus","id":1}' -H "Content-Type: application/json" localhost:12539

// Result
{
  "jsonrpc": "2.0",
  "result": {
    "bestHash": "0x7bbb518ec0b8671a60e9c98619137b0d52522a9ef9490c9b4c23c30f178312f8",
    "chainId": "0x405",
    "ethereumSpaceChainId": "0x406",
    "networkId": "0x405",
    "epochNumber": "0x2fa1a24",
    "blockNumber": "0x70a7fda",
    "pendingTxNumber": "0x83b",
    "latestCheckpoint": "0x2f91bc0",
    "latestConfirmed": "0x2fa19e1",
    "latestState": "0x2fa1a20",
    "latestFinalized": "0x2fa1854" // 已被 PoS 链确认的最新 Epoch
  },
  "id": 1
}

Day3 - 使用内置合约在链上读取 EpochNumber 等信息

在链下我们可以借助SDK提供的接口或直接通过RPC cfx_getStatus 获取 EpochNumber 等信息。在链上的合约中,则可以借助内置合约ConfluxContext来获取相关信息。合约可以借助ConfluxContext提供的epochNumber(), posHeight(), finalizedEpochNumber() 函数获取当前的区块链状态。

ConfluxContext的地址为CFX:TYPE.BUILTIN:AAEJUAAAAAAAAAAAAAAAAAAAAAAAAAAAAU5XA6TK73 / CFXTEST:TYPE.BUILTIN:AAEJUAAAAAAAAAAAAAAAAAAAAAAAAAAAAUV2XPKD3X。包含 abi 的 metadata 文件可以在这里获取。

pragma solidity >=0.4.15;

contract ConfluxContext {
    /*** Query Functions ***/
    /**
     * @dev get the current epoch number
     * @return the current epoch number
     */
    function epochNumber() public view returns (uint256) {}
    /**
     * @dev get the height of the referred PoS block in the last epoch
`    * @return the current PoS block height
     */
    function posHeight() public view returns (uint256) {}
    /**
     * @dev get the epoch number of the finalized pivot block.
     * @return the finalized epoch number
     */
    function finalizedEpochNumber() public view returns (uint256) {}
}

Day4 - 估算gas消耗时的 gasUsedgasLimit

通过SDK或RPC cfx_estimateGasAndCollateral 估算交易gas消耗时,得到的返回值 estimate 中包括 estimate.gasUsedestimate.gasLimit 字段。其中 estimate.gasUsed 字段代表交易将实际消耗的燃气数,但由于在EVM中每次子函数调用只会传入63/64的gasLimit,直接将交易txtx.gasLimit字段直接设置为estimate.gasUsed常常会引发 gas 不足的问题。通常推荐使用 estimate.gasLimit 作为交易的燃气上限 tx.gasLimit ,该值为 estimate.gasUsed4/3 倍。这样一般可以保证 gas 上限设置得足够, gasUsed 以外的燃气消耗也能够全额返还。

在以太坊中,超出gasUsed的燃气消耗将会全额返还,但Conflux中返还的燃气消耗至多为 tx.gasLimit * 1/4。换言之,设置的tx.gasLimit大于 gasUsed * 4/3 将会导致 gasUsed 以外的燃气消耗无法被全额返还。更详细的 gas 知识可以参考 Gas 科普

// Request
curl -X POST --data '{"method":"cfx_estimateGasAndCollateral","id":1,"jsonrpc":"2.0","params":[{"from":"cfx:type.user:aarc9abycue0hhzgyrr53m6cxedgccrmmyybjgh4xg","to":"cfx:type.contract:acc7uawf5ubtnmezvhu9dhc6sghea0403y2dgpyfjp","data":"0x","gasPrice":"0x2540be400","nonce":"0x0"}]}' -H "Content-Type: application/json" localhost:12539

// Result
{
  "jsonrpc": "2.0",
  "result": {
    "gasLimit": "0x6d60", // 21000 * 4/3
    "gasUsed": "0x5208", // 21000
    "storageCollateralized": "0x80"
  },
  "id": 1
}

Day5 - 交易中的 EpochHeight 参数

Conflux中的交易中需要指定EpochHeight参数。当 -100000 < currentEpochNumber - tx.epochHeight < 100000 时交易才会被执行。一般将交易的EpochHeight设置为当前的EpochNumber即可。如果交易对执行的时间点比较敏感,也可以通过设置更小的EpochNumber来避免交易被过晚执行。

产生 100000 个 Epoch 需要 100000 秒左右,约 28 个小时

DevTips

Week5 - 8.1

Day1

Conflux Core Space 地址格式有哪些?

Conflux Core Space 地址格式有哪些?

Conflux Core Space 地址格式有两种

  1. Hex格式, 与以太坊格式类似,不同的是用户地址首字符置为1,合约地址首字符置为8; 例:0x1defad05b632ba2cef7ea20731021657e20a7596
  2. Base32格式,对Hex格式地址进行 base32 编码且附加地址属性描述; 例:CFX:TYPE.USER:AAKPBX01FZM1XP89CB7URF6YVYXH3GGX5E9HZ07B38

参考

Day2

Base32格式由哪几部分组成,为什么推荐使用Base32格式?

Base32 地址格式由哪几部分组成

Base32 编码是什么

Base32 编码指将 每5bit数值 编码为一个Base32字符;如将`0b00000"(对应16进制表示"0x00")编码为"a"

所有编码对应关系为 0x00~0x1f <==> abcdefghjkmnprstuvwxyz0123456789

Base32 格式地址包含哪几部分

  1. Network-prefix : 标识网络类型,cfx表示主网,cfxtest表示测试网
  2. Address type : 地址类型TYPE.XXX,包括用户地址、合约地址、内置合约地址、未知地址类型
  3. Payload : 对 Hex格式地址前面加一个全零字节的值进行base32编码的结果
  4. Checksum : 校验位,尾部8个字符,用于校验地址的正确性

示例如下:

CFX:TYPE.USER:AAKPBX01FZM1XP89CB7URF6YVYXH3GGX5E9HZ07B38
|   |        |                                  |--- Checksum
|   |        |--- Payload
|   |--- 地址类型
|--- 网络标识

Base32 地址格式比Hex地址格式增加了网络类型和地址类型信息,增强了辨识度

Base32 格式地址与 Hex 格式地址可以互相转换

为什么推荐使用 Base32格式?

由于Hex格式的地址与以太坊地址首字符不同可能导致在用户转账时误将以太坊格式当做conflux地址使用,导致转账或跨链时将代币转到错误的地址造成财产损失,所以推荐使用Base32地址,而同样为了避免该问题,官方主流钱包Fluent及大多社区钱包也不支持使用Hex地址格式转账。

参考

Day3

eSpace 地址

Conflux 从 v2.0 开始引入了一个全新的网络空间 eSpace (原空间被命名为 Core Space),该空间实现了以太坊的完全兼容,允许用户和开发者,直接使用以太坊生态的钱包和工具跟该空间交互。即eSpace的地址生成方式、地址格式、以及助记词derive path都是与以太坊完全一致的。

所以可以使用 Metamask 作为 eSpace 的钱包。

地址生成规则: espce地址生成原理

详情参见

Day4

Core Space 账号在 eSpace 的映射地址

Core Space 账号在 eSpace 的映射地址属于eSpace的用户地址类型

计算规则如下

  1. 将 Core Space 的 base32 地址 decode 为 bytes 类型
  2. 对 bytes 进行 keccak 哈希计算
  3. 取哈希结果的后 20 bytes,转换为 hex 格式

映射地址是为了两个空间资产跨链设计的概念,使用内置合约 CrossSpaceCall 可以转移eSpace映射地址资产到指定地址从而实现资产跨链,同时该内置合约也支持调用eSpace合约。

参考

Day5

Core Space 内置合约有哪些

Conflux 引入内置合约用于更好的系统维护和链上治理。现在 Conflux core space 有 6 个内置合约:主网上线时就有AdminControlSponsorWhitelistControlStaking合约,在 v2 硬分叉时又增加了 ConfluxContext, PoSRegister, ConfluxContext。 这些函数只能通过CALLSTATICCALL操作调用。使用操作 CALLCODEDELEGATECALL 与内部合约交互会触发错误。

他们的Hex格式地址为:

  1. AdminControl: 0x0888000000000000000000000000000000000000
  2. SponsorWhitelistControl: 0x0888000000000000000000000000000000000001
  3. Staking: 0x0888000000000000000000000000000000000002
  4. ConfluxContext: 0x0888000000000000000000000000000000000004
  5. PoSRegister: 0x0888000000000000000000000000000000000005
  6. CrossSpaceCall: 0x0888000000000000000000000000000000000006

参考

DevTips

Week6- PoS

Day1 如何参与 PoS?

参与 Conflux PoS 目前可以获得 15% 左右的年化收益,参与方式有两种:

  1. 自己运行一个 Conflux 节点,通过 Staking 内置合约将 CFX 质押,然后通过 PoSRegister 内置合约参与进 PoS
  2. 直接选择一个 PoS Pool,将 CFX 质押进矿池中,可以随时解质押,提取收益。

第一种方式自主,可控,第二种方式简单方便。

Day2 PoS 常见参数

Day3 PoS 对 PoW 区块的 finalized

PoS 链每产生一个区块中都会包含对 PoW 区块的确认信息 pivotDecision 其中包含所确认的 PoW pivot chain 的高度和 hash 信息,被 PoS 确认后的区块将不会再被 Revert。

PoW 区块中也有一个字段 posReference 表示 PoW 对 PoS 的引用。

如何获取 PoW 最新的 finalized 高度(区块号):

  1. 访问 cfx_getStatus 返回数据的 latestFinalized 即为最新 finalized 高度
  2. 访问 cfx_epochNumber 并传参 latest_finalized 返回的 number 也是最新的 finalized 高度

Day4 ForceRetire

PoS 节点需要保证自身的稳定性,持续参与 PoS 机制,越稳定的节点,奖励越高。如果一个节点参选为 PoS 委员会,但在任内期间超过 1小时没有正常参与 PoS 出块投票,那么该节点将会被强制退出。该节点的所有投票将会自动变为 unlock 状态。节点强制退休状态会持续 7 天。unlock 的票 会持续 7 - 14 天,强制退休期间,如果进行质押操作,票也会自动 unlock 直到满14 天才能 withdraw。

在下一次 hardfork 升级中(2.1.0)将会调整节点的 forceRetire 参数,从原来的一小时,改为三小时。即一个委员会节点不工作超过三小时后才会强制退休。此调整目的是为了给 PoS 运维人员留更多的处理时间。增加节点的鲁棒性

Day5 PoS 收益

参与 Conflux PoS 不仅能帮助提高 Conflux 网络的安全性,还能获取一定的收益。目前全网质押量为 1.3 亿,理论年化收益为 16.6%。

收益计算参数包含以下三个:

  1. 基础收益率 4%
  2. 总流通量(totalCirculate):发行总量-四年锁-两年锁-销毁量(0地址余额)
  3. PoS 当前质押量(totalStake)

MommyTalk1660285733686.png

PoS 收益每小时发放一次,直接发放至 PoS 节点账号绑定的 PoW 地址中,如果是通过 PoS 矿池参与,每小时会计算一次收益,随时可领取。

DevTips

Week7-8.15

Day1 CIP23与EIP712的异同

在用钱包为结构化的数据签名时,用户看到的只是一串处理后的hex字符串,无法得知具体签名的内容,这对信息、财产等安全造成了极大的威胁。为了解决该问题,以太坊引入了EIP712。与此相对的,conflux引入了CIP23来作为解决方案。CIP23与EIP712的差异总结如下:

Day2 如何基于CIP23签署消息

具体的,conflux-sdk为我们提供了相应的sdk简化签署消息的过程。以java-sdk为例:

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()));
    }
}

Day3 如何基于cip23去验证签名的真伪

基于cip23签署的消息有两种验证方式,分别是基于sdk与合约验签。

以java-sdk为例,基于sdk的验签方法为

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());
    }
}

这其中最主要的是recover()函数,我们来看看该函数的实现:

    public static String recoverSignature(SignatureData sd, byte[] data, String address) {
        String addressRecovered = null;

        // Iterate for each possible key to recover
        for (int i = 0; i < 4; i++) {
            BigInteger publicKey =
                    org.web3j.crypto.Sign.recoverFromSignature(
                            (byte) i,
                            new ECDSASignature(
                                    new BigInteger(1, sd.getR()), new BigInteger(1, sd.getS())),
                            data);

            if (publicKey != null) {
                addressRecovered =  Numeric.prependHexPrefix(Keys.getAddress(publicKey.toString()));

                if (addressRecovered.equals(address)) {
                    break;
                }
            }
        }

        return addressRecovered;
    }

以太坊的加密算法中提供了四种签名模式,在四种签名模式下,从签名中去还原公钥,再用公钥去得到地址,与目标地址进行匹配。具体的四种签名模式可参考https://github.com/web3j/web3j/blob/7dea3d99c5bdbfcc03aaeaa8575fb0c9a9a771ab/crypto/src/main/java/org/web3j/crypto/Sign.java#:~:text=public%20static%20BigInteger%20recoverFromSignature(int%20recId%2C%20ECDSASignature%20sig%2C%20byte%5B%5D%20message)%20%7B

对于合约验签,我们需要在合约当中去实现结构化数据类型,具体可见: https://wiki.conflux123.xyz/link/60#bkmrk-%E5%90%88%E7%BA%A6%E9%AA%8C%E7%AD%BE

Day4 如何用fluent钱包去签名

java-sdk的test例程中提供了两种方式去签名,一种是未知签名,用sdk签名后再用sdk进行recover,另一种是已知签名。那么如何得到这个已知签名呢?可以用fluent等方式来获得。具体的fluent钱包为我们提供了相当的provider rpc doc。fluent钱包不仅支持cfx下的签名,还支持eth环境下的签名。

为了实现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!"
    }
}`]
  })

弹出的消息框中有消息的message字段

在签署后可以得到同样的签名

Day5 目前支持cip23的sdk

目前java-sdk与js-sdk支持cip23,对应的例程分别在java-sdk cip23例程js-sdk cip23例程

后续go-sdk与python-sdk也会支持上cip23,敬请期待!

DevTips

Week8-8.21

8.22 Epoch Number, Block Number,Height 与 Epoch Height 的区别

Conflux 采用了树图共识,常常会采用 epoch numberblock number 来标识区块,同时也有着 heightepoch height 与区块相关的字段。这些字段的区别如下:

  1. Epoch Number(纪元):如图所示,Conflux 会根据共识挑选出蓝色的(pivot)区块,并根据这些区块划分出若干个连续的 epoch,每个 epoch 中会包含一个或多个区块。例如(1)A、C、E、H 在不同的 epoch 中,epoch number 不同且递增;(2)B、C的epoch number相同。

  2. Block Number:一旦 epoch 的划分被决定,每个区块会被确定唯一的 block numberblock number 决定了交易的执行顺序。图中的所有区块的 block numebr 都不相同。

需要注意的是,新上链的区块不稳定,可能发生区块顺序的重组导致epoch numberblock number发生变化。

  1. Height(区块高度):height 为区块所包含的字段,等于其父区块的Height+1。蓝色(pivot)区块的height总是和其epoch number相等。图中 C、D、G 区块的Height相同。

  2. Epoch Height(目标纪元):epoch height 是交易中发送者所指定的字段,代表着该笔交易被执行时的合法epoch number范围。交易中的 EpochHeight 参数 对这一点进行了更详尽的解释。

tree_graph.jpeg

8.23 存储抵押机制

在Conflux中引入了存储抵押(Collateral for storage)机制,作为使用存储的定价方式。合约的交互者在占用存储空间时需要锁定一笔资金,作为占用存储空间的抵押物。在相应的存储空间被释放或被他人覆盖前,抵押物都会被锁定,而被锁定的抵押物所产生的相应利息会直接分配给矿工,用于存储空间的维护。

在Conflux网络中,每个存储条目占用空间是64字节,我们称最后向某个存储条目写入的账户称为该存储条目的所有者。而对于每一个存储条目,其所有者会被锁定 1/16 CFX。当其他账户修改了某个存储条目时,存储条目的所有者会改变,原所有者抵押的资金(1/16 CFX)会被解锁,新所有者则会锁定同样的资金。当合约被销毁时,所有空间会被释放,对应的存储抵押也会被全部解锁。

更加细节的解析可以参考Conflux的存储抵押机制

8.24 代付中存储抵押的更新

Conflux 中的代付机制除了支持 gas 的代付外也支持存储抵押的代付。赞助者可以通过调用代付合约的setSponsorForCollateral(address contractAddr)并发送一笔CFX更新存储抵押代付,新发送的CFX需要大于原有的存储抵押。其后,原赞助者为存储抵押支付的所有CFX(包括已被使用的存储抵押与未被使用的存储抵押)将会被全部返还。

8.25 在内置合约 Staking 中质押 CFX

在 Conflux 中,除了因交易的存储开销导致CFX被抵押外,账户还可以与内置合约 Staking 交互主动质押 CFX。质押的 CFX 目前有两种用途:

  1. 用于参与POS链。参考如何参与POS?
  2. 用于参与治理投票,包括治理与即将上线的CIP-94 On-chain DAO Vote for Chain Parameters

8.26 与 Pivot 区块相关的 RPC 方法

Pivot 区块(下图中蓝色的区块)十分重要,决定了 Conflux 中区块(中交易)实际的执行顺序。Conflux 的 RPC 也提供了数个与 Pivot 区块相关的方法:

tree_graph.jpeg

  1. cfx_getStatuscfx_getBestBlockHashcfx_getStatus 获得的 bestHash 字段代表了在最新一个epoch中Pivot 区块的哈希值。cfx_getBestBlockHash 所获取的也是该值。
// getStatus
{
  "jsonrpc": "2.0",
  "result": {
    "bestHash": "0xe4bf02ad95ad5452c7676d3dfc2e57fde2a70806c2e68231c58c77cdda5b7c6c",
    "chainId": "0x1",
    "networkId": "0x1",
    "blockNumber": "0x1a80325",
    "epochNumber": "0xaf28ab",
    "latestCheckpoint": "0xada520",
    "latestConfirmed": "0xaf2885",
    "latestState": "0xaf28a7",
    "latestFinalized": "0x2a420c",
    "ethereumSpaceChainId": "0x22b9",
    "pendingTxNumber": "0x0"
  },
  "id": 1
}
  1. cfx_getBlocksByEpoch。该 RPC 会返回某一个 Epoch 下的所有区块,并按照 blockNumber 排序。换言之,该返回值中的最后一个区块为 Pivot 区块.
// Result
{
    "jsonrpc": "2.0",
    "result": [
        "0x618e813ed93f1020bab13a1ab77e1550da6c89d9c69de837033512e91ac46bd0",
        "0x0f6ac81dcbc612e72e0019681bcec32254a34bd29a6bbab91e5e8dc37ecb64d5",
        "0xad3238c00456adfbf847d251b004c1e306fe637227bb1b9917d77bd5b207af68",
        "0x0f92c2e796be7b016d8b74c6c270fb1851e47fabaca3e464d407544286d6cd34",
        "0x5bcc2b8d2493797fcadf7b80228ef5b713eb9ff65f7cdd86562db629d0caf721",
        "0x7fcdc6fff506b19a2bd72cd3430310915f19a59b046759bb790ba4eeb95e9956",
        "0xf4f33ed08e1c625f4dde608eeb92991d77fff26122bab28a6b3a2037511dcc83",
        "0xa3762adc7f066d5cb62c683c2655be3bc3405ff1397f77d2e1dbeff2d8522e00",
        "0xba7588476a5ec7e0ade00f060180cadb7430fd1be48940414baac48c0d39556d",
        "0xe4dc4541d07118b598b2ec67bbdaa219eb1d649471fe7b5667a0001d83b1e9b6",
        "0x93a15564544c57d6cb68dbdf60133b318a94439e1f0a9ccb331b0f5a0aaf8049" // pivot
    ],
    "id": 1
}
  1. cfx_getBlockByHashWithPivotAssumption。该 RPC 与常用的 cfx_getBlockByHash 类似,不同点在于,该 RPC 需要传入三个参数:(1)blockHash(2)assumedPivotHash(3)epochNumber。当后面两个参数与blockHash相匹配时,该RPC才会返回 blockHash 对应的区块信息,否则会返回错误。
DevTips

Week9 - 8.29

Day1 conflux 合约工具库

openzepplin是以太坊开发最重要的合约库, conflux-contracts 则是为了方便conflux开发者而封装的合约集合,主要包含

Day2 合约中如何判断是否为cfx链

conflux 链上的erc1820地址与以太坊不同, 以太坊为0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24, conflux core space为0x88887eD889e776bCBe2f0f9932EcFaBcDfCd1820 ,通过判断该特征即可,conflux-contracts提供了该实现

Day3 如何通过合约一次访问多个合约方法

dapp经常需要同时获取很多合约状态,如果每次读取合约都发送一次rpc请求,则需要并发大量请求,这样容易对服务区造成很大压力导致rpc node服务器性能下降或者被限流。而使用multicall进行则可以一次rpc请求访问多个合约方法,从而避免该问题。

multicall 提供了一系列方法方便一次访问多个合约方法,实现原理为在合约方法中批量 static call, 使用该方法需要我们事先根据要访问的合约方法abi编码好数据。

如要获取地址cfxtest:aanpu16mtgc7dke5xhuktyfyef8f00pz8a2z5mc14gcfxtest:aaskvgxcfej371g4ecepx9an78ngrke5ay9f8jtbggusdt 余额

results = muticall.aggregate(usdt_address,["0x70a0823100000000000000000000000016c85f8a7985d1a49b99e097d0b4217c5b5995f0","0x70a082310000000000000000000000001c989a6229119edcda2088c9fc0bef9666a49b05"])

ABI编解码的方法各个SDK都有提供,请参考具体使用的sdk

Day4 Fluent小知识 - Fluent 如何导入多套助记词

1. Fluent 如何导入多套助记词

当我们想要分场景使用不同助记词管理私钥组以隔离风险时,是不是苦于找不到这样的钱包?其实Fluent已经提供了这样的功能,那怎么使用呢?👇

点击左上角钱包名称

在弹出框中点击右上角"+"

即可再次导入一套助记词

现在可以看到新导入的助记词了

Fluent 同时也支持多个网络,当前支持以太坊跟Conflux,以后还会陆续增加其他链的支持。

Day5 Fluent冷知识

  1. Fluent 相同助记词可以同时在conflux和ethereum使用,但是由助记词生成的钱包在同样的序号下使用的derive path不同,所以生成的私钥也是不一样的。 所以不要混用。
  1. Fluent 没看到测试网络,Fluent是不支持测试网么?

其实Fluent是支持测试网的,只是由于普通用户通常不需要使用测试网,所以默认隐藏了起来,通过高级选项就可以打开测试网了。

DevTips

Week10-NFT 标准

近两年NFT,元宇宙发展如火如荼,那么到底什么是NFT ? NFT 又称非同质化代币,核心特性是每个代币都是独一无二的,跟其他代币是不相同的。在以太坊标准里 NFT 是通过一系列 EIP 来定义的,本周来带领大家了解一下主流的标准

Day1 - ERC721

ERC-721 是最早的 NFT 标准, 早期 CryptoKitties 等项目均采用该标准。与 20 代币最大的区别是:每个 token 都有唯一的 ID,且 token 不可进行拆分

其核心接口如下:

interface ERC721 /* is ERC165 */ {
    // events
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    function balanceOf(address _owner) external view returns (uint256);
	function ownerOf(uint256 _tokenId) external view returns (address);
	function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
	function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
	function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
	function approve(address _approved, uint256 _tokenId) external payable;
	function setApprovalForAll(address _operator, bool _approved) external;
	function getApproved(uint256 _tokenId) external view returns (address);
	function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

写接口包含:

  1. transferFrom
  2. safeTransferFrom
  3. approve
  4. setApprovalForAll

读接口则包含:

  1. balanceOf
  2. ownerOf
  3. getApproved
  4. isApprovedForAll

另外 ERC721 还有一些常用的扩展,包括:

  1. metadata 扩展
  2. 枚举扩展
  3. 可暂停扩展
  4. 可销毁扩展

Day2 ERC1155

ERC1155 是另外一个采用较多的标准, 该标准 与 ERC20 的区别是,在一个合约中可以同时包含多个 token,与 ERC721 的区别在于,每个 tokenId 的数量,可以不止有一个。

另外 ERC1155 支持批量操作,比如批量转账,批量mint,批量查询余额

interface ERC1155 /* is ERC165 */ {
    event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value);
    event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
    event URI(string _value, uint256 indexed _id);

    function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
    function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
    function balanceOf(address _owner, uint256 _id) external view returns (uint256);
    function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);
    function setApprovalForAll(address _operator, bool _approved) external;
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

Day3 ERC2981

ERC2981 主要用于实现 NFT 版税功能,可以实现在 NFT 每次转移时,支付一定收益给 NFT 的创造者或者版权所有者。该标准适用于 ERC721 或 ERC1155 token.

该标准主要包含一个方法 royaltyInfo 用于确认某 tokenId 在某售价情况下,应该给谁支付多少的版税:

interface IERC2981 is IERC165 {
    function royaltyInfo(
        uint256 _tokenId,
        uint256 _salePrice
    ) external view returns (
        address receiver,
        uint256 royaltyAmount
    );
}

Day4 SBT 灵魂绑定代币

SBT 又称灵魂绑定 Token,简单来说可以理解为 不可转移 NFT。其应用场景包含:身份认证,投票, KYC 等

目前能够支持 SBT 的 EIP 提案,我们已经看到的有 EIP-4973、EIP-5114、ERC721S 等,还有 Solv Protocol 提出的 EIP-3525。

SBT 综述 - 孟岩

Day5 ERC3525 半匀质化通证代币

这是一个通用标准,适用面非常广阔。它可以把多个相似但并不相同的通证识别为“同类”,然后允许同类之间进行相互转账等特殊操作。从效果上,相当于同类之间可以进行合并、拆分、碎片化等数学操作

直截了当地解释 ERC-3525 与 ERC-1155 的差别 - 孟岩

概览

除此之外 NFT 相关的 标准还有很多,NFT 协议全览:标准协议、流动性协议和跨链协议 对他们进行了全面的梳理和总结

DevTips

Week11-9.13

内置合约

在conflux的v2版本的分叉后,增加了ConfluxContext, PoSRegister, CrossSpaceCall, ParamsControl四个内置合约,那么这四个内置合约实现了什么功能呢?可以参见本周的devtips

Day1 ConfluxContext

该内置合约提供了三个方法去查询conflux相关的信息,包括当前的epochNumber, posHeight和最终确认的pivot块。 该合约的地址为0x0888000000000000000000000000000000000004.

该合约的abi为:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.15;

interface ConfluxContext {
    /*** Query Functions ***/
    /**
     * @dev get the current epoch number
     * @return the current epoch number
     */
    function epochNumber() external view returns (uint256);
    /**
     * @dev get the height of the referred PoS block in the last epoch
`    * @return the current PoS block height
     */
    function posHeight() external view returns (uint256);
    /**
     * @dev get the epoch number of the finalized pivot block.
     * @return the finalized epoch number
     */
    function finalizedEpochNumber() external view returns (uint256);
}

那么如何去调用该合约呢?以java sdk为例:

        
    public static void test(String contractAddr, String caller) throws Exception {
        Cfx cfx = Cfx.create("https://test.confluxrpc.com");
        Account acc = Account.create(cfx, caller);
        Address address = new Address(contractAddr);
        String hash = acc.call(address, "epochNumber");
        cfx.waitForReceipt(hash);
        Optional<Receipt> receipt = cfx.getTransactionReceipt(hash).sendAndGet();
        if (receipt.isPresent()) {
				.......
        } else {
           .......
        }

    }

Day2 PoSRegister

PoSRegister合约提供了一系列方法去和PoS链进行交互

该合约的方法介绍如下:

该合约的合约地址为0x0888000000000000000000000000000000000005

该合约的abi如下:

// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;

interface PoSRegister {
    /**
     * @dev Register PoS account
     * @param indentifier - PoS account address to register
     * @param votePower - votes count
     * @param blsPubKey - BLS public key
     * @param vrfPubKey - VRF public key
     * @param blsPubKeyProof - BLS public key's proof of legality, used to against some attack, generated by conflux-rust fullnode
     */
    function register(
        bytes32 indentifier,
        uint64 votePower,
        bytes calldata blsPubKey,
        bytes calldata vrfPubKey,
        bytes[2] calldata blsPubKeyProof
    ) external;

    /**
     * @dev Increase specified number votes for msg.sender
     * @param votePower - count of votes to increase
     */
    function increaseStake(uint64 votePower) external;

    /**
     * @dev Retire specified number votes for msg.sender
     * @param votePower - count of votes to retire
     */
    function retire(uint64 votePower) external;

    /**
     * @dev Query PoS account's lock info. Include "totalStakedVotes" and "totalUnlockedVotes"
     * @param identifier - PoS address
     */
    function getVotes(bytes32 identifier) external view returns (uint256, uint256);

    /**
     * @dev Query the PoW address binding with specified PoS address
     * @param identifier - PoS address
     */
    function identifierToAddress(bytes32 identifier) external view returns (address);

    /**
     * @dev Query the PoS address binding with specified PoW address
     * @param addr - PoW address
     */
    function addressToIdentifier(address addr) external view returns (bytes32);

    /**
     * @dev Emitted when register method executed successfully
     */
    event Register(bytes32 indexed identifier, bytes blsPubKey, bytes vrfPubKey);

    /**
     * @dev Emitted when increaseStake method executed successfully
     */
    event IncreaseStake(bytes32 indexed identifier, uint64 votePower);

    /**
     * @dev Emitted when retire method executed successfully
     */
    event Retire(bytes32 indexed identifier, uint64 votePower);
}

具体的介绍可见posRegister

那么如何去调用该合约呢?可以参考java-examples

Day3 CrossSpaceCall

Conflux通过分片,存在core space与esapce。core space与espace的账户间存在一一对应关系。CrossSpaceCall为用户提供了一系列方法实现两个space的交互。

该合约的方法如下:

该合约的地址为0x0888000000000000000000000000000000000006

该合约的abi如下:

// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;

interface CrossSpaceCall {

    event Call(bytes20 indexed sender, bytes20 indexed receiver, uint256 value, uint256 nonce, bytes data);

    event Create(bytes20 indexed sender, bytes20 indexed contract_address, uint256 value, uint256 nonce, bytes init);

    event Withdraw(bytes20 indexed sender, address indexed receiver, uint256 value, uint256 nonce);

    event Outcome(bool success);

    function createEVM(bytes calldata init) external payable returns (bytes20);
    
    function transferEVM(bytes20 to) external payable returns (bytes memory output);

    function callEVM(bytes20 to, bytes calldata data) external payable returns (bytes memory output);

    function staticCallEVM(bytes20 to, bytes calldata data) external view returns (bytes memory output);

    function withdrawFromMapped(uint256 value) external;

    function mappedBalance(address addr) external view returns (uint256);

    function mappedNonce(address addr) external view returns (uint256);
}

具体的介绍可见CrossSpaceCall

那么如何去调用该合约呢?可以参考java-examples

那么如何将core space的地址转为espace的地址呢?java-sdk提供了方法

    public String getMappedEVMSpaceAddress() {
        String hexAddr =  this.getHexAddress();
        hexAddr = hexAddr.substring(2, hexAddr.length());
        byte[] t = Hash.sha3(Numeric.hexStringToByteArray(hexAddr));

        byte[] mappedBuf = new byte[20];
		   System.arraycopy(t, t.length - 20, mappedBuf, 0, 20);

        return Keys.toChecksumAddress("0x" + BaseEncoding.base16().encode(mappedBuf));
    }

Day4 ParamsControl

ParamsControl合约为在不用硬分叉的情况下去修改区块奖励等全局参数成为可能。该合约由CIP-94引入。

该合约的方法如下:

该合约的地址为0x0888000000000000000000000000000000000006

该合约的abi如下:

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface ParamsControl {
    struct Vote {
        uint16 topic_index;
        uint256[3] votes;
    }

    /*** Query Functions ***/
    /**
     * @dev cast vote for parameters
     * @param vote_round The round to vote for
     * @param vote_data The list of votes to cast
     */
    function castVote(uint64 vote_round, Vote[] calldata vote_data) external;

    /**
     * @dev read the vote data of an account
     * @param addr The address of the account to read
     */
    function readVote(address addr) external view returns (Vote[] memory);

    /**
     * @dev Current vote round
     */
    function currentRound() external view returns (uint64);

    /**
     * @dev read the total votes of given round
     * @param vote_round The vote number
     */
    function totalVotes(uint64 vote_round) external view returns (Vote[] memory);

    event CastVote(uint64 indexed vote_round, address indexed addr, uint16 indexed topic_index, uint256[3] votes);
    event RevokeVote(uint64 indexed vote_round, address indexed addr, uint16 indexed topic_index, uint256[3] votes);
}

调用该合约的方法与更加详细的讲解可见JS交互方法

v2分叉后的内置合约讲完啦~其余的V1版本的合约介绍可见内置合约

DevTips

Week12 Contract address

9.20 Conflux 中 create2 地址的计算

与以太坊相同,Conflux 支持在合约中以 create2 部署合约至指定地址。Conflux 中由 create2 部署的合约地址计算方式与以太坊中大体相同,不同点在于Conflux会修改地址的首位以标识地址的类型。下面的代码描述了合约的hex地址具体的计算方式:

# 也可以使用 Web3.py
# from web3 import Web3
from conflux_web3 import Web3

# 尽量保证salt为bytes32,以避免编码方式带来的计算结果不匹配
def compute_address_using_salt(salt: bytes, bytecode_hash: bytes, deployer: HexAddress):
    core_part = Web3.solidityKeccak(
        ["bytes1", "address", "bytes32", "bytes32"],
        ["0xff", deployer, salt, bytecode_hash]
    )
    return "0x8"+ core_part.hex()[-39:]

9.21 Conflux 中合约部署地址的计算

Conflux 中合约部署地址的计算(非create2)与以太坊有较大不同。在以太坊中,合约部署的地址与部署者地址与nonce相关,合约的字节码不会对部署的地址造成影响。但在Conflux中,合约的字节码也会影响部署的地址。其计算方式与create2类似,下面的代码描述了合约的hex地址具体的计算方式:

# 也可以使用 Web3.py
# from web3 import Web3
from conflux_web3 import Web3

def compute_address_using_nonce(nonce: int, bytecode_hash: bytes, deployer: HexAddress):
    core_part = Web3.solidityKeccak(
        ["bytes1", "address", "bytes32", "bytes32"],
        ["0x00", deployer, nonce.to_bytes(32, "little"), bytecode_hash]
    )
    return "0x8"+ core_part.hex()[-39:]

9.22 Create2Factory 合约

Conflux 在 CIP-31 中引入了 Create2Factory 合约。该合约hex地址为0x8a3a92281df6497105513b18543fd3b60c778e40,借助该合约,开发者能够方便地调用create2操作码,将合约部署至特定地址。

可以在ConfluxScan中查看该合约:主网测试网

9.23 在Conflux中使用OppenZeppelin的Clones, Create2合约

OppenZeppelin 提供了Clones合约与Create2合约帮助开发者部署minimal proxy合约或使用create2字节码部署合约。对于习惯使用OppenZeppelin的开发者,conflux-contracts中也提供了对应的代码,对合约地址计算进行了适配。代码见Clones.sol, Create2.sol

DevTips

Week13 - 9.26

Day1 存储抵押

Conflux Core Space 引入了存储抵押 (CFS) 机制作为使用存储的定价方法。与以太坊的一次性存储费相比,CFS 机制会更加公平合理。原则上,该机制需要锁定一定数量的资金作为抵押品来占用存储空间。在相应的存储空间被释放或被他人覆盖之前,抵押品将被锁定,锁定的CFX产生的相应利息将直接分配给矿工,用于维护存储空间。因此,Conflux 的存储成本也取决于存储空间被占用的时间长短。

存储抵押计算公式为 存储抵押 = 占用字节数 * 1CFX /1024 ;即每占用1KB空间,需要支付1CFX

使用场景

当操作智能合约且导致新增存储空间时,就需要为该新增的存储支付存储抵押

举个栗子

如下面是一个最简化的只有mint功能的ERC20合约

pragma solidity ^0.8.0;
contract MinimalERC20 {
    mapping(address => uint256) public balances;
    
    function mint(address account, uint256 amount) public {
        balances[account] += amount;
    }
}

一开始用户a的balance是0,所以balances中没有用户a的状态;当 mint(a,10)时,用户a的balance变为10。 这时变量balances会新增一对存储,key为a,value为10。 在合约中addressuint256都是占用32个字节;所以需要为该存储抵押64*1CFX/1024=0.0625CFX

而当释放该存储时该存储抵押将原路退还给存储抵押支付者。

更多详细介绍请参见存储抵押

Day2 Keystore

无论在保存或者传输私钥时都有可能被泄露的风险,所以对私钥加密后再保存或传输更为稳妥。传统文件通常采用ZIP或者RAR压缩包的方法来加密;而区块链世界更通用的方式是采用 keystore 格式加密存储,keystore格式也可以被多数钱包APP及客户端所导入、导出。

keystore生成过程,历经4个步骤:

keystore解密的过程与生成过程相反:

加密后的keystore 如代码清单下所示:

{
  "version": 3,
  "id": "7d5d99c8-f455-49aa-8b89-6c795a7cdd46",
  "address": "7c52e508c07558c287d5a453475954f6a547ec41",
  "crypto": {
      "kdf": "scrypt",
      "kdfparams": {
          "dklen": 32,
          "salt": "a4f9677eaf6f72394da51e16695899ad3e9b4f2228ad4eca5ef2a5c36093fe12",
          "n": 262144,
          "r": 8,
          "p": 1
      },
      "cipher": "aes-128-ctr",
      "ciphertext": "d89df5ef74f51ae485308e6dce8991dd80674e111f8073f9efa52cb2dd6eca3f",
      "cipherparams": {
          "iv": "6b064c5b09a154d9877d3a07e610a567"
      },
      "mac": "30949eb085ce342a6a488fd51fa5e3231e45f7515efa10c19ea0d46270c73f06"
  }
}

这串 JSON 格式的 keystore 代码可以被市面上大多数钱包 App 读取并合法导入。当导入时,仅需根据客户端程序提示输入 123456 将其解密即可。在网络中传输 keystore 是安全的,因为即使黑客窃取到了密文也不知道如何解密。

在 keystore 中的重要参数和对应的解释如下表。

名词 解释
id 随机生成的一个 uuid 格式的字符串
address 该 keystore 对应的公开地址
crypto 加密后的密文区域以及加密参数
kdf 全称Key Derivation Function,加密密匙生成算法, 举例中选用了Scrypt 算法
kdfparams Scrypt 算法所需要的必要参数
cypher 加密私钥选用的对称加密算法,举例中选用了AES-128-CTR 算法
cyphertext 加密后的密码文
cypherparams AES-128-CTR 算法所需要的必要输入参数
mac cyphertext与加密密匙的校验值,防止keystore被中途篡改

当前 go-conflux-sdkjs-conflux-sdkjava-conflux-sdk 都支持创建和导入导出keystore文件;其它sdk后续也会支持

参考文章:

  1. https://ethbook.abyteahead.com/ch2/keystore.html

Day3 Conflux智能合约开发工具

为了方便开发者更快速的开发Dapp,Conflux 提供了如下智能合约开发工具

详情参见 Tools

Day4 代理合约

Solidity合约部署在链上之后,代码是不可变的(immutable)。这样既有优点,也有缺点:

有没有办法在合约部署后进行修改或升级呢?答案是有的,那就是代理模式。