Create a blockchain with golang in just 200 lines of code.

In this article you will use Go (golang) language to create your own blockchain, understand how the hash function maintains the integrity of the blockchain, master how to create and add new blocks, implement multiple nodes to generate blocks through competition, browse through browser to view the entire chain and learn all the other basics about blockchain.

However, consensus algorithms such as Proof of Work (PoW) and Proof of Stake (PoS) will not be covered in this article. At the same time, in order to allow you to see the blockchain and the addition of blocks more clearly, we will discuss the process of network interaction. Simplified, the content of P2P network such as the process of "whole network broadcasting" will be added in subsequent articles.

development environment

We assume that you already have some development experience in Go. After installing and configuring the Go development environment, we also need to obtain the following dependencies:

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

spewIt can help us to view the two data structures struct and slice directly in the terminal.

~$ go get github.com/gorilla/mux

Gorilla's muxpackage is very popular and we use it to write web handlers.

~$ go get github.com/joho/godotenv

godotenvIt can help us read the configuration file in the root directory of the project .env, so that the configuration such as http port does not have to be hard-coded into the code. For example like this:

ADDR=8080

Next, we create a main.gofile . Most of the work after that revolves around this file, so let's start writing code!

import dependencies

We import all dependent packages declaratively:

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 we define a structure that represents the data model of each block that makes up the blockchain:

type Block struct {
    Index     int
    Timestamp string
    BPM       int
    Hash      string
    PrevHash  string
}
  • Index is the position of this block in the entire chain
  • Timestamp is obviously the timestamp when the block was generated
  • Hash is the hash value generated by the SHA256 algorithm for this block
  • PrevHash represents the SHA256 hash of the previous block
  • BPM beats per minute, or heart rate

Next, we define a structure to represent the entire chain. The simplest representation is a slice of Block:

var Blockchain []Block

We use a hashing algorithm (SHA256) to determine and maintain the correct order of blocks and blocks in the chain, ensuring that the PrevHash value of each block is equal to the hash value of the previous block, so that the chain is constructed in the correct block order:

180 lines of go code let you thoroughly understand what the blockchain is

Hashing and generating new blocks

Why do we need hashing? Mainly for two reasons:

  • Uniquely identifies data on the premise of saving space. The hash is calculated using the data of the entire block, in our case, the data of the entire block is calculated by SHA256 into a fixed-length unforgeable string.
  • Maintain chain integrity. By storing the hash of the previous block, we are able to ensure the correct order of each block in the chain. Any tampering with the data will change the hash and break the chain. Taking the medical and health field we are engaged in as an example, for example, a malicious third party modifies the unhealthy BPM value in one or several blocks in order to adjust the price of "life insurance", then the entire chain becomes untrustworthy .

We then write a function that computes the SHA256 hash of the given data:

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

The calculateHash function accepts a block and calculates a SHA256 hash from the Index, Timestamp, BPM, and PrevHash values ​​in the block. Next we can write a function that generates a 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
}

Among them, Index is obtained by incrementing the Index of the given previous block, the timestamp is obtained directly by the time.Now() function, the Hash value is calculated by the previous calculateHash function, and PrevHash is the given previous block. The hash value of the block.

check block

After getting the block generation, we need a function to help us determine whether a block has been tampered with. Check the Index to see if the block is incremented correctly, check whether the PrevHash is consistent with the Hash of the previous block, and then use calculateHash to check whether the Hash value of the current block is correct. Through these steps, we can write a verification function:

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
}

In addition to verifying blocks, we also have a problem: both nodes generate blocks and add to their respective chains, so who should we take? We'll leave the details here for the next article, but let's remember a principle first: always choose the longest chain:

180 lines of go code let you thoroughly understand what the blockchain is

Generally speaking, a longer chain means that its data (state) is updated, so we need a function that can help us switch the local expired chain to the latest chain:

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

At this point, we have basically completed all the important functions. Next, we need a convenient and intuitive way to view our chain, including data and status. Viewing web pages through a browser is probably the most appropriate way!

web service

I'm guessing you're pretty familiar with traditional web services and development, so you'll know this part at a glance.

Using the Gorilla/mux package, let's start by writing 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
}

The port number is obtained through the .env mentioned above, and after adding some basic configuration parameters, the web service can already listen and serve!

Next, let's define different endpoints and corresponding handlers. For example, a GET request to "/" we can view the entire chain, and a POST request to "/" can create blocks.

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

GET request handler:

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

To simplify, we return the entire chain directly in JSON format, you can visit localhost:8080 or 127.0.0.1:8080 in your browser to view (8080 here is the port number ADDR you defined in .env).

The handler of the POST request is a little more complicated. Let's first define the payload of the POST request:

type Message struct {
    BPM int
}

Let's take a look at the implementation of 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)

}

The payload defined above can be used in our POST request body, for example:

{"BPM":75}

Remember the generateBlock function we wrote earlier? It accepts a "previous block" parameter, and a BPM value. After the POST handler accepts the request, it can obtain the BPM value in the request body, and then a new block can be generated with the help of the function of generating the block and the function of verifying the block!

In addition to this, you can also:

  • Using spew.Dump this function can print struct, slice and other data to the console in a very beautiful and easy-to-read way, which is convenient for us to debug.
  • When testing POST requests, you can use the POSTMAN chrome plugin, which is more intuitive and convenient than curl.

After the POST request is processed, regardless of whether the block was created successfully or not, we need to return a response to the client:

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

Almost done.

Next, we "assemble" these functions about the blockchain and web services:

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 here is the most important part of the main function. It is used to initialize the blockchain. After all, the PrevHash of the first block is empty.

Oh yeah! finished

The complete code can be obtained from here: Github repo

Let's start it:

~$ go run main.go

In the terminal, we can see the log information of the web server startup and print out the information of the genesis block:

180 lines of go code let you thoroughly understand what the blockchain is

Then we open the browser and visit the address localhost:8080. We can see that the page displays the current information of the entire blockchain (of course, there is currently only one genesis block):

180 lines of go code let you thoroughly understand what the blockchain is

Next, we send some POST requests through POSTMAN:

180 lines of go code let you thoroughly understand what the blockchain is

Refresh the page just now, there are now some more blocks in the chain, which are exactly what we just generated, and you can see that the order and hash value of the blocks are correct.

180 lines of go code let you thoroughly understand what the blockchain is

Summarize

We have just completed a blockchain of our own. Although it is very simple (ugly), it has basic capabilities such as block generation, hash calculation, and block verification. Next, you can continue to learn other important knowledge of blockchain, such as consensus algorithms such as proof of work, proof of stake, or smart contracts, Dapps, side chains, and more.

At present, this implementation does not include any P2P network content, we will supplement this part in the next article, of course, we encourage you to practice it yourself on this basis!

If you want to learn Ethereum DApp development efficiently , you can visit the most popular online interactive tutorials provided by Huizhi.com:

More other content is also available on this Ethereum blog .

原文:Code your own blockchain in less than 200 lines of Go!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325315196&siteId=291194637