首先,感谢Coral Health的《Code your own blockchain in less than 200 lines of Go!》所提供的Go实现简单区块链一文,鉴于本土朋友希望了解区块链的代码实现,且又对英文解读水平较差。本主在此根据原文进行了亲身实践,亲测可行。
那就开始吧。
- 环境准备与开发工具使用,需详细了解,请阅读我之前写的《入门篇》
- 定义区块、区块链
- 定义Hash算法
- 生成块
- 块的校验
- 确定主链(类比以太坊)
- 构建Web Server
1、环境准备
创建block.go文件。除了Go语言环境、开发工具(Goland)的配置、安装之外,本文涉及的代码依赖还需要:
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、定义区块、区块链
type Block struct { Index int //索引 Timestamp string //时间戳 BPM int //一分钟心跳的频率,脉搏 Hash string //哈希 PrevHash string //前一个哈希 } var Blockchain []Block //区块链
使用散列来确定并保持块的顺序。通过确保PrevHash每一个Block与Hash前一个相同,Block我们知道构成链的块的正确顺序。
使用散列这种方式的原因有两点:
1、 为了节省空间。哈希值来自块上的所有数据。在例子中,我们只有一些数据点,但如果数量有上千个或上百万个先前块的数据。将这些数据散列到单个SHA256字符串中,散列比一次又一次复制前面块中的所有数据要有效得多。
2、保持区块链的完整性。通过存储之前的哈希,就像我们在上图中所做的那样,我们能够确保区块链中的区块按照正确的顺序排列。(这样还能有效的防止恶意链的出现)
3、定义Hash算法
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算法,也可以按照这种模式进行使用。
//定义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、生成块
//生成块block func genarateBlock(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 //此处error为后期完善复杂业务时留用,(可以省略) }
5、校验块
//验证块,查看是否被篡改 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称为叔块。
//判断链长最长的作为正确的链进行覆盖(作为主链) func replaceChain(newBlocks []Block) { if len(newBlocks) > len(Blockchain) { Blockchain = newBlocks } }
到此,生成链的主要逻辑基本实现,接下来,就开始构建Web Server。
7、构建Web Server
(1)设置路由
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
查询所有区块信息
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的方法
//响应json格式化封装 func respondWithJson(w http.ResponseWriter, r *http.Request, code int, 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的信息
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的形式
PORT = 8080
在这里需要注意的一点,就是,项目路径必须在GOPATH下。
(7)接着,就可以写web运行程序了
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。
//建立主程序入口 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存储等。
有任何建议或问题,欢迎加微信一起学习交流,欢迎从事IT,热爱IT,喜欢深挖源代码的行业大牛加入,一起探讨。
个人微信号:bboyHan