×
Community Blog Build a Blockchain with Less Than 200 Lines of Go

Build a Blockchain with Less Than 200 Lines of Go

Blockchain technology holds tremendous potential to transform business processes across all verticals.

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 then 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!

1

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.

2

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 SHA256 based on 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 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.

3

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 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.

4

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

5

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

6

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.

7

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.

Find similar articles and learn more about Alibaba Cloud’s products and solutions at www.alibabacloud.com/blog.

0 0 0
Share on

Alibaba Clouder

2,605 posts | 747 followers

You may also like

Comments

Alibaba Clouder

2,605 posts | 747 followers

Related Products

  • LedgerDB

    A ledger database that provides powerful data audit capabilities.

    Learn More
  • Blockchain as a Service

    BaaS provides an enterprise-level platform service based on leading blockchain technologies, which helps you build a trusted cloud infrastructure.

    Learn More
  • Security Solution

    Alibaba Cloud is committed to safeguarding the cloud security for every business.

    Learn More
  • Function Compute

    Alibaba Cloud Function Compute is a fully-managed event-driven compute service. It allows you to focus on writing and uploading code without the need to manage infrastructure such as servers.

    Learn More