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
Makefile
build:
go build -o ./bin/projectx
run: build
./bin/projectx
test:
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 networksTransport
interface contains 4 methods:
Consume
: returns RPC channel (for internal use)Connect
: to connect 1 peer to anotherSendMessage
: message to another peer using RPC channelAddr
: returns address
network/transport.go
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,
network/local_transport.go
package network
import (
"fmt"
"sync"
)
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 {
t.lock.Lock()
defer t.lock.Unlock()
t.peers[tr.Addr()] = tr.(*LocalTransport)
return nil
}
func (t *LocalTransport) SendMessage(to NetAddr, message []byte) error {
t.lock.RLock()
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
network/local_transport_test.go
package network
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestConnect(t *testing.T) {
tra := NewLocalTransport("A").(*LocalTransport)
trb := NewLocalTransport("B").(*LocalTransport)
tra.Connect(trb)
trb.Connect(tra)
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)
tra.Connect(trb)
trb.Connect(tra)
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:
NewServer
: creates serverStart
: it starts all server service and initialize all transports by callinginitTransport
method and logs the all RPC messagesinitTransport
: It pipelines all transport layer RPC messages to server level channel
network/server.go
package network
import (
"fmt"
"time"
)
type ServerOpts struct {
Transports []Transport
}
type Server struct {
ServerOpts
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() {
s.initTransports()
ticker := time.NewTicker(5 * time.Second)
free:
for {
select {
case rpc := <-s.rpcChan:
fmt.Println(rpc)
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
}
}(tr)
}
}
Send Message (p2p)
Testing all at once from main
package,
main.go
package main
import (
"ProjectX/network"
"fmt"
"time"
)
func main() {
trLocal := network.NewLocalTransport("LOCAL")
trRemote := network.NewLocalTransport("REMOTE")
trLocal.Connect(trRemote)
trRemote.Connect(trLocal)
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)
s.Start()
}
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.