🌉❄️📝/Writing/A minimal Bitcoin-inspired implementation/⛓️Chains
Get Notion free

Chains

In the previous post, we introduced mining and created the genesis block. Let's mine more blocks and 'chain' them together - we call this the blockchain.

Initialization

The genesis block is set as the first block in the blockchain. We also keep a running counter how many blocks have been mined.

genesis_hash, genesis_block = init_genesis_block()

blockchain = genesis_block
block_counter = 1

As before, we use port numbers to represent accounts.

port = 7000

Let's review steps 1 to 4 in mining a new block, introduced previously.

  1. Get the hash of the previous block, and combine that with the timestamp and nonce to create a header.
  1. Hash the header twice.
  1. If the first two bytes are not zero, increment the nonce and try again.
  1. If the first two bytes are zero, create a reward transaction and combine that with the header to create a new block.

In addition, we introduce steps 5 to 6. We run all the steps to mine up to 10 blocks.

  1. Append the new block to the blockchain.
  1. Re-initialize the previous hash, timestamp and nonce values to mine the next block.

For our purposes, the first non-zero previous hash is the hash of the genesis block. We set up the loop to iterate through 1,000 nonce values at a time - we'll see why in the next post. Let's also print out (without delimiters) the block hash every time a new block is mined.

previous_hash = genesis_hash
timestamp = 1634700600
nonce = 0

print(
    f"CREATE block {block_counter - 1}: {binascii.hexlify(genesis_hash).decode()}"
)

while True:
    is_new_block, _, current_hash, header = run_proof_of_work(
        previous_hash, timestamp, nonce, 1000
    )

    # Retry if not solved after 1000 nonce values.
    if not is_new_block:
        nonce += 1000
        continue

    # Create transaction to award block if solved.
    transaction_counter, transactions = init_transactions(port)
    block = init_block(header, transaction_counter, transactions)

    blockchain += block
    block_counter += 1

    print(
        f"CREATE block {block_counter - 1}: {binascii.hexlify(current_hash).decode()}"
    )

    if block_counter >= 10:
        break

    # Reset values for next block header.
    previous_hash = current_hash
    timestamp += 600
    nonce = 0
CREATE block 0: 00001ff58495c3dc2a1aaa69ebca4e9b3e05e63e5a319fb73bcdccbcdbba1e72
CREATE block 1: 000071e6ff5b358e57339b42d45b20acc0f112c218fa435b3ffa8f239b777347
CREATE block 2: 0000e7d62e199111cc9da5227d7029c8e1224a40a2927e312596732835947e7d
CREATE block 3: 00008d85f05207e0db65b0d5c15136ec45ee57a52b9189c8b85e8126435cf6d1
CREATE block 4: 000098989806054e1d3cf0b9a061e7b4492251300dc8d2281424612af7660280
CREATE block 5: 000035736dd26882f64b58d6a95c37ebf849dc8a6d3a4d55083e0c4498440976
CREATE block 6: 0000e136c7f750925dec1f4f6f9d806d1274c90249788042d335de32db69641f
CREATE block 7: 00000953bf124a4c47ea23175c0c0dd0a66bb0841f4428f205032fb7b1274f97
CREATE block 8: 000073bfe1dc76721bcf70e40d2ec5520d969c60170a8d6bc50b09c9a95526ba
CREATE block 9: 000074271e92a82bf58f083d536444f7a12dba90f6432d03de0d573cf0979e38

Each block is 75 bytes i.e. 1 + 69 + 1 + 4, so a blockchain with 10 blocks would be 750 bytes.

print(f"blockchain:\n{prettify(blockchain)}")
blockchain:
4b-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-61-6f-8a-e0-00-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-01-14-a6-01-00-00-1b-58-4b-00-00-00-1f
f5-84-95-c3-dc-2a-1a-aa-69-eb-ca-4e-9b-3e-05-e6
3e-5a-31-9f-b7-3b-cd-cc-bc-db-ba-1e-72-61-6f-8d
38-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-e5
90-01-00-00-1b-58-4b-00-00-00-71-e6-ff-5b-35-8e
57-33-9b-42-d4-5b-20-ac-c0-f1-12-c2-18-fa-43-5b
3f-fa-8f-23-9b-77-73-47-61-6f-8f-90-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-0f-17-01-00-00-1b
58-4b-00-00-00-e7-d6-2e-19-91-11-cc-9d-a5-22-7d
70-29-c8-e1-22-4a-40-a2-92-7e-31-25-96-73-28-35
94-7e-7d-61-6f-91-e8-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-00-01-8c-7a-01-00-00-1b-58-4b-00-00-00
8d-85-f0-52-07-e0-db-65-b0-d5-c1-51-36-ec-45-ee
57-a5-2b-91-89-c8-b8-5e-81-26-43-5c-f6-d1-61-6f
94-40-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
91-ec-01-00-00-1b-58-4b-00-00-00-98-98-98-06-05
4e-1d-3c-f0-b9-a0-61-e7-b4-49-22-51-30-0d-c8-d2
28-14-24-61-2a-f7-66-02-80-61-6f-96-98-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-d3-32-01-00-00
1b-58-4b-00-00-00-35-73-6d-d2-68-82-f6-4b-58-d6
a9-5c-37-eb-f8-49-dc-8a-6d-3a-4d-55-08-3e-0c-44
98-44-09-76-61-6f-98-f0-00-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-e6-1c-01-00-00-1b-58-4b-00-00
00-e1-36-c7-f7-50-92-5d-ec-1f-4f-6f-9d-80-6d-12
74-c9-02-49-78-80-42-d3-35-de-32-db-69-64-1f-61
6f-9b-48-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-03-cd-01-00-00-1b-58-4b-00-00-00-09-53-bf-12
4a-4c-47-ea-23-17-5c-0c-0d-d0-a6-6b-b0-84-1f-44
28-f2-05-03-2f-b7-b1-27-4f-97-61-6f-9d-a0-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-a5-c7-01-00
00-1b-58-4b-00-00-00-73-bf-e1-dc-76-72-1b-cf-70
e4-0d-2e-c5-52-0d-96-9c-60-17-0a-8d-6b-c5-0b-09
c9-a9-55-26-ba-61-6f-9f-f8-00-00-00-00-00-00-00
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
00-00-00-00-00-00-02-3e-18-01-00-00-1b-58

Let's make this clearer by highlighting every other block. Note that each block starts with a byte representing the value 75, or 4b in hex.

Validation

Suppose we have the opposite problem, in which we are given a blockchain and need to check that it is indeed valid. To make iterating through each block easier, let's create a helper function.

from typing import Generator

def iterate_blockchain(blockchain: bytes, byte_index: int = 0) -> Generator:
    """Helper function to simplify iterating through blocks in the blockchain."""
    block_counter = 0

    while True:
        block_size = blockchain[byte_index]
        block = blockchain[byte_index : byte_index + block_size]

        byte_index += block_size
        block_counter += 1

        yield block, block_counter, byte_index
blocks.py

For validation, we check that:

  1. The previous hash attribute in the current header matches the hash of the previous block.
  1. The hash of the current header starts with two zero bytes.
HASH_SIZE: int = 32

def validate_blockchain(blockchain: bytes) -> Tuple[bool, int, Optional[bytes]]:
    """Check that all headers in the blockchain satisfy proof-of-work and indeed form a chain."""
    previous_hash = (0).to_bytes(32, byteorder="big")
    blockchain_size = len(blockchain)

    for block, block_counter, byte_index in iterate_blockchain(
        blockchain, byte_index=0
    ):
        header = block[1 : 1 + HEADER_SIZE]

        if header[1 : 1 + HASH_SIZE] != previous_hash:
            return False, block_counter, None

        guess = hashlib.sha256(hashlib.sha256(header).digest())

        if guess.hexdigest()[:4] != "0000":
            return False, block_counter, None

        previous_hash = guess.digest()

        if byte_index == blockchain_size:
            break

    return True, block_counter, previous_hash
blocks.py

Now let's run our blockchain through the validation function, to confirm the result is as expected.

is_valid, block_counter, latest_hash = validate_blockchain(blockchain)

print(f"is_valid: {is_valid}\n")
print(f"block_counter: {block_counter}\n")
print(f"latest_hash:\n{prettify(latest_hash)}")
is_valid: True

block_counter: 10

latest_hash:
00-00-74-27-1e-92-a8-2b-f5-8f-08-3d-53-64-44-f7
a1-2d-ba-90-f6-43-2d-03-de-0d-57-3c-f0-97-9e-38

In the next section, we'll implement multiple mining nodes that broadcast to each other.