Skip to content

Instantly share code, notes, and snippets.

@cdetrio
Created January 24, 2019 00:49
Show Gist options
  • Save cdetrio/874e8e6fd044665ec90fd9473dbc8cd7 to your computer and use it in GitHub Desktop.
Save cdetrio/874e8e6fd044665ec90fd9473dbc8cd7 to your computer and use it in GitHub Desktop.
estimating stateless contract block sizes on Ethereum

Estimating stateless contract block sizes on Ethereum

Results of running the script for 100 blocks (#7021700 - #7021800).

>>> sizes = [852946, 221839, 637053, 672740, 1093313, 225852, 1678145, 761029, 1419458, 1152028, 653192, 67545, 29260, 249825, 1526471, 76492, 791376, 0, 204216, 973265, 561570, 579943, 1515495, 1738738, 1717572, 395549, 354647, 16870, 551612, 89760, 1848829, 290080, 505966, 22421, 1471108, 28740, 1578582, 880692, 13205, 1442855, 1255767, 644168, 1634534, 2631529, 869398, 437570, 452382, 1236448, 870726, 203800, 570037, 446159, 6947, 55375, 599809, 308926, 113536, 0, 605908, 357154, 829124, 1583076, 1413534, 854483, 198182, 1015926, 1080430, 59555, 1106610, 205846, 445871, 379150, 980513, 72306, 1293635, 40396, 942147, 561540, 297171, 1035896, 917608, 272911, 776640, 0, 1293114, 627284, 3675, 965540, 32970, 168510, 113672, 863180, 25912, 1245664, 532234, 576862, 1332396, 590317, 760537, 1295538, 1543689]
>>> len(sizes)
101
>>> np.mean(sizes)
698258.3762376237
>>> np.percentile(sizes, 50)
599809.0
>>> np.percentile(sizes, 95)
1634534.0
>>> np.percentile(sizes, 75)
1080430.0
>>> np.percentile(sizes, 25)
205846.0
>>> np.percentile(sizes, 5)
13205.0
>>> max(sizes)
2631529
block #7021700: 852946 bytes
block #7021701: 221839 bytes
block #7021702: 637053 bytes
block #7021703: 672740 bytes
block #7021704: 1093313 bytes
block #7021705: 225852 bytes
block #7021706: 1678145 bytes
block #7021707: 761029 bytes
block #7021708: 1419458 bytes
block #7021709: 1152028 bytes
block #7021710: 653192 bytes

block #7021711: 67545 bytes
block #7021712: 29260 bytes
block #7021713: 249825 bytes
block #7021714: 1526471 bytes
block #7021715: 76492 bytes
block #7021716: 791376 bytes
block #7021717: 0 bytes
block #7021718: 204216 bytes
block #7021719: 973265 bytes
block #7021720: 561570 bytes
block #7021721: 579943 bytes

block #7021722: 1515495 bytes
block #7021723: 1738738 bytes
block #7021724: 1717572 bytes
block #7021725: 395549 bytes
block #7021726: 354647 bytes
block #7021727: 16870 bytes
block #7021728: 551612 bytes
block #7021729: 89760 bytes
block #7021730: 1848829 bytes

block #7021731: 290080 bytes
block #7021732: 505966 bytes
block #7021733: 22421 bytes
block #7021734: 1471108 bytes
block #7021735: 28740 bytes
block #7021736: 1578582 bytes
block #7021737: 880692 bytes
block #7021738: 13205 bytes
block #7021739: 1442855 bytes
block #7021740: 1255767 bytes

block #7021741: 644168 bytes
block #7021742: 1634534 bytes
block #7021743: 2631529 bytes
block #7021744: 869398 bytes
block #7021745: 437570 bytes
block #7021746: 452382 bytes
block #7021747: 1236448 bytes
block #7021748: 870726 bytes
block #7021749: 203800 bytes
block #7021750: 570037 bytes

block #7021751: 446159 bytes
block #7021752: 6947 bytes
block #7021753: 55375 bytes
block #7021754: 599809 bytes
block #7021755: 308926 bytes
block #7021756: 113536 bytes
block #7021757: 0 bytes
block #7021758: 605908 bytes
block #7021759: 357154 bytes
block #7021760: 829124 bytes

block #7021761: 1583076 bytes
block #7021762: 1413534 bytes
block #7021763: 854483 bytes
block #7021764: 198182 bytes
block #7021765: 1015926 bytes
block #7021766: 1080430 bytes
block #7021767: 59555 bytes
block #7021768: 1106610 bytes
block #7021769: 205846 bytes
block #7021770: 445871 bytes

block #7021771: 379150 bytes
block #7021772: 980513 bytes
block #7021773: 72306 bytes
block #7021774: 1293635 bytes
block #7021775: 40396 bytes
block #7021776: 942147 bytes
block #7021777: 561540 bytes
block #7021778: 297171 bytes
block #7021779: 1035896 bytes
block #7021780: 917608 bytes

block #7021781: 272911 bytes
block #7021782: 776640 bytes
block #7021783: 0 bytes
block #7021784: 1293114 bytes
block #7021785: 627284 bytes
block #7021786: 3675 bytes
block #7021787: 965540 bytes
block #7021788: 32970 bytes
block #7021789: 168510 bytes
block #7021790: 113672 bytes

block #7021791: 863180 bytes
block #7021792: 25912 bytes
block #7021793: 1245664 bytes
block #7021794: 532234 bytes
block #7021795: 576862 bytes
block #7021796: 1332396 bytes
block #7021797: 590317 bytes
block #7021798: 760537 bytes
block #7021799: 1295538 bytes
block #7021800: 1543689 bytes
import json
import sys
import requests
head = {"Content-type": "application/json"}
# debug_traceTransaction script to get all touched storage locations for a tx
tracer_script = "{ data: [], step: function(log) { if (log.op.toString() == \"SSTORE\" || log.op.toString() == \"SLOAD\") { var location = log.stack.peek(0); this.data.push( {op: log.op.toString(), location: location.toString(16), contract: toHex(log.contract.getAddress())} ); } }, fault: function() { return \"error\"; }, result: function() { return this.data; } }"
# curl --header "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"debug_traceTransactionWitness","params":["0x6c919fd479697e59df670501c8cce85e1bcbe7a20af193baba8acb7ab6a59e30", {"tracer": "{ data: [], step: function(log) { if (log.op.toString() == \"SSTORE\" || log.op.toString() == \"SLOAD\") { var location = log.stack.peek(0); this.data.push( {op: log.op.toString(), location: location.toString(16), contract: toHex(log.contract.getAddress())} ); } }, fault: function() { return \"error\"; }, result: function() { return this.data; } }"}],"id":5}' http://localhost:8545
def sizeOfTxWitness(tx_witness):
# tx_witness is same as proofs_for_tx
# dict with keys being contract addresses
# '0x6dea55ba04a37fddd05e1fd979c30aa0e634e837':
# {'address': '0x6dea55ba04a37fddd05e1fd979c30aa0e634e837',
# 'storageHash': '0xebb084cd7fdfaa7bd2ca7b08b45b778a3101f9323f221cb4f608d8578f4d50b8',
# 'storageProof': [{'key': '0x0', 'value': '0x85d4780b73119b644ae5ecd22b376', 'proof': ['0xf90211a0f7d4f786dcbba72c73cb57d65d1860cfc313d1ff65281abbba514b7434a8de7fa0e856f68c65373465c9efecc80de72b199df949dc13eb0ced87911fcb20d13294a07544af8f2ce60f55ffaf52a1af8dfb8c467934fa7e1f392dc7de3e103c39c8fda0d7b415d1343c1a93f66d86910a2fc5c2ebe177b864cb0893c0d0cb17b6775029a005902822971ea6d056b23e3cbb3509ec32244348e1721769ea37f8bc6a26a2e4a0d9df80bbb8294a30b9c3c4a63eda7d05f94d345cf69cc351c850a8f71dd5573da02f0374f70e3f65444bc90cb2b81154ec859a46891bbbc79d3696ad1034332eb1a0ad14a54e3fa59e5a983680535469307f7ec87a43e5d5de9d332d4f4009e6acc2a0e87129a4297fabc68fcd87f2bf24d30236039512b783b9c468105eab145a709ea0a05207641b4bfc2c0d6c6701ee4c9a8d378064a1497e78f070080a91b1861bb1a0822341970924e5095f01f553aaff5f09b6a2d0565296da9a3e1f44f21d5762f8a0c84ada85c9e62d7d53f8556dc2e4fc6f1571dae71907881e068baeb38238182aa0fb8f3e45e1cee000f28768738a443df032745c2476ebd385a6e51156fabddee3a0bc34001e0002e3d1eab535942b7cab1b1de0731750bb9422595eb1041e4e27aca04ec7413e5d29fd5b8a3c30b78f3382700110e63fbadb02536930620bbf0aa76da06b61088e80564eae6b5d67e4d1f3d61be7f07136d11c707c95b78ab7fb4a979580','0xf90211a0212cfe1b66fa481bb6f8142f5ce806cff1d5f603466b8616e444b68c4d056914a085fc940e1a2817ccad137c69fb35cd4715d40468a9f0bdf234cb990e67438213a05d994412d0a580d74df4febeab5f742125935f917a16f5d77779829c7f5faf5ba090e4f9544517cdd050d4d945dc22f69020b56dcc0fc987735249151053af8c01a0513af332ee99fcb0ffd04303cbcfcf4e236559367690eff282f70b2756a81a3da06a722b651ae055d7579eb45aaad30f0334eaa612645c835581cd42d83c6dbc8aa044ed3f87498f5f2d390729da19f7824ab9b2da0356c43777cc10e00a2246bb66a04f868b02b3a8dbe5a614fb71110d3263c49162499e521e8d8e44e9c184b7ea3ca0c44a7d9593254d07d2dd9b4ce1452eec3e54808a136653d8fb78dbc7c5240422a07feb1c2336a635aacb206ae1eb68a59b48e1486a80b80bbdcef24503da3f6dc3a00ba071ff9618e76747c72b1f4c41cc3b41c27c515070e750e45a3a2b78f408f2a0eb8d3bcdd8ea9ce5b8d0dd4467bab515315134a786cc3db47674ceeb6da0d0b9a0beb7e2e6b70fd2e10298a6575ce50c9c1927b1fc172521b67300a86cf6617834a0fb8861283ece5631a81cbeb714eaa9959ffd998d988424b46de77d4320e8a5c2a0ade86900c11bb1528ca916c8304e5bceb9c3444868adbb271c36b293fd9e9c2aa0d76392d372aeeee121e0b71543caf3fc36f8bf8724b6957233212e46048274ec80','0xf901b1a0c519ccfae243eff48d7a109595bc925151b25fdf1c2249fcb1e39128dd049b60a0951ccb97037f542eb784210f77bb1a8a692061cb1ea0dc21d6842bd084bcd50ea0fcf58348406877282b566614eee773654496b2cbb0ef2ad50288d097c0ac4be8a03bba320400b31a03364d627f727dc5a15a032627f0d412305f7c4b06fde36f31a0f73ea4f844a220aa441cb5d4ed7c5e8460b062f4e7ca32d006378a7fa12e52b680a0cac32435937324f911c4f21346eac2be3de1703380fb7be8cf6789eeba4e2474a03149936adf08c23e63ff2c9a8c17472ca36fb39cc1b9452b86a5e4156a53ba44a05c6bd4d6f424bd2958ce010593992d4322a4ad586597a3e34fe33794981ec07780a0ef5ea7a182d7aa2f6c0de8d8036dbb652f296559a710dbeb34fe42751cb7cb9ca0706216ad427ff6f32c27c62153bc7ae304ebae77ef47d9ecdb0e2681b44c97f2a0794be2b6e01bdc1d1ec59654f974f7f8f9a995c61a57fba4813e522ca986dfe3a03c594d3f2602ea28ffdab90e24672f594e45e487020639a4dac8463a7bc65eeea05bd92b1652b0fe39578a05cab1efb2de4e766e306577beb5a973cd9308d8778f8080','0xf851808080808080808080808080a0f21dcbeb48f5a49dc50288fc2d332c085a874400847283320d16d607448b92b6a04db38b36c06c3c4d297d685c7fd15420c42252460287ae3a853ba5ff85575cb0808080', '0xf19f20ecd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563908f085d4780b73119b644ae5ecd22b376']},
# ... more {'key', 'value', 'proof'}
print("tx touches {} contracts.".format(len(tx_witness.keys())))
total_size = 0
for contract in tx_witness:
if contract != tx_witness[contract]['address']:
sys.exit("ERROR! contract address is messed up.")
total_size += 20 # size of contract address
storage_proofs = tx_witness[contract]['storageProof']
print("{} storage keys touched in contract {}".format(len(storage_proofs), contract))
for key_proof in storage_proofs:
print("doing key {}...".format(key_proof['key']))
total_size += 32 # size of storage key
# ignore size of value for now
for merkle_node in key_proof['proof']:
hex_length = len(merkle_node) - 2
byte_size = hex_length / 2
total_size += byte_size
# do next touched contract
# did all touched contracts in tx
#print("witness size: {}".format(total_size))
return total_size
def getProofsForTouches(touches_in_tx):
# curl --header "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_getProof","params":["0xea38eaa3c86c8f9b751533ba2e562deb9acded40",["0x52419b386ce9c40b298ef864f6f9232f796eabab80a3f81a2a24d8e1018caa14","0xc4cba991f74e7ac769beca22e8f2aeca08a2ba638096cc15d357d1ca5f87a8e7"],"0x6b23ea"],"id":1}' http://localhost:8545
proofs_for_tx = {}
touches_by_contract = touches_in_tx
for contract in touches_by_contract:
keys_touched = touches_by_contract[contract]
keys_touched_hex = ["0x{}".format(k['location']) for k in keys_touched]
payload = {
'method': 'eth_getProof',
'params': [contract, keys_touched_hex, hex(7021700)],
'id': 1
}
response = requests.post("http://localhost:8545", data=json.dumps(payload), headers=head)
#print("proofs for contract {}".format(contract))
#print(response.text)
r = response.json()['result']
storageProofs = {}
storageProofs['address'] = r['address']
storageProofs['storageHash'] = r['storageHash']
storageProofs['storageProof'] = r['storageProof']
print('storageProofs: {}'.format(storageProofs))
proofs_for_tx[contract] = storageProofs
return proofs_for_tx
def traceBlockAndGetWitnessSize(txs_to_trace):
proofs_by_tx = {}
for tx in txs_to_trace:
print("getting trace for tx {}".format(tx))
payload = {
'method': 'debug_traceTransaction',
'params': [tx, {'tracer': tracer_script}],
'id': 1
}
response = requests.post("http://localhost:8545", data=json.dumps(payload), headers=head)
#print(response.text)
try:
trace_touches = response.json()['result']
except:
print("got error parsing debug_traceTransaction response!", sys.exc_info()[0])
print(response.text)
sys.exit("got error parsing debug_traceTransaction response. stopping.")
# sort traces into dict, indexed by contract address
touches_by_contract = {}
for touch in trace_touches:
address = touch['contract']
if address not in touches_by_contract:
touches_by_contract[address] = []
same_loc = list(filter(lambda t: t['location'] == touch['location'], touches_by_contract[address]))
if not same_loc:
touches_by_contract[address].append({'op': touch['op'], 'location': touch['location']})
else:
# same_loc should always be length 1
if len(same_loc) is not 1:
print("ERROR WITH SAME_LOC!")
break
if same_loc[0]['op'] is 'SSTORE' and touch['op'] is SLOAD:
same_loc[0]['op'] = 'SLOAD' # sload overrides sstore
print("touches_by_contract: {}".format(touches_by_contract))
# now get the storage proofs for all touched contracts and storage locations
tx_proofs = getProofsForTouches(touches_by_contract)
print("got proofs for tx: {}".format(tx))
proofs_by_tx[tx] = tx_proofs
# done getting proofs for tx's
print("got proofs for all txs: {}".format(proofs_by_tx))
block_witness_size = 0
for tx in proofs_by_tx:
witness_size = sizeOfTxWitness(proofs_by_tx[tx])
print("tx {} has witness size {}".format(tx, witness_size))
block_witness_size += witness_size
print("total size of block witness: {}".format(block_witness_size))
return block_witness_size
# curl --header "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_getProof","params":["0x5c2c629feefcc07b338e97e39c73d2db33a85548",["0x1fef3744f4fa28ec9914695a58fd34ee7414218a94c6aa27eb874af0218ef0a8","0x8f3901471e122af74b0ac91231e0147b7f7f02dd0c8e90e40afde2c74be23f47"],"0x6b2188"],"id":1}' http://localhost:8545
# start at block 7021700
# do getBlock, with full transaction objects
# for each transaction, do eth_getCode at the block num, on the `to` address.
# if `to` address has code, then do debug_traceTransaction to get list of storage locations
# sort list of storage locations by contract address
# for each accessed contract, do eth_getProof on all the touched storage locations
# bundle the results of eth_getProof storage proofs
def getWitnessSizeForBlock(block_num):
payload = {
'method': 'eth_getBlockByNumber',
'params': [hex(block_num), True],
'id': 1
}
response = requests.post("http://localhost:8545", data=json.dumps(payload), headers=head)
#print(response.text)
r = response.json()
block = r['result']
print("gasUsed: {}".format(block['gasUsed']))
print("tx count: {}".format(len(block['transactions'])))
txs_to_trace = []
for idx, tx in enumerate(block['transactions']):
if tx['to'] is None:
print("deploy tx in block!: {}".format(tx))
txs_to_trace.append(tx['hash'])
continue
payload = {
'method': 'eth_getCode',
'params': [tx['to'], hex(block_num)],
'id': 1
}
print("getting code: {}".format(payload))
response = requests.post("http://localhost:8545", data=json.dumps(payload), headers=head)
if 'result' not in response.json():
print("bad result!: {}".format(response.json()))
print("tx: ".format(tx))
break
code = response.json()['result']
print("tx {} has code: {}".format(idx, code))
if len(code) > 2:
txs_to_trace.append(tx['hash'])
print("txs_to_trace: {}".format(txs_to_trace))
print("number of tx's to trace: {}".format(len(txs_to_trace)))
block_witness_size = traceBlockAndGetWitnessSize(txs_to_trace)
print("got block_witness_size: {}".format(block_witness_size))
return block_witness_size
START_BLOCK = 7021700
block_sizes = {}
for i in range(START_BLOCK, START_BLOCK+10):
print("doing block {}...".format(i))
size = getWitnessSizeForBlock(i)
print("got size for block #{}: {}".format(i, size))
block_sizes[i] = size
print("got some block witness sizes:")
for i in block_sizes:
print(" block #{}: {} bytes".format(i, block_sizes[i]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment