区块链那么火,作为一个有理想的工程师,不去学习下实在说不过去。

什么是区块链(BlockChain)?我觉得这篇文章讲的通俗易懂,推荐一下:《1分钟了解区块链的本质》,这里摘抄一下总结:

  • 区块是一块存储空间,可以存储数据
  • 区块链不但像链表一样把区块串起来,还有约定了一系列的方法管理这些数据,所以它是存储系统
  • 区块链有很多节点,每个节点都保存了全部的数据,所以它是高可用的
  • 每一个中心节点都可以生成区块,并写入数据,所以每一个点都是中心节点,或者说区块链是去中心化的,要想控制整个系统,必须控制一半以上的节点,才能控制投票,于是这个系统没有管理员

综上,区块链实际上是一个没有管理员的,去中心化的,每个节点都拥有全部数据的分布式存储系统。只要你愿意,你随时可以成为区块链中的一个节点,并参与区块的生成与写入,比特币就是基于这个分布式存储上的电子货币。

由于节点很多,很多数据需要同步,这个系统的存储容量其实不大,目前全球存储比特币的区块链也就100多G。

好,什么是区块链就到这里,网上有很多资料也可以查阅。下面我们通过不到200行的Go代码实现一个简单的区块链,来近距离的感受一下简易版的区块链。当然,这个不是我弄出来的,是国外一个大牛。推荐读原文:《Code your own blockchain in less than 200 lines of Go!》。

这里就不全文翻译了,代码和原理都很简单,这里简单记录一下流程以及关键点。

  1. 建立数据模型:定义区块链以及每个节点。

    // Block in Blockchain
    type Block struct {
        Index     int    // 节点在区块链中的位置
        Timestamp string // 数据写入的时间戳
        BPM       int    // 每分钟心跳数,beats per minute
        Hash      string // 本节点数据哈希值
        PrevHash  string // 前一个节点哈希值
    }
    
    // BlockChain is Block slice
    var BlockChain []Block

    这里区块链每个节点的记录的数据就是BPM,其它说明在注释中都有,不再赘述。数据模型如下图所示:
    BlockChain.png

  2. 增加一些基本操作函数:(1)生成新节点(2)校验节点合法性(3)多个链时如何处理。

    // 计算节点哈希值
    func calculateHash(block Block) string {
        // 使用节点除`Hash`字段之外的其他字段计算本节点哈希值
        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)
    }
    
    // 生成新的节点
    func generateBlock(oldBlock Block, BPM int) (Block, error) {
        var newBlock Block
    
        t := time.Now()
    
        newBlock.Index = oldBlock.Index + 1
        newBlock.BPM = BPM
        newBlock.Timestamp = t.String()
        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
        }
    }

    这里的函数实现都非常简单,是个合格的程序员都看得懂。说一下最后一个函数replaceChain,对于分布式系统而言,数据一致性一直是个挑战,区块链作为一个分布式存储自然也不例外。这里采取的策略比较简单,遇到多个链的时候,以最长的链为准,如下图所示:
    BlockChain2.png

  3. 实现一个WebServer,这个其实和区块链没啥关系,只是为了方便使用和显示。

    func run() error {
        mux := makeMuxRouter()
        s := &http.Server{
            Addr:           ":8080",
            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("/", handleWriteBlockChain).Methods("POST")
    
        return muxRouter
    }
    
    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))
    }
    
    func handleWriteBlockChain(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.StatusInternalServerError, 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)
    }
    
    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)
    }

    这就是一个非常简单的Go版本的Webserver了,其实还可以不使用gorilla/mux包,就用Go原生的http包就行,当然这些都不是重点。

  4. 最后来个main函数就大功告成了。

    func main() {
        go func() {
            t := time.Now()
            genesisBlock := Block{0, t.String(), 0, "", ""}
            spew.Dump(genesisBlock)
            BlockChain = append(BlockChain, genesisBlock)
        }()
        log.Fatal(run())
    }

    main函数里面我们先构造了一个初始节点。

因为原文已经讲的比较清楚了,而且代码实现也非常简单,所以这里就不再赘述啥了,一切都在代码中。其实,如果仅从这个例子看的话,区块链非常像链表。当然还是非常不一样的。原文是一个系列,我也还没看完,后面如果感觉有值得分析的部分会转载过来。还是那句话,强烈推荐看原文。

PS:完整代码在这里