Part 2: Networking – Developing your own blockchain code in less than 200 lines

Developing your own blockchain code in less than 200 lines

We were stunned at the reception we received in our first “Code your own blockchain in less than 200 lines of Go!” post. What was meant to be a little tutorial for beginning blockchain developers has taken on a life of its own. We’ve been bombarded with requests to do an add-on post where we integrate networking.

The last post showed you how to code up your own blockchain, complete with hashing and validation of each new block. But it all ran in a single terminal (node). How do we get another node to connect to our main instance and have them contribute new blocks, and how do we broadcast the updated blockchain to all the other nodes?

We’ll show you here.

Workflow

  • The first terminal creates a genesis block and a TCP server to which new nodes can connect

Step 1

  • Additional terminals are opened and TCP connections are made to the first terminal
  • New terminals write a block to the first terminal

Step 2

  • The first terminal validates the block
  • The first terminal broadcasts the new blockchain to each of the new nodes

Step 3

  • All terminals now have synced blockchains!

After the tutorial, try this yourself: have each of the new terminals also act as “First” terminals with different TCP ports and have the other terminals connect to it for a true network!

What you will be able to do

  • Run a terminal that provides a genesis block
  • Fire up as many additional terminals as you like and have them write blocks to the first terminal
  • Have the first terminal broadcast updated blocks to the new terminals

What you won’t be able to do

Like the last post, the aim of this tutorial is to get a basic network of nodes working so you can decide on where your blockchain journey goes from here. You won’t be able to have computers in other networks writing to your first terminal, but this can be readily achieved by putting your binary in the cloud. Also, blockchain broadcasting will be simulated to each of the nodes. Don’t worry, we’ll explain all of this shortly.

Let’s get coding!

Some of this will be a review of the last post. We’ll be using many of the same block generation, hashing and validation functions. But we won’t be using any of the HTTP functionality since we’ll be viewing results in the console and we’ll be using TCP for networking.

What’s the difference between TCP and HTTP?

We won’t get into much detail here but all you need to know is that TCP is a base protocol that transmits data. HTTP is built on top of TCP to utilize this data transmission on the web and in browsers. When you view a website, you’re using HTTP, which is supported by an underlying data transmission protocol, called TCP.

For this tutorial, we’ll be working with TCP as we won’t be needing to view anything in a browser. Go has a nice net package that provides all the TCP connection functions we need.

Setup, Imports and Review

Some of this will be a review from Part 1. For blockchain generation and validation, we’ll be using the same functions from the last post. Bear with us while we do a quick review. Don’t worry, we’ll get to the new stuff soon!

Setup

Create a .env file in your root directory and add the following line:

ADDR=9000

We store the TCP port number we want to use (in this case 9000) in an environment variable called ADDR.

If you haven’t done it already, grab the following packages:

go get github.com/davecgh/go-spew/spew to pretty print our blockchain to the console

go get github.com/joho/godotenv to load the variables in our .env file

Create an empty main.go file. We’ll be putting all our code here.

Imports

Let’s do our standard package declaration and write up the imports we’ll need.


package main

import (
	"bufio"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"io"
	"log"
	"net"
	"os"
	"strconv"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/joho/godotenv"
)

Review

The following code snippets are explained in much greater detail in Part 1. Refer to it if you need a refresher. We’ll be moving fast through this.

Let’s create our Block struct and declare our Blockchain as a slice of Block

// Block represents each 'item' in the blockchain
type Block struct {
	Index     int
	Timestamp string
	BPM       int
	Hash      string
	PrevHash  string
}

// Blockchain is a series of validated Blocks
var Blockchain []Block

Now create our hashing function that we need when generating new blocks.

// SHA256 hashing
func calculateHash(block Block) string {
	record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
	h := sha256.New()
	h.Write([]byte(record))
	hashed := h.Sum(nil)
	return hex.EncodeToString(hashed)
}

Our block generation function:

// create a new block using previous block's hash
func generateBlock(oldBlock Block, BPM int) (Block, error) {

	var newBlock Block

	t := time.Now()

	newBlock.Index = oldBlock.Index + 1
	newBlock.Timestamp = t.String()
	newBlock.BPM = BPM
	newBlock.PrevHash = oldBlock.Hash
	newBlock.Hash = calculateHash(newBlock)

	return newBlock, nil
}

Make sure our new block is valid by checking its PrevHashagainst the Hash of the previous block.

// make sure block is valid by checking index, and comparing the hash of the previous block
func isBlockValid(newBlock, oldBlock Block) bool {
	if oldBlock.Index+1 != newBlock.Index {
		return false
	}

	if oldBlock.Hash != newBlock.PrevHash {
		return false
	}

	if calculateHash(newBlock) != newBlock.Hash {
		return false
	}

	return true
}

Now we ensure we take the longest chain as the true blockchain:

// make sure the chain we're checking is longer than the current blockchain
func replaceChain(newBlocks []Block) {
	if len(newBlocks) > len(Blockchain) {
		Blockchain = newBlocks
	}
}

Nice! We’ve basically got all the blockchain related functions we need with all the HTTP related stuff from Part 1 stripped out. We can now proceed to networking.

Networking

Finally! We’re at the good stuff. Let’s set up a network that can pass around new blocks, integrate them into our blockchain, and broadcast new blockchains back to the network.

Let’s start with our main function, as that is a nice abstraction that will help us understand the overall flow.

Right before we do that, let’s declare a global variable called bcServer (short for blockchain server) under our other struct declarations that is a channel that takes in incoming blocks.

// bcServer handles incoming concurrent Blocks
var bcServer chan []Block

Side note: Channels are one of the most popular features of Go and allow for elegant streamlining of data reading/writing and are most often used to prevent data races. They become particularly powerful when used concurrently, as multiple Go routines can write to the same channel. Traditionally in Java and other C-like language, you’d have to open and close the “gates” for incoming data through mutexes. Channels in Go make this much easier, although there are still places for mutexes in Go. Learn more about channels here.

Now let’s declare our main function and load up the environment variable from our .env file that sits in our root directory. Remember, the only environment variable is ADDR which is our TCP port number 9000 which we’ll use in a second. Also, let’s instantiate our bcServer in the main function.

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}

	bcServer = make(chan []Block)

	// create genesis block
	t := time.Now()
	genesisBlock := Block{0, t.String(), 0, "", ""}
	spew.Dump(genesisBlock)
	Blockchain = append(Blockchain, genesisBlock)
}

Now we need to write up our TCP server. Remember, you can think of TCP servers as being similar to HTTP servers but there is no browser component. All data transmission will be done in the console through our terminal. We will be handling multiple connections to our TCP port. Add this to your main function below the last line.

// start TCP and serve TCP server
	server, err := net.Listen("tcp", ":"+os.Getenv("ADDR"))
	if err != nil {
		log.Fatal(err)
	}
	defer server.Close()

This fires up our TCP server at port 9000. It’s important to defer server.Close() so the connection closes cleanly when we no longer need it. Learn more about defer here.

Now we need to create a new connection each time we receive a connection request, and we need to serve it. Add this below the last line.

for {
		conn, err := server.Accept()
		if err != nil {
			log.Fatal(err)
		}
		go handleConn(conn)
	}

We just made an infinite loop where we accept new connections. We want to concurrently deal with each connection through a separate handler in a Go routine go handleConn(conn), so we don’t clog up our for loop. This is how we can serve multiple connections concurrently.

The perceptive reader will jump up and say, “hey wait! We don’t have a handleConn function!”. You’re right. But let’s take a quick breather. Nice job, we just wrote up our entire main function. It looks like this:

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}

	bcServer = make(chan []Block)

	// create genesis block
	t := time.Now()
	genesisBlock := Block{0, t.String(), 0, "", ""}
	spew.Dump(genesisBlock)
	Blockchain = append(Blockchain, genesisBlock)

	// start TCP and serve TCP server
	server, err := net.Listen("tcp", ":"+os.Getenv("ADDR"))
	if err != nil {
		log.Fatal(err)
	}
	defer server.Close()

	for {
		conn, err := server.Accept()
		if err != nil {
			log.Fatal(err)
		}
		go handleConn(conn)
	}

}

Let’s write up that handleConn function now. It only takes in one argument, a nicely packaged net.Conn interface. Interfaces in Go are amazing and in our opinion, what separates Go from every other C-based language. Concurrency and Go routines get all the hype but interfaces and the fact that they’re satisfied implicitly are the language’s most powerful feature. If you don’t use interfaces in Go yet, get familiar with them as quickly as you can. Interfaces are your next step in your journey to becoming a 10x Go developer!

Put in the skeleton of the function and start it off with a clean defer statement to close each connection when we’re done with it.

func handleConn(conn net.Conn) {
	defer conn.Close()
}

Now we need to allow the client to add new blocks for us to integrate into the blockchain. We’re going to use the same pulse rate from Part 1. Take your own pulse over a minute and keep that number in your head. That will be our BPM (beats per minute).

To achieve the above we need to:

  • prompt the client to enter their BPM
  • scan the client’s input from stdin
  • create a new block with this data, using the generateBlockisBlockValid, and replaceChain functions we created previously
  • put the new blockchain in the channel we created to be broadcast to the network
  • allow the client to enter a new BPM

Here is the code to do that, in the exact order described above.

io.WriteString(conn, "Enter a new BPM:")

	scanner := bufio.NewScanner(conn)

	// take in BPM from stdin and add it to blockchain after conducting necessary validation
	go func() {
		for scanner.Scan() {
			bpm, err := strconv.Atoi(scanner.Text())
			if err != nil {
				log.Printf("%v not a number: %v", scanner.Text(), err)
				continue
			}
			newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], bpm)
			if err != nil {
				log.Println(err)
				continue
			}
			if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
				newBlockchain := append(Blockchain, newBlock)
				replaceChain(newBlockchain)
			}

			bcServer <- Blockchain
			io.WriteString(conn, "\nEnter a new BPM:")
		}
	}()

We create a new scanner. Thefor scanner.Scan() loop is tucked away in its own Go routine so it can run concurrently and separately from other connections. We do a quick string conversion of our BPM (which will always be an integer, so we check for that). We do our standard block generation and validation checks and create a new blockchain with the new block.

The syntax bcServer <- Blockchain just means we’re throwing our new blockchain into the channel we created. Then we prompt the client to enter a new BPM to create the next block.

Broadcasting

We need to broadcast the new blockchain to all the connections being served by our TCP server. Since we’re coding this up on one computer, we’ll be simulating how data gets transmitted to all clients. Under the last line of code, in the same handleConn function, we need to

  • Marshall our new blockchain into JSON so we can read it nicely
  • write the new blockchain to the consoles of each of our connections
  • set a timer to do this periodically so we’re not getting inundated with blockchain data. This is also what you see in live blockchain networks, where new blockchains are broadcast every X minutes. We’ll use 30 seconds
  • Pretty print the main blockchain to the first terminal, just so we can see what’s going on and ensure blocks being added by different nodes are indeed being integrated into the main blockchain

Here is the code, in this exact order.

// simulate receiving broadcast
	go func() {
		for {
			time.Sleep(30 * time.Second)
			output, err := json.Marshal(Blockchain)
			if err != nil {
				log.Fatal(err)
			}
			io.WriteString(conn, string(output))
		}
	}()

	for _ = range bcServer {
		spew.Dump(Blockchain)
	}

Nice! Our handleConn function is done. In fact, the whole program is done and we’ve kept it to under 200 lines. That wasn’t bad at all was it?

Find out the completed code below

package main

import (
	"bufio"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"io"
	"log"
	"net"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/joho/godotenv"
)

// Block represents each 'item' in the blockchain
type Block struct {
	Index     int
	Timestamp string
	BPM       int
	Hash      string
	PrevHash  string
}

// Blockchain is a series of validated Blocks
var Blockchain []Block

// bcServer handles incoming concurrent Blocks
var bcServer chan []Block
var mutex = &sync.Mutex{}

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}

	bcServer = make(chan []Block)

	// create genesis block
	t := time.Now()
	genesisBlock := Block{0, t.String(), 0, "", ""}
	spew.Dump(genesisBlock)
	Blockchain = append(Blockchain, genesisBlock)

	httpPort := os.Getenv("PORT")

	// start TCP and serve TCP server
	server, err := net.Listen("tcp", ":"+httpPort)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("HTTP Server Listening on port :", httpPort)
	defer server.Close()

	for {
		conn, err := server.Accept()
		if err != nil {
			log.Fatal(err)
		}
		go handleConn(conn)
	}

}

func handleConn(conn net.Conn) {

	defer conn.Close()

	io.WriteString(conn, "Enter a new BPM:")

	scanner := bufio.NewScanner(conn)

	// take in BPM from stdin and add it to blockchain after conducting necessary validation
	go func() {
		for scanner.Scan() {
			bpm, err := strconv.Atoi(scanner.Text())
			if err != nil {
				log.Printf("%v not a number: %v", scanner.Text(), err)
				continue
			}
			newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], bpm)
			if err != nil {
				log.Println(err)
				continue
			}
			if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
				newBlockchain := append(Blockchain, newBlock)
				replaceChain(newBlockchain)
			}

			bcServer <- Blockchain
			io.WriteString(conn, "\nEnter a new BPM:")
		}
	}()

	// simulate receiving broadcast
	go func() {
		for {
			time.Sleep(30 * time.Second)
			mutex.Lock()
			output, err := json.Marshal(Blockchain)
			if err != nil {
				log.Fatal(err)
			}
			mutex.Unlock()
			io.WriteString(conn, string(output))
		}
	}()

	for _ = range bcServer {
		spew.Dump(Blockchain)
	}

}

// make sure block is valid by checking index, and comparing the hash of the previous block
func isBlockValid(newBlock, oldBlock Block) bool {
	if oldBlock.Index+1 != newBlock.Index {
		return false
	}

	if oldBlock.Hash != newBlock.PrevHash {
		return false
	}

	if calculateHash(newBlock) != newBlock.Hash {
		return false
	}

	return true
}

// make sure the chain we're checking is longer than the current blockchain
func replaceChain(newBlocks []Block) {
	mutex.Lock()
	if len(newBlocks) > len(Blockchain) {
		Blockchain = newBlocks
	}
	mutex.Unlock()
}

// SHA256 hasing
func calculateHash(block Block) string {
	record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
	h := sha256.New()
	h.Write([]byte(record))
	hashed := h.Sum(nil)
	return hex.EncodeToString(hashed)
}

// create a new block using previous block's hash
func generateBlock(oldBlock Block, BPM int) (Block, error) {

	var newBlock Block

	t := time.Now()

	newBlock.Index = oldBlock.Index + 1
	newBlock.Timestamp = t.String()
	newBlock.BPM = BPM
	newBlock.PrevHash = oldBlock.Hash
	newBlock.Hash = calculateHash(newBlock)

	return newBlock, nil
}

The Fun Stuff

Let’s try this baby out! Let’s fire up our application by going to our directory and go run main.go

As expected, we see our genesis block. What’s happening at the same time is that we’ve started a TCP server at port 9000 that can take in multiple connections. So let’s do that.

Open a new terminal window and connect to our TCP server with nc localhost 9000 . We’ll be using different color terminals to make it clear these are different clients. Do this a couple times with a couple terminal sessions to activate multiple clients.

Now type in a BPM to any of the clients. And KAPOW! We see the new block added to the first terminal! Networking in action!

Here’s where it gets REALLY cool. Wait 30 seconds. Go to one of the other clients and you’ll see the new blockchain broadcast to all the clients, even if those clients never inputted a BPM!

Next steps

Congratulations! Not only did you create your own blockchain from the last tutorial, you’ve now added networking ability to it. There are several places you can go from here:

  • To have a bigger network running on your local environment, create multiple directories that hold a copy of the application each with different TCP ports. For each terminal session, serve a TCP port and connect to a different one so you can receive and send data.
  • Connect data being streamed from multiple ports. This is a topic for another tutorial but is done readily.

This is all a blockchain network is. It needs to take in incoming data and also broadcast data externally. Doing both in a single terminal session is effectively what a mining setup is.

  • If you want to try this with you friends, set up the server in the cloud using your favorite hosting provider. Have your friends connect to it and send in data. There are some additional security considerations here. If there is demand we’ll write up a tutorial for this too.

You are well on your way to intimately understanding multiple facets of the blockchain. From here, we’d recommend reading up on consensus algorithms like Proof of Work or Proof of Stake.

Or…you can just wait for us to write a new blog post about it 🙂

Source link