nimbus-eth1 icon indicating copy to clipboard operation
nimbus-eth1 copied to clipboard

Implement dispatching to forked ops

Open zah opened this issue 5 years ago • 2 comments

1) Modify the current opcode table to include the following information

a) opcode number b) opcode name c) forks where the opcode was changed

For example, it can look like this:

opcodeTable:
  0x11 sdiv
  0x12 create [Tangerine, Spurious]

2) Introduce a forkedOp macro

This will be similar to the current op macro, but creating a proc with an additional static enum parameter called fork.

forkedOp create:
  when fork >= Tangerine:
    ...

3) Compile the VM instruction dispatching loop for each fork

This will consult the opcodeTable and it will place calls to the forked procs using the correct static parameters. The information in the table is enough to ensure that only the minimal number of instantiations are created (i.e. only 3 versions of create are needed - "Initial", "After Tangerine" and "After Spurious")

4) Make sure the block validation logic uses the correct dispatching loop depending on the block number

zah avatar Aug 28 '18 19:08 zah

I had the following idea in mind for new opcodes when I made the current architecture, similar to gas costs you have a BaseOpcodeTable.

See https://github.com/status-im/nimbus/blob/master/nimbus/vm/interpreter_dispatch.nim#L18-L27

# Note pseudocode

let FrontierOpDispatch {.compileTime.}: array[Op, NimNode] = block:
  fill_enum_table_holes(Op, newIdentNode"invalidInstruction"):
    [
      Stop: newIdentNode "toBeReplacedByBreak",
      Add: newIdentNode "add",
      Mul: newIdentNode "mul",
      Sub: newIdentNode "sub",
      Div: newIdentNode "divide",
      Sdiv: newIdentNode "sdiv",
      Mod: newIdentNode "modulo", 
      ...
      ExtCodeSize: newIdentNode "extCodeSize",
      ExtCodeCopy: newIdentNode "extCodeCopy",
      # ReturnDataSize: introduced in Byzantium
      # ReturnDataCopy: introduced in Byzantium
      ...
      # f0s: System operations
      Create: newIdentNode "create",
      Call: newIdentNode "call",
      CallCode: newIdentNode "callCode",
      Return: newIdentNode "returnOp",
      DelegateCall: newIdentNode "delegateCall",
      # StaticCall: introduced in Byzantium
      # Revert: introduced in Byzantium
      # Invalid: newIdentNode "invalid",
      SelfDestruct: newIdentNode "selfDestruct"
    ]

func byzantiumOpDispatch(previous_opcodes: array[Op, NimNode]): array[Op, NimNode] =
  result = previous_opcodes

  # Now assuming new opcodes are introduced:
  result[ReturnDataSize] = newidentNode "returnDataSize"
  result[ReturnDataCopy] = newidentNode "returnDataCopy"
  result[StaticCall] =  newidentNode "staticCall"
  result[Revert] =  newidentNode "revert"
  result[Invalid] =  newidentNode "invalid"

let ByzantiumOpDispatch {.compileTime.} = FrontierOpDispatch.byzantiumOpDispatch

This was to make sure that invalid opcodes at Genesis time (but valid after Tangerine/Byzantium) are correctly reported invalid.

For opcodes that where patched over hardforks the when fork >= Tangerine is clearer.

mratsim avatar Aug 29 '18 10:08 mratsim

I think we only need a way to specify that a certain opcode was introduced in certain fork. Then the single opcode table will still be enough to figure out that this particular opcode is not valid in the Genesis block.

The only strong assumption of the single opcode table is that opcodes won't change their numeric IDs across forks, which is a reasonable assumption.

zah avatar Aug 29 '18 14:08 zah

I believe we can close this issue, our current evm have solve this, although not using macro.

jangko avatar Dec 22 '22 02:12 jangko