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

[Image 1]

Introduction

Hey it's a me again drifter1!

This is the second dev blog for the Blockchain implementation. During the previous days, I made some small tweaks in the Node Endpoints, which in turn led to the DNS server and client now having the same exact endpoints. I will also take this opportunity to explain more in-depth how this initial connection actually works!

During this time I also began with the structure and endpoints for storing and sharing the general Blockchain info, such as the height (length) of the chain, total transactions, total addresses etc. Of course, its still in a very early stage. There has also been added a new directory within the client's directory for storing the blocks as individual JSON files. But, better explain some of the stuff now, then forget it later when it becomes more complicated!

So, without further ado, let's get straight into it!


GitHub Repository


Node Endpoint and Callback Tweaks

All right then, the first important change in the code occured as follows.

During some testing I discovered that the dns server (127.0.0.1:42020) was accidentally being added as a node in the nodes.json file of the clients. Fixing this was quite simple. As you might remember, request helper functions have been implemented in order for the code to be easier to read. And, let's also not forget, that the endpoint that was used for the connection check, also took an optional JSON node as an argument. Well, you might get the gist by now. Even though, this request node is optional, the general request for the connection check endpoint always send this information. So, now this has been fixed, by making this "input" completely optional for the connection check function.

This is the change, I'm talking about:

def general_connection_check(target_node: dict, opt_json_node: dict = {}):
    return requests.post("http://" + str(json_destruct_node(target_node)) + "/", json=opt_json_node).json()
As such, the dns server now simply calls general_connection_check(json_node), with json_node being each of the known nodes (nodes in the local nodes.json file).

Well, during that time, I also realised, that there is no need for separate initial connection function, and that its much simpler to just make the get nodes endpoint take in an optional json node argument. That way, the general get nodes function becomes:

def general_retrieve_nodes(target_node: dict, opt_json_node: dict = {}):
    return requests.get("http://" + str(json_destruct_node(target_node)) + "/nodes/", json=opt_json_node).json()
and clients that request the nodes of another client, are also added to the nodes.json file of that client.

There was no need, for making things so complicated! And, as it was already mentioned during the Introduction, this now led to the DNS server and client, having the same endpoints, the Node Endpoints, as I now call them:


Full-on Initial Connection Example

So, let's now get to a full-on network setup example. The following Figure describes the circumstances...

Two clients and one DNS server make up this network. Let's suppose that initially there are only one client (127.0.0.1:55000) and one DNS server (127.0.0.1:42020). Of course, the only one that can begin the whole procedure of "getting-to-know-each-other" is the client. As only the client knows the DNS server address, and makes requests to it, as a last resort.

Whilst, contacting the DNS server, the client will use the general_retrieve_nodes() helper function, and with it pass its JSON node representation. This way, the DNS server will now, in its callback, add that client to its JSON nodes. The local nodes.json file of the DNS server is now:

[
    {
        "ip_address" : "127.0.0.1",
        "port" : 55000
    }
]
The number of loops now doesn't matter, for the client, as that specific client will continue contacting the DNS server, because it has not reached the required known nodes limit, and because it can't add itself to its known nodes!

So, let's now get the second client (127.0.0.1:50000) in the game. That client will also, as a last resort, contact the DNS server. That way, the client will be added to the nodes.json file of the DNS server:

[
    {
        "ip_address" : "127.0.0.1",
        "port" : 55000
    },
    {
        "ip_address" : "127.0.0.1",
        "port" : 50000
    }
]
and also receive back the information that it needs for contacting the other client, during its next loop.

This way, either of the two clients (it doesn't matter who), will in the next loop try contacting the other node, or receive the required information for the other client whilst contacting the DNS server. For example, in this case, client 2 already knowns client 1, but client 1 doesn't know about client 2's existence yet. Client 1 will "get-to-know" client 2, either when client 2 contacts client 1 directly, or as a response from the DNS server.

Executing the code as shown in the Figure, and making REST API requests using Insomnia, after the "equilibrium" is reached, gives us the following outcomes...

Retrieving the nodes from the DNS server:

Retrieving the nodes from the first client (port 55000):

Retrieving the nodes from the second client (port 50000):

As you can see, the DNS server knows both clients, and the other clients know each other!


Draft Blockchain Info

Similar to the Node structure, a draft Blockchain structure with all required function for constructing, destructing and checking if the JSON Blockchain info is valid has been implemented. It's part of the common directory. And so it will be shared amongst all "blockchain-carrying" entities.

For now, the information that has been added is:

  • name (default : "test_blockchain")
  • height (default : -1)
  • total transactions (default : 0)
  • total addresses (default : 0)
  • block time (default : 300)
  • block reward (default : 1.5
Of course there is a blockchain_defaults.json file for loading in those values.

For now, there have also been created two endpoints for accessing this locally stored file within each client's directory:

  • GET ip_address:port/blockchain/ → For retrieving the blockchain info
  • PUT ip_address:por/blockchain/ → For updating/replacing the blockchain info
Those are defined in the blockchain_endpoints.py file.

As such, the Flask endpoints which are needed for the client are now added as follows:

app = Flask(__name__)
node_endpoints(app, settings) blockchain_endpoints(app, settings)


Draft Blocks Directory

Additionally, a new directory within the client's directory has also been created, with the name "blocks". In there the various blocks will be stored as individual blockX.json files. A default/setting has also been added for the folder name and file template, as well as the needed terminal arguments:

parser.add_argument("-bf", "--blocks", default=None, type=str,
    help="blocks foldername (default : blocks)")
parser.add_argument("-bt", "--block_temp", default=None, type=str,
    help="block filename template (default : block)")

Nothing more has yet been implemented!


RESOURCES:

References

  1. https://www.python.org/
  2. https://flask.palletsprojects.com/en/2.0.x/
  3. https://docs.python-requests.org/
  4. https://docs.microsoft.com/en-us/windows/wsl/install-win10
  5. https://www.docker.com/
  6. https://code.visualstudio.com/
  7. 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!

I will keep you posted on my journey! Expect such articles to come out weekly, or even twice a week, until I'm finished!

Keep on drifting!

H2
H3
H4
3 columns
2 columns
1 column
4 Comments