美文网首页区块链 | Distributed Ledger Technology
Go学习之编码实现区块链 - 【blockchain】

Go学习之编码实现区块链 - 【blockchain】

作者: 817785ed4a10 | 来源:发表于2018-06-13 17:44 被阅读47次

    首先,感谢Coral Health的《Code your own blockchain in less than 200 lines of Go!》所提供的Go实现简单区块链一文,鉴于本土朋友希望了解区块链的代码实现,且又对英文解读水平较差。本主在此根据原文进行了亲身实践,亲测可行。

    那就开始吧。

    - 环境准备与开发工具使用,需详细了解,请阅读我之前写的《入门篇》

    - 定义区块、区块链

    - 定义Hash算法

    - 生成块

    - 块的校验

    - 确定主链(类比以太坊)

    - 构建Web Server

    1、环境准备

    创建block.go文件。除了Go语言环境、开发工具(Goland)的配置、安装之外,本文涉及的代码依赖还需要:

    [cpp]view plaincopy

    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"  

    )  

    对于相应的依赖,使用go get 命令进行下载即可,使用Goland的朋友可以使用alt+Enter神技自动下载

    2、定义区块、区块链

    原文作者所处的行业属于医疗健康行业,定义区块的属性时,以一个BPM属性来维护后续的代码实现,在后续开发种,本属性可以根据自己对应想实现的业务进行相应的转换。

    [cpp]view plaincopy

    type Block struct {  

    Indexint    //索引  

    Timestamp string//时间戳  

    BPMint    //一分钟心跳的频率,脉搏  

    Hash      string//哈希  

    PrevHash  string//前一个哈希  

    }  

    var Blockchain []Block//区块链  

    使用散列来确定并保持块的顺序。通过确保PrevHash每一个Block与Hash前一个相同,Block我们知道构成链的块的正确顺序。

    使用散列这种方式的原因有两点:

    1、 为了节省空间。哈希值来自块上的所有数据。在例子中,我们只有一些数据点,但如果数量有上千个或上百万个先前块的数据。将这些数据散列到单个SHA256字符串中,散列比一次又一次复制前面块中的所有数据要有效得多。

    2、保持区块链的完整性。通过存储之前的哈希,就像我们在上图中所做的那样,我们能够确保区块链中的区块按照正确的顺序排列。(这样还能有效的防止恶意链的出现)

    3、定义Hash算法

    在这里使用了区块链当中常用的一种算法,就是SHA256。

    SHA-256

    安全散列算法SHA(Secure Hash Algorithm)是美国国家安全局 (NSA) 设计,美国国家标准与技术研究院(NIST) 发布的一系列密码散列函数,包括 SHA-1、SHA-224、SHA-256、SHA-384 和 SHA-512 等变体。主要适用于数字签名标准(DigitalSignature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。SHA-1已经不是那边安全了,google和微软都已经弃用这个加密算法。为此,我们使用热门的比特币使用过的算法SHA-256作为实例。其它SHA算法,也可以按照这种模式进行使用。

    [cpp]view plaincopy

    //定义hash算法 SHA256 hasing  

    func calculateHash(block Block) string {  

    //strconv.Itoa 将整数转换为十进制字符串形式(即:FormatInt(i, 10) 的简写)  

        record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash  

    h := sha256.New()//创建一个Hash对象  

    h.Write([]byte(record))//h.Write写入需要哈希的内容  

    hashed := h.Sum(nil)//h.Sum添加额外的[]byte到当前的哈希中,一般不是经常需要这个操作  

    return hex.EncodeToString(hashed)  

    }  

    4、生成块

    [cpp]view plaincopy

    //生成块block  

    func genarateBlock(oldBlock Block, BPMint) (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 //此处error为后期完善复杂业务时留用,(可以省略)  

    }  

    5、校验块

    [cpp]view plaincopy

    //验证块,查看是否被篡改  

    func isBlockValid(newBlock Block, oldBlock Block)bool {  

    if newBlock.Index != oldBlock.Index+1 {  

    return false  

        }  

    if newBlock.PrevHash != oldBlock.Hash {  

    return false  

        }  

    if calculateHash(newBlock) != newBlock.Hash {  

    return false  

        }  

    return true  

    }  

    6、确定主链

    类似于以太坊,当由于“巧合”,在同一时间生成新块的情况下,可能产生两条链的情况,出现了分支,再进行后一次计算时,以最长的链为主链,其它成为分链或者支链。以太坊将分链上生成的块block称为叔块。

    [cpp]view plaincopy

    //判断链长最长的作为正确的链进行覆盖(作为主链)  

    func replaceChain(newBlocks []Block) {  

    if len(newBlocks) > len(Blockchain) {  

            Blockchain = newBlocks  

        }  

    }  

    到此,生成链的主要逻辑基本实现,接下来,就开始构建Web Server。

    7、构建Web Server

    (1)设置路由

    [cpp]view plaincopy

    func makeMuxRouter() http.Handler {  

        muxRouter := mux.NewRouter()  

    muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")  

    muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")  

    return muxRouter  

    }  

        handleGetBlockchain:处理GET请求,查询所有块信息

    handleWriteBlock:添加新块Block,POST请求

    返回“/”路径的路由,实现后通过http://localhost:8080进行访问即可

    (2)定义Message

    在这里定义Message类,实现的一个逻辑就是,添加新块时,使用POST请求,传入BPM脉搏信息来创建区块。

    先看一下效果,可以使用PostMan工具发送Post请求。

    传输的参数为JSON格式,{“BPM”:30 }

    (3)handleGetBlockchain

    查询所有区块信息

    [cpp]view plaincopy

    func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {  

    //json.MarshalIndent() - 格式化输出json  

    bytes, err := json.MarshalIndent(Blockchain,"", "\t")  

    if err != nil {  

            http.Error(w, err.Error(), http.StatusInternalServerError)  

    return  

        }  

        io.WriteString(w, string(bytes))  

    }  

    (4)定义输出时,格式化json的方法

    [cpp]view plaincopy

    //响应json格式化封装  

    func respondWithJson(w http.ResponseWriter, r *http.Request, codeint, payload interface{}) {  

    reponse, err := json.MarshalIndent(payload,"", "\t")  

    if err != nil {  

    w.WriteHeader(http.StatusInternalServerError)//响应错误状态码  

    w.Write([]byte("HTTP 500: Internal Server Error"))    //响应错误内容  

    return  

        }  

        w.WriteHeader(code)  

        w.Write(reponse)  

    }  

    (5)handleWriteBlock

    创建新区块Block,并返回创建Block的信息

    [cpp]view plaincopy

    func handleWriteBlock(w http.ResponseWriter, r *http.Request) {  

    w.Header().Set("Content-Type", "application/json")  

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

        oldBlock := Blockchain[len(Blockchain)-1]  

        mutex.Lock()  

        newBlock, err := genarateBlock(oldBlock, m.BPM)  

    if err != nil {  

    log.Println("newBlock create failed", err.Error())  

        }  

        mutex.Unlock()  

    if isBlockValid(newBlock, oldBlock) {  

    Blockchain = append(Blockchain, newBlock)//将新建的块block加入  

    spew.Dump(Blockchain)//调试使用,将结构打印到控制台  

        }  

        respondWithJson(w, r, http.StatusCreated, newBlock)  

    }  

    (6)编写配置端口文件(.env)

    在项目根路径下创建一个prop.env(文件后缀为.env即可),编写文件内容以KEY=VALUE的形式

    [cpp]view plaincopy

    PORT = 8080  

    在这里需要注意的一点,就是,项目路径必须在GOPATH下。

    (7)接着,就可以写web运行程序了

    [cpp]view plaincopy

    var mutex = &sync.Mutex{}  //用于确保线程安全的Lock  

    func run() error {  

    mux := makeMuxRouter()//编写路由  

    httpAddr := os.Getenv("PORT")  

    log.Println("Listening on", os.Getenv("PORT"))  

        s := &http.Server{  

    Addr:":" + httpAddr,  

            Handler:        mux,  

            ReadTimeout:    time.Second * 10,  

            WriteTimeout:   time.Second * 10,  

            MaxHeaderBytes: 1 << 20,  

        }  

    if err := s.ListenAndServe(); err != nil {  

    return err  

        }  

    return nil  

    }  

    (8)创建主程序

    在主程序当中,构建第一个创世块 genesisBlock。

    [cpp]view plaincopy

    //建立主程序入口  

    func main() {  

    err := godotenv.Load("example.env") //读取根目录中的.env文件  

    if err != nil {  

            log.Fatal(err)  

        }  

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

    }  

    8、运行程序

    使用 go run block.go 命令

    浏览器访问:localhost:8080

    创建新块Block,POST请求(上文提到),再次查询

    再次添加

    再次查询

    -------------------------------------------

    区块链基本实现完成~

    后续就可以参与更加复杂的区块链程序编写了,如工作证明PoW,股权证明PoS,智能合约,Dapps,侧链,P2P,网络广播机制,IPFS存储等。

    相关文章

      网友评论

        本文标题:Go学习之编码实现区块链 - 【blockchain】

        本文链接:https://www.haomeiwen.com/subject/bpqseftx.html