Programming - Implementing a Blockchain using Python and Flask [Dev Blog 11]

in Programming & Dev3 years ago

[Image 1]

Introduction

Hey it's a me again drifter1!

Here we are in the "finished" version of the master's task. Last time I promised that I would get you through a complete Example run. So, let's do this today, and as I already said, changes to this project will come out more rarely from now on.

And before I forget to mention it! I thought of live-streaming my coding sessions on Twitch (or something similar in the crypto-space), so tell me If you would be interested!

So, without further ado, let's get more in-depth!


GitHub Repository


Start a DNS Server

Any network needs a way in. In the case of my blockchain implementation this is done using the DNS server, which is basically a full node directory. The DNS Server periodically contacts its known nodes and so it always up-to-date to which nodes are active / online. When a node wants to connect to the network (initial network connection) it contacts the DNS in order to retrieve nodes to connect and communicate to.

When inside the /src directory, a DNS server can be started as follows:

python dns_server.py
which leads to using the default settings:
  • ip address : 127.0.0.1
  • port : 42020
  • directory : ../.dns_server

The DNS server's directory looks like this:

where nodes.json is an empty nodes array, or [].


Start some Full Nodes

Next up, we need to start the most essential part of the system, which are the full nodes. At least one such node is needed for the blockchain to be operational. Full nodes store the complete blockchain, and validate transactions and blocks.

A full node (with default settings) can be started using:

python full_node.py
which leads to a random port in the range [50000, 60000] and the directory ../.full_node

Specifying a specific port is as simple as adding -p port as an argument (argparse library). And, of course if we want to run more then one full nodes from the same code folder, the directory also needs to be set. This is also easy to do, using the -d directory argument. For example, let's run 3 more full nodes with directories of the form ../.full_nodeX, where X will be 2, 3 and 4:

python full_node.py -d ../.full_node2
python full_node.py -d ../.full_node3
python full_node.py -d ../.full_node4

This leads to the creation of the following directories:

Letting the system run for a complete node-update cycle (60 seconds), the nodes should then known each other (3 node entries per nodes.json file).

The DNS Server stores all nodes, leading to the following nodes.json file:

[
    {
        "ip_address": "127.0.0.1",
        "port": 59672
    },
    {
        "ip_address": "127.0.0.1",
        "port": 55826
    },
    {
        "ip_address": "127.0.0.1",
        "port": 58470
    },
    {
        "ip_address": "127.0.0.1",
        "port": 52018
    }
]
and in each full node's nodes.json file, the node entry referring to itself will be missing.


Run Testing Script and Miner

The testing script can be used to create a correctly formed transaction. So, let's run it once on an empty chain, in order to generate a wallet only, as no UTXO entries will exist yet:

python testing.py
In my run, this leads to the generation of address 0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b.

Let's use this address as a rewarding address of a miner using the argument -ra address:

python miner.py -ra 0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b

Running the miner and the pressing CTRL+C after a solution has been posted, ledas to the creation of the genesis block:

{
    "timestamp": 1631869461,
    "height": 0,
    "creator": "0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b",
    "reward": 1.5,
    "fees": 0,
    "nonce": "000a373e",
    "transaction_count": 1,
    "transactions": [
        {
            "timestamp": 1631869461,
            "inputs": [
                {
                    "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000",
                    "output_index": 0,
                    "output_address": "0x0000000000000000000000000000000000000000",
                    "output_value": 1.5,
                    "output_hash": "0000000000000000000000000000000000000000000000000000000000000000",
                    "signature": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
                }
            ],
            "outputs": [
                {
                    "index": 0,
                    "address": "0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b",
                    "value": 1.5,
                    "hash": "ccd59985c83d510049f860262a55f5a2cbbaef0e60053b50b113c872f00bc9b4"
                }
            ],
            "value": 1.5,
            "fee": 0,
            "hash": "3ce4a56abbb99938f75877deb998f3730281a5b1b16b92e246c43d561c768d8f"
        }
    ],
    "hash": "00000e338ba6ff951a127a68d64b030bc1995636a1d5066c52e6bf646cf97226",
    "prev_hash": "0000000000000000000000000000000000000000000000000000000000000000"
}
which is shared amongst all full nodes.

The coinbase transaction's output leads to the first UTXO entry:

{
    "utxo_outputs": [
        {
            "block_height": 0,
            "transaction_hash": "3ce4a56abbb99938f75877deb998f3730281a5b1b16b92e246c43d561c768d8f",
            "transaction_index": 0,
            "output_index": 0
        }
    ],
    "balance": 1.5
}

And the Blockchain Info structure, was also correctly updated:

{
    "name": "test_blockchain",
    "height": 0,
    "total_transactions": 1,
    "total_addresses": 1,
    "block_time": 300,
    "block_reward": 1.5,
    "rich_list": [
        {
            "address": "0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b",
            "balance": 1.5
        }
    ]
}


Post Transaction using Testing Script

Running the testing script again, now that an UTXO entry exists, will leads to the creation of a valid transaction, which will be posted to the complete network.

The transaction sends 0.3 and 0.2 to two dummy recipient addresses, and the remainder back to the origin address, of course minus some fee. The input has been signed using the correct private key, which corresponds to the address referenced in the output (public key can be retrieved from the hash-signature pair, and hashing it and trimming it to 20 bytes + "0x" prefix leads to the address).

The JSON transaction looks as follows:

{
    "timestamp": 1631869702,
    "inputs": [
        {
            "transaction_hash": "3ce4a56abbb99938f75877deb998f3730281a5b1b16b92e246c43d561c768d8f",
            "output_index": 0,
            "output_address": "0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b",
            "output_value": 1.5,
            "output_hash": "ccd59985c83d510049f860262a55f5a2cbbaef0e60053b50b113c872f00bc9b4",
            "signature": "fbb25fd615e8bb16e1686daf10a9b011b5f0685b7a551cab5f10eb498523811aaca3240ee3f973be83cdfb637e607e795a410b4b5c818c9abae3e5f30f2ff12b"
        }
    ],
    "outputs": [
        {
            "index": 0,
            "address": "0x60a192daca0804e113d6e6d41852c611be5de0bf",
            "value": 0.3,
            "hash": "fbd6925c0bd8d12964fab3ce2a497395eb2e3941c8db00d75ff351d3d9705280"
        },
        {
            "index": 1,
            "address": "0xb24e0a3dbf9095d62418a86462792855aa24ba16",
            "value": 0.2,
            "hash": "0000000000000000000000000000000000000000000000000000000000000000"
        },
        {
            "index": 2,
            "address": "0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b",
            "value": 0.999,
            "hash": "0000000000000000000000000000000000000000000000000000000000000000"
        }
    ],
    "value": 1.499,
    "fee": 0.001,
    "hash": "5efd164880e72609b4d5e879daf640ce4b3130de2307c6c0c220d5bbcecb39b2"
}


Run Miner again

Running the miner again, will now lead to the creation of Block 1, which will contain this transaction:

{
    "timestamp": 1631870110,
    "height": 1,
    "creator": "0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b",
    "reward": 1.5,
    "fees": 0.001,
    "nonce": "00127eb7",
    "transaction_count": 2,
    "transactions": [
        {
            "timestamp": 1631870110,
            "inputs": [
                {
                    "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000",
                    "output_index": 0,
                    "output_address": "0x0000000000000000000000000000000000000000",
                    "output_value": 1.501,
                    "output_hash": "0000000000000000000000000000000000000000000000000000000000000000",
                    "signature": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
                }
            ],
            "outputs": [
                {
                    "index": 0,
                    "address": "0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b",
                    "value": 1.501,
                    "hash": "36d48623bc158d6fb0fe00217b8155b54ff354ac7e8daecf7a4a723c63b18997"
                }
            ],
            "value": 1.501,
            "fee": 0,
            "hash": "b23350365f61f5d96c0f063d31dfe1acac8587145e43714fad91e07de9ccc376"
        },
        {
            "timestamp": 1631869702,
            "inputs": [
                {
                    "transaction_hash": "3ce4a56abbb99938f75877deb998f3730281a5b1b16b92e246c43d561c768d8f",
                    "output_index": 0,
                    "output_address": "0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b",
                    "output_value": 1.5,
                    "output_hash": "ccd59985c83d510049f860262a55f5a2cbbaef0e60053b50b113c872f00bc9b4",
                    "signature": "fbb25fd615e8bb16e1686daf10a9b011b5f0685b7a551cab5f10eb498523811aaca3240ee3f973be83cdfb637e607e795a410b4b5c818c9abae3e5f30f2ff12b"
                }
            ],
            "outputs": [
                {
                    "index": 0,
                    "address": "0x60a192daca0804e113d6e6d41852c611be5de0bf",
                    "value": 0.3,
                    "hash": "fbd6925c0bd8d12964fab3ce2a497395eb2e3941c8db00d75ff351d3d9705280"
                },
                {
                    "index": 1,
                    "address": "0xb24e0a3dbf9095d62418a86462792855aa24ba16",
                    "value": 0.2,
                    "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                },
                {
                    "index": 2,
                    "address": "0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b",
                    "value": 0.999,
                    "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                }
            ],
            "value": 1.499,
            "fee": 0.001,
            "hash": "5efd164880e72609b4d5e879daf640ce4b3130de2307c6c0c220d5bbcecb39b2"
        }
    ],
    "hash": "0000031dc84fdab9e0f6b09fe6fa2a9926dca6cfd7f0fa86ced6148b05ef2d16",
    "prev_hash": "00000e338ba6ff951a127a68d64b030bc1995636a1d5066c52e6bf646cf97226"
}

And of course, new UTXO's, which correspond to the addresses referenced in the Outputs:

For example, the complete UTXO for the testing script's / miner's address is now:
{
    "utxo_outputs": [
        {
            "block_height": 1,
            "transaction_hash": "b23350365f61f5d96c0f063d31dfe1acac8587145e43714fad91e07de9ccc376",
            "transaction_index": 0,
            "output_index": 0
        },
        {
            "block_height": 1,
            "transaction_hash": "5efd164880e72609b4d5e879daf640ce4b3130de2307c6c0c220d5bbcecb39b2",
            "transaction_index": 1,
            "output_index": 2
        }
    ],
    "balance": 2.5
}

Run more Miners

Let's now run 3 miners, with reward addresses, the addresses for which UTXO's now exist, and different directories:

python miner.py -ra 0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b
python miner.py -ra 0x60a192daca0804e113d6e6d41852c611be5de0bf -d ../.miner2
python miner.py -ra 0xb24e0a3dbf9095d62418a86462792855aa24ba16 -d ../.miner3

Letting them run up to block 10, led to the following Blockchain Info:

{
    "name": "test_blockchain",
    "height": 10,
    "total_transactions": 12,
    "total_addresses": 3,
    "block_time": 300,
    "block_reward": 1.5,
    "rich_list": [
        {
            "address": "0x9adc3034a5afa876721fab76ca2e1e1c0c6ce76b",
            "balance": 8.5
        },
        {
            "address": "0x60a192daca0804e113d6e6d41852c611be5de0bf",
            "balance": 4.8
        },
        {
            "address": "0xb24e0a3dbf9095d62418a86462792855aa24ba16",
            "balance": 3.2
        }
    ]
}


Run another Full Node

Let's now run another node, the fifth, and let it synchronize with the network:

python full_node.py -d ../.full_node5

After synchronizing, it ends up with the same exact data, as the other full nodes:

Here we could now continue on with stopping some full node(s), running the miners again, running the full node(s) again. They would again synchronize correctly...

Interesting right! We shall see, how we can extend the system even more, maybe in Twitch streams, as I already mentioned earlier! :)


RESOURCES:

References

  1. https://www.python.org/
  2. https://flask.palletsprojects.com/en/2.0.x/
  3. https://docs.python-requests.org/
  4. https://pypi.org/project/mnemonic/0.20/
  5. https://pypi.org/project/ecdsa/
  6. https://docs.microsoft.com/en-us/windows/wsl/install-win10
  7. https://www.docker.com/
  8. https://code.visualstudio.com/
  9. https://insomnia.rest/

Images

  1. https://pixabay.com/illustrations/blockchain-block-chain-technology-3019121/
The rest is screenshots or made using draw.io

Previous dev blogs of the series


Final words | Next up

And this is actually it for today's post!

From now on, this series will be put into slow-mode, which means posting (maybe even streaming) monthly at most!

Keep on drifting!

Sort:  

Can you please tell me what is still missing here to be like production ready or it is ready?

Hey @bajwolf,

This is a simple, educational implementation, and not meant to be used as an actual blockchain system, but to understand how such a system operates. Of course, it's not a simulator, as the individual parts such as nodes, miners and the dns server (or node index) communicate with each other. What's missing is basically more testing and optimizations. I don't think that it would work in a real-life scenario...as is.

Sincerely,
@drifter1

OMG, this is so amazing! I always wanted to know how people could create their own blockchain, and in Python!! It' so cool
Thanks for the post


Glad that you enjoy this series the same way I did! 😃
I have lots of ideas, and I also want to get into other types of content (such as videos, streams, etc.). So, feedback really helps...it keeps me going!Hey @luis96xd,

Best Regards,
@drifter1

Making videos would be so cool!

Hello again,
I'm currently thinking of:

  • a video tutorial series on implementing a CPU, which basically means covering Computer Architecture and Microprocessors, but from a "developer" point of view. So, not only theory, but practice as well
  • maybe small livestreams on various "extensions" that I make for the blockchain project, and
  • a fun little "Logic Design using Minecraft Redstone" series is also on my mind

Do you maybe have some specific suggestions for content? 😃

Sincerely,
@drifter1

Wow, this topics you mentioned sound so interesting! and advanced :O

What if you make a video about creating a circuit simulator?
Or some Artificial Intelligence topic
What about using an API related to Blockchain or Web3 Apps :D