您正在查看: Surou 发布的文章

使用eosio-java计算transaction_id

开源地址:https://github.com/EOSIO/eosio-java

计算方法

Hex.toHexString(Sha256Hash.hash(Hex.decode(processor.getSerializedTransaction())));

参考:https://github.com/EOSIO/eosio-java/issues/96

Ethereum POS + POA链系统合约测试

文章作用

本片文章主要是为了满足没有接触过Ethereum合约的同学,熟悉当前系统合约,以及了解Ethereum合约测试的交互流程。
基于文中所对应的系统合约,主要是测试阶段会尽量详细以及给出预计的测试结果做对比。
在相关系统合约未开源之前,不建议提前浏览此文章。

POS + POA 系统合约开源地址

待开源

合约组成

Validators 验证者合约

主要数据结构如下

// 验证者数据状态
enum Status {
    Inactivated,                       // 未激活
    Activated,                         // 已激活
    Disabled                           // 已禁用
}

// 验证者描述信息
struct Description {
    string nickname;                    // 昵称
    string location;                    // 位置
    string website;                     // 网址
    string contact;                     // 联系
    string remarks;                     // 备注
}

// 验证者数据结构
struct Validator {
    address payable rewardReceiveAddress;// 奖励接收地址
    address claimRewardAddress;          // 执行领取奖励地址
    Status status;                       // 节点状态
    uint256 totalStakeCoins;             // 总抵押代币数
    uint256 totalPendingStakeCoins;      // 总待合入的抵押代币数
    Description description;             // 节点描述
    uint256 shareRatioBase;              // 分成比例(1/shareRatioBase)
    uint256 bpPendingClaimCoins;         // 当前节点待领取的奖励
    uint256 issueTotalRewardsCoins;      // 支持者应该分的总奖励
    uint256 issueBalanceRewardsCoins;    // 支持者应该分的剩余奖励
    uint256 totalDisabledCoins;          // 总监禁的代币数(监禁的代币会被分发给其余验证者)
    uint256 bpLastClaimRewardTime;       // 最后领取奖励的时间
    uint256 lastEditValidatorInfoTime;   // 最后一次编辑验证者信息
    address[] stakers;                   // 当前验证者所支持的账户地址
}

// 抵押地址信息
struct StakingInfo {
    uint256 pendingStakeCoins;           // 对该节点待合入的抵押
    uint256 stakeBalanceCoins;           // 抵押的余额
    uint256 lastClaimRewardTime;         // 最后领取奖励的时间
}

// 投票者统一数据
struct UserCoreData {
    address payable rewardReceiveAddress;// 奖励接收地址
    address claimRewardAddress;          // 执行领取奖励地址
    uint256 totalUnstakeBalanceCoins;    // 取消抵押的余额
    uint256 lastUnstakeClaimTime;        // 最后取消抵押时的块高度
}

mapping(address => Validator) validatorInfo;                // 验证者信息
mapping(address => mapping(address => StakingInfo)) staked; // 用户对于各个验证者的抵押数据 (staker => validator => info)
mapping(address => UserCoreData) coreStaked;                // 用户抵押核心数据
address[] public workingValidators;                         // 当前正在工作验证者地址
address[] public topValidators;                             // 当前出块验证者地址(epoch 周期内更新)
address[] public activedValidators;                         // 处于激活状态的验证者地址
uint256 public totalStake;                                  // 全网当前总抵押
uint256 public totalPendingStake;                           // 全网当前待合入的总抵押
uint256 public totalDisabledCoins;                          // 全网历史总罚没

合约对外接口

合约初始化
initialize(address[] calldata vals, address proposalAddr, address punishContractAddr, address configContractAddr, uint256 epoch)

参数

  • vals 初始化出块验证者地址
  • proposalAddr 提案合约地址
  • punishContractAddr 惩罚治理合约地址
  • configContractAddr 全局配置合约地址
  • epoch 出块周期
设置用户奖励相关地址
setUserCoreRewardAddress(address payable rewardReceiveAddress, address claimRewardAddress)

参数

  • rewardReceiveAddress 用户投票奖励接收地址
  • claimRewardAddress 用户奖励待领取地址
抵押投票
stake(address validator, uint256 stakeCoins)

参数

  • validator 将要投票的节点
  • stakeCoins 投票代币数
    如果当前抵押总量大于转账金额,则消费已取消抵押待领取的代币数。
    对于抵押成功后,抵押代币量,会进去待投票数据,等待下一领取周期后,才会进入真正的抵押投票数据
取消投票
unstake(address validator, uint256 unstakeCoins)

参数

  • validator 将要赎回的验证者
  • unstakeCoins 取消投票的代币数
    赎回时,优先扣除待投票数据
    赎回成功后,对应的代币数,将进入待领取(实时)
领取赎回
refund()

领取待赎回的代币,时间间隔默认3day,每次执行unstake都会刷新,已最后一次赎回时间为开始计算时间。

领取出块验证奖励
claimBpReward(address validator)

参数

  • validator 对应的验证者地址
    执行操作的账户地址需要为创建节点信息时执行的claimRewardAddress账户地址
    执行期间会根据创建节点时,设置的shareRatioBase分配比例,进行期间的奖励划分,该验证者会直接收到自己应得的奖励,接收奖励的账户地址为创建节点时配置的rewardReceiveAddress奖励接收账户(对于创世验证节点,初始化为该账户地址)。
    同时记录支持该节点的抵押用户部分的奖励,等待用户方主动过来领取。(如果领取周期内未及时领取的用户奖励,将会合到下一周期统一继续分分配)
领取投票奖励
claimReward(address voter, address validator)

参数

  • voter 投票者账户地址
  • validator 验证者账户地址

操作的执行权限地址可以为当前投票账户地址,也可以为setUserCoreRewardAddress设置中的claimRewardAddress代为领取地址,同理奖励接收地址如果未设置,则为当前投票地址。

更新当前工作验证者地址 (链程序调用)
updateWorkingValidators(address[] memory newSet, uint256 epoch)

参数

  • newSet 当前工作验证者地址组,及为链程序执行getTopValidators()获取的头部地址,
  • epoch 出块周期
上缴出块奖励
distributeBlockReward()

验证节点上缴出块奖励,奖励上缴后,根据各正在工作的验证节点所抵押的比例进行分配。

创建出块验证节点信息
function createOrEditValidator(
        address payable rewardReceiveAddress, // 奖励接收地址
        address claimRewardAddress,           // 奖励代为领取地址
        string calldata nickname,             // 昵称
        string calldata location,             // 位置
        string calldata website,              // 网址
        string calldata contact,              // 联系方式
        uint256 shareRatioBase,               // 奖励分配比例(1/N)
        string calldata remarks               // 备注
    )
重新激活节点
tryReactive(address validator)

当节点处于非Active时,置为激活状态

更新头部出块节点
updateTopValidators()

重新计算头部出块节点地址,为下一周期链程序获取做准备

其他数据获取类接口省略

Proposal 提案合约

struct ProposalInfo {
    address proposer;    // 提案发起者
    address dst;         // 提议谁加入
    string details;      // 描述信息
    uint256 createTime;  // 创建时间
    uint16 agree;        // 同意总数
    uint16 reject;       // 拒绝总数
    bool isEnd;          // 是否已结束 
}

struct VoteInfo {
    address voter;        // 投票者
    uint256 voteTime;     // 投票时间
    uint16 votedTimes;    // 已投票次数
    bool isAgree;         // 是否同意
}

合约接口

合约初始化
initialize(address validatorContractAddr, address configContractAddr)

参数

  • validatorContractAddr 验证者合约地址
  • configContractAddr 全局合约配置地址
创建提案
createProposal(address val, string calldata details)

参数

  • val 申请出块的验证者地址
  • details 备注信息
    通过监听event获取创建完的提案id
取消提案
cancelProposal(bytes32 id)

创建者可取消创建的提案

对提案投票
voteProposal(bytes32 id, bool isAgree)

对于提案,只有正在出块的节点才有资格投票,经过 1/2+1通过,通过后会把提案中的地址添加到验证者列表,状态为Active
默认全局配置,同一个提案,有最多三次的投票修改机会

Punish 惩罚治理合约

稍后补充

PublicBase 通用基类合约

稍后补充

Config 全局配置合约

稍后补充

Ownable 合约权限合约

稍后补充

为模拟测试修改合约

为了测试方便,我们直接使用普通账户模拟出块账户进行合约的调用
需要注释掉下PublicBase合约中的两个判断条件

modifier onlyMiner() {
    //require(msg.sender == block.coinbase, "Miner only");
    _;
}

modifier onlyBlockEpoch(uint256 epoch) {
    //require(block.number % epoch == 0, "Block epoch only");
    _;
}

合约部署步骤

先将各个合约编译好,通过 myetherwallet工具将以下合约部署到链上

  • Validators
  • Proposal
  • Punish
  • Config

各个合约部署完后的地址

合约名 地址
Validators 0x113D0CD580edB0a70e8De5F92C15145f83211318
Proposal 0x00C8D6d9736347f66c069C9285a5b99B8ddEFDC7
Punish 0x8049F22032C2F7b7fD0981014953bFe5CE891373
Config 0xB3bB2C011277DB3CcB66250617437C42Df69455B

初始化各个系统合约

此处是为了模拟出块验证者测试,生产中当前初始化过程将在链程序打第一个块时做相应的初始化,各个初始化参数配置在genesis.json

"config": {
....
    "congress": {
      "period": 3,
      "epoch": 200,
      "validatorsContractName": "validators",
      "punishContractName": "punish",
      "proposalContractName": "proposal",
      "configContractName": "config",
      "validatorsContractAddr": "0x113D0CD580edB0a70e8De5F92C15145f83211318",
      "punishContractAddr": "0x8049F22032C2F7b7fD0981014953bFe5CE891373",
      "proposalContractAddr": "0x00C8D6d9736347f66c069C9285a5b99B8ddEFDC7",
      "configContractAddr": "0xB3bB2C011277DB3CcB66250617437C42Df69455B"
    }
....
  }

相应的合约bytecode 也会初始化加到genesis.json中的alloc里的各个对应地址。
此篇文章我们只讲模拟测试,更灵活控制测试的步骤。

初始化Validators

对于后面的合约交互统一使用myetherwallet工具

合约测试步骤

稍后补充

备注

文中所涉及的系统合约暂时并没有开源,等后面计划开源后,再做补充

参考

https://github.com/HuobiGroup/huobi-eco-chain
https://github.com/HuobiGroup/huobi-eco-contracts

CompilerError: Stack too deep, try removing local variables.

原因

是一个function里面的变量 数量不能超过(16),包括了入参和返回值。

解决方法

  1. 减少方法内不必要的变量
  2. 使用结构体减少单个变量参数

参考

https://medium.com/1milliondevs/compilererror-stack-too-deep-try-removing-local-variables-solved-a6bcecc16231

EOS链的RAM资源扩增

背景

某个基于EOS开发的链,提供了免费的账户注册接口,执行调用后,空投账户免费为其抵押CPU NET和购买RAM。由于接口没有做严格有效的防刷处理,导致账户注册严重超过预期,导致链内系统资源浪费严重,以及空投部分系统代币占用过多

逻辑处理

  1. 增加注册接口的防范级别,比如增加IP注册次数,相同公钥,App机器码统计。三方有效的验证码验证,App内核心校验参数加密,增加对方破解成本。
  2. 对于已经刷入的账户做筛选,以及资源回收,比如,注册多长时间内从未使用
  3. 增加付费和邀请注册逻辑。提供与EOS目前主流钱包一样的逻辑,由三方账户进行代为抵押购买,或者直接RMB支付
  4. 由项目方提供投抵押的系统代币。提供空投服务程序给项目方,项目方账户持续为自己项目内账户补充所需的空投代币,服务程序也可以支持邀请码注册,项目方生成邀请码赠送或者出售给自己的用户

链处理

  1. 一次性扩增RAM容量。(eosio->setram)

    void system_contract::setram( uint64_t max_ram_size ) {
       require_auth( get_self() );
    
       check( _gstate.max_ram_size < max_ram_size, "ram may only be increased" ); /// decreasing ram might result market maker issues
       check( max_ram_size < 1024ll*1024*1024*1024*1024, "ram size is unrealistic" );
       check( max_ram_size > _gstate.total_ram_bytes_reserved, "attempt to set max below reserved" );
    
       auto delta = int64_t(max_ram_size) - int64_t(_gstate.max_ram_size);
       auto itr = _rammarket.find(ramcore_symbol.raw());
    
       /**
        *  Increase the amount of ram for sale based upon the change in max ram size.
        */
       _rammarket.modify( itr, same_payer, [&]( auto& m ) {
          m.base.balance.amount += delta;
       });
    
       _gstate.max_ram_size = max_ram_size;
    }
  2. 每块持续新增。(eosio->setramrate)
    设置完每块新增量后

    void system_contract::setramrate( uint16_t bytes_per_block ) {
       require_auth( get_self() );
    
       update_ram_supply();
       _gstate2.new_ram_per_block = bytes_per_block;
    }

    当下次有购买内存的操作时,执行时间差内的容量扩增

    void system_contract::buyram( const name& payer, const name& receiver, const asset& quant )
    {
       require_auth( payer );
       update_ram_supply();

参考

https://github.com/EOSIO/eosio.contracts/blob/d7bc0a5cc8c0c2edd4dc61b0126517d0cb46fd94/contracts/eosio.system/src/eosio.system.cpp#L67

https://github.com/EOSIO/eosio.contracts/blob/d7bc0a5cc8c0c2edd4dc61b0126517d0cb46fd94/contracts/eosio.system/src/eosio.system.cpp#L105

https://github.com/EOSIO/eosio.contracts/blob/d7bc0a5cc8c0c2edd4dc61b0126517d0cb46fd94/contracts/eosio.system/src/delegate_bandwidth.cpp#L46

https://developers.eos.io/manuals/eosio.contracts/latest/key-concepts/ram

缩小以太坊合同规模,以应对合同体积限制

为什么有限制?

2016年 11月22日,Spurious Dragon硬叉推出了 EIP-170,该协议增加了24.576 kb的智能合约大小限制。对于您作为Solidity开发人员而言,这意味着当您向合同中添加越来越多的功能时,在某些时候您将达到极限,并且在部署错误时会看到以下错误:

Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.

引入此限制是为了防止拒绝服务(DOS)攻击。任何签订合同的呼吁都是相对便宜的。但是,合约调用对以太坊节点的影响取决于被调用合约代码的大小(从磁盘读取代码,预处理代码,将数据添加到Merkle证明中)不成比例地增加。每当您遇到这种情况时,攻击者只需很少的资源即可为他人带来很多工作,那么您就有可能遭受DOS攻击。

最初,这没有什么问题,因为一个自然合同大小限制是块气限制。显然,需要在包含合同所有字节码的事务中部署合同。如果然后仅将一个事务包含在一个块中,则可以用尽所有气体,但是它不是无限的。但是,在这种情况下,问题在于阻气限值会随时间变化,并且在理论上是不受限制的。在EIP-170时,块气限制仅为470万。现在,区块气体限制上个月才再次增加到1190万。

进行优化

对各个合约进行优化之前,我们要先知道各个合约各自的体积,Truffle -contract-size插件是帮助您的一个好工具,如果您使用的是Truffle

安装插件

npm install truffle-contract-size

配置插件

在truffle-config.js文件中加入

plugins: ["truffle-contract-size"]

运行插件

Run truffle run contract-size

此时就可以看到各个合约的各自的体积了

针对合约进行压缩

分拆合约部分逻辑到子合约

这应该始终是您的第一种方法。您如何将合同分成多个较小的合同?通常,它会迫使您为合同设计出良好的体系结构。从代码可读性的角度来看,始终首选较小的合同。对于拆分合同,请问自己:

  • 哪些功能属于同一类?每套功能在其自己的合同中可能是最好的。
  • 哪些功能不需要读取合同状态或仅读取状态的特定子集?
  • 您可以拆分存储和功能吗?

减少中间memory变量的创建

比如

function get(uint id) returns (address,address) {
    MyStruct memory myStruct = myStructs[id];
    return (myStruct.addr1, myStruct.addr2);
}

修改为

function get(uint id) returns (address,address) {
    return (myStructs[id].addr1, myStructs[id].addr2);
}

相差0.28kb。您很可能会在合同中找到许多类似的情况,而这些情况实际上加起来可观。

缩短错误提示

比如

require(msg.sender == owner, "Only the owner of this contract can call this function");

修改为

require(msg.sender == owner, "OW1");

每次修改完,重新编译合约,然后查看优化大小,是否已到达临界内

参考

https://soliditydeveloper.com/max-contract-size