Evm升级最新Yolo功能概览和问题定位

  • 科帆
  • 更新于 2020-08-26 23:59
  • 阅读 2773

Uniswap部署依赖Istanbul中的chainid,从君士坦丁堡升级到YoloV1最新版本,就可以在链上正常部署。本文讲粗略讲一下升级涉及功能和问题的定位解决。

Istanbul升级

  • 1344中添加chainidopcode获取链的当前chainid
  • 1884中添加balanceopcede查询调用方余额
  • 2200中状态加载和存储的gas费增加更新

YoloV1升级

  • 2315subroutines,包含如下opcode BEGINSUB,RETURNSUB,JUMPSUB

功能点

整合gas各个版本的升级,删除了paramsgas_table 不同版本更新opcodegas费用,提高了状态加载和存储的费用如SLOAD BALANCE EXTCODEHASH 常量gas费从opcode对应的函数中移除,赋值给constantGas 操作数big.Int类型为uint256.Int,提高25%执行效率 整合内存拷贝内存创建gas计算溢出检查 减少zk使用的bn256 gas费,添加blake2Fbls预编译合约 增加ReturnStack可返回错误结果方便debug

SLOAD BALANCE EXTCODEHASH 存储访问操作码,16年因为EXTCODESIZE被dos攻击,状态读取和存储异常耗时,需要提高gas费。

错误输出

之前合约执行错误只会输出execution reverted,现在可以打印哪里报错

execution reverted: TransferHelper: TRANSFER_FROM_FAILED

sol里面找到如下报错,为对方Approve金额较低导致。

require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');

容易出错的地方

  • opcodegas费计算错误
  • constantGas 常量已从gas_table中移除,直接在代码中根据解析器分支使用不同的opcode gas
  • dynamicGas 计算在内存占用费用

Evm测试TraceLog

  • 跟旧版本是否兼容,需要重新同步旧的历史块
  • 功能是否完善,Uniswap应用是否可部署成功

合约解析器

下面规定了不同的分叉使用的EVMInterpreter解析器是不一样的。

// NewEVMInterpreter returns a new instance of the Interpreter.
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
    // We use the STOP instruction whether to see
    // the jump table was initialised. If it was not
    // we'll set the default jump table.
    if cfg.JumpTable[STOP] == nil {
        var jt JumpTable
        switch {
        case evm.chainRules.IsYoloV1:
            jt = yoloV1InstructionSet
        case evm.chainRules.IsIstanbul:
            jt = istanbulInstructionSet
        case evm.chainRules.IsConstantinople:
            jt = constantinopleInstructionSet
        case evm.chainRules.IsByzantium:
            jt = byzantiumInstructionSet
        default:
            jt = frontierInstructionSet
        }
        cfg.JumpTable = jt
    }
    return &EVMInterpreter{
        evm: evm,
        cfg: cfg,
    }
}

预编译合约

不同的升级使用的PrecompiledContract预编译合约也不一样的,预编译合约主要是处理一些复杂的数学计算,这类计算evm执行起来较慢,所以以预编译合约的形式加入到evm里面,合约执行的过程不是汇编执行而是具体代码

func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
    var precompiles map[common.Address]PrecompiledContract
    switch {
    case evm.chainRules.IsYoloV1:
        precompiles = PrecompiledContractsYoloV1
    case evm.chainRules.IsIstanbul:
        precompiles = PrecompiledContractsIstanbul
    case evm.chainRules.IsByzantium:
        precompiles = PrecompiledContractsByzantium
    default:
        precompiles = PrecompiledContractsHomestead
    }
    p, ok := precompiles[addr]
    return p, ok
}

这是升级中加的一些预编译合约,很多是支持零知识证明的,椭圆配对曲线称为BLS12-381,为过渡eth2.0提供基础,该预编译使精简客户端成为可能。

// PrecompiledContractsYoloV1 contains the default set of pre-compiled Ethereum
// contracts used in the Yolo v1 test release.
var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
    common.BytesToAddress([]byte{1}):  &ecrecover{},
    common.BytesToAddress([]byte{2}):  &sha256hash{},
    common.BytesToAddress([]byte{3}):  &ripemd160hash{},
    common.BytesToAddress([]byte{4}):  &dataCopy{},
    common.BytesToAddress([]byte{5}):  &bigModExp{},
    common.BytesToAddress([]byte{6}):  &bn256AddIstanbul{},
    common.BytesToAddress([]byte{7}):  &bn256ScalarMulIstanbul{},
    common.BytesToAddress([]byte{8}):  &bn256PairingIstanbul{},
    common.BytesToAddress([]byte{9}):  &blake2F{},
    common.BytesToAddress([]byte{10}): &bls12381G1Add{},
    common.BytesToAddress([]byte{11}): &bls12381G1Mul{},
    common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{},
    common.BytesToAddress([]byte{13}): &bls12381G2Add{},
    common.BytesToAddress([]byte{14}): &bls12381G2Mul{},
    common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{},
    common.BytesToAddress([]byte{16}): &bls12381Pairing{},
    common.BytesToAddress([]byte{17}): &bls12381MapG1{},
    common.BytesToAddress([]byte{18}): &bls12381MapG2{},
}

新旧版本问题

合约执行中如果计算的gas不一致,区块验证就会报错。这个问题相当严重

问题查找

抓取合约执行过程的Trace log分析不同点,截取了如下日志。

{"pc":2373,"op":84,"gas":"0xeddb6","gasCost":"0x320","memory":"0x000000000000000000000000735bce5ecc8455eb9bf8270aa138ce05e069b4c1656502bc70f67e407a17d7e0cba13a8b062e13793501bed56cecc8aee887bd7d0000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x1bf73a65","0x181","0x2de96ad91d62a796c4ba80e0a862520f9c8a7805","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x0","0x47f37265329d36000","0x1","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x1137c4082672bb033743321bcc426da68e2052bc996d994df96a0e1cd48b5b60"],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"SLOAD","error":""}
{"pc":2374,"op":144,"gas":"0xeda96","gasCost":"0x3","memory":"0x000000000000000000000000735bce5ecc8455eb9bf8270aa138ce05e069b4c1656502bc70f67e407a17d7e0cba13a8b062e13793501bed56cecc8aee887bd7d0000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x1bf73a65","0x181","0x2de96ad91d62a796c4ba80e0a862520f9c8a7805","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x0","0x47f37265329d36000","0x1","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x1"],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"SWAP1","error":""}

将上面的0xeddb60xeda96相减是800十六进制为0x320

{"pc":2373,"op":84,"gas":"0xeddb6","gasCost":"0xc8","memory":"0x000000000000000000000000735bce5ecc8455eb9bf8270aa138ce05e069b4c1656502bc70f67e407a17d7e0cba13a8b062e13793501bed56cecc8aee887bd7d0000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x1bf73a65","0x181","0x2de96ad91d62a796c4ba80e0a862520f9c8a7805","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x0","0x47f37265329d36000","0x1","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x1137c4082672bb033743321bcc426da68e2052bc996d994df96a0e1cd48b5b60"],"depth":1,"refund":0,"opName":"SLOAD","error":""}
{"pc":2374,"op":144,"gas":"0xedcee","gasCost":"0xc8","memory":"0x000000000000000000000000735bce5ecc8455eb9bf8270aa138ce05e069b4c1656502bc70f67e407a17d7e0cba13a8b062e13793501bed56cecc8aee887bd7d0000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x1bf73a65","0x181","0x2de96ad91d62a796c4ba80e0a862520f9c8a7805","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x0","0x47f37265329d36000","0x1","0x735bce5ecc8455eb9bf8270aa138ce05e069b4c1","0x0","0x1"],"depth":1,"refund":0,"opName":"SWAP1","error":""}

这个是升级前的日志,相减得200十六进制为0xc8

查询84opcode0x54 SLOADIstanbulSloadGasEIP1884调节到了800,这个由于分叉高度设置太低导致使用yoloV1InstructionSet解析器,其实是没问题的,调节YoloV1Block高度即可

    "YOLOv1": {
        ChainID:             big.NewInt(1),
        HomesteadBlock:      big.NewInt(0),
        EIP150Block:         big.NewInt(0),
        EIP155Block:         big.NewInt(0),
        EIP158Block:         big.NewInt(0),
        ByzantiumBlock:      big.NewInt(0),
        ConstantinopleBlock: big.NewInt(0),
        PetersburgBlock:     big.NewInt(0),
        IstanbulBlock:       big.NewInt(0),
        YoloV1Block:         big.NewInt(0),
    },

功能不完善问题

eth部署合约正确的指令流程

{"pc":23,"op":96,"gas":"0x278abc","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":[],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"PUSH1","error":""}
{"pc":25,"op":81,"gas":"0x278ab9","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x40"],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"MLOAD","error":""}
{"pc":26,"op":70,"gas":"0x278ab6","gasCost":"0x2","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x80"],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"CHAINID","error":""}
{"pc":27,"op":144,"gas":"0x278ab4","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x80","0x539"],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"SWAP1","error":""}

升级后evm的流程

{"pc":23,"op":96,"gas":"0x5c54030","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":[],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"PUSH1","error":""}
{"pc":25,"op":81,"gas":"0x5c5402d","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x40"],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"MLOAD","error":""}
{"pc":26,"op":70,"gas":"0x5c5402a","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080","memSize":96,"stack":["0x80"],"returnStack":[],"returnData":null,"depth":3,"refund":0,"opName":"CHAINID","error":"invalid opcode: CHAINID"}
{"pc":1507,"op":96,"gas":"0x17740a","gasCost":"0x3","memory":"","memSize":11904,"stack":["0xc9c65396","0x95","0x537e697c7ab75a26f9ecf0ce810e3154dfcaaf44","0x3a220f351252089d385b29beca14e27f204c296a","0x0","0x3a220f351252089d385b29beca14e27f204c296a","0x537e697c7ab75a26f9ecf0ce810e3154dfcaaf44","0x80","0xb81ff3c7140db37a05a85068ea614a23c0d72ec176588a2f45c5dd53550b6c88","0x0"],"returnStack":[],"returnData":null,"depth":2,"refund":0,"opName":"PUSH1","error":""}

pc指针可以看操作码70后结果开始不一样了,70对于的opcodeCHAINID,而CHAINIDIstanbul后加入的,由于IstanbulBlock高度设置较高,功能为开启,所以跳转不一样。 trace log也有报错"opName":"CHAINID","error":"invalid opcode: CHAINID"

总结

两个问题都是YoloV1Block未正确设置导致加载的EVMInterpreter解析器不对导致

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
科帆
科帆
江湖只有他的大名,没有他的介绍。