Constantinople features pt 1

This series will dive into the features planned (or, at least currently being considered) for constantinople.

We will start with EIP 210: Blockhash refactoring

Blockhash refactoring

Currently, before CONSTANTINOPLE, a contract can obtain the last 256 blockhashes, via the opcode BLOCKHASH. The BLOCKHASH operation takes one parameter, and is written in solidity-assembly as

blockhash(b) returns hash of block nr b - only for last 256 blocks excluding current

The EIP proposes to both replace and expand this, so that contracts can have access to a lot more than than the last 256 hashes.

  • This makes it possible for contracts to verify that they are on the expected fork. An oracle an lookup the hash of a certain blocknumber to verify it’s presence in history.
  • This enables improving the light client protocol, since a light client can infer from the state-data what historical blocks make up the chain
  • Additionally, it is noted on the EIP, it

    removes the need for implementaitons to have an explicit way to look into historical block hashes, simplifying the protocol definition and removing a large component of the “implied state” (information that is technically state but is not part of the state tree) and thereby making the protocol more “pure””.

Implementation

The implementation for EIP 210 is a bit peculiar, and has three aspects.

  • A contract is placed at the address 0xff at CONSTANTINOPLE fork block. . This is the contract which does storing and lookups of hashes.
  • Upon every block, before executing any transactions, the contract is invoked with a special SUPERUSER sender: 0xfffffffffffffffffffffffffffffffffffffffe.
    • This call contains a blockhash to store, for the previous block.
  • After 256 blocks have passed in CONSTANTINOPLE, any invocations of BLOCKHASH are replaced with a CALL to 0xff, where the b is used as CALLDATA, and the call is provided with 1M gas. However, the actual gasCost is 800, a raise from the previous 20.

Something that is important for callers to know, is that it does not store all blockhashes.

  • Slots 0...255 store the most recent 256 blockhashes
  • Slots 256...511 store the most recent 256 blockhashes where blocknumber % 256 == 0
  • Slots 512...767 store the most recent 256 blockhashes where blocknumber % 65536 == 0

This is the implementation of the contract

with offset = 0:
    if msg.sender == 0xfffffffffffffffffffffffffffffffffffffffe:
        with bn = block.number - 1:
            while 1:
                ~sstore(offset + ~mod(bn, 256), ~calldataload(0))
                if ~mod(bn, 256):
                    ~stop()
                bn = ~div(bn, 256)
                offset += 256
    elif ~calldataload(0) < block.number:
        with tbn = ~calldataload(0):
            with dist_minus_one = block.number - tbn - 1:
                while dist_minus_one >= 256 && ~mod(tbn, 256) == 0:
                    offset += 256
                    tbn = ~div(tbn, 256) 
                    dist_minus_one = ~div(dist_minus_one, 256)
                if dist_minus_one >= 256:
                    return(0)
                return(~sload(offset + ~mod(tbn, 256)))
    else:
        return(0)

Loading data from the contract

The loading-specific part of the contract is the latter piece:

    elif ~calldataload(0) < block.number:
        with tbn = ~calldataload(0):
            with dist_minus_one = block.number - tbn - 1:
                while dist_minus_one >= 256 && ~mod(tbn, 256) == 0:
                    offset += 256
                    tbn = ~div(tbn, 256) 
                    dist_minus_one = ~div(dist_minus_one, 256)
                if dist_minus_one >= 256:
                    return(0)
                return(~sload(offset + ~mod(tbn, 256)))

A couple of things to take note of here are:

  • The worst-case for this algorithm is when dst_minus_one is large. Thus, requesting the hash for block 0 is the max gas-case (regardless of whether there actually is anything to load at that slot or not).
  • Requesting 0 will cost more with time, the larger the distance grows.

We can experiment with the contract, using evm

A minor patch is needed to make evm respect the genesis blocknumber. See PR

#!/usr/bin/env python

import json
from evmlab import vm,genesis

def main():
    geth = vm.GethVM("/home/user/QubesIncoming/work/evm")
    g = genesis.Genesis()
    contract = "600073fffffffffffffffffffffffffffffffffffffffe33141561005957600143035b60011561005357600035610100820683015561010081061561004057005b6101008104905061010082019150610022565b506100e0565b4360003512156100d4576000356001814303035b61010081121515610085576000610100830614610088565b60005b156100a75761010083019250610100820491506101008104905061006d565b610100811215156100bd57600060a052602060a0f35b610100820683015460c052602060c0f350506100df565b600060e052602060e0f35b5b50"

    g.setConfigMetropolis()
    g.setBlockNumber("0x6acfc0")# 7M
    (geth_g, parity_g) = g.export()
    g_out = geth.execute(code = contract, input="", genesis = geth_g, json=True, gas=1000000, memory=True)
    for i in range(0,len(g_out)):
        print("g: %d: %s" % (i, vm.toText(json.loads(g_out[i]))))

if __name__ == '__main__':
    main()

The program above uses evmlab to synthesize a suitable state, and under the hood calls evm – the standalone EVM bundled in go-ethereum. It will execute on blocknumber 7M, and request the hash for block 0 (no CALLDATA) .

Here is the output

/home/user/QubesIncoming/work/evm --code 600073fffffffffffffffffffffffffffffffffffffffe33141561005957600143035b60011561005357600035610100820683015561010081061561004057005b6101008104905061010082019150610022565b506100e0565b4360003512156100d4576000356001814303035b61010081121515610085576000610100830614610088565b60005b156100a75761010083019250610100820491506101008104905061006d565b610100811215156100bd57600060a052602060a0f35b610100820683015460c052602060c0f350506100df565b600060e052602060e0f35b5b50 --prestate /tmp/genesis-genesis-geth_aCsc3x0F.json --gas 1000000 --json run
g: 0: pc     0 op      PUSH1( 96) gas  0xf4240 depth  1 stack []
g: 1: pc     2 op     PUSH20(115) gas  0xf423d depth  1 stack ['0x0']
g: 2: pc    23 op     CALLER( 51) gas  0xf423a depth  1 stack ['0x0', '0xfffffffffffffffffffffffffffffffffffffffe']
g: 3: pc    24 op         EQ( 20) gas  0xf4238 depth  1 stack ['0x0', '0xfffffffffffffffffffffffffffffffffffffffe', '0x73656e646572']
g: 4: pc    25 op     ISZERO( 21) gas  0xf4235 depth  1 stack ['0x0', '0x0']
g: 5: pc    26 op      PUSH2( 97) gas  0xf4232 depth  1 stack ['0x0', '0x1']
g: 6: pc    29 op      JUMPI( 87) gas  0xf422f depth  1 stack ['0x0', '0x1', '0x59']
g: 7: pc    89 op   JUMPDEST( 91) gas  0xf4225 depth  1 stack ['0x0']
g: 8: pc    90 op     NUMBER( 67) gas  0xf4224 depth  1 stack ['0x0']
g: 9: pc    91 op      PUSH1( 96) gas  0xf4222 depth  1 stack ['0x0', '0x6acfc0']
g: 10: pc    93 op CALLDATALOAD( 53) gas  0xf421f depth  1 stack ['0x0', '0x6acfc0', '0x0']
g: 11: pc    94 op        SLT( 18) gas  0xf421c depth  1 stack ['0x0', '0x6acfc0', '0x0']
g: 12: pc    95 op     ISZERO( 21) gas  0xf4219 depth  1 stack ['0x0', '0x1']
g: 13: pc    96 op      PUSH2( 97) gas  0xf4216 depth  1 stack ['0x0', '0x0']
g: 14: pc    99 op      JUMPI( 87) gas  0xf4213 depth  1 stack ['0x0', '0x0', '0xd4']
g: 15: pc   100 op      PUSH1( 96) gas  0xf4209 depth  1 stack ['0x0']
g: 16: pc   102 op CALLDATALOAD( 53) gas  0xf4206 depth  1 stack ['0x0', '0x0']
g: 17: pc   103 op      PUSH1( 96) gas  0xf4203 depth  1 stack ['0x0', '0x0']
g: 18: pc   105 op       DUP2(129) gas  0xf4200 depth  1 stack ['0x0', '0x0', '0x1']
g: 19: pc   106 op     NUMBER( 67) gas  0xf41fd depth  1 stack ['0x0', '0x0', '0x1', '0x0']
g: 20: pc   107 op        SUB(  3) gas  0xf41fb depth  1 stack ['0x0', '0x0', '0x1', '0x0', '0x6acfc0']
g: 21: pc   108 op        SUB(  3) gas  0xf41f8 depth  1 stack ['0x0', '0x0', '0x1', '0x6acfc0']
g: 22: pc   109 op   JUMPDEST( 91) gas  0xf41f5 depth  1 stack ['0x0', '0x0', '0x6acfbf']
g: 23: pc   110 op      PUSH2( 97) gas  0xf41f4 depth  1 stack ['0x0', '0x0', '0x6acfbf']
g: 24: pc   113 op       DUP2(129) gas  0xf41f1 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x100']
g: 25: pc   114 op        SLT( 18) gas  0xf41ee depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x100', '0x6acfbf']
g: 26: pc   115 op     ISZERO( 21) gas  0xf41eb depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x0']
g: 27: pc   116 op     ISZERO( 21) gas  0xf41e8 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x1']
g: 28: pc   117 op      PUSH2( 97) gas  0xf41e5 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x0']
g: 29: pc   120 op      JUMPI( 87) gas  0xf41e2 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x0', '0x85']
g: 30: pc   121 op      PUSH1( 96) gas  0xf41d8 depth  1 stack ['0x0', '0x0', '0x6acfbf']
g: 31: pc   123 op      PUSH2( 97) gas  0xf41d5 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x0']
g: 32: pc   126 op       DUP4(131) gas  0xf41d2 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x0', '0x100']
g: 33: pc   127 op        MOD(  6) gas  0xf41cf depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x0', '0x100', '0x0']
g: 34: pc   128 op         EQ( 20) gas  0xf41ca depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x0', '0x0']
g: 35: pc   129 op      PUSH2( 97) gas  0xf41c7 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x1']
g: 36: pc   132 op       JUMP( 86) gas  0xf41c4 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x1', '0x88']
g: 37: pc   136 op   JUMPDEST( 91) gas  0xf41bc depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x1']
g: 38: pc   137 op     ISZERO( 21) gas  0xf41bb depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x1']
g: 39: pc   138 op      PUSH2( 97) gas  0xf41b8 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x0']
g: 40: pc   141 op      JUMPI( 87) gas  0xf41b5 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x0', '0xa7']
g: 41: pc   142 op      PUSH2( 97) gas  0xf41ab depth  1 stack ['0x0', '0x0', '0x6acfbf']
g: 42: pc   145 op       DUP4(131) gas  0xf41a8 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x100']
g: 43: pc   146 op        ADD(  1) gas  0xf41a5 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x100', '0x0']
g: 44: pc   147 op      SWAP3(146) gas  0xf41a2 depth  1 stack ['0x0', '0x0', '0x6acfbf', '0x100']
g: 45: pc   148 op        POP( 80) gas  0xf419f depth  1 stack ['0x100', '0x0', '0x6acfbf', '0x0']
g: 46: pc   149 op      PUSH2( 97) gas  0xf419d depth  1 stack ['0x100', '0x0', '0x6acfbf']
g: 47: pc   152 op       DUP3(130) gas  0xf419a depth  1 stack ['0x100', '0x0', '0x6acfbf', '0x100']
g: 48: pc   153 op        DIV(  4) gas  0xf4197 depth  1 stack ['0x100', '0x0', '0x6acfbf', '0x100', '0x0']
g: 49: pc   154 op      SWAP2(145) gas  0xf4192 depth  1 stack ['0x100', '0x0', '0x6acfbf', '0x0']
g: 50: pc   155 op        POP( 80) gas  0xf418f depth  1 stack ['0x100', '0x0', '0x6acfbf', '0x0']
g: 51: pc   156 op      PUSH2( 97) gas  0xf418d depth  1 stack ['0x100', '0x0', '0x6acfbf']
g: 52: pc   159 op       DUP2(129) gas  0xf418a depth  1 stack ['0x100', '0x0', '0x6acfbf', '0x100']
g: 53: pc   160 op        DIV(  4) gas  0xf4187 depth  1 stack ['0x100', '0x0', '0x6acfbf', '0x100', '0x6acfbf']
g: 54: pc   161 op      SWAP1(144) gas  0xf4182 depth  1 stack ['0x100', '0x0', '0x6acfbf', '0x6acf']
g: 55: pc   162 op        POP( 80) gas  0xf417f depth  1 stack ['0x100', '0x0', '0x6acf', '0x6acfbf']
g: 56: pc   163 op      PUSH2( 97) gas  0xf417d depth  1 stack ['0x100', '0x0', '0x6acf']
g: 57: pc   166 op       JUMP( 86) gas  0xf417a depth  1 stack ['0x100', '0x0', '0x6acf', '0x6d']
g: 58: pc   109 op   JUMPDEST( 91) gas  0xf4172 depth  1 stack ['0x100', '0x0', '0x6acf']
g: 59: pc   110 op      PUSH2( 97) gas  0xf4171 depth  1 stack ['0x100', '0x0', '0x6acf']
g: 60: pc   113 op       DUP2(129) gas  0xf416e depth  1 stack ['0x100', '0x0', '0x6acf', '0x100']
g: 61: pc   114 op        SLT( 18) gas  0xf416b depth  1 stack ['0x100', '0x0', '0x6acf', '0x100', '0x6acf']
g: 62: pc   115 op     ISZERO( 21) gas  0xf4168 depth  1 stack ['0x100', '0x0', '0x6acf', '0x0']
g: 63: pc   116 op     ISZERO( 21) gas  0xf4165 depth  1 stack ['0x100', '0x0', '0x6acf', '0x1']
g: 64: pc   117 op      PUSH2( 97) gas  0xf4162 depth  1 stack ['0x100', '0x0', '0x6acf', '0x0']
g: 65: pc   120 op      JUMPI( 87) gas  0xf415f depth  1 stack ['0x100', '0x0', '0x6acf', '0x0', '0x85']
g: 66: pc   121 op      PUSH1( 96) gas  0xf4155 depth  1 stack ['0x100', '0x0', '0x6acf']
g: 67: pc   123 op      PUSH2( 97) gas  0xf4152 depth  1 stack ['0x100', '0x0', '0x6acf', '0x0']
g: 68: pc   126 op       DUP4(131) gas  0xf414f depth  1 stack ['0x100', '0x0', '0x6acf', '0x0', '0x100']
g: 69: pc   127 op        MOD(  6) gas  0xf414c depth  1 stack ['0x100', '0x0', '0x6acf', '0x0', '0x100', '0x0']
g: 70: pc   128 op         EQ( 20) gas  0xf4147 depth  1 stack ['0x100', '0x0', '0x6acf', '0x0', '0x0']
g: 71: pc   129 op      PUSH2( 97) gas  0xf4144 depth  1 stack ['0x100', '0x0', '0x6acf', '0x1']
g: 72: pc   132 op       JUMP( 86) gas  0xf4141 depth  1 stack ['0x100', '0x0', '0x6acf', '0x1', '0x88']
g: 73: pc   136 op   JUMPDEST( 91) gas  0xf4139 depth  1 stack ['0x100', '0x0', '0x6acf', '0x1']
g: 74: pc   137 op     ISZERO( 21) gas  0xf4138 depth  1 stack ['0x100', '0x0', '0x6acf', '0x1']
g: 75: pc   138 op      PUSH2( 97) gas  0xf4135 depth  1 stack ['0x100', '0x0', '0x6acf', '0x0']
g: 76: pc   141 op      JUMPI( 87) gas  0xf4132 depth  1 stack ['0x100', '0x0', '0x6acf', '0x0', '0xa7']
g: 77: pc   142 op      PUSH2( 97) gas  0xf4128 depth  1 stack ['0x100', '0x0', '0x6acf']
g: 78: pc   145 op       DUP4(131) gas  0xf4125 depth  1 stack ['0x100', '0x0', '0x6acf', '0x100']
g: 79: pc   146 op        ADD(  1) gas  0xf4122 depth  1 stack ['0x100', '0x0', '0x6acf', '0x100', '0x100']
g: 80: pc   147 op      SWAP3(146) gas  0xf411f depth  1 stack ['0x100', '0x0', '0x6acf', '0x200']
g: 81: pc   148 op        POP( 80) gas  0xf411c depth  1 stack ['0x200', '0x0', '0x6acf', '0x100']
g: 82: pc   149 op      PUSH2( 97) gas  0xf411a depth  1 stack ['0x200', '0x0', '0x6acf']
g: 83: pc   152 op       DUP3(130) gas  0xf4117 depth  1 stack ['0x200', '0x0', '0x6acf', '0x100']
g: 84: pc   153 op        DIV(  4) gas  0xf4114 depth  1 stack ['0x200', '0x0', '0x6acf', '0x100', '0x0']
g: 85: pc   154 op      SWAP2(145) gas  0xf410f depth  1 stack ['0x200', '0x0', '0x6acf', '0x0']
g: 86: pc   155 op        POP( 80) gas  0xf410c depth  1 stack ['0x200', '0x0', '0x6acf', '0x0']
g: 87: pc   156 op      PUSH2( 97) gas  0xf410a depth  1 stack ['0x200', '0x0', '0x6acf']
g: 88: pc   159 op       DUP2(129) gas  0xf4107 depth  1 stack ['0x200', '0x0', '0x6acf', '0x100']
g: 89: pc   160 op        DIV(  4) gas  0xf4104 depth  1 stack ['0x200', '0x0', '0x6acf', '0x100', '0x6acf']
g: 90: pc   161 op      SWAP1(144) gas  0xf40ff depth  1 stack ['0x200', '0x0', '0x6acf', '0x6a']
g: 91: pc   162 op        POP( 80) gas  0xf40fc depth  1 stack ['0x200', '0x0', '0x6a', '0x6acf']
g: 92: pc   163 op      PUSH2( 97) gas  0xf40fa depth  1 stack ['0x200', '0x0', '0x6a']
g: 93: pc   166 op       JUMP( 86) gas  0xf40f7 depth  1 stack ['0x200', '0x0', '0x6a', '0x6d']
g: 94: pc   109 op   JUMPDEST( 91) gas  0xf40ef depth  1 stack ['0x200', '0x0', '0x6a']
g: 95: pc   110 op      PUSH2( 97) gas  0xf40ee depth  1 stack ['0x200', '0x0', '0x6a']
g: 96: pc   113 op       DUP2(129) gas  0xf40eb depth  1 stack ['0x200', '0x0', '0x6a', '0x100']
g: 97: pc   114 op        SLT( 18) gas  0xf40e8 depth  1 stack ['0x200', '0x0', '0x6a', '0x100', '0x6a']
g: 98: pc   115 op     ISZERO( 21) gas  0xf40e5 depth  1 stack ['0x200', '0x0', '0x6a', '0x1']
g: 99: pc   116 op     ISZERO( 21) gas  0xf40e2 depth  1 stack ['0x200', '0x0', '0x6a', '0x0']
g: 100: pc   117 op      PUSH2( 97) gas  0xf40df depth  1 stack ['0x200', '0x0', '0x6a', '0x1']
g: 101: pc   120 op      JUMPI( 87) gas  0xf40dc depth  1 stack ['0x200', '0x0', '0x6a', '0x1', '0x85']
g: 102: pc   133 op   JUMPDEST( 91) gas  0xf40d2 depth  1 stack ['0x200', '0x0', '0x6a']
g: 103: pc   134 op      PUSH1( 96) gas  0xf40d1 depth  1 stack ['0x200', '0x0', '0x6a']
g: 104: pc   136 op   JUMPDEST( 91) gas  0xf40ce depth  1 stack ['0x200', '0x0', '0x6a', '0x0']
g: 105: pc   137 op     ISZERO( 21) gas  0xf40cd depth  1 stack ['0x200', '0x0', '0x6a', '0x0']
g: 106: pc   138 op      PUSH2( 97) gas  0xf40ca depth  1 stack ['0x200', '0x0', '0x6a', '0x1']
g: 107: pc   141 op      JUMPI( 87) gas  0xf40c7 depth  1 stack ['0x200', '0x0', '0x6a', '0x1', '0xa7']
g: 108: pc   167 op   JUMPDEST( 91) gas  0xf40bd depth  1 stack ['0x200', '0x0', '0x6a']
g: 109: pc   168 op      PUSH2( 97) gas  0xf40bc depth  1 stack ['0x200', '0x0', '0x6a']
g: 110: pc   171 op       DUP2(129) gas  0xf40b9 depth  1 stack ['0x200', '0x0', '0x6a', '0x100']
g: 111: pc   172 op        SLT( 18) gas  0xf40b6 depth  1 stack ['0x200', '0x0', '0x6a', '0x100', '0x6a']
g: 112: pc   173 op     ISZERO( 21) gas  0xf40b3 depth  1 stack ['0x200', '0x0', '0x6a', '0x1']
g: 113: pc   174 op     ISZERO( 21) gas  0xf40b0 depth  1 stack ['0x200', '0x0', '0x6a', '0x0']
g: 114: pc   175 op      PUSH2( 97) gas  0xf40ad depth  1 stack ['0x200', '0x0', '0x6a', '0x1']
g: 115: pc   178 op      JUMPI( 87) gas  0xf40aa depth  1 stack ['0x200', '0x0', '0x6a', '0x1', '0xbd']
g: 116: pc   189 op   JUMPDEST( 91) gas  0xf40a0 depth  1 stack ['0x200', '0x0', '0x6a']
g: 117: pc   190 op      PUSH2( 97) gas  0xf409f depth  1 stack ['0x200', '0x0', '0x6a']
g: 118: pc   193 op       DUP3(130) gas  0xf409c depth  1 stack ['0x200', '0x0', '0x6a', '0x100']
g: 119: pc   194 op        MOD(  6) gas  0xf4099 depth  1 stack ['0x200', '0x0', '0x6a', '0x100', '0x0']
g: 120: pc   195 op       DUP4(131) gas  0xf4094 depth  1 stack ['0x200', '0x0', '0x6a', '0x0']
g: 121: pc   196 op        ADD(  1) gas  0xf4091 depth  1 stack ['0x200', '0x0', '0x6a', '0x0', '0x200']
g: 122: pc   197 op      SLOAD( 84) gas  0xf408e depth  1 stack ['0x200', '0x0', '0x6a', '0x200']
g: 123: pc   198 op      PUSH1( 96) gas  0xf3fc6 depth  1 stack ['0x200', '0x0', '0x6a', '0x0']
g: 124: pc   200 op     MSTORE( 82) gas  0xf3fc3 depth  1 stack ['0x200', '0x0', '0x6a', '0x0', '0xc0']
g: 125: pc   201 op      PUSH1( 96) gas  0xf3fab depth  1 stack ['0x200', '0x0', '0x6a']
g: 126: pc   203 op      PUSH1( 96) gas  0xf3fa8 depth  1 stack ['0x200', '0x0', '0x6a', '0x20']
g: 127: pc   205 op     RETURN(243) gas  0xf3fa5 depth  1 stack ['0x200', '0x0', '0x6a', '0x20', '0xc0']
g: 128: output 0000000000000000000000000000000000000000000000000000000000000000 gasUsed 0x29b

It uses 0x29b (667) gas. How about further in the future?

Let’s modify it a bit

def main():
    geth = vm.GethVM("/home/user/QubesIncoming/work/evm")
    g = genesis.Genesis()
    contract = "600073fffffffffffffffffffffffffffffffffffffffe33141561005957600143035b60011561005357600035610100820683015561010081061561004057005b6101008104905061010082019150610022565b506100e0565b4360003512156100d4576000356001814303035b61010081121515610085576000610100830614610088565b60005b156100a75761010083019250610100820491506101008104905061006d565b610100811215156100bd57600060a052602060a0f35b610100820683015460c052602060c0f350506100df565b600060e052602060e0f35b5b50"
    g.setConfigMetropolis()
    blocknum = 15
    while blocknum < 1600000000:
	    g.setBlockNumber(hex(blocknum))# 7M
	    (geth_g, parity_g) = g.export()
	    g_out = geth.execute(code = contract, input="", genesis = geth_g, json=True, gas=1000000, memory=True)
	    gasUsed = json.loads(g_out[-1])['gasUsed']
	    print("blocknum %d, result: %s" % (blocknum, int(gasUsed,16)))
	    blocknum = blocknum *2

if __name__ == '__main__':
    main()

Result

blocknum 15, result: 405
blocknum 30, result: 405
blocknum 60, result: 405
blocknum 120, result: 405
blocknum 240, result: 405
blocknum 480, result: 536
blocknum 960, result: 536
blocknum 1920, result: 536
blocknum 3840, result: 536
blocknum 7680, result: 536
blocknum 15360, result: 536
blocknum 30720, result: 536
blocknum 61440, result: 536
blocknum 122880, result: 667
blocknum 245760, result: 667
blocknum 491520, result: 667
blocknum 983040, result: 667
blocknum 1966080, result: 667
blocknum 3932160, result: 667
blocknum 7864320, result: 667
blocknum 15728640, result: 667
blocknum 31457280, result: 798
blocknum 62914560, result: 798
blocknum 125829120, result: 798
blocknum 251658240, result: 798
blocknum 503316480, result: 798
blocknum 1006632960, result: 798

It seems that somewhere before block 31M, it goes up to 798 gas. Thus, we can conclude that setting the gasCost to 800 seems reasonable. However, if someone was to CALL it directly, there would be an additional 700 gas for the CALL, ending up at 700+667=1367 gas – so there’s actually about 40% discount in there.

The BLOCKHASH to CALL transition is oddity, since it is endowed with 1M gas. My guess is that since client implementation may choose to replace the evm-execution with a native implementation (like a precompile), and other operations are later repriced, so that e.g. SLOAD costs more, a non-native implementation could go out-of-gas when executing the code, whereas the native implementation would not. Thus, to prevent potential consensus errors due to repricing, there is a factor 1000 more gas provided (1M) than should ever be needed (~1000).

Storing data

The storing part is this:

            while 1:
                ~sstore(offset + ~mod(bn, 256), ~calldataload(0))
                if ~mod(bn, 256):
                    ~stop()
                bn = ~div(bn, 256)
                offset += 256

We can experiment with storing data aswell, with evm and evmlab.

#!/usr/bin/env python

import json
from evmlab import compiler as c
from evmlab import vm,genesis

CODE = "0x600073fffffffffffffffffffffffffffffffffffffffe33141561005957600143035b60011561005357600035610100820683015561010081061561004057005b6101008104905061010082019150610022565b506100e0565b4360003512156100d4576000356001814303035b61010081121515610085576000610100830614610088565b60005b156100a75761010083019250610100820491506101008104905061006d565b610100811215156100bd57600060a052602060a0f35b610100820683015460c052602060c0f350506100df565b600060e052602060e0f35b5b50"

def main():
    geth = vm.GethVM("/home/user/QubesIncoming/work/evm")
    g = genesis.Genesis()
    g.addPrestateAccount({'code': CODE, 'balance': "0x00", "nonce":"0x01", "address":"0x00000000000000000000000000000000000000ff"})
    g.setConfigMetropolis()
    g.setBlockNumber("0x6acfc0")# 7M
    (geth_g, parity_g) = g.export()
    g_out = geth.execute(input="deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", receiver="0x00000000000000000000000000000000000000ff ",sender="0xfffffffffffffffffffffffffffffffffffffffe",genesis = geth_g, json=True, gas=1000000, memory=True)
    for i in range(0,len(g_out)):
        print("g: %d: %s" % (i, vm.toText(json.loads(g_out[i]))))

if __name__ == '__main__':
    main()

Here, we simply set the contract as a genesis alloc, and call it from SUPERUSERwith a bogus blockhash of deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef. Running it yields:

/home/user/QubesIncoming/work/evm --prestate /tmp/genesis-genesis-geth_xIG6cG5S.json --gas 1000000 --sender 0xfffffffffffffffffffffffffffffffffffffffe --receiver 0x00000000000000000000000000000000000000ff  --input deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef --json run
g: 0: pc     0 op      PUSH1( 96) gas  0xf4240 depth  1 stack []
g: 1: pc     2 op     PUSH20(115) gas  0xf423d depth  1 stack ['0x0']
g: 2: pc    23 op     CALLER( 51) gas  0xf423a depth  1 stack ['0x0', '0xfffffffffffffffffffffffffffffffffffffffe']
g: 3: pc    24 op         EQ( 20) gas  0xf4238 depth  1 stack ['0x0', '0xfffffffffffffffffffffffffffffffffffffffe', '0xfffffffffffffffffffffffffffffffffffffffe']
g: 4: pc    25 op     ISZERO( 21) gas  0xf4235 depth  1 stack ['0x0', '0x1']
g: 5: pc    26 op      PUSH2( 97) gas  0xf4232 depth  1 stack ['0x0', '0x0']
g: 6: pc    29 op      JUMPI( 87) gas  0xf422f depth  1 stack ['0x0', '0x0', '0x59']
g: 7: pc    30 op      PUSH1( 96) gas  0xf4225 depth  1 stack ['0x0']
g: 8: pc    32 op     NUMBER( 67) gas  0xf4222 depth  1 stack ['0x0', '0x1']
g: 9: pc    33 op        SUB(  3) gas  0xf4220 depth  1 stack ['0x0', '0x1', '0x6acfc0']
g: 10: pc    34 op   JUMPDEST( 91) gas  0xf421d depth  1 stack ['0x0', '0x6acfbf']
g: 11: pc    35 op      PUSH1( 96) gas  0xf421c depth  1 stack ['0x0', '0x6acfbf']
g: 12: pc    37 op     ISZERO( 21) gas  0xf4219 depth  1 stack ['0x0', '0x6acfbf', '0x1']
g: 13: pc    38 op      PUSH2( 97) gas  0xf4216 depth  1 stack ['0x0', '0x6acfbf', '0x0']
g: 14: pc    41 op      JUMPI( 87) gas  0xf4213 depth  1 stack ['0x0', '0x6acfbf', '0x0', '0x53']
g: 15: pc    42 op      PUSH1( 96) gas  0xf4209 depth  1 stack ['0x0', '0x6acfbf']
g: 16: pc    44 op CALLDATALOAD( 53) gas  0xf4206 depth  1 stack ['0x0', '0x6acfbf', '0x0']
g: 17: pc    45 op      PUSH2( 97) gas  0xf4203 depth  1 stack ['0x0', '0x6acfbf', '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef']
g: 18: pc    48 op       DUP3(130) gas  0xf4200 depth  1 stack ['0x0', '0x6acfbf', '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', '0x100']
g: 19: pc    49 op        MOD(  6) gas  0xf41fd depth  1 stack ['0x0', '0x6acfbf', '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', '0x100', '0x6acfbf']
g: 20: pc    50 op       DUP4(131) gas  0xf41f8 depth  1 stack ['0x0', '0x6acfbf', '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', '0xbf']
g: 21: pc    51 op        ADD(  1) gas  0xf41f5 depth  1 stack ['0x0', '0x6acfbf', '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', '0xbf', '0x0']
g: 22: pc    52 op     SSTORE( 85) gas  0xf41f2 depth  1 stack ['0x0', '0x6acfbf', '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', '0xbf']
g: 23: pc    53 op      PUSH2( 97) gas  0xef3d2 depth  1 stack ['0x0', '0x6acfbf']
g: 24: pc    56 op       DUP2(129) gas  0xef3cf depth  1 stack ['0x0', '0x6acfbf', '0x100']
g: 25: pc    57 op        MOD(  6) gas  0xef3cc depth  1 stack ['0x0', '0x6acfbf', '0x100', '0x6acfbf']
g: 26: pc    58 op     ISZERO( 21) gas  0xef3c7 depth  1 stack ['0x0', '0x6acfbf', '0xbf']
g: 27: pc    59 op      PUSH2( 97) gas  0xef3c4 depth  1 stack ['0x0', '0x6acfbf', '0x0']
g: 28: pc    62 op      JUMPI( 87) gas  0xef3c1 depth  1 stack ['0x0', '0x6acfbf', '0x0', '0x40']
g: 29: pc    63 op       STOP(  0) gas  0xef3b7 depth  1 stack ['0x0', '0x6acfbf']
g: 30: output  gasUsed 0x4e89

The gas used is 0x4e89, or 20105 in gas.

We can also use this to see how/where the data is stored, what slot is used.

#!/usr/bin/env python

import json
from evmlab import compiler as c
from evmlab import vm,genesis

CODE = "0x600073fffffffffffffffffffffffffffffffffffffffe33141561005957600143035b60011561005357600035610100820683015561010081061561004057005b6101008104905061010082019150610022565b506100e0565b4360003512156100d4576000356001814303035b61010081121515610085576000610100830614610088565b60005b156100a75761010083019250610100820491506101008104905061006d565b610100811215156100bd57600060a052602060a0f35b610100820683015460c052602060c0f350506100df565b600060e052602060e0f35b5b50"
ADDR="0x00000000000000000000000000000000000000ff"
SUPERUSER="0xfffffffffffffffffffffffffffffffffffffffe"
def main():
    geth = vm.GethVM("/home/user/QubesIncoming/work/evm")
    g = genesis.Genesis()
    g.addPrestateAccount({'code': CODE, 'balance': "0x00", "nonce":"0x01", "address":ADDR})
    g.setConfigMetropolis()
    blocknum = 2 #Executing at blocknum 1 (storing blnum 0) causes infinite loop
    while blocknum < 1024:
        g.setBlockNumber(hex(blocknum))# 7M
        (geth_g, parity_g) = g.export()
        g_out = geth.execute(input="deadbeefdeadbeef{:0>20x}".format(blocknum), receiver=ADDR,sender=SUPERUSER,genesis = geth_g, json=True, gas=1000000, memory=True)
        gasUsed = json.loads(g_out[-1])['gasUsed']
        for i in range(0,len(g_out)):
            op = json.loads(g_out[i])
            if "opName" in op.keys() and op["opName"] == 'SSTORE':
                pos = op['stack'][-1]
                data = op['stack'][-2]
                print("Blocknum %d: SSTORE( %s , %s) " % (blocknum, pos,data))
        print("blocknum %d, result: %s" % (blocknum, int(gasUsed,16)))
        blocknum = blocknum+1

if __name__ == '__main__':
    main()



Looking at the output:

python3 blockstore_2.py| grep SSTORE
Blocknum 2: SSTORE( 0x1 , 0xdeadbeefdeadbeef000000000000000000020000000000000000000000000000) 
Blocknum 3: SSTORE( 0x2 , 0xdeadbeefdeadbeef000000000000000000030000000000000000000000000000) 
Blocknum 4: SSTORE( 0x3 , 0xdeadbeefdeadbeef000000000000000000040000000000000000000000000000) 
[... snip ...]
Blocknum 249: SSTORE( 0xf8 , 0xdeadbeefdeadbeef000000000000000000f90000000000000000000000000000) 
Blocknum 250: SSTORE( 0xf9 , 0xdeadbeefdeadbeef000000000000000000fa0000000000000000000000000000) 
Blocknum 251: SSTORE( 0xfa , 0xdeadbeefdeadbeef000000000000000000fb0000000000000000000000000000) 
Blocknum 252: SSTORE( 0xfb , 0xdeadbeefdeadbeef000000000000000000fc0000000000000000000000000000) 
Blocknum 253: SSTORE( 0xfc , 0xdeadbeefdeadbeef000000000000000000fd0000000000000000000000000000) 
Blocknum 254: SSTORE( 0xfd , 0xdeadbeefdeadbeef000000000000000000fe0000000000000000000000000000) 
Blocknum 255: SSTORE( 0xfe , 0xdeadbeefdeadbeef000000000000000000ff0000000000000000000000000000) 
Blocknum 256: SSTORE( 0xff , 0xdeadbeefdeadbeef000000000000000001000000000000000000000000000000) 
Blocknum 257: SSTORE( 0x0 , 0xdeadbeefdeadbeef000000000000000001010000000000000000000000000000) 
Blocknum 257: SSTORE( 0x101 , 0xdeadbeefdeadbeef000000000000000001010000000000000000000000000000) 
Blocknum 258: SSTORE( 0x1 , 0xdeadbeefdeadbeef000000000000000001020000000000000000000000000000) 
Blocknum 259: SSTORE( 0x2 , 0xdeadbeefdeadbeef000000000000000001030000000000000000000000000000) 
Blocknum 260: SSTORE( 0x3 , 0xdeadbeefdeadbeef000000000000000001040000000000000000000000000000) 
Blocknum 261: SSTORE( 0x4 , 0xdeadbeefdeadbeef000000000000000001050000000000000000000000000000) 
Blocknum 262: SSTORE( 0x5 , 0xdeadbeefdeadbeef000000000000000001060000000000000000000000000000) 
Blocknum 263: SSTORE( 0x6 , 0xdeadbeefdeadbeef000000000000000001070000000000000000000000000000) 

We can see that the first 256 blocks are stored in 0x1 .. 0xff. Block 256 is stored both in 0x0 (overwriting the entry there previously) and in 0x101 (257).

Let’s modify it to check interesting points in the first billion blocks:


    while blocknum < 1000000000:
        g.setBlockNumber(hex(blocknum))# 7M
        (geth_g, parity_g) = g.export()
        g_out = geth.execute(input="deadbeefdeadbeef{:0>20x}".format(blocknum), receiver=ADDR,sender=SUPERUSER,genesis = geth_g, json=True, gas=1000000, memory=True)
        gasUsed = json.loads(g_out[-1])['gasUsed']
        for i in range(0,len(g_out)):
            op = json.loads(g_out[i])
            if "opName" in op.keys() and op["opName"] == 'SSTORE':
                pos = op['stack'][-1]
                data = op['stack'][-2]
                print("Blocknum %d: SSTORE( %s , %s) " % (blocknum, pos,data))
        print("blocknum %d, result: %s" % (blocknum, int(gasUsed,16)))
        blocknum = (blocknum-1)*256+1

Running it:

python3 blockstore_2.py| grep SSTORE
Blocknum 257: SSTORE( 0x0 , 0xdeadbeefdeadbeef000000000000000001010000000000000000000000000000) 
Blocknum 257: SSTORE( 0x101 , 0xdeadbeefdeadbeef000000000000000001010000000000000000000000000000) 
Blocknum 65537: SSTORE( 0x0 , 0xdeadbeefdeadbeef000000000000000100010000000000000000000000000000) 
Blocknum 65537: SSTORE( 0x100 , 0xdeadbeefdeadbeef000000000000000100010000000000000000000000000000) 
Blocknum 65537: SSTORE( 0x201 , 0xdeadbeefdeadbeef000000000000000100010000000000000000000000000000) 
Blocknum 16777217: SSTORE( 0x0 , 0xdeadbeefdeadbeef000000000000010000010000000000000000000000000000) 
Blocknum 16777217: SSTORE( 0x100 , 0xdeadbeefdeadbeef000000000000010000010000000000000000000000000000) 
Blocknum 16777217: SSTORE( 0x200 , 0xdeadbeefdeadbeef000000000000010000010000000000000000000000000000) 
Blocknum 16777217: SSTORE( 0x301 , 0xdeadbeefdeadbeef000000000000010000010000000000000000000000000000) 

Sometime after block 16M, the block storage does four storages, which is the realistic maximum for another few years. At that block, it will use 80441 gas – but actually, a lot less (45k), since three of those are overwriting existing data, so it would wind up at around 35441 gas.

Thoughts

  • I expect that most clients will implement this as a ‘pseudo-precompile’: place the code in the state at the correct address, but also place a precompile at the same location. The precompile can calculate the slot natively, and fetch the relevant data. For storing data, a different native method will likely be used instead.
  • If it is indeed implemented by calling the contract, care must be taken to not actually tally up the gas expenditure, as it should not be counted towards the block gas limit.
  • The contract uses a couple of DIV operations which could be rewritten as SHR, which are also going into CONSTANTINOPLE. Also, the loops could probably be optmized a bit, favouring the ‘common’ cases (block numbers not divisable by 256) at the expense of the more uncommon ones.
  • As currently defined, there is no way to get the genesis hash. It could be implemented as a special case if blocknumber is 0. I’m not sure it’s entirely needed, but there may be usecases where it is useful (?). If so, the genesis hash could be inserted in two ways
    1. Created in a special storage slot at the same time the code is created, or
    2. Inserted into the appropriate place in the contract bytecode, basically the same way as a compile-time constant.
  • The contract does not conform to any ABI-format, since it interprets the entire CALLDATA as argument (leaving no room for method signature). However, as it is currently implemented, I see no reason to actually CALL-call it, instead of BLOCKHASH-calling it, thus making it ABI-compliant may be nonsensical.
    • In order to actually ABI-call it, as is, one would have to find a signature which resolves to 0x00000000 and then upshift the blocknumber by 32 bits before calling it, so that it winds on the first 64 bytes of CALLDATA

2018-04-27

tweets

favorites