Skip to content

Commit

Permalink
Add Stack and Computation Benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
Bhargavasomu committed Jan 9, 2019
1 parent 000333a commit 139f45b
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 60 deletions.
9 changes: 8 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
version: 2.0

# heavily inspired by:
# heavily inspired by:
# https://raw.githubusercontent.com/pinax/pinax-wiki/6bd2a99ab6f702e300d708532a6d1d9aa638b9f8/.circleci/config.yml

common: &common
Expand Down Expand Up @@ -133,6 +133,12 @@ jobs:
- image: circleci/python:3.6
environment:
TOXENV: py36-benchmark
py36-stack-benchmark:
<<: *common
docker:
- image: circleci/python:3.6
environment:
TOXENV: py36-stack-benchmark
py36-native-blockchain-byzantium:
<<: *common
docker:
Expand Down Expand Up @@ -237,6 +243,7 @@ workflows:
- py36-native-blockchain-transition
- py36-vm
- py36-benchmark
- py36-stack-benchmark
- py36-core
- py36-transactions
- py36-database
Expand Down
4 changes: 2 additions & 2 deletions eth/_utils/blobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
zpad_right,
)
from eth._utils.merkle import (
calc_merkle_root,
get_merkle_root_from_items,
)

from eth.constants import (
Expand All @@ -44,7 +44,7 @@ def iterate_chunks(collation_body: bytes) -> Iterator[Hash32]:
def calc_chunk_root(collation_body: bytes) -> Hash32:
check_body_size(collation_body)
chunks = list(iterate_chunks(collation_body))
return calc_merkle_root(chunks)
return get_merkle_root_from_items(chunks)


def check_body_size(body: bytes) -> bytes:
Expand Down
89 changes: 61 additions & 28 deletions eth/_utils/merkle.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

import math
from typing import (
cast,
Hashable,
Iterable,
NewType,
Sequence,
Union,
)

from cytoolz import (
Expand All @@ -20,8 +20,8 @@
reduce,
take,
)
from eth_hash.auto import (
keccak,
from eth.beacon._utils.hash import (
hash_eth2,
)
from eth_typing import (
Hash32,
Expand All @@ -36,17 +36,23 @@


def get_root(tree: MerkleTree) -> Hash32:
"""Get the root hash of a Merkle tree."""
"""
Get the root hash of a Merkle tree.
"""
return tree[0][0]


def get_branch_indices(node_index: int, depth: int) -> Sequence[int]:
"""Get the indices of all ancestors up until the root for a node with a given depth."""
def get_branch_indices(node_index: int, depth: int) -> Iterable[int]:
"""
Get the indices of all ancestors up until the root for a node with a given depth.
"""
return tuple(take(depth, iterate(lambda index: index // 2, node_index)))


def get_merkle_proof(tree: MerkleTree, item_index: int) -> Sequence[Hash32]:
"""Read off the Merkle proof for an item from a Merkle tree."""
def get_merkle_proof(tree: MerkleTree, item_index: int) -> Iterable[Hash32]:
"""
Read off the Merkle proof for an item from a Merkle tree.
"""
if item_index < 0 or item_index >= len(tree[-1]):
raise ValidationError("Item index out of range")

Expand All @@ -64,16 +70,20 @@ def get_merkle_proof(tree: MerkleTree, item_index: int) -> Sequence[Hash32]:


def _calc_parent_hash(left_node: Hash32, right_node: Hash32) -> Hash32:
"""Calculate the parent hash of a node and its sibling."""
return keccak(left_node + right_node)
"""
Calculate the parent hash of a node and its sibling.
"""
return hash_eth2(left_node + right_node)


def verify_merkle_proof(root: Hash32,
item: Hashable,
item: Union[bytes, bytearray],
item_index: int,
proof: MerkleProof) -> bool:
"""Verify a Merkle proof against a root hash."""
leaf = keccak(item)
"""
Verify a Merkle proof against a root hash.
"""
leaf = hash_eth2(item)
branch_indices = get_branch_indices(item_index, len(proof))
node_orderers = [
identity if branch_index % 2 == 0 else reversed
Expand All @@ -87,28 +97,51 @@ def verify_merkle_proof(root: Hash32,
return proof_root == root


def _hash_layer(layer: Sequence[Hash32]) -> Sequence[Hash32]:
"""Calculate the layer on top of another one."""
return tuple(_calc_parent_hash(left, right) for left, right in partition(2, layer))
def _hash_layer(layer: Sequence[Hash32]) -> Iterable[Hash32]:
"""
Calculate the layer on top of another one.
"""
return tuple(
_calc_parent_hash(left, right)
for left, right in partition(2, layer)
)


def calc_merkle_tree(items: Sequence[Union[bytes, bytearray]]) -> MerkleTree:
"""
Calculate the Merkle tree corresponding to a list of items.
"""
leaves = tuple(hash_eth2(item) for item in items)
return calc_merkle_tree_from_leaves(leaves)

def calc_merkle_tree(items: Sequence[Hashable]) -> MerkleTree:
"""Calculate the Merkle tree corresponding to a list of items."""
if len(items) == 0:
raise ValidationError("No items given")
n_layers = math.log2(len(items)) + 1

def get_merkle_root_from_items(items: Sequence[Union[bytes, bytearray]]) -> Hash32:
"""
Calculate the Merkle root corresponding to a list of items.
"""
return get_root(calc_merkle_tree(items))


def calc_merkle_tree_from_leaves(leaves: Sequence[Hash32]) -> MerkleTree:
if len(leaves) == 0:
raise ValueError("No leaves given")
n_layers = math.log2(len(leaves)) + 1
if not n_layers.is_integer():
raise ValidationError("Item number is not a power of two")
raise ValueError("Number of leaves is not a power of two")
n_layers = int(n_layers)

leaves = tuple(keccak(item) for item in items)
tree = cast(MerkleTree, tuple(take(n_layers, iterate(_hash_layer, leaves)))[::-1])
reversed_tree = tuple(take(n_layers, iterate(_hash_layer, leaves)))
tree = MerkleTree(tuple(reversed(reversed_tree)))

if len(tree[0]) != 1:
raise Exception("Invariant: There must only be one root")

return tree


def calc_merkle_root(items: Sequence[Hashable]) -> Hash32:
"""Calculate the Merkle root corresponding to a list of items."""
return get_root(calc_merkle_tree(items))
def get_merkle_root(leaves: Sequence[Hash32]) -> Hash32:
"""
Return the Merkle root of the given 32-byte hashes.
Note: it has to be a full tree, i.e., `len(values)` is an exact power of 2.
"""
return get_root(calc_merkle_tree_from_leaves(leaves))
6 changes: 5 additions & 1 deletion eth/beacon/_utils/hash.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from typing import (
Union,
)

from eth_typing import Hash32
from eth_hash.auto import keccak


def hash_eth2(data: bytes) -> Hash32:
def hash_eth2(data: Union[bytes, bytearray]) -> Hash32:
"""
Return Keccak-256 hashed result.
Note: it's a placeholder and we aim to migrate to a S[T/N]ARK-friendly hash function in
Expand Down
10 changes: 10 additions & 0 deletions scripts/stack_benchmark/contract_data/test_stack.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pragma solidity >=0.4.23;

contract TestStack {
function doLotsOfPops() public{
uint v = 0;
for (uint i=0; i<100; i++) {
v += 100;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"contracts" :
{
"scripts/stack_benchmark/contract_data/test_stack.sol:TestStack" :
{
"abi" : "[{\"constant\":false,\"inputs\":[],\"name\":\"doLotsOfPops\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
"bin" : "608060405234801561001057600080fd5b5060a78061001f6000396000f3fe6080604052348015600f57600080fd5b50600436106045576000357c010000000000000000000000000000000000000000000000000000000090048063a2b86eb614604a575b600080fd5b60506052565b005b600080905060008090505b60648110156077576064820191508080600101915050605d565b505056fea165627a7a7230582095c50d5af0ee6c9d807acda8903e20ebad2919c44f22bc7508d97b7f35b62d660029"
}
},
"version" : "0.5.2+commit.1df8f40c.Linux.g++"
}
100 changes: 100 additions & 0 deletions scripts/stack_benchmark/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env python

import pathlib

from eth_utils import (
encode_hex,
decode_hex,
)

from web3 import (
Web3
)

from eth.constants import (
CREATE_CONTRACT_ADDRESS
)

from scripts.benchmark._utils.chain_plumbing import (
FUNDED_ADDRESS,
FUNDED_ADDRESS_PRIVATE_KEY,
get_all_chains,
)
from scripts.benchmark._utils.compile import (
get_compiled_contract
)
from scripts.benchmark._utils.tx import (
new_transaction,
)

CONTRACT_FILE = 'scripts/stack_benchmark/contract_data/test_stack.sol'
CONTRACT_NAME = 'TestStack'
W3_TX_DEFAULTS = {'gas': 0, 'gasPrice': 0}
FIRST_TX_GAS_LIMIT = 367724
SECOND_TX_GAS_LIMIT = 62050


def execute_TestStack_contract():
contract_interface = get_compiled_contract(
pathlib.Path(CONTRACT_FILE),
CONTRACT_NAME
)
w3 = Web3()

# Get the chains
chains = tuple(get_all_chains())
chain = chains[0]

# Instantiate the contract
test_stack_contract = w3.eth.contract(
abi=contract_interface['abi'],
bytecode=contract_interface['bin']
)

# Build transaction to deploy the contract
w3_tx1 = test_stack_contract.constructor().buildTransaction(W3_TX_DEFAULTS)

tx = new_transaction(
vm=chain.get_vm(),
private_key=FUNDED_ADDRESS_PRIVATE_KEY,
from_=FUNDED_ADDRESS,
to=CREATE_CONTRACT_ADDRESS,
amount=0,
gas=FIRST_TX_GAS_LIMIT,
data=decode_hex(w3_tx1['data']),
)

block, receipt, computation = chain.apply_transaction(tx)
deployed_contract_address = computation.msg.storage_address
assert computation.is_success

# Interact with the deployed contract by calling the totalSupply() API ?????
test_stack_contract = w3.eth.contract(
address=Web3.toChecksumAddress(encode_hex(deployed_contract_address)),
abi=contract_interface['abi'],
)

# Execute the computation
w3_tx2 = test_stack_contract.functions.doLotsOfPops().buildTransaction(W3_TX_DEFAULTS)

tx = new_transaction(
vm=chain.get_vm(),
private_key=FUNDED_ADDRESS_PRIVATE_KEY,
from_=FUNDED_ADDRESS,
to=deployed_contract_address,
amount=0,
gas=SECOND_TX_GAS_LIMIT,
data=decode_hex(w3_tx2['data']),
)

block, receipt, computation = chain.apply_transaction(tx)
# print(computation._memory._bytes)
print(computation._stack.values)


def main():
execute_TestStack_contract()


if __name__ == '__main__':
main()
Loading

0 comments on commit 139f45b

Please sign in to comment.