引言:
首先告诉大家,代码我托管在码云上,大家可以到gitee上搜索mychain关键字,找“到七色花-开源中国”那个项目,将我代码拉取到本地,对照着代码、README.md和本文一起学习。
一、软件架构:
标题我们已经说了,这里先使用Go语言进行开发,方便快捷。如果你是学Java开发的朋友,也不用慌,环境代码都很容懂。本项目就采用Go加其HTTP server模块,构建一个简单的区块链系统。
二、环境搭建:
这块我不详细说了,网上一找一大把,我丢给大家几个步骤和地址,大家照着去做就行了。
1. 安装go语言环境,下载地址:https://www.golangtc.com/download
2. 安装Goland(推荐),http://www.jetbrains.com/go/download
3. 打开Goland、配置SDK。
4. 导入项目,配置自己的GOPATH为项目所在目录。
三、建项编码:
按照我前面一系列文章,大家知道了,一个区块链是由多个区块连接在一起构成,而一个区块又包括区块头与区块主体,区块头里最重要的就是进行Hash算法。大致有了这些概念后,我们对这些事物进行抽象,形成如下一些代码。
1、首先,创建一个Block.go,定义好我们的区块类、哈希计算函数(这里我们使用sha256算法)、生成新区块函数、生成创世区块函数(所谓的创世区块,就是指整个区块链中的第0个区块,他没有父区块)。
package core
import (
"crypto/sha256"
"encoding/hex"
"time"
)
//定义区块
type Block struct {
Index int64 //区块编号
Timestamp int64 //区块链时间戳
PrevBlockHash string //上一个区块哈希值
Hash string //当前区块哈希值
Data string //区块数据
}
//计算哈希值
func calculateHash(b Block) string {
//简单将区块的编号、时间戳、父区块哈希值及当前区块的数据进行拼接
blockData := string(b.Index) + string(b.Timestamp) + b.PrevBlockHash + b.Data
//进行哈希运算
hashInBytes := sha256.Sum256([]byte(blockData))
hashInStr := hex.EncodeToString(hashInBytes[:])
//log.Printf("blockData = %s , hashInStr = %s", blockData, hashInStr)
return hashInStr
}
//生成区块(传入父区块,新区块的数据体)
func GenerateNewBlock(preBlock Block, data string) Block {
newBlock := Block{}
//区块编号自增
newBlock.Index = preBlock.Index + 1
newBlock.PrevBlockHash = preBlock.Hash
newBlock.Timestamp = time.Now().Unix()
newBlock.Data = data
//计算哈希值
newBlock.Hash = calculateHash(newBlock)
return newBlock
}
//生成创世区块
func GenerateGenesisBlock() Block {
preBlock := Block{}
//创世区块索引一般为0
preBlock.Index = -1
//创世区块没有父区块,所以他的父区块哈希值设置为空字符串即可
preBlock.Hash = ""
//复用生成区块函数,生成创世区块
return GenerateNewBlock(preBlock, "Genesis Block")
}
2、然后创建一个Blockchain.go文件,用于实现对区块链的相关定义及操作。代码上我都加了详细的注释,这里我就不过多介绍,要点主要包括:
a.区块链对象就是一个区块对象数组;
b.抽象出追加区块链函数;
c.追加新区块时,重点是进行数据校验,不是给给数据就追加上去;
type Blockchain struct {
Blocks []*Block //区块数组
}
//生成新的区块链
func NewBlockchain() *Blockchain {
//创建创世区块,将其追加到区块链的第一个区块中
genesisBlock := GenerateGenesisBlock()
blockchain := Blockchain{}
blockchain.ApendBlock(&genesisBlock)
log.Println("完成:【创建创世区块,将其追加到区块链的第一个区块中】")
return &blockchain
}
//将新的数据创建新的区块链,并追加到当前的区块链中
func (bc *Blockchain) SendData(data string) {
//从当前区块链中,获取到最后一个区块,作为父区块
preBlock := bc.Blocks[len(bc.Blocks)-1]
//生成新区块
newBlock := GenerateNewBlock(*preBlock, data);
//添加到当前区块链中
bc.ApendBlock(&newBlock)
log.Println("完成:【新的数据添加的区块链】")
}
//添加区块,将新的区块添加到区块链中
func (bc *Blockchain) ApendBlock(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 (bc *Blockchain) Print() {
for _, block := range bc.Blocks {
fmt.Printf("Index: %d\n", block.Index)
fmt.Printf("Prev.Hash: %s\n", block.PrevBlockHash)
fmt.Printf("Curr.Hash: %s\n", block.Hash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Timestamp:%d\n", block.Timestamp)
fmt.Println()
}
}
//数据校验,传入新区块及其父区块
func isValid(newBlock Block, oldBlock Block) bool {
if newBlock.Index-1 != oldBlock.Index {
return false
}
if newBlock.PrevBlockHash != oldBlock.Hash {
return false
}
if calculateHash(newBlock) != newBlock.Hash {
return false
}
return true
}
3、好了,核心代码就这两文件,接下来我们创建一个HTTP Server 的入口文件Server.go
package main
import (
"net/http"
"qiyang.com/mychain/core"
"encoding/json"
"io"
)
//定义全局的区块链属性
var blockchain *core.Blockchain
//启动服务
func run() {
//定义两个API,映射到两个处理函数
http.HandleFunc("/blockchain/get", blockchainGetHandler)
http.HandleFunc("/blockchain/write", blockchainWriteHandler)
//监听端口8888
http.ListenAndServe("localhost:8888", nil)
}
//获取当前区块链信息
func blockchainGetHandler(w http.ResponseWriter, r *http.Request){
//将区块链数据进行JSON化
bytes , error := json.Marshal(blockchain)
//异常捕获
if error != nil {
http.Error(w, error.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.NewBlockchain()
//启动服务
run()
}
四、运行测试:
1、找到main函数,运行起来:
2、运行好以后,你的控制台会出现:
3、打开浏览器,访问http://localhost:8888/blockchain/get,这就是创世区块信息。
4、然后我们往区块链中写入一个新区块:
5、然后你可以疯狂,往里面写区块了,留意下你的PrevBlockHash与父区块是否一致哦……
好了,再花点时间重温下这些代码吧,现在区块与区块链里一般都包含哪些属性你也清楚了,再回过头思考下,区块链技术的那些特征,是不是都能对上了?
领取专属 10元无门槛券
私享最新 技术干货