使用GO语言进行首个区块链程序的编程实践

引言:

首先告诉大家,代码我托管在码云上,大家可以到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与父区块是否一致哦……

好了,再花点时间重温下这些代码吧,现在区块与区块链里一般都包含哪些属性你也清楚了,再回过头思考下,区块链技术的那些特征,是不是都能对上了?

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180720G0PNH900?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码关注腾讯云开发者

领取腾讯云代金券