Skip to main content

WTF Opcodes极简入门: 10. 区块信息指令

我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。

推特:@0xAA_Science

社区:Discord微信群官网 wtf.academy

所有代码和教程开源在github: github.com/WTFAcademy/WTF-Opcodes


在这一讲,我们将介绍EVM中用于查询区块信息的9个指令,包括BLOCKHASHCOINBASEPREVRANDAO等。我们将在用Python写的极简版EVM中添加对这些操作的支持。

区块信息

我们在写智能合约时经常会用到区块链信息,比如生成伪随机数时我们会使用blockhashblock.number,和block.timestamp:

    /** 
* 链上伪随机数生成
* keccak256(abi.encodePacked())中填上一些链上的全局变量/自定义变量
* 返回时转换成uint256类型
*/
function getRandomOnchain() public view returns(uint256){
/*
* 本例链上随机只依赖区块哈希,调用者地址,和区块时间,
* 想提高随机性可以再增加一些属性比如nonce等,但是不能根本上解决安全问题
*/
bytes32 randomBytes = keccak256(abi.encodePacked(blockhash(block.number-1), msg.sender, block.timestamp));
return uint256(randomBytes);
}

EVM提供了一系列指令让智能合约访问当前或历史区块的信息,包括区块哈希、时间戳、coinbase等。

这些信息一般保存在区块头(Header)中,但我们可以为在极简EVM中添加current_block属性来模拟这些区块信息:

def __init__(self, code):
self.code = code
self.pc = 0
self.stack = []
self.memory = bytearray()
self.current_block = {
"blockhash": 0x7527123fc877fe753b3122dc592671b4902ebf2b325dd2c7224a43c0cbeee3ca,
"coinbase": 0x388C818CA8B9251b393131C08a736A67ccB19297,
"timestamp": 1625900000,
"number": 17871709,
"prevrandao": 0xce124dee50136f3f93f19667fb4198c6b94eecbacfa300469e5280012757be94,
"gaslimit": 30,
"chainid": 1,
"selfbalance": 100,
"basefee": 30,
}

区块信息指令

下面,我们介绍这些区块信息指令:

  1. BLOCKHASH: 查询特定区块(最近的256个区块,不包括当前区块)的hash,它的操作码为0x40,gas消耗为20。。它从堆栈中弹出一个值作为区块高度(block number),然后将该区块的hash压入堆栈,如果它不属于最近的256个区块,则返回0(你可以使用NUMBER指令查询当前区块高度)。但是为了简化,我们在这里只考虑当前块。

    def blockhash(self):
    if len(self.stack) < 1:
    raise Exception('Stack underflow')
    number = self.stack.pop()
    # 在真实场景中, 你会需要访问历史的区块hash
    if number == self.current_block["number"]:
    self.stack.append(self.current_block["blockhash"])
    else:
    self.stack.append(0) # 如果不是当前块,返回0
  1. COINBASE: 将当前区块的coinbase(矿工/受益人)地址压入堆栈,它的操作码为0x41,gas消耗为2。

    def coinbase(self):
    self.stack.append(self.current_block["coinbase"])
  2. TIMESTAMP: 将当前区块的时间戳压入堆栈,它的操作码为0x42,gas消耗为2。

    def timestamp(self):
    self.stack.append(self.current_block["timestamp"])
  3. NUMBER: 将当前区块高度压入堆栈,它的操作码为0x43,gas消耗为2。

    def number(self):
    self.stack.append(self.current_block["number"])
  4. PREVRANDAO: 替代了原先的DIFFICULTY(0x44) 操作码,其返回值是beacon链随机性信标的输出。此变更允许智能合约在以太坊转向权益证明(PoS)后继续从原本的DIFFICULTY操作码处获得随机性。它的操作码为0x44,gas消耗为2。

    def prevrandao(self):
    self.stack.append(self.current_block["prevrandao"])
  5. GASLIMIT: 将当前区块的gas限制压入堆栈,它的操作码为0x45,gas消耗为2。

    def gaslimit(self):
    self.stack.append(self.current_block["gaslimit"])
  6. CHAINID: 将当前的链ID压入堆栈,它的操作码为0x46,gas消耗为2。

    def chainid(self):
    self.stack.append(self.current_block["chainid"])
  7. SELFBALANCE: 将合约的当前余额压入堆栈,它的操作码为0x47,gas消耗为5。

    def selfbalance(self):
    self.stack.append(self.current_block["selfbalance"])
  8. BASEFEE: 将当前区块的基础费(base fee)压入堆栈,它的操作码0x48,gas消耗为2。

    def basefee(self):
    self.stack.append(self.current_block["basefee"])

下面,我们在极简EVM中添加对这些操作码的支持:

BLOCKHASH = 0x40
COINBASE = 0x41
TIMESTAMP = 0x42
NUMBER = 0x43
PREVRANDAO = 0x44
GASLIMIT = 0x45
CHAINID = 0x46
SELFBALANCE = 0x47
BASEFEE = 0x48

def run(self):
while self.pc < len(self.code):
op = self.next_instruction()

# ... 其他指令的处理 ...

elif op == BLOCKHASH:
self.blockhash()
elif op == COINBASE:
self.coinbase()
elif op == TIMESTAMP:
self.timestamp()
elif op == NUMBER:
self.number()
elif op == PREVRANDAO:
self.prevrandao()
elif op == GASLIMIT:
self.gaslimit()
elif op == CHAINID:
self.chainid()
elif op == SELFBALANCE:
self.selfbalance()
elif op == BASEFEE:
self.basefee()

总结

这一讲,我们介绍了EVM中与区块链信息相关的指令,这些指令允许智能合约访问与其所在区块链相关的信息。这些信息有很多用途,比如判断交易是否超时,或者检查合约的余额。

课后习题: 请尝试写出一段字节码,该字节码会先压入当前区块链的高度,然后获取它的区块哈希。