本文主要利用 Go 语言对区块链模型进行了简单的实现,通过 GoLand 创建链式结构和一个简单的 http server,对外暴露读写接口,运行 rpc 并以地址访问形式向区块链发送数据和读取数据。
简单区块链的实现大致步骤分为:
(1)创建 Block
(2)创建 Blockchain
(3)创建 Http server
在实现之前,首先通过 GoLand 创建 blockchainDemo 项目,并建立对应的 Block.go
、Blockchain.go
以及 Server.go
文件,具体的目录如下。
一、创建 Block
- 创建 Block 文件
- 创建 Block 结构体与相关函数
package core
import (
"crypto/sha256"
"encoding/hex" "time")
type Block struct {
Index int64 // 区块编号
Timestamp int64 // 区块时间戳
PrevBlochHash string // 上一个区块哈希值
Hash string // 当前区块哈希值
Data string // 区块数据
}
// calculateHash 计算哈希值
func calculateHash(b Block) string {
blockData := string(b.Index) + string(b.Timestamp) + b.PrevBlochHash + b.Data
hashInBytes := sha256.Sum256([]byte(blockData))
return hex.EncodeToString(hashInBytes[:])
}
// GenerateNewBlock 创建新区块
func GenerateNewBlock(preBlock Block, data string) Block {
newBlock := Block{}
newBlock.Index = preBlock.Index + 1
newBlock.PrevBlochHash = preBlock.Hash
newBlock.Timestamp = time.Now().Unix()
newBlock.Data = data
newBlock.Hash = calculateHash(newBlock)
return newBlock
}
// GenerateGenesisBlock 创建世纪区块
func GenerateGenesisBlock() Block {
preBlock := Block{}
preBlock.Index = -1
preBlock.Hash = ""
return GenerateNewBlock(preBlock, "Genesis Block")
}
这里需要注意的是,在创建世纪区块时,考虑到对 GenerateNewBlock 函数的复用,因此,创建了一个 preBlock 来辅助世纪块的创建,实际区块链上世纪块前面不存在其他区块,且世纪块以 0 作为索引。
二、创建 Blockchain
- 创建 Blockchain 文件
- 创建 Blockchain 结构体及相关方法
package core
import (
"fmt"
"log")
type Blockchain struct {
Blocks []*Block
}
// CreateBlockchain 创建区块链
func CreateBlockchain() *Blockchain {
genesisBlock := GenerateGenesisBlock()
blockchain := Blockchain{}
blockchain.AppendBlock(&genesisBlock)
return &blockchain
}
// SendData 向区块链添加数据
func (bc *Blockchain) SendData(data string) {
preBlock := bc.Blocks[len(bc.Blocks)-1]
newBlock := GenerateNewBlock(*preBlock, data)
bc.AppendBlock(&newBlock)
}
// AppendBlock 向区块链添加新区块
func (bc *Blockchain) AppendBlock(newBlock *Block) {
if len(bc.Blocks) == 0 {
bc.Blocks = append(bc.Blocks, newBlock)
return
}
if isValid(*newBlock, *bc.Blocks[len(bc.Blocks)-1]) {
bc.Blocks = append(bc.Blocks, newBlock)
} else {
log.Fatal("invalid block")
}
}
// 判断新添加的区块是否合法
func isValid(newBlock Block, oldBlock Block) bool {
if newBlock.Index-1 != oldBlock.Index {
return false
}
if newBlock.PrevBlochHash != oldBlock.Hash {
return false
}
if calculateHash(newBlock) != newBlock.Hash {
return false
}
return true
}
// Print 对区块链上的区块内容进行打印
func (bc *Blockchain) Print() {
for _, block := range bc.Blocks {
fmt.Printf("Index: %d\n", block.Index)
fmt.Printf("Prev.Hash: %s\n", block.PrevBlochHash)
fmt.Printf("Curr.Hash: %s\n", block.Hash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Timestamp: %d\n", block.Timestamp)
}
}
三、创建 Http server
- 创建 http server
- 提供 API 访问接口
package main
import (
"blockchainDemo/core"
"encoding/json" "io" "net/http")
var blockchain *core.Blockchain
func run() {
http.HandleFunc("/blockchain/get", blockchainGetHandler)
http.HandleFunc("/blockchain/write", blockchainWriteHandler)
http.ListenAndServe("localhost:8888", nil)
}
// 读接口
func blockchainGetHandler(w http.ResponseWriter, r *http.Request) {
bytes, err := json.Marshal(blockchain)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.WriteString(w, string(bytes))
}
// 写接口
func blockchainWriteHandler(w http.ResponseWriter, r *http.Request) {
blockData := r.URL.Query().Get("data")
blockchain.SendData(blockData)
blockchainGetHandler(w, r)
}
func main() {
blockchain = core.CreateBlockchain()
run()
}
四、模型测试
在 GoLand 上运行 http server,从代码中可知监听端口为 8888,因此,读写操作对应的地址是:
http://localhost:8888/blockchain/get
http://localhost:8888/blockchain/write?data=Send 1 BTC to Me
参数 data 的内容可自定义
// 网页显示效果
{
"Blocks":[
{
"Index":0,
"Timestamp":1674227301,
"PrevBlochHash":"",
"Hash":"90d7d6d9adc8a6dd4eca1e30d8c1a8556a8e3e508da81f30a9e520c2ee7124b0",
"Data":"Genesis Block"
},
{
"Index":1,
"Timestamp":1674227338,
"PrevBlochHash":"90d7d6d9adc8a6dd4eca1e30d8c1a8556a8e3e508da81f30a9e520c2ee7124b0",
"Hash":"1a7c54ce0eaba45d5591640925405d90e74d9da5fa919b4e6eefa2447b2a4fb0",
"Data":"Send 1 BTC to Me"
}
]
}