2.2.14 Cycle 14 - Adding Web sockets for dynamic Nodes.

Design

Objectives

Currently the only way a node can contribute to a network is if it either has a static public DNS/IP address that it can be reached at or if it has a direct local connection to a node with such an address. This is a problem because it means that users have to setup some kind of port forwarding, networking tunnel or some other somewhat complicated method just to connect to the network which increases the barrier to entry and is obviously not good.

The solution to this is to introduce web sockets so that "private" nodes without a public address can open a communication tunnel to a public node that can then communicate with it in either direction and therefore continue to use it for the network without having such a high barrier to entry.

Usability Features

  • Internal functionality - The actual logic of message parsing, validation and what to do with that data should be seperated out such that it can be reused by any method of communication (http, websocket, etc)

  • Bidirectional communication - Allows worker nodes to communicate with and get information from the network in realtime without having to publically share networking data.

Key Variables

Variable Name
Use

This object holds all the methods/functions for the web-socket server and is what allows web-socket connections to be generated and used.

This is the object which is sent and received through web-socket connections and allows messages to be standardised.

Pseudocode

The amount of code that this cycle will require is likely to be quite significant so I will be making the pseudocode for this cycle much higher level so it is easier to understand what is going on.

However the below pseudocode still contains all the key functionality of the websockets server.

The Websocket Server

Development

Since the web sockets have two types of connections, servers and clients, some kind of wrapper function will be required to prevent all parts of the code from having to have two methods for both objects and instead simplifying it to being called on the wrapper object and then that object handling how to deal with the client and server respectively.

The other key section of code that needs to be developed is how the web-socket object is then passed around the code to various api endpoint functions, as these api endpoints will need to be able to send their own web-socket messages for functionality such as message forwarding. To do this, I plan to simply pass the object as a "shared" parameter into the api generator so that the endpoints can use it as required, however since I am not completely sure on how V will handle this I will first write a test program which will be shown below in the outcome section.

The reason that I didn't use the shared parameter when dealing with the configuration object earlier on is because when I last looked into this type of parameter the Vweb module didn't fully support it, thus it caused some weird side effects, but Vweb should now support shared parameters to their full extent so it should all work as expected.

Outcome

The test code

This was the basic test code I wrote in order to confirm that shared parameters do in fact work as they are described to and that I can therefore use them to pass the web-socket connections around to the various api endpoints as required.

This code will be ran and demonstrated in test 1 of the testing section further along in this cycle.

The web socket client file

This code handles the ability for multiple client web-socket connections and will be used by private nodes that do not intend to host servers for other clients to connect to and hence need the ability to create and store multiple client connections whilst still being able to create connections easily.

The web socket server file

This file contains all the logic and code required to assemble a web-socket server for use in public nodes, although it may seems as if both the client and server files do very similar things, they are fundamentally different in the fact that the web-socket module built into V does not support creating connections as a server object or receiving them as a client.

This means that public nodes that wish to accept incoming connections must use the server object type whilst private nodes that wish to send out outgoing connections must use the client object type, even if the logic in each is almost identical.

The generic web socket file

This file includes two main sections: one for wrapping functionality over both the client and server objects so the rest of the code doesn't have to worry about handling both object types and can just call any methods it needs to call; and the other for receiving messages then decoded and using them as they would be used by the http side of the project.

The wrapper object

This is the first half of the code and simply helps make it easier for other parts of the code to deal with the web-socket connections as described in the summary of this file.

The generic functions

These functions are for use by both the client and server web-socket objects and are also useful in other parts of the program hence are stored as "generic functions" as they can be used generically.

The commit for this code is available here.

Challenges

The main challenge faced through the development of this cycle was not one of my own code, but instead of the language that I am using's standard library. In particular, the log module that is included within it.

The Problem

The error in particular was due to a the generation of a function to generate a 'logger' (an object that allows for the logging of the program in a log file or terminal output) which upon being fed the value 'nil' (representing nothing) should generate a default logger using the Log object, however in certain circumstances it was instead attempting to generate the logger from a voidptr object - which is just an empty object in C.

This happens because Vlang is built on top of the low level language C, and therefore when it is compiled, the code gets compiled from V to C and then from C to an executable for whatever platform is being targeted.

This is the C code being generated by Vlang.

This is the C code that should've been generated.

The fix

Although completing the actual fix was fairly quick and easy, finding what was broken in the first place was a lot more complicated. This involved writing testing files to ensure it was actually a compiler issue and not just my code, then careful reading of the semi-compiled C files generated by Vlang to find the compiler error, then changing pieces of code in the compiler one line at a time to figure out how to prevent it from happening until eventually - after about 3 days of repetitive bug hunting - I stumbled upon what I needed.

The quick fix for this was relatively simple and only required changing the default value for the logger inside the net.websocket module (which is where the logger module was incorrectly generating code) from nil to a Log object although this didn't actually fix the root problem, it did solve it enough for me to carry on working on my project and fixing the root problem would've been far outside the scope of this project.

After creating this fix and validating that it didn't break anything else, I realised that other developers with less experience in this kind of thing might be having this issue and be getting completely stuck so I decided to share my edited version of the compiler by submitting a 'pull request' to the Github repository. This allows the team that make the Vlang compiler to approve my change and make it part of the official Vlang compiler.

One of the members of the Vlang team then confirmed that the bug I had discovered did in fact exist, validated that my version didn't break anything else, created a test to stop it happening in the future and then merged my changes into the compiler. This means that as a byproduct of this A Level project I have contributed to the compiler of a language used by hundreds of thousands of people!

The code I changed

Here are the two fixes I made, the top image looks like I changed a lot more than I actually did but that's just due to me changing the code comments slightly.

The code I changed (green is my code and red is what it replaced)
The code I changed (green is my code and red is what it replaced)
A screenshot of my commit in the official V git repository.

Testing

Tests

Test
Instructions
What I expect
What actually happens
Pass/Fail

1

Run the test code, navigate to "http://localhost:8000/test"

A number to be displayed which increases by 1 for each page refresh.

as expected

Pass

2

Send a message using web sockets

The message sent to be transmitted and received successfully.

Only works when sending to/from a macOS device (+ possibly Windows)

Fail

3

Send multiple messages for a prolonged period of time (once every 0.5 seconds for 30 seconds).

The messages to continue to be transmitted and validated consistently during the entire time.

Ran successfully for approximately 10 seconds and then crashed.

Fail

Evidence

Test 1

Test 2

Websocket message attempted to be sent from a Linux machine
Websocket message successfully sent between two macOS devices.

Test 3

Sending messages once every 0.5 seconds for a prolonged period of time.

Last updated