200行go代码实现区块链

本文翻译自《Code your own blockchain in less than 200 lines of Go!》,国内被墙无法访问。由于我的英语水平有限,所以也不逐段翻译,而是概括其关键内容。

你可以学到什么

  • 创建自己的blockchain
  • 理解哈希算法是怎样保证blockchain的完整性
  • 理解新块是如何被添加的
  • 如何解决多个节点竞争问题
  • 通过浏览器来查看你的blockchain
  • 写新的blocks
  • 对blockchain有基本的了解

本文不包括以下内容

为了简单起见,本文内容不包括一致性算法如POW、POS,网络广播等内容。

开始

配置

因为我们要用GO语言实现,所以这里假设各位已经有了一定go语言编程的基础,首先我们需要下载几个模块

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

spew可以理解为一种结构化输出工具,可以让我们更清晰地查看structs 和slices数据。

go get github.com/gorilla/mux

Gorilla/mux 是一个用于Web开发的组件

go get github.com/joho/godoenv

Gotdotenv 是一个读取在项目根目录的.env文件中的配置信息的组件,本文中我们的web服务端口就定义在该文件中

现在我们创建一个.env文件并写入

ADDR=8080

创建一个main.go文件,我们所有的代码将会写入该文件中,并且不会多于200行。

导入模块

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

数据结构

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

Block用于保存要写入的区块链中的数据,每一个字段的意思如下:
- Index 表示区块号(区块高度)
- Timestamp 自动产生的表示该块的生成时间
- BPM 可以理解为写入区块的数据
- Hash 本块的哈希值
- PrevHash 前一个块的哈希值

然后创建一个Block的数组切片用于代表区块链

var Blockchain []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)
}

我们使用到了Index、Timestamp、BPM、PrevHash 字段用于计算当前块的哈希值

我们再写一个用于产生新块的方法

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
}

代码很简单,就不废话了。

区块正确性验证


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
}

本方法用于验证区块链是否被恶意篡改,首先检查区块高度,再检随后的块中的哈希值是否等于前一个块的哈希值,最后检查本块的哈希值是否正确。

如果有两个节点,当两个节点上的区块链长度不同时,我们应该选择哪条链呢?最简单的方法,选择较长的那个。

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

Web 服务

这里假设您已经熟悉,并有一定的GO WEB开发经验。

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
}

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

处理GET请求

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

我们将整个Blokcchain转换为JSON串作为GET请求的返回值 。

我们使用POST请求添加新块,求稍微复杂一些,我们需要一个结构体

type Message struct {
    BPM int
}

用作参数传递。
处理方法:

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)

}

我们上边使用了一个封装后的方法respondWithJson 当出现错误的时候返回HTTP:500,成功的话正常返回。

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

最后我们还需要一个Main方法,作为程序入口。

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

}

完成并测试

启动web服务

 go run main.go

使用浏览器访问我们的web服务,我们可以看到如下结果
这里写图片描述
我们使用POSTMAN测试添加新区块
这里写图片描述
我们刷新一下浏览器,我们就可以看到我们刚添加的块
这里写图片描述

最后

这最后一段看得懂,但翻译好麻烦,我决定给上英文原文了。
Congrats!! You just wrote up your own blockchain with proper hashing and block validation. You should now be able to control your own blockchain journey and explore more complicated topics like Proof of Work, Proof of Stake, Smart Contracts, Dapps, Side Chains and more.

What this tutorial doesn’t address is how new blocks get mined using Proof of Work. This would be a separate tutorial but plenty of blockchains exist without Proof of Work mechanisms. In addition, the network broadcasting is currently simulated by writing and viewing the blockchain in a web server. There is no P2P component in this tutorial.

下面链接是我转成PDF的英文原文有兴趣大家可以阅读一下。

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

展开阅读全文

没有更多推荐了,返回首页