makeLog指令函数如何在以太坊虚拟机中运行

How does makeLog instruction function works in Ethereum virtual machine

以下代码片段是 geth 中 instructions.go 文件的组成部分。

// make log instruction function
func makeLog(size int) executionFunc {
    return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
        if interpreter.readOnly {
            return nil, ErrWriteProtection
        }
        topics := make([]common.Hash, size)
        stack := scope.Stack
        mStart, mSize := stack.pop(), stack.pop()
        for i := 0; i < size; i++ {
            addr := stack.pop()
            topics[i] = addr.Bytes32()
        }

        d := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64()))
        interpreter.evm.StateDB.AddLog(&types.Log{
            Address: scope.Contract.Address(),
            Topics:  topics,
            Data:    d,
            // This is a non-consensus field, but assigned here because
            // core/state doesn't know the current block number.
            BlockNumber: interpreter.evm.Context.BlockNumber.Uint64(),
        })

        return nil, nil
    }
}

问题是,log0、log1、Log2 等操作码是如何工作的,它们在以太坊虚拟机中的用途是什么?

LOG<n> 操作码用于发出事件日志。

<n> 值取决于事件的索引和 non-indexed 主题的数量。由于 <n> 值有限(当前为 4),每个事件定义的最大索引主题也有限制(当前为 3,因此也可以处理同一事件的未索引主题)。

Solidity 中的示例:

event MyEmptyEvent();
event MyEvent(bool indexed, bool indexed, bool, bool);

function foo() external {
    // Produces the `LOG0` opcode as there are no topics
    emit MyEmptyEvent();

    // Produces the `LOG3` opcode
    // as the 2 indexed topics are stored separately
    // but the unindexed topics are stored as 1 topic with concatenated value
    emit MyEvent(true, true, true, true);
}

在一个交易被包含在一个挖掘的区块中之后,产生的事件日志与其他状态变化(例如存储值和地址余额)一起被广播。

有一个很棒的 article 更深入地描述了细节。