Skip to content

Commit 64853f0

Browse files
authored
Merge pull request #1091 from cburgdorf/christoph/docs/cookbook
Add cookbook, fix examples, doctestify code
2 parents de252b5 + ce8f5a2 commit 64853f0

File tree

5 files changed

+208
-163
lines changed

5 files changed

+208
-163
lines changed

docs/cookbooks/index.rst

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
Cookbooks
2+
=========
3+
4+
The Cookbooks are collections of simple recipes that demonstrate good practices to accomplish
5+
common tasks. The examples are usually short answers to simple "How do I..." questions that go
6+
beyond simple API descriptions but also don't need a full guide to become clear.
7+
8+
.. _evm_cookbook:
9+
10+
EVM Cookbook
11+
~~~~~~~~~~~~
12+
13+
.. _evm_cookbook_recipe_using_the_chain_object:
14+
15+
Using the Chain object
16+
----------------------
17+
18+
A "single" blockchain is made by a series of different virtual machines
19+
for different spans of blocks. For example, the Ethereum mainnet had
20+
one virtual machine for blocks 0 till 1150000 (known as Frontier),
21+
and another VM for blocks 1150000 till 1920000 (known as Homestead).
22+
23+
The :class:`~eth.chains.chain.Chain` object manages the series of fork rules,
24+
after you define the VM ranges. For example, to set up a chain that would track
25+
the mainnet Ethereum network until block 1920000, you could create this chain
26+
class:
27+
28+
.. doctest::
29+
30+
>>> from eth import constants, Chain
31+
>>> from eth.vm.forks.frontier import FrontierVM
32+
>>> from eth.vm.forks.homestead import HomesteadVM
33+
>>> from eth.chains.mainnet import HOMESTEAD_MAINNET_BLOCK
34+
35+
>>> chain_class = Chain.configure(
36+
... __name__='Test Chain',
37+
... vm_configuration=(
38+
... (constants.GENESIS_BLOCK_NUMBER, FrontierVM),
39+
... (HOMESTEAD_MAINNET_BLOCK, HomesteadVM),
40+
... ),
41+
... )
42+
43+
Then to initialize, you can start it up with an in-memory database:
44+
45+
.. doctest::
46+
47+
>>> from eth.db.backends.memory import MemoryDB
48+
>>> from eth.chains.mainnet import MAINNET_GENESIS_HEADER
49+
50+
>>> # start a fresh in-memory db
51+
52+
>>> # initialize a fresh chain
53+
>>> chain = chain_class.from_genesis_header(MemoryDB(), MAINNET_GENESIS_HEADER)
54+
55+
.. _evm_cookbook_recipe_creating_a_chain_with_custom_state:
56+
57+
Creating a chain with custom state
58+
----------------------------------
59+
60+
While the previous recipe demos how to create a chain from an existing genesis header, we can
61+
also create chains simply by specifing various genesis parameter as well as an optional genesis
62+
state.
63+
64+
.. doctest::
65+
66+
>>> from eth_keys import keys
67+
>>> from eth import constants
68+
>>> from eth.chains.mainnet import MainnetChain
69+
>>> from eth.db.backends.memory import MemoryDB
70+
>>> from eth_utils import to_wei, encode_hex
71+
72+
73+
74+
>>> # Giving funds to some address
75+
>>> SOME_ADDRESS = b'\x85\x82\xa2\x89V\xb9%\x93M\x03\xdd\xb4Xu\xe1\x8e\x85\x93\x12\xc1'
76+
>>> GENESIS_STATE = {
77+
... SOME_ADDRESS: {
78+
... "balance": to_wei(10000, 'ether'),
79+
... "nonce": 0,
80+
... "code": b'',
81+
... "storage": {}
82+
... }
83+
... }
84+
85+
>>> GENESIS_PARAMS = {
86+
... 'parent_hash': constants.GENESIS_PARENT_HASH,
87+
... 'uncles_hash': constants.EMPTY_UNCLE_HASH,
88+
... 'coinbase': constants.ZERO_ADDRESS,
89+
... 'transaction_root': constants.BLANK_ROOT_HASH,
90+
... 'receipt_root': constants.BLANK_ROOT_HASH,
91+
... 'difficulty': constants.GENESIS_DIFFICULTY,
92+
... 'block_number': constants.GENESIS_BLOCK_NUMBER,
93+
... 'gas_limit': constants.GENESIS_GAS_LIMIT,
94+
... 'extra_data': constants.GENESIS_EXTRA_DATA,
95+
... 'nonce': constants.GENESIS_NONCE
96+
... }
97+
98+
>>> chain = MainnetChain.from_genesis(MemoryDB(), GENESIS_PARAMS, GENESIS_STATE)
99+
100+
.. _evm_cookbook_recipe_getting_the_balance_from_an_account:
101+
102+
Getting the balance from an account
103+
-----------------------------------
104+
105+
Considering our previous example, we can get the balance of our pre-funded account as follows.
106+
107+
.. doctest::
108+
109+
>>> current_vm = chain.get_vm()
110+
>>> account_db = current_vm.state.account_db
111+
>>> account_db.get_balance(SOME_ADDRESS)
112+
10000000000000000000000
113+
114+
.. _evm_cookbook_recipe_building_blocks_incrementally:
115+
116+
Building blocks incrementally
117+
------------------------------
118+
119+
The default :class:`~eth.chains.chain.Chain` is stateless and thus does not keep a tip block open
120+
that would allow us to incrementally build a block. However, we can import the
121+
:class:`~eth.chains.chain.MiningChain` which does allow exactly that.
122+
123+
.. doctest::
124+
125+
>>> from eth.chains.base import MiningChain
126+
127+
Please check out the :doc:`Understanding the mining process
128+
</guides/eth/understanding_the_mining_process>` guide for a full example that demonstrates how
129+
to use the :class:`~eth.chains.chain.MiningChain`.

docs/guides/eth/building_chains.rst

Lines changed: 0 additions & 88 deletions
This file was deleted.

docs/guides/eth/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,5 @@ This section aims to provide hands-on guides to demonstrate how to use Py-EVM. I
1111
quickstart
1212
building_an_app_that_uses_pyevm
1313
architecture
14-
building_chains
1514
understanding_the_mining_process
1615
creating_opcodes

docs/guides/eth/understanding_the_mining_process.rst

Lines changed: 78 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Understanding the mining process
22
================================
33

4-
In the :doc:`Building Chains Guide <building_chains>` we already learned how to
5-
use the :class:`~eth.chains.base.MiningChain` class to create a single
4+
From the :ref:`EVM Cookbook<evm_cookbook>` we can already learn how to
5+
use the :class:`~eth.chains.base.Chain` class to create a single
66
blockchain as a combination of different virtual machines for different spans
77
of blocks.
88

@@ -38,7 +38,9 @@ block first because, after all, one primary use case for the Ethereum blockchain
3838
For the sake of simplicity though, we'll mine an empty block as a first example (meaning the block
3939
will not contain any transactions)
4040

41-
As a refresher, he's where we left of as part of the :doc:`Building Chains Guide </guides/eth/building_chains>`.
41+
As a refresher, he's how we create a chain as demonstrated in the
42+
:ref:`Using the chain object recipe<evm_cookbook_recipe_using_the_chain_object>` from the
43+
cookbook.
4244

4345
::
4446

@@ -308,74 +310,76 @@ Finally, we can call :func:`~eth.chains.base.MiningChain.apply_transaction` and
308310
What follows is the complete script that demonstrates how to mine a single block with one simple
309311
zero value transfer transaction.
310312

311-
::
312-
313-
from eth_keys import keys
314-
from eth_utils import decode_hex
315-
from eth_typing import Address
316-
317-
from eth.consensus.pow import mine_pow_nonce
318-
from eth import constants, MiningChain
319-
from eth.vm.forks.byzantium import ByzantiumVM
320-
from eth.db.backends.memory import MemoryDB
321-
322-
323-
GENESIS_PARAMS = {
324-
'parent_hash': constants.GENESIS_PARENT_HASH,
325-
'uncles_hash': constants.EMPTY_UNCLE_HASH,
326-
'coinbase': constants.ZERO_ADDRESS,
327-
'transaction_root': constants.BLANK_ROOT_HASH,
328-
'receipt_root': constants.BLANK_ROOT_HASH,
329-
'difficulty': 1,
330-
'block_number': constants.GENESIS_BLOCK_NUMBER,
331-
'gas_limit': constants.GENESIS_GAS_LIMIT,
332-
'timestamp': 1514764800,
333-
'extra_data': constants.GENESIS_EXTRA_DATA,
334-
'nonce': constants.GENESIS_NONCE
335-
}
336-
337-
SENDER_PRIVATE_KEY = keys.PrivateKey(
338-
decode_hex('0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8')
339-
)
340-
341-
SENDER = Address(SENDER_PRIVATE_KEY.public_key.to_canonical_address())
342-
343-
RECEIVER = Address(b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02')
344-
345-
klass = MiningChain.configure(
346-
__name__='TestChain',
347-
vm_configuration=(
348-
(constants.GENESIS_BLOCK_NUMBER, ByzantiumVM),
349-
))
350-
351-
chain = klass.from_genesis(MemoryDB(), GENESIS_PARAMS)
352-
vm = chain.get_vm()
353-
354-
nonce = vm.get_transaction_nonce(SENDER)
355-
356-
tx = vm.create_unsigned_transaction(
357-
nonce=nonce,
358-
gas_price=0,
359-
gas=100000,
360-
to=RECEIVER,
361-
value=0,
362-
data=b'',
363-
)
364-
365-
signed_tx = tx.as_signed_transaction(SENDER_PRIVATE_KEY)
366-
367-
chain.apply_transaction(signed_tx)
368-
369-
# We have to finalize the block first in order to be able read the
370-
# attributes that are important for the PoW algorithm
371-
block = chain.get_vm().finalize_block(chain.get_block())
372-
373-
# based on mining_hash, block number and difficulty we can perform
374-
# the actual Proof of Work (PoW) mechanism to mine the correct
375-
# nonce and mix_hash for this block
376-
nonce, mix_hash = mine_pow_nonce(
377-
block.number,
378-
block.header.mining_hash,
379-
block.header.difficulty)
380-
381-
block = chain.mine_block(mix_hash=mix_hash, nonce=nonce)
313+
.. doctest::
314+
315+
>>> from eth_keys import keys
316+
>>> from eth_utils import decode_hex
317+
>>> from eth_typing import Address
318+
>>> from eth import constants
319+
>>> from eth.chains.base import MiningChain
320+
>>> from eth.consensus.pow import mine_pow_nonce
321+
>>> from eth.vm.forks.byzantium import ByzantiumVM
322+
>>> from eth.db.backends.memory import MemoryDB
323+
324+
325+
>>> GENESIS_PARAMS = {
326+
... 'parent_hash': constants.GENESIS_PARENT_HASH,
327+
... 'uncles_hash': constants.EMPTY_UNCLE_HASH,
328+
... 'coinbase': constants.ZERO_ADDRESS,
329+
... 'transaction_root': constants.BLANK_ROOT_HASH,
330+
... 'receipt_root': constants.BLANK_ROOT_HASH,
331+
... 'difficulty': 1,
332+
... 'block_number': constants.GENESIS_BLOCK_NUMBER,
333+
... 'gas_limit': constants.GENESIS_GAS_LIMIT,
334+
... 'timestamp': 1514764800,
335+
... 'extra_data': constants.GENESIS_EXTRA_DATA,
336+
... 'nonce': constants.GENESIS_NONCE
337+
... }
338+
339+
>>> SENDER_PRIVATE_KEY = keys.PrivateKey(
340+
... decode_hex('0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8')
341+
... )
342+
343+
>>> SENDER = Address(SENDER_PRIVATE_KEY.public_key.to_canonical_address())
344+
345+
>>> RECEIVER = Address(b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02')
346+
347+
>>> klass = MiningChain.configure(
348+
... __name__='TestChain',
349+
... vm_configuration=(
350+
... (constants.GENESIS_BLOCK_NUMBER, ByzantiumVM),
351+
... ))
352+
353+
>>> chain = klass.from_genesis(MemoryDB(), GENESIS_PARAMS)
354+
>>> vm = chain.get_vm()
355+
356+
>>> nonce = vm.state.account_db.get_nonce(SENDER)
357+
358+
>>> tx = vm.create_unsigned_transaction(
359+
... nonce=nonce,
360+
... gas_price=0,
361+
... gas=100000,
362+
... to=RECEIVER,
363+
... value=0,
364+
... data=b'',
365+
... )
366+
367+
>>> signed_tx = tx.as_signed_transaction(SENDER_PRIVATE_KEY)
368+
369+
>>> chain.apply_transaction(signed_tx)
370+
(<ByzantiumBlock(#Block #1...)
371+
>>> # We have to finalize the block first in order to be able read the
372+
>>> # attributes that are important for the PoW algorithm
373+
>>> block = chain.get_vm().finalize_block(chain.get_block())
374+
375+
>>> # based on mining_hash, block number and difficulty we can perform
376+
>>> # the actual Proof of Work (PoW) mechanism to mine the correct
377+
>>> # nonce and mix_hash for this block
378+
>>> nonce, mix_hash = mine_pow_nonce(
379+
... block.number,
380+
... block.header.mining_hash,
381+
... block.header.difficulty
382+
... )
383+
384+
>>> chain.mine_block(mix_hash=mix_hash, nonce=nonce)
385+
<ByzantiumBlock(#Block #1)>

0 commit comments

Comments
 (0)