Write a simple blockchain in GO language

Hits: 0

The general concept of blockchain.

[According to my personal understanding, a block is a module that saves data one by one, and then a blockchain is a linked list] that connects each block. At the same time, the reason why the blockchain is fair is that it uses encryption and cannot be reversed, etc. That is, as long as the data is successfully loaded into the blockchain, there is no possibility of tampering or elimination.

As shown in the figure, it is the implementation mechanism of the blockchain. Each block saves the [Hash] value of the previous block, and generates its own Hash for the next block to save.

The specific knowledge about the block is explained in Here . About the private chain, the public chain, and the white paper, you can take a look if you are interested.

Let’s start today’s topic.

First of all, today’s tasks are the following six:

  • Create your own blockchain
  • Learn how hashing maintains the integrity of the blockchain
  • See how to add new blocks
  • See how tiebreakers are resolved when multiple nodes generate blocks
  • View your blockchain in a web browser
  • write new block

First, you need to download the third-party packages required in the code.
There are three third-party packages needed in this project:
go get github.com/davecgh/go-spew/spew
spew can visualize structs and slices in the console.
go get github.com/gorilla/mux
gorilla/mux is a popular package for writing web handlers.
go get github.com/joho/godotenv
godotenv allows us to read from a .env file kept in the root of the directory, so there is no need to hardcode things like HTTP ports.

In the first step we create a file in the root of the .env directory that defines the port that will serve http requests. Just add a line to this file:
PORT=8080

If you think it’s a bit long-winded to go into detail, you can skip directly to the full code below!

Created like this:
Next, create a main.go file. From now on, all content will be written into this file. Due to the simple implementation, there is no sub-module processing, which is also convenient for everyone to understand the code logic.

Create a structure

type Block struct { 
    Index      int     //The position of the data record in the block 
    Timestamp string  //Data writing time 
    BPM        int    //The frequency of inserting the block 
    Hash       string  //The SHA256 identifier of this piece of data 
    PrevHash   string  //Previous block identifier 
}

Create the next Blockchain based on the structure

var Blockchain []Block

The next step is to generate the hash of the block itself:

//SHA256 hash value used to generate block 
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 the generated Hash 
    return hex.EncodeToString(hashed)
}

Then a complete new block is generated

//Generation of new block 
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
}

Then it is to confirm whether the block has been changed

We check Index to make sure they have increased as expected. Also check that the PrevHash is indeed the same as the Hash of the previous block. Finally, we want to double-check the hash of the current block by running the function calculateHash again on the current block.

//Verify whether the block has been tampered with 
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
}

throw a question

If both nodes of our blockchain ecosystem add blocks to their chain and we both receive them. If we choose, we choose the longest chain.

The two nodes may just have different chain lengths, so naturally the longer node will be the latest and have the latest blocks. So make sure that the new chain we want to receive is longer than the current chain. If so, we can overwrite our chain with a new chain with new blocks.

Then if we solve this problem, we only need to compare the length of the chain to complete this operation:

//Two blocks compete, long chain takes precedence 
func replaceChain ( newBlocks []Block ) {
     if  len ( newBlocks ) > len ( Blockchain ) {
        Blockchain = newBlocks
    }
}

At this point, the basic operations on the block have been completed, the next step is to process the routing, and then you are done.

First think, set the route, we implement it in the simplest way, Get to get the data of the block, Post to insert its own new block.

First is Get:

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

Next is Post:

The reason we use a separate Message structure in the first place is to accept the request body of the JSON POST request that we will use to write the new chunk. This allows us to simply send a POST request with the following body, and the handler will populate the rest of the blocks for us:

{“BPM”:50}

type Message struct {
    BPM int
}

//Insert data into the block 
func handleWriteBlock ( w http.ResponseWriter, r *http.Request ) {
     var m Message

    decoder := json.NewDecoder(r.Body)
    if err := decode.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)
        // print the result to the console
        spew.Dump(Blockchain)
    }
    respondWithJson(w, r, http.StatusCreated, newBlock)
}

After decoding the request body into a var m Messagestructure, we create a new block by passing the previous block and the new block into the generateBlock function we wrote earlier. We use the isBlockValid function we created earlier to do a quick check to make sure the new block is ok.

A few notes

  • The purpose of spew.Dump is to print our structure to the console for debugging purposes.
  • For testing POST requests, we like to use Postman.
Next, we will deal with the question of whether Post is inserted into the blockchain.

When our POST request succeeds or fails, we can use the respondWithJSON method below to tell us what happened.

//Determine whether the POST request fails 
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)
}

followed by the specified route

//Set the route for easy browser access. 
func makMuxRouter () http.Handler {
    muxRouter := mux.NewRouter()
    muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
    muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
    return muxRouter
}

Next let the block run

func run() error {
    mux := makMuxRouter()
    httpAddr := os.Getenv("PORT")
    log.Println("Listening on ", os.Getenv("PORT"))
    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
}

The port we choose comes from our .env file that we created earlier. log.Println lets us know that the server is up and running. Let’s do some more configuration on the server ListenAndServe.

Finally aggregate all the functions into the main function

Let’s wire all these different blockchain functions, web handlers and web servers into the main function:

func main ()  {
     //read variables in .env
    err := godotenv.Load()
    if err != nil {
        log.Fatal(err)
    }

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

Points to Note

  • godotenv.Load() allows us to read variables like port numbers from a .env file placed in the root of the directory, so we don’t have to hardcode them (note the capitalization!) throughout the application.
  • genesisBlock is the most important part of this function. We need to provide our blockchain with an initial block, otherwise a new block won’t be able to compare its previous hash to anything because the previous hash doesn’t exist.
  • We isolate the genesis block into its own goroutine so that we can separate our concerns from the blockchain logic and web server logic. This works without goroutines, but it’s more appropriate to do so.

Then you’re done!

rungo run main.go

Now, visit localhost:8080.
Use Postman to send a POST request to add a block.

Refresh your browser. Now we can see all the new blocks in the chain, as well as the new block hash that matches the old block PrevHash!

Attach all the code here:

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "github.com/davecgh/go-spew/spew"
    "github.com/gorilla/mux"
    "github.com/joho/godotenv"
    "io"
    "log"
    "net/http"
    "os"
    "time"
)

type Block struct {
    Index      int     //The position of the data record in the block 
    Timestamp string  //Data writing time 
    BPM        int     //The frequency of inserting the block 
    Hash       string  //The SHA256 identifier of this piece of data 
    PrevHash   string  //The identifier of the previous block symbol
}

type Message struct {
    BPM int
}

var Blockchain []Block

func main() {
     //Read variables in .env
    err := godotenv.Load()
    if err != nil {
        log.Fatal(err)
    }

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

//SHA256 hash value used to generate block 
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)
}

//Generation of new block 
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
}

//Verify whether the block has been tampered with 
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
}

//Two blocks compete, long chain takes precedence 
func replaceChain ( newBlocks []Block ) {
     if  len ( newBlocks ) > len ( Blockchain ) {
        Blockchain = newBlocks
    }
}

//Set the route for easy browser access. 
func makMuxRouter () http.Handler {
    muxRouter := mux.NewRouter()
    muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
    muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
    return muxRouter
}

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

//Insert data into the block 
func handleWriteBlock ( w http.ResponseWriter, r *http.Request ) {
     var m Message

    decoder := json.NewDecoder(r.Body)
    if err := decode.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)
        // print the result to the console
        spew.Dump(Blockchain)
    }
    respondWithJson(w, r, http.StatusCreated, newBlock)
}

//Determine whether the POST request fails 
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)
}

//Run the block. 
func run () error {
    mux := makMuxRouter()
    httpAddr := os.Getenv("PORT")
    log.Println("Listening on ", os.Getenv("PORT"))
    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
}

For more knowledge about blockchain, here are some websites I often read:

Create Your Own Project – Blockchain
Visualization Block
Bitcoin Article

You may also like...

Leave a Reply

Your email address will not be published.