WTF Opcodes极简入门: 10. 区块信息指令 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 推特:@0xAAScience 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/WTFAcademy/WTF-Opcodes 在这一讲,我们将介绍EVM中用于查询区块信息的9个指令,包括 , , 等。我们将在用Python写的极简版EVM中添加对这些操作的支持。 区块信息 我们在写智能合约时经常会用到区块链信息,比如生成伪随机数时我们会使用 , ,和 : EVM提供了一系列指令让智能合约访问当前或历史区块的信息,包括区块哈希、时间戳、coinbase等。
我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。
所有代码和教程开源在github: github.com/WTFAcademy/WTF-Opcodes
在这一讲,我们将介绍EVM中用于查询区块信息的9个指令,包括BLOCKHASH,COINBASE,PREVRANDAO等。我们将在用Python写的极简版EVM中添加对这些操作的支持。
我们在写智能合约时经常会用到区块链信息,比如生成伪随机数时我们会使用blockhash,block.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, }
下面,我们介绍这些区块信息指令:
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
COINBASE: 将当前区块的coinbase(矿工/受益人)地址压入堆栈,它的操作码为0x41,gas消耗为2。
def coinbase(self): self.stack.append(self.current_block["coinbase"])
TIMESTAMP: 将当前区块的时间戳压入堆栈,它的操作码为0x42,gas消耗为2。
def timestamp(self): self.stack.append(self.current_block["timestamp"])
NUMBER: 将当前区块高度压入堆栈,它的操作码为0x43,gas消耗为2。
def number(self): self.stack.append(self.current_block["number"])
PREVRANDAO: 替代了原先的DIFFICULTY(0x44) 操作码,其返回值是beacon链随机性信标的输出。此变更允许智能合约在以太坊转向权益证明(PoS)后继续从原本的DIFFICULTY操作码处获得随机性。它的操作码为0x44,gas消耗为2。
def prevrandao(self): self.stack.append(self.current_block["prevrandao"])
GASLIMIT: 将当前区块的gas限制压入堆栈,它的操作码为0x45,gas消耗为2。
def gaslimit(self): self.stack.append(self.current_block["gaslimit"])
CHAINID: 将当前的链ID压入堆栈,它的操作码为0x46,gas消耗为2。
def chainid(self): self.stack.append(self.current_block["chainid"])
SELFBALANCE: 将合约的当前余额压入堆栈,它的操作码为0x47,gas消耗为5。
def selfbalance(self): self.stack.append(self.current_block["selfbalance"])
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中与区块链信息相关的指令,这些指令允许智能合约访问与其所在区块链相关的信息。这些信息有很多用途,比如判断交易是否超时,或者检查合约的余额。
课后习题: 请尝试写出一段字节码,该字节码会先压入当前区块链的高度,然后获取它的区块哈希。