Connection part three – Receiving messages
In the previous posts, all that we’ve done was to construct and send messages to another node on the network. In this post, we’ll see what happens to incoming messages.
First stop – The ReceiverManager
:
class ReceiverManager(Thread): def __init__(self, sock): Thread.__init__(self) self.sendingQueue = Utils.globals.sendingQueue self.sock = sock self.ping = "" self.outfile = open("data_received_from_node.txt", 'w') def run(self): while True: try: # get only the header's message header = self.sock.recv(24) if len(header) <= 0: raise Exception("Node disconnected (received 0bit length message)") headerStream = BytesIO(header) parsedHeader = HeaderParser(headerStream) # get the payload payload = self.recvall(parsedHeader.payload_size) payloadStream = BytesIO(payload) self.manager(parsedHeader, payloadStream) except Exception as e: print(e) break print("Exit receiver Thread")
The receivermanager
always runs in the background, checking our Thread
for any incoming packets. Once it receives a packet, it will immediately cut its first 24 bytes.
header = self.sock.recv(24)
The first 24 bytes are the header. If you remember from this post, every Bitcoin message will starts with header, and the header is always exactly 24 bytes long.

This header is now parsed as a string of bytes and passed to the HeaderParser class in Bitpy/Network/HeaderParser.py
headerStream = BytesIO(header) parsedHeader = HeaderParser(headerStream)
Second stop – The HeaderParser
class:
The HeaderParser
class takes the first 24 bytes as a long string of bytes, and then it reads them in the same order that we’ve seen before.
Size (Bytes) | Name | Data type | Description |
4 | Start string | char[4] | The network identifier |
12 | Command name | char[12] | The name of the command. |
4 | Payload size | uint32 | Len(payload) |
4 | Checksum | char[4] | SHA256(SHA256(payload))[:4] |
First 4 bytes for the Start string
(or Magic number), another 12 bytes for Command name
, the next 4 bytes are the Payload size
and the last 4 bytes are the checksum
.

class HeaderParser: def __init__(self, header): # Packets is a stream self.magic = read_hexa(header.read(4)) self.command = header.read(12) self.payload_size = read_uint32(header.read(4)) self.checksum = read_hexa(header.read(4)) self.header_size = 4 + 12 + 4 + 4 def to_string(self): display = "\n-------------HEADER-------------" display += "\nMagic:\t %s" % self.magic display += "\nCommand name :\t %s" % self.command display += "\nPayload size :\t %s" % self.payload_size display += "\nChecksum :\t\t %s" % self.checksum display += "\nheader Size:\t\t %s" % self.header_size display += "\n" return display
We’ve also defined the to_string
function which basically makes it easier to print a human readable version of the message header.
You might’ve noticed that currently our code just accept the checksum
field from the received message without checking it. This is of course a security flaw in our code. The checksum
filed is there to help us verify the authenticity of the message. That is one of the ways we can make sure that no one tempered or changed the message on its way from the sender node to our node. But for the time being we’ll assume that the message is indeed authentic and we’ll accept the checksum
as is.
Third stop – Back to the ReceiverManager
:
Now that we have our header, it’s time to get the payload. The size of the payload was defined in the header of the message. We need to cut that amount of bytes from our incoming packets, just as we cut the first 24 bytes of the header. There’s however one extra step in our code. Instead of using the built in sock.recv
function (as we did for the header) we’ve decided to implement our own recevall
function. The rational was that since we have no way to predetermine the size of the payload, and since the built in sock.recv can’t handle large packets of unknown size, it would be wiser to break the payload into smaller parts and append them together. This has nothing to do with the Bitcoin protocol, it’s only our way to make sure that the code will properly handle large messages.
def recvall(self, length): parts = [] while length > 0: part = self.sock.recv(length) if not part: raise EOFError('socket closed with %d bytes left in this part'.format(length)) length -= len(part) parts.append(part) return b''.join(parts)
So now, after we’ve cut the required amount of bytes that represents the payload of our message, and we have both our header (which was already parsed) and our payload (yet to be parsed), we’ll pass them both to the receivermanager
manager
function.
Forth stop – Manager
:
def manager(self, parsedHeader, payloadStream): command = parsedHeader.command.decode("utf-8") message = {"timestamp": time.time(), "command": command, "header": parsedHeader.to_string(), "payload": ""} if command.startswith('ping'): ping = Ping.DecodePing(payloadStream) pong = Pong.EncodePong(ping.nonce) packet = PacketCreator(pong) self.sendingQueue.put(packet.forge_packet()) message["payload"] = str(ping.nonce) self.display(message) elif command.startswith('inv'): inv = Inv.DecodeInv(payloadStream) message["payload"] = inv.get_decoded_info() self.display(message) elif command.startswith('addr'): addr = Addr.DecodeAddr(payloadStream) message["payload"] = addr.get_decoded_info() self.display(message) elif command.startswith('pong'): pong = Pong.DecodePong(payloadStream) message["payload"] = pong.get_decoded_info() self.display(message) elif command.startswith('version'): version = Version.DecodeVersion(payloadStream) message["payload"] = version.get_decoded_info() self.display(message)
The manager function does a very simple thing. It checks the command of the message (the command is part of the header) and then it sends the message payload to be parsed by the corresponding functions. For example. If the manager sees that the command is «pong», it will use the decodepong
method in Bitpay/Packets/control_messages/pong.py
to extract the desire fields out of it. (You can read more about «pong», «ping» and «verack» messages in this post.).
Divergence
We have our pared message, both its header and payload. And now we need to decide what to do with them. For some messages this might be the end of the line. There’s nothing more we can do with them. Some might require us to act. «ping» message should be answered by a «pong» message, transactions should be checked and relayed (We’ll talk about transactions in later posts), «version» messages should be acknowledged by sending back a «verack» message.
A major part of learning the Bitcoin protocol is learning how each and every message should be dealt with. Which fields of information it contains and what is the meaning of this information. We’ve already talked about some of the messages in previous posts (see here for «ping», «pong» and «verack» messages, and here for «version» message.) and as our project will have more features implemented, so we’ll discuss other type of messages and how to deal with them.