Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How to Build a Blockchain With Less Than 200 Lines of Go

DZone's Guide to

How to Build a Blockchain With Less Than 200 Lines of Go

Want to learn how to build your own simple blockchain? Check out this tutorial that demonstrates how to create a blockchain with less than 200 lines of Go code.

· Security Zone ·
Free Resource

Protect your applications against today's increasingly sophisticated threat landscape.

Have you wondered if it is possible to code your own blockchain with less than 200 lines of Go? If you find this question intriguing, then, you have come to the right place. What can be more effective in learning and practicing this emerging technology than developing your own blockchain? Let us give it a try!

Before we start, measure your heart rate in terms of beats per minute and record it. We will use this figure later.

Once you have completed this article, you should be able to:

  • Create your own blockchain
  • Understand how a hash function maintains the integrity of the blockchain
  • Create and add a new block
  • Understand how multiple nodes compete to generate a block
  • View the entire chain using a browser
  • Understand other basic features of blockchains

However, consensus algorithms, such as proof-of-work (PoW) and proof-of-stake (PoS), will not be featured in this article. Also, to make the processes of adding a blockchain and a block clearer, we have simplified the network interaction process and left out the "network-wide broadcasting" process and the likes of P2P networks.

Let's get started!

Configuring the Development Environment

This quick guide assumes that you already have some experience in Go programming. After installing and configuring the Go development environment, we need to obtain the following dependencies.

Spew enables us to view struct and slice data structures on the console directly:

go get github.com/davecgh/go-spew/spew


The Gorilla/mux package is very popular. We use it to implement the web handler:

go get github.com/gorilla/mux


Godotenv helps us read the  .env configuration file from the root directory of the project so that we do not need to add HTTP port configurations to the code.

go get github.com/joho/godotenv
ADDR=8080


Next, create a main.go file. Most of our subsequent work will depend on this file. So, let's start programming!

Creating the Chain

Importing Dependencies

Import all dependency packages by using the following statements:

package main

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


Data Model

Next, define a structure. Struct indicates the data model of all the blocks that form the blockchain:

type Block struct {
    Index     int
    Timestamp string
    BPM       int
    Hash      string
    PrevHash  string
}


  • Index indicates the position of the block in the entire chain
  • Timestamp is the timestamp when the block is created
  • Hash is a hash value generated by the block by using SHA256
  •  PrevHash indicates the hash value generated by the previous block using SHA256
  • BPM indicates the number of heart beats per minute, that is, the heart rate. Remember what we mentioned earlier?

Then, let us define another structure to represent the entire chain. The simplest presentation is the slice of a block:

var Blockchain []Block


We use the hash algorithm (SHA256) to determine and maintain the correct sequence of blocks in a chain, which ensures that the  PrevHash value of each block is equal to the hash value of its previous block. In this way, the chain is constructed in the correct block sequence.

Image title

Hash and Block Generation

Why do we need hash? There are two main reasons:

  • Hash uniquely identifies the data without sacrificing the space. You can obtain a hash by calculating the data of the entire block. In our examples, we have calculated the data of the entire block using SHA256 to obtain an almost-unique, fixed-size string.
  • Hash maintains the integrity of the chain. By storing the hash value of the previous block, we can ensure that each block is in a correct sequence in the chain. Any tampering with the data changes the hash value and breaks the chain. For example, in our healthcare field, if a malicious third-party adjusts the price of "life insurance" by changing the BPM of one or more blocks to a value that indicates an unhealthy condition, the entire chain became untrusted.

Let's continue to write a function to calculate the hash value of the given data using SHA256:

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


This calculateHash function accepts one block and calculates a hash value using the SHA256-based Index, Timestamp, BPM, and  PrevHash values of the block. Next, let's write a block generation function:

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
}


You can obtain the index by incrementing the given index value of the previous block and the timestamp by using the time.Now() function. Also, you can calculate the hash by using the calculateHash function.  PrevHash  is the given hash value of the previous block.

Block Validation

After generating a block, we need a function to help us determine whether someone has tampered with the block. View the index value to check whether the block is correctly incremented. Check whether the  PrevHash  value is the same as the hash value of the previous block and, then, run calculateHash to check whether the hash value of the current block is correct. We can write a validation function as follows:

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
}


When validating the block, we encounter a problem: when two nodes generate blocks and add the blocks to their respective chains, which one should we use? We would like to leave these details to next article. But, here, let's remember one principle: always select the longest chain.

Image title

Generally, the longest chain indicates that its data (status) is the latest. Therefore, we need a function to help us replace the local expired chain with the latest chain:

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


So far, we have used all the important functions. Next, we need to view our chain, including the data and status, in a visual and convenient way. The best way to view the chain is on a web page on a browser.

Web Service

We are assuming that you are very familiar with conventional web services and development, so this part should be easy enough to understand.

By using the Gorilla/mux package, we can write a function to initialize our web service:

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
}


You can obtain the port number using the .env configuration file. After adding some basic configuration parameters, the web service can be listened to and served.

Next, let's define different endpoints and corresponding handlers. For example, a GET  request with a slash ("/") enables us to view the entire chain, and a POST request with a slash ("/") enables us to create a block.

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


Define the handler for a GET request:

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


For simplicity, we have enabled the web server to return the entire chain in JSON format. You can view the chain by visiting localhost:8080 or 127.0.0.1:8080 in the browser (8080 is the port number ADDR defined in .env by you).

The handler for the POST  request is somewhat complicated to define. First, let's define the payload for the POST  request:

type Message struct {
    BPM int
}


Let's see how we can implement the handler:

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)

}


You can use the preceding payload in our POST request; for example:

{"BPM":75}


Do you still remember the generateBlock function that we wrote? It accepts one "previous block" parameter and one BPM value. After the POST  handler accepts the request, you can obtain the BPM value from the request body. Then, using the block generation function and the block validation function, you can generate a new block.

Besides, you can also:

  • Use the spew.Dump  function to print struct and slice data to the console in an elegant and easy-to-read form, which simplifies debugging.
  • Use the chrome plug-in POSTMAN  to test the POST  request, which is more visual and convenient than curling.

After processing the POST  request, we need to return a response to the client, regardless of whether we have created the block successfully:

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


Assembling all the Parts

We are almost done! Next, let's "assemble" the functions for the blockchain and functions for the web service:

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

}


The  genesisBlock  is the most critical part of the main function. We can use it to initialize the blockchain, because the  PrevHash value of the first block is empty.

That's it!

Let's run it:

go run main.go


In the UE, we can view the log about the web server initialization and print the information about the genesis block.

Image title

Then, let's open the browser and visit localhost:8080. The information about the current entire blockchain gets displayed on the web page (there is only one genesis block at present).

Image title

Then, let us use POSTMAN  to send some POST  requests:

Image title

Refresh the preceding web page. You will find more blocks getting displayed in the current chain. These are the same blocks that we just generated. You can view that the block sequence and hash values are all correct.

Image title

Conclusion

We have just completed creating our blockchain with less than 200 lines of Go. Although it is simple, it delivers the basic capabilities of generating blocks, calculating hash values, and validating blocks. Later, you can continue to learn other important things about the blockchain, for example, consensus algorithms, such as PoW and PoS, smart contract, Dapp, and side chain.

The current implementation does not include any P2P network-related content. You can obtain the source code from GitHub from this link.

Rapidly detect security vulnerabilities in your web, mobile and desktop applications with IBM Application Security on Cloud. Register Now

Topics:
security ,blockchain ,trends ,go ,pow ,pos ,chain ,blocks ,prevhash

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}