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.

at 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.

sender: . 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

store the most recent 256 blockhashes Slots 256...511 store the most recent 256 blockhashes where blocknumber % 256 == 0

store the most recent 256 blockhashes where 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).

is large. Thus, requesting the hash for block 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 SUPERUSER with 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.

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.

operations which could be rewritten as , which are also going into . Also, the loops could probably be optmized a bit, favouring the ‘common’ cases (block numbers not divisable by ) 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 Created in a special storage slot at the same time the code is created, or Inserted into the appropriate place in the contract bytecode, basically the same way as a compile-time constant.

hash. It could be implemented as a special case if blocknumber is . 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 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

as argument (leaving no room for method signature). However, as it is currently implemented, I see no reason to actually -call it, instead of -calling it, thus making it ABI-compliant may be nonsensical.

2018-04-27