Creating a Blockchain: Part 1 - Transport layer

Transport layer


4 min read

Creating a Blockchain: Part 1 - Transport layer

Project setup

ProjectX is the name of our project

We'll set up the whole architecture here and will change it as per requirements. So, As of now, we will create a Makefile in the root directory(ProjectX) for running scripts


    go build -o ./bin/projectx
run: build 
    go test -v ./...

File and directory structure looks like this,

We are focusing on a transport layer, which will handle the node connections in the network

Creating network module

go mod network

Transport layer structure

Creating a Transport Interface with needed properties, which will help to derive further networks
Transport interface contains 4 methods:

  1. Consume: returns RPC channel (for internal use)

  2. Connect: to connect 1 peer to another

  3. SendMessage: message to another peer using RPC channel

  4. Addr: returns address


package network

type NetAddr string

type RPC struct {
    From    NetAddr
    Payload []byte

type Transport interface {
    Consume() <-chan RPC
    Connect(Transport) error
    SendMessage(NetAddr, []byte) error
    Addr() NetAddr

Local transport implementation

Implementing local_transport network,


package network

import (

type LocalTransport struct {
    addr      NetAddr
    consumeCh chan RPC
    lock      sync.RWMutex
    peers     map[NetAddr]*LocalTransport

func NewLocalTransport(addr NetAddr) Transport {
    return &LocalTransport{
        addr:      addr,
        consumeCh: make(chan RPC, 1024),
        peers:     make(map[NetAddr]*LocalTransport),

Defining needed methods for LocalTransport to implement Transport interface

func (t *LocalTransport) Consume() <-chan RPC {
    return t.consumeCh

func (t *LocalTransport) Connect(tr Transport) error {
    defer t.lock.Unlock()
    t.peers[tr.Addr()] = tr.(*LocalTransport)
    return nil

func (t *LocalTransport) SendMessage(to NetAddr, message []byte) error {
    defer t.lock.RUnlock()

    peer, ok := t.peers[to]
    if !ok {
        return fmt.Errorf("%v failed to send message to %v", t.addr, to)

    peer.consumeCh <- RPC{
        From:    t.addr,
        Payload: message,
    return nil

func (t *LocalTransport) Addr() NetAddr {
    return t.addr

Test transport connection

To check, whether it's running or not, we are coding the test file


package network

import (


func TestConnect(t *testing.T) {
    tra := NewLocalTransport("A").(*LocalTransport)
    trb := NewLocalTransport("B").(*LocalTransport)


    assert.Equal(t, tra.peers[trb.addr], trb)
    assert.Equal(t, trb.peers[tra.addr], tra)

func TestSendMessage(t *testing.T) {
    tra := NewLocalTransport("A").(*LocalTransport)
    trb := NewLocalTransport("B").(*LocalTransport)


    message := []byte("hello sid!!")

    assert.Nil(t, tra.SendMessage(trb.addr, message))

    rpc := <-trb.Consume()
    assert.Equal(t, rpc.Payload, message)
    assert.Equal(t, rpc.From, tra.addr)

run it by,

make test

Server and Logs

Creating Server to contain multiple transports and which also handles other server level activities as well.

Server methods:

  1. NewServer: creates server

  2. Start: it starts all server service and initialize all transports by calling initTransport method and logs the all RPC messages

  3. initTransport: It pipelines all transport layer RPC messages to server level channel


package network

import (

type ServerOpts struct {
    Transports []Transport

type Server struct {
    rpcChan  chan RPC
    quitChan chan struct{}

func NewServer(opts ServerOpts) *Server {
    return &Server{
        ServerOpts: opts,
        rpcChan:    make(chan RPC),
        quitChan:   make(chan struct{}, 1),

func (s *Server) Start() {
    ticker := time.NewTicker(5 * time.Second)

    for {
        select {
        case rpc := <-s.rpcChan:
        case <-s.quitChan:
            break free
        case <-ticker.C:
            fmt.Println("Every 5 seconds")

    fmt.Println("Server shut down")

func (s *Server) initTransports() {
    for _, tr := range s.Transports {
        go func(tr Transport) {
            for rpc := range tr.Consume() {
                s.rpcChan <- rpc

Send Message (p2p)

Testing all at once from main package,


package main

import (

func main() {
    trLocal := network.NewLocalTransport("LOCAL")
    trRemote := network.NewLocalTransport("REMOTE")


    go func() {
        for {
            msg := []byte("Hello Local")
            trRemote.SendMessage(trLocal.Addr(), msg)
            time.Sleep(1 * time.Second)

    opts := network.ServerOpts{
        Transports: []network.Transport{trLocal},

    s := network.NewServer(opts)


Expected output: remote transport send message to local transport every 1 second which will be logged by server, also it will log reminder at every 5 seconds

and here we go,

make run

As expected!!!๐Ÿ˜‰

The following blog post will explore the code related to blocks and transactions.โœจ

In this blog series, I'll be sharing code snippets related to blockchain architecture. While the code will be available on my GitHub, I want to highlight that the entire architecture isn't solely my own. I'm learning as I go, drawing inspiration and knowledge from various sources, including a helpful YouTube playlist that has contributed to my learning process.

Did you find this article valuable?

Support Siddharth Patel by becoming a sponsor. Any amount is appreciated!