Go编码实现区块链

原blog:https://blog.csdn.net/han0373/article/details/80450825

1、环境准备

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

 
  1. package main

  2.  
  3. import (

  4. "crypto/sha256"

  5. "encoding/hex"

  6. "encoding/json"

  7. "io"

  8. "log"

  9. "net/http"

  10. "os"

  11. "time"

  12.  
  13. "github.com/davecgh/go-spew/spew"

  14. "github.com/gorilla/mux"

  15. "github.com/joho/godotenv"

  16. )

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

2、定义区块、区块链

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

 
  1. type Block struct {

  2. Index int //索引

  3. Timestamp string //时间戳

  4. BPM int //一分钟心跳的频率,脉搏

  5. Hash string //哈希

  6. PrevHash string //前一个哈希

  7. }

  8.  
  9. 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算法,也可以按照这种模式进行使用。

 
  1. //定义hash算法 SHA256 hasing

  2. func calculateHash(block Block) string {

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

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

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

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

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

  10. return hex.EncodeToString(hashed)

  11. }

4、生成块

 
  1. //生成块block

  2. func genarateBlock(oldBlock Block, BPM int) (Block, error) {

  3. var newBlock Block

  4. t := time.Now()

  5.  
  6. newBlock.Index = oldBlock.Index + 1

  7. newBlock.Timestamp = t.String()

  8. newBlock.BPM = BPM

  9. newBlock.PrevHash = oldBlock.Hash

  10. newBlock.Hash = calculateHash(newBlock)

  11.  
  12. return newBlock, nil //此处error为后期完善复杂业务时留用,(可以省略)

  13. }

5、校验块

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

  2. func isBlockValid(newBlock Block, oldBlock Block) bool {

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

  4. return false

  5. }

  6. if newBlock.PrevHash != oldBlock.Hash {

  7. return false

  8. }

  9. if calculateHash(newBlock) != newBlock.Hash {

  10. return false

  11. }

  12. return true

  13. }

6、确定主链

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

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

  2. func replaceChain(newBlocks []Block) {

  3. if len(newBlocks) > len(Blockchain) {

  4. Blockchain = newBlocks

  5. }

  6. }

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

7、构建Web Server

(1)设置路由

 
  1. func makeMuxRouter() http.Handler {

  2. muxRouter := mux.NewRouter()

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

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

  5. return muxRouter

  6. }

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

    handleWriteBlock:添加新块Block,POST请求

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

(2)定义Message

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

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

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

(3)handleGetBlockchain

查询所有区块信息

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

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

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

  4.  
  5. if err != nil {

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

  7. return

  8. }

  9. io.WriteString(w, string(bytes))

  10. }

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

 
  1. //响应json格式化封装

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

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

  4. if err != nil {

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

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

  7. return

  8. }

  9. w.WriteHeader(code)

  10. w.Write(reponse)

  11. }

(5)handleWriteBlock

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

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

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

  3. var m Message

  4.  
  5. decoder := json.NewDecoder(r.Body)

  6. if err := decoder.Decode(&m); err != nil {

  7. respondWithJson(w, r, http.StatusBadRequest, r.Body)

  8. return

  9. }

  10. defer r.Body.Close()

  11.  
  12. oldBlock := Blockchain[len(Blockchain)-1]

  13. mutex.Lock()

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

  15. if err != nil {

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

  17. }

  18. mutex.Unlock()

  19.  
  20. if isBlockValid(newBlock, oldBlock) {

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

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

  23. }

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

  26.  
  27. }

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

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

PORT = 8080

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

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

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

  2.  
  3. func run() error {

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

  5.  
  6. httpAddr := os.Getenv("PORT")

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

  8. s := &http.Server{

  9. Addr: ":" + httpAddr,

  10. Handler: mux,

  11. ReadTimeout: time.Second * 10,

  12. WriteTimeout: time.Second * 10,

  13. MaxHeaderBytes: 1 << 20,

  14. }

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

  17. return err

  18. }

  19. return nil

  20. }

(8)创建主程序

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

 
  1. //建立主程序入口

  2. func main() {

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

  4. if err != nil {

  5. log.Fatal(err)

  6. }

  7.  
  8. go func() {

  9. t := time.Now()

  10. genesisBlock := Block{} //创世块的创建,第一个块生成

  11. genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), ""}

  12. spew.Dump(genesisBlock)

  13.  
  14. mutex.Lock()

  15. Blockchain = append(Blockchain, genesisBlock)

  16. mutex.Unlock()

  17.  
  18. }()

  19. log.Fatal(run())

  20. }

 

8、运行程序

使用 go run block.go 命令

浏览器访问:localhost:8080

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

再次添加

再次查询

猜你喜欢

转载自blog.csdn.net/xinsuiqingfeng/article/details/81004736