首页 > 区块链 > Go语言200行写区块链源代码分析
起来分期  

Go语言200行写区块链源代码分析

摘要:区块链 go以下文章来源于飞雪无情 ,作者飞雪无情Github上有一个Repo,是一个使用Go语言(golang),不到200行代码写的区块链源代码,准确的说是174行。原作者起了个名字是 Code your own blockchain in less than 200 lines of Go!

以下文章来源于飞雪无情 ,作者飞雪无情

Github上有一个Repo,是一个使用Go语言(golang),不到200行代码写的区块链源代码,准确的说是174行。原作者起了个名字是 Code your own blockchain in less than 200 lines of Go! 而且作者也为此写了一篇文章。https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc

这篇文章是一个大概的思路和代码的实现,当然还有很多代码的逻辑没有涉及,所以我就针对这不到200行的代码进行一个分析,包含原文章里没有涉及到的知识点,对Go语言,区块链都会有一个更深的认识。

所有的源代码都在这里:https://github.com/nosequeldeebee/blockchain-tutorial/blob/master/main.go

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

在源代码的开头,是作者引入的一些包,有标准的,也有第三方的。像sha256,hex这些标准包是为了sha-256编码用的,其他还有启动http服务,打印日志的log,并发控制的sync,时间戳的time。

第三方包有三个,其中两个我都详细介绍过,相信大家不会陌生。

go-spew是一个变量结构体的调试利器,可以打印出变量结构体对应的数据和结构,调试非常方便

gorilla/mux是一个web路由服务,可以很简单的帮我们构建web服务。

不过目前用gin的比较多,也推荐使用gin https://github.com/gin-gonic/gin。

godotenv是一个读取配置文章的库,可以让我们读取.env格式的配置文件,比如从配置文件里读取IP、PORT等。不过目前配置文件还是推荐YAML和TOML,对应的第三方库是:

gopkg.in/yaml.v21https://github.com/BurntSushi/toml

既然要写一个区块链,那么肯定的有一个区块的实体,我们通过golang的struct来实现。

// Block represents each 'item' in the blockchaintype Block struct {    Index     int    Timestamp string    BPM       int    Hash      string    PrevHash  string}

Block里包含几个字段:

Index 就是Block的顺序索引Timestamp是生成Block的时间戳BPM,作者说代表心率,每分钟心跳数Hash是通过sha256生成的散列值,对整个Block数据的HashPrevHash 上一个Block的Hash,这样区块才能连在一起构成区块链

有了区块Block了,那么区块链就非常好办了。

// Blockchain is a series of validated Blocksvar Blockchain []Block

就是这么简单,一个Block数组就是一个区块链。区块链的构成关键在于Hash和PrevHash,通过他们一个个串联起来,就是一串Block,也就是区块链。因为相互之间通过Hash和PrevHash进行关联,所以整个链很难被篡改,链越长被篡改的成本越大,因为要把整个链全部改掉才能完成篡改的目的,这样的话,其他节点验证这次篡改肯定是不能通过的。

既然关键点在于Hash,所以我们要先算出来一个Block的数据的Hash,也就是对Block里的字段进行Hash,计算出一个唯一的Hash值。

// SHA256 hasingfunc calculateHash(block Block) string {    record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash    h := sha256.New()    h.Write([]byte(record))    hashed := h.Sum(nil)    return hex.EncodeToString(hashed)}

sha256是golang内置的sha256的散列标准库,可以让我们很容易的生成对应数据的散列值。从源代码看,是把Block的所有字段进行字符串拼接,然后通过sha256进行散列,散列的数据再通过hex.EncodeToString转换为16进制的字符串,这样就得到了我们常见的sha256散列值,类似这样的字符串8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92。

Block的散列值被我们计算出来了,Block的Hash和PrevHash这两个字段搞定了,那么我们现在就可以生成一个区块了,因为其他几个字段都是可以自动生成的。

// create a new block using previous block's hashfunc generateBlock(oldBlock Block, BPM int) Block {    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}

因为区块链是顺序相连的,所以我们在生成一个新的区块的时候,必须知道上一个区块,也就是源代码里的oldBlock。另外一个参数BPM就是我们需要在区块里存储的数据信息了,这里作者演示的例子是心率,我们可以换成其他业务中想要的数据。

Index是上一个区块的Index+1,保持顺序;Timestamp通过time.Now()可以得到;Hash通过calculateHash方法计算出来。这样我们就生成了一个新的区块。

在这里作者并没有使用POW(工作量证明)这类算法来生成区块,而是没有任何条件的,这里主要是为了模拟区块的生成,演示方便。

区块可以生成了,但是生成的区块是否可信,我们还得对他进行校验,不能随便生成一个区块。在比特币(BitCoin)中校验比较复杂,这里作者采用了简单模拟的方式。

// make sure block is valid by checking index, and comparing the hash of the previous blockfunc 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}

简单的对比Index,Hash是否是正确的,并且重新计算了一遍Hash,防止被篡改。

到了这里,关于区块链的代码已经全部完成了,剩下的就是把区块链的生成、查看等包装成一个Web服务,可以通过API、浏览器访问查看。因为作者这里没有实现P2P网络,所以采用的是WEB服务的方式。

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

通过mux定义了两个Handler,URL都是/,但是对应的Method是不一样的。

GET方法通过handleGetBlockchain函数实现,用于获取区块链的信息。

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

Blockchain是一个[]Block,handleGetBlockchain函数的作用是把Blockchain格式化为JSON字符串,然后显示出来。io.WriteString是一个很好用的函数,可以往Writer里写入字符串。更多参考 Go语言实战笔记(十九)| Go Writer 和 Reader

'POST'方法通过handleWriteBlock函数实现,用于模拟区块的生成。

func handleWriteBlock(w http.ResponseWriter, r *http.Request) {    w.Header().Set("Content-Type", "application/json")    //使用了一个Mesage结构体,更方便的存储BPM    var msg Message    //接收请求的数据信息,类似{"BPM":60}这样的格式    decoder := json.NewDecoder(r.Body)    if err := decoder.Decode(&msg); err != nil {        respondWithJSON(w, r, http.StatusBadRequest, r.Body)        return    }    defer r.Body.Close()    //控制并发,生成区块链,并且校验    mutex.Lock()    prevBlock := Blockchain[len(Blockchain)-1]    newBlock := generateBlock(prevBlock, msg.BPM)    //校验区块链    if isBlockValid(newBlock, prevBlock) {        Blockchain = append(Blockchain, newBlock)        spew.Dump(Blockchain)    }    mutex.Unlock()    //返回新的区块信息    respondWithJSON(w, r, http.StatusCreated, newBlock)}

以上代码我进行了注释,便于理解。主要是通过POST发送一个{"BPM":60}格式的BODY来添加区块,如果格式正确,那么就生成区块进行校验,合格了就加入到区块里;如果格式不对,那么返回错误信息。

用于控制并发的锁可以参考Go语言实战笔记(十七)| Go 读写锁

这个方法里有个Message结构体,主要是为了便于操作方便。

// Message takes incoming JSON payload for writing heart ratetype Message struct {    BPM int}

返回的JSON信息,也被抽取成了一个函数respondWithJSON,便于公用。

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

好了,快完成了,以上Web的Handler已经好了,现在我们要启动我们的Web服务了。

// web serverfunc run() error {    mux := makeMuxRouter()    //从配置文件里读取监听的端口    httpPort := os.Getenv("PORT")    log.Println("HTTP Server Listening on port :", httpPort)    s := &http.Server{        Addr:           ":" + httpPort,        Handler:        mux,        ReadTimeout:    10 * time.Second,        WriteTimeout:   10 * time.Second,        MaxHeaderBytes: 1 << 20,    }    if err := s.ListenAndServe(); err != nil {        return err    }    return nil}

和原生的http.Server基本一样,应该比较好理解。mux其实也是一个Handler,这就是整个Handler处理链。现在我们就差一个main主函数来启动我们整个程序了。

//控制并发的锁var mutex = &sync.Mutex{}func main() {    //加载env配置文件    err := godotenv.Load()    if err != nil {        log.Fatal(err)    }    //开启一个goroutine生成一个创世区块    go func() {        t := time.Now()        genesisBlock := Block{}        genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), ""}        spew.Dump(genesisBlock)        mutex.Lock()        Blockchain = append(Blockchain, genesisBlock)        mutex.Unlock()    }()    log.Fatal(run())}

整个main函数并不太复杂,主要就是加载env配置文件,开启一个go协程生成一个创世区块并且添加到区块链的第一个位置,然后就是通过run函数启动Web服务。

一个区块链都有一个创世区块,也就是第一个区块。有了第一个区块我们才能添加第二个,第三个,第N个区块。创世区块因为是第一个区块,所以它是没有PrevHash的。

终于可以运行了,假设我们设置的PORT是8080,现在我们通过go run main.go启动这个简易的区块链程序,就可以看到控制台输出的创世区块信息。然后我们通过浏览器打开http://localhost:8080也可以看到这个区块链的信息,里面只有一个创世区块。

如果我们要新增一个区块,通过curl或者postman,向http://localhost:8080 发送body格式为{"BPM":60}的POST的信息即可。然后在通过浏览器访问http://localhost:8080查看区块链信息,验证是否已经添加成功。

到这里,整个源代码的分析已经完了,我们看下这个简易的区块链涉及到多少知识:

sha256散列字节到16进制转换并发同步锁Web服务配置文件后向式链表结构体JSON……

等等,上面的很多知识,我已经在文章中讲解或者通过以前些的文章说明,大家可以看一下,详细了解。

免责声明
世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:msy2134。