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:
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!
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!
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"
)
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
}
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.
Why do we need hash? There are two main reasons:
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.
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.
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.
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:
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)
}
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.
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).
Then, let us use POSTMAN to send some POST requests:
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.
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.
Alibaba Cloud Launches HiTSDB to Accelerate Migration of IoT Devices to the Cloud
2,599 posts | 762 followers
FollowAlibaba Clouder - August 12, 2019
Alibaba Clouder - September 27, 2018
Alibaba Clouder - April 16, 2020
Alibaba Clouder - November 6, 2019
Alibaba Clouder - August 8, 2018
vic - August 30, 2019
2,599 posts | 762 followers
FollowA ledger database that provides powerful data audit capabilities.
Learn MoreBaaS provides an enterprise-level platform service based on leading blockchain technologies, which helps you build a trusted cloud infrastructure.
Learn MoreAlibaba Cloud is committed to safeguarding the cloud security for every business.
Learn MoreAlibaba 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 MoreMore Posts by Alibaba Clouder