预定公链开发视频课

黎跃春区块链博客


黎跃春,江湖人称春哥,孔壹学院、 链乎创始人,区块链职业教育布道师,通信和信息技术培养工程区块链高级授课专家。


链乎问答社区
链乎公众号

002 - go 实现简单的区块链

作者:黎跃春

博客:http://liyuechun.org

官网:http://kongyixueyuan.com

安装依赖软件

$ go get github.com/davecgh/go-spew/spew

$ go get github.com/gorilla/mux

$ go get github.com/joho/godotenv
  • spew 在控制台中格式化输出相应的结果。

  • gorilla/mux 是编写web处理程序的流行软件包。

  • godotenv 可以从我们项目的根目录的 .env 文件中读取数据。

实现区块链

新建 main.go,然后实现区块链的代码

package main

import (
        "crypto/sha256"
        "encoding/hex"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "net/http"
        "os"
        "strconv"
        "strings"
        "sync"
        "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
}
  • Index 是区块链中数据记录的位置

  • Timestamp 是自动确定的,并且是写入数据的时间

  • BPM 或每分钟跳动,是你的脉率

  • Hash 是代表这个数据记录的SHA256标识符

  • PrevHash 是链中上一条记录的SHA256标识符

区块数组

定义 Blockchain 存储各个 Block

var Blockchain []Block

生成区块

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
}

生成区块的 HashcalculateHash 函数连接 IndexTimestampBPMPrevHashBlock 我们提供作为参数,并返回SHA256哈希为一个字符串。

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 来确保它们按预期递增。我们也检查以确保我们 PrevHash 的确与 Hash 前一个区块相同。最后,我们希望通过在当前块上 calculateHash 再次运行该函数来检查当前块的散列。让我们写一个 isBlockValid 函数来完成所有这些事情并返回一个 bool

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
}

web 服务器

使用 run() 创建我们的服务器

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
}

makeMuxRouter 主要定义路由处理,当收到 GET 请求,就会调用 handleGetBlockchain 方法。当收到 POST 请求,就会调用 handleWriteBlock 方法。

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

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

handleWriteBlock 主要是生成新的区块。

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)

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

主函数

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}

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

}

godotenv.Load() 允许我们从 根目录的文件 .env 读取相应的变量。

genesisBlock 创建初始区块。

run() 启动 web 服务

启动web服务器

$ go run main.go

可以通过 Postman软件模拟网络请求。

通过 GET 访问 http://localhost:8080 可以获取区块链信息。

通过 POST 访问 http://localhost:8080 可以添加新的区块信息。

通过 curl 测试

GET

curl http://localhost:8080

源码下载

go实现简单的区块链

版权声明:博客中的文章版权归博主所有,未经授权,禁止转载,转载请注明出处,合作请联系:liyc1215(微信)

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦