Create a Blockchain Application in Go: A Beginner's Guide

If you're looking to get started in blockchain development, Go is a great choice.

Create a Blockchain Application in Go: A Beginner's Guide
Freepik
Go from beginner to blockchain developer in no time!

Introduction

In this article, we'll show you how to create a simple blockchain application using Golang. We will also be discussing the concept of a blockchain and how it can be used to store data in a secure and tamper-proof manner.

Pre-requisites

In order to follow along with this article, you will need to have a basic understanding of Go programming. In addition, you should be familiar with the concept of a blockchain.

What is a Blockchain?

A blockchain is a distributed database that is used to store data in a secure and tamper-proof manner. A blockchain is composed of a series of blocks, each of which contains a cryptographic hash of the previous block, a timestamp, and data.

Blockchains are used to store data in a distributed manner so that no single entity can control or tamper with the data. This makes blockchains ideal for storing data that needs to be secure, such as financial transactions.

Creating a Blockchain in Go

We will be using the Go programming language to create our blockchain. Go is a great choice for creating a blockchain because it is a statically typed language that is easy to read and write.

We will start by creating a file called main.go. We will then import the following packages:

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

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

We will then create a struct called Block to represent a block in our blockchain. This struct will contain the following fields:

// Block represents a block in the blockchain.
type Block struct {
	Index     int
	Timestamp string
	BPM       int
	Hash      string
	PrevHash  string
}
  • The Index field will be used to store the index of the block in the blockchain.
  • The Timestamp field will be used to store the timestamp of when the block was created.
  • The BPM field will be used to store the heart rate in beats per minute.
  • The Hash field will be used to store the cryptographic hash of the block.
  • The PrevHash field will be used to store the cryptographic hash of the previous block in the blockchain.

We will then create a global variable called Blockchain to store our blockchain. Blockchain will be a slice of blocks.

// Blockchain is a slice of blocks.
var Blockchain []Block

Next, we will create a struct to represent the message that we will be sending to our blockchain. This struct will contain the following field:

  • BPM

The BPM field will be used to store the heart rate in beats per minute.

// Message takes incoming JSON payload for writing heart rate.
type Message struct {
	BPM int
}

We will then create a function called calculateHash. This function will take a block as a parameter and will return the cryptographic hash of the block. The calculateHash function will first create a string by concatenating the Index, Timestamp, BPM, and PrevHash fields. It will then compute the SHA-256 hash of this string and return the hex-encoded string.

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)
}

We will then create a function called generateBlock. This function will take an old block and a BPM as parameters. It will then create a new block by setting the Index, Timestamp, BPM, Hash, and PrevHash fields. The Hash field will be set by calling the calculateHash function. The Timestamp field will be set by using the time package.

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
}

We will then create a function called isBlockValid. This function will take a new block and an old block as parameters. It will then check if the new block is valid by comparing the Index, PrevHash, and Hash fields.

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
}

We will then create a function called replaceChain. This function will take a new blockchain as a parameter. It will then check if the new blockchain is longer than the current blockchain. If it is, it will replace the current blockchain with the new blockchain.

func replaceChain(newBlocks []Block) {
	if len(newBlocks) > len(Blockchain) {
		Blockchain = newBlocks
	}
}

We will then create a function called handleGetBlockchain. This function will be used to handle HTTP GET requests to the / endpoint. It will return the current blockchain as a JSON string.

func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
	bytes, err := json.MarshalIndent(Blockchain, "", "  ")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	io.WriteString(w, string(bytes))
}

We will then create a function called handleWriteBlock. This function will be used to handle HTTP POST requests to the / endpoint. It will decode the request body as a JSON object and extract the BPM field. It will then create a new block by calling the generateBlock function. If the new block is valid, it will be added to the blockchain.

func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
	var m Message

	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&m); err != nil {
		respondWithJSON(w, r, http.StatusBadRequest, r.Body)
		return
	}
	defer r.Body.Close()

	newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
	if err != nil {
		respondWithJSON(w, r, http.StatusInternalServerError, m)
		return
	}
	if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
		newBlockchain := append(Blockchain, newBlock)
		replaceChain(newBlockchain)
		spew.Dump(Blockchain)
	}

	respondWithJSON(w, r, http.StatusCreated, newBlock)

}

We will then create a function called respondWithJSON. This function will take a http.ResponseWriter, http.Request, int, and interface as parameters. It will then marshal the interface into a JSON string and write it to the http.ResponseWriter.

func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
	response, err := json.MarshalIndent(payload, "", "  ")
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("HTTP 500: Internal Server Error"))
		return
	}
	w.WriteHeader(code)
	w.Write(response)
}

Now, let’s create themain function. In this function, we will first create a genesis block by setting the Index, Timestamp, BPM, Hash, and PrevHash fields. We will then append the genesis block to the blockchain.

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

	go func() {
		t := time.Now()
		genesisBlock := Block{0, t.String(), 0, "", ""}
		spew.Dump(genesisBlock)
		Blockchain = append(Blockchain, genesisBlock)
	}()
	log.Fatal(run())

}

Creating a Router for the HTTP Server

We will create the function run that starts the HTTP server and listens for incoming HTTP requests on the port set in the ADDR environment variable. When it receives a request, it passes it to the router we created in the makeMuxRouter function.

We then create an http.Server and set the Addr, Handler, ReadTimeout, WriteTimeout, and MaxHeaderBytes fields. We will then call the ListenAndServe method to start the server.

func run() error {
	mux := makeMuxRouter()
	httpAddr := os.Getenv("ADDR")
	log.Println("Listening on ", os.Getenv("ADDR"))
	s := &http.Server{
		Addr:           ":" + httpAddr,
		Handler:        mux,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	if err := s.ListenAndServe(); err != nil {
		return err
	}

	return nil
}

Finally, we create the function makeMuxRouter which creates a new router and sets the route handlers for the HTTP server. The route handler for the root route "/" is set to handleGetBlockchain, which is the function that responds to HTTP GET requests sent to the server.

The route handler for the write route "/" is set to handleWriteBlock, which is the function that responds to HTTP POST requests sent to the server.

func makeMuxRouter() http.Handler {
	muxRouter := mux.NewRouter()
	muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
	muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
	return muxRouter
}

The full code and instructions on how to run the program are available on my GitHub repository. Please make sure to give it a star!

Conclusion

In this article we discussed the Go programming language and how it can be used to create a blockchain. We also discussed the concept of a blockchain and how it can be used to store data in a secure and tamper-proof manner.


Subscribe to my weekly newsletter for the latest insights on Golang, DevOps, Cloud-Native, Linux, Kubernetes, Networking, Security, and more.

Follow me on Twitter!


Additional Resources and References: