Messages part three – Ping Pong and VerAck
VerAck
When establishing connection, we first need to send a version message to the node we wish to connect to. But keeping our connection alive will require the use of 3 more messages: ping, pong and VerAck.
The VerAck message is the most simple type of message, it’s basically an empty message, it has no payload, only a header. Alexis and I have decided that any type of message will have its own file to construct and parse its payload, even if that message doesn’t have any payload. It helps us to maintain the structure of our project.
Bitpy/Packets/control_messages/verack.py:
class EncodeVerack: def __init__(self): self.command_name = "verack" # Verack messages have an empty payload def forge(self): return "" # No need this class because, like GetAddr, there is no payload class DecodeVerack: def __init__(self, payload=""): pass
As you can see, the function forge
will return an empty string and the class DecodeVerAck
won’t do anything.
Pay attention that even though the VerAck payload is empty, this message still needs to have a header. The header will contain 4 bytes for the Magic number (or starting string), 12 bytes for the command name (VerAck), 4 bytes for the size of the payload (which in this case will be zero) and another 4 bytes for the checksum (which in this case will always be the same).
Ping and Pong
One node can always send a ping request to the other. That is how peers on the network can check if the connection is still alive. Ping request is a nothing more than a message, just like any other Bitcoin message, that contains one field:
Size (Bytes) | Name | Data type | Description |
8 | nonce | uint_64 | A random number |
Alice sends Bob a ping message. This ping message will contain 8 bytes of random number. Bob will receive this ping message from Alice, and will acknowledge her request by sending her a pong message, this pong message will contain the same random number that Alice sent to Bob.
Let’s have a look at the code implementation
Bitpy/Packets/control_messages/ping.py:
import random from Utils.dataTypes import * class EncodePing: def __init__(self): self.command_name = "ping" self.nonce = to_uint64(random.getrandbits(64)) def forge(self): return self.nonce class DecodePing: def __init__(self, payload): self.nonce = payload.read(8)
When we want to construct a ping message we’ll use the class EncodePing
which will set the field nonce
to be 8 bytes long random number (64 bit = 8 bytes). When we’ll receive a ping message, we’ll use the class DecodePing
with the payload of the incoming message to find out the nonce
of that ping message (We’re only reading the nonce in the ping message, we haven’t used it yet). We’ll use this nonce
when we’ll construct our returning (pong) message.
Bitpy/Packets/control_messages/pong.py:
from Utils.dataTypes import * class EncodePong: def __init__(self, ping_received): self.command_name = "pong" self.nonce = ping_received def forge(self): return self.nonce class DecodedPong: def __init__(self, pong_received): self.command_name = "pong" self.nonce = read_uint64(pong_received.read(8)) def get_decoded_info(self): return "\npong :\t\t %s" % self.nonce
The class EncodePong
will take the payload of the incoming ping message and will assign it to be the nonce of the pong message (the message that we’ll send back). The DecodedPong
class will receive the payload of the pong and will assign it to the self variable nonce
. We can use the get_decoded_info
function to display this nonce number. Because the pong is just a returning message, there’s nothing else we need to do with it.