基于Golang在单机下创建一个区块链
前端时间wld很火,这段时间meme币也如火如荼,所以我打算看看区块链到底是什么。
区块链定义
区块链的数据结构
废话不多说,区块链,其实是由区块头、区块节点组成
区块节点中,又包括上一个区块的地址、Nonce、时间戳、区块信息等。
这里说说区块信息。
每一个区块链的用处,都是用来存储交易信息的,但是一个区块链只存储一个信息特别站内存,那么有没有办法存放多比交易?区块链套链表数组?理论可以,但是有可能某个数组中,会被修改,无法校验。
这里提到校验,主要原因是因为,区块链有一个特性:去中心化。
去中心化是啥?
简单来说,传统的B/S或C/S架构,都是很多个客户端对接一台或一个集群上的服务器,而去中心化就是说,服务器不再管理数据,所有数据在每个客户端节点上面自行存在,最终通过分布式共识算法来做到区块同步。
但是这里不说去中心化分布式共识的实现,感兴趣的话可以去看看Raft算法和bftp算法。
这里的代码,我是用Golang来实现,因为Golang在后续写分布式共识上比较方便,语法也比Java简洁,当然,你也可以用C/C++来实现。
区块链表
type BlockChain struct { | |
Nodes []*BlockNode `json:"nodes,omitempty"` // 节点数组 | |
Version string `json:"version,omitempty"` // 版本号 | |
TreeSize int64 `json:"treeSize,omitempty"` // 默克尔树大小 | |
} |
这个结构体中,记录了一系列的区块指针数组,当前区块链的版本号,对于TreeSize,具体的作用是用于表示该区块链中,默克尔树的最大大小(为了存储大量数据以及便于校验)。
区块节点
type BlockNode struct { | |
// 前一个结点的"Hash值 | |
Prev"Hash string `json:"prev"Hash,omitempty"` | |
// 当前节点的版本号 | |
Version string `json:"version,omitempty"` | |
// 当前节点创建时的时间戳 | |
Datestamp long `json:"datestamp,omitempty"` | |
// 当前节点中默克尔树 | |
RootTree merkal.MerkalTree `json:"rootTree,omitempty"` | |
// 默克尔书的"Hash码 | |
RootTreeCode string `json:"propertyCode,omitempty"` | |
// 令牌,用于难度计算,计算 nonce 值的过程就是对区块 header 不断的运算哈希,直至找到能使区块哈希小于 target 的 nonce。(这里不做过多的阐述,这和挖矿有关) | |
Nonce string `json:"nonce,omitempty"` | |
// 当前节点默克尔树的大小 | |
LocalTreeSize int64 `json:"LocalTreeSize,omitempty"` | |
} |
默克尔树
先来说说默克尔树的数据结构是什么样的,为什么能够快速进行区块链的校验。
假设我们有一堆消息数组,我们分成n组,然后再两两一组,依次这样,知道剩余一个节点为止。
每一个节点存储的内容,是其来源的两个节点的"Hash值,
type MerkalTree struct { | |
RootNode *MerkalNode `json:"rootNode,omitempty"` | |
DataSet *[]Message `json:"dataSet,omitempty"` | |
RootHash string `json:"root"Hash,omitempty"` | |
} |
如果校验失败,那么我们跟着根节点递归下去校验,即可,时间复杂度是$log_n$
如果说,节点信息是奇数呢?
很简单,保留,把其他的合并即可,奇数的最后合并,或者一开始就把奇数节点复制一份,但是不建议这么做,这样做空间一定会加倍
信息原型
package merkal | |
import ( | |
"encoding/json" | |
"reflect" | |
"time" | |
) | |
type Message struct { | |
Title string `json:"title"` | |
Text string `json:"text"` | |
CreateTime time.Time `json:"createTime"` | |
} | |
func (this *Message) String() string { | |
res, _ := json.Marshal(this) | |
return string(res) | |
} | |
func (this *Message) Equals(other Message) bool { | |
return reflect.DeepEqual(this, other) | |
} |
区块链的操作逻辑
哈希操作—深度哈希的实现
这里需要把哈希单独拿出来,为什么?因为这里不是用的地址,也不是像C系语言一样手搓哈希表的哈希计算方式,我们需要保证信息和节点的强一致性,所以我们是对内容进行哈希。
那么如何实现呢?
要实现深度哈希计算,那么首先就是要实现一个深拷贝的方法
Golang有着深拷贝的实现,但是我们要加密。
我们可以模仿js实现深拷贝的方式,转换为json字符串,然后我们再将json字符串进行RSA加密得到哈希值
对于区块节点的hash计算
type BlockChain struct { | |
Nodes []*BlockNode `json:"nodes,omitempty"` // 节点数组 | |
Version string `json:"version,omitempty"` // 版本号 | |
TreeSize int64 `json:"treeSize,omitempty"` // 默克尔树大小 | |
} | |
func (this *BlockChain) String() string { | |
marshal, _ := json.Marshal(this) | |
return string(marshal) | |
} | |
func NewBlockChain(treeSize int64) *BlockChain { | |
return &BlockChain{TreeSize: treeSize, Version: "V1.0.0"} | |
} | |
func hash(node BlockNode) string { | |
sum256 := sha256.Sum256([]byte(node.String())) | |
return fmt.Sprintf("0x%x", sum256) | |
} |
在这里,我们使用Sprintf来进行16进制输出
好了,这里是基础部分,接下来是比较重要的
默克尔树节点的hash计算
这里,我们要注意,默克尔树的哈希又两种数据结构,一种是信息原型,另一种是默克尔树叶,所以我们使用一个统一接口来实现,然后我们再向下转型
func hash(o1 common.Object, o2 common.Object) string { | |
t1 := "" | |
if o1 != nil { | |
te, f := (o1).(*Message) | |
if f { | |
let := *te | |
t1 = let.String() | |
//fmt.Println(let, "=", t1) | |
} else { | |
let := *(o1).(*MerkalNode) | |
t1 = let.String() | |
//fmt.Println(let, "=", t1) | |
} | |
} | |
t2 := "" | |
if o2 != nil { | |
te, f := (o2).(*Message) | |
if f { | |
let := *te | |
t2 = let.String() | |
//fmt.Println(let, "=", t2) | |
} else { | |
let := *(o2).(*MerkalNode) | |
t2 = let.String() | |
//fmt.Println(let, "=", t2) | |
} | |
} | |
//fmt.Println("正在哈希", t1, t2) | |
bytes := []byte(t1 + t2) | |
sum256 := sha256.Sum256(bytes) | |
return fmt.Sprintf("0x%x", string(sum256[:])) | |
} |
默克尔树的校验
func (this *MerkalTree) Valid(other MerkalTree) (*Message, bool) { | |
if this.RootHash == other.RootHash { | |
return nil, true | |
} | |
return this.RootNode.Valid(other.RootNode, this.DataSet, other.DataSet) | |
} | |
func (this *MerkalNode) Valid(other *MerkalNode, oldData *[]Message, newData *[]Message) (*Message, bool) { | |
if this.Equals(other) { | |
return nil, true | |
} | |
if this.Left != nil && other.Left == nil { | |
return nil, false | |
} | |
if (this.Right != nil && other.Right == nil) || (this.Right == nil && other.Right != nil) { | |
return nil, false | |
} | |
if this.Left == nil && other.Left != nil { | |
return nil, true | |
} | |
if this.Left == nil && this.Right == nil && this.Rp != -1 && this.Lp != -1 { | |
LpMsg := (*newData)[other.Lp] | |
if this.Lp != other.Lp { | |
return &LpMsg, false | |
} | |
RpMsg := (*newData)[other.Rp] | |
if this.Rp != other.Rp { | |
return &RpMsg, false | |
} | |
oldLpMsg := (*oldData)[other.Lp] | |
if LpMsg != oldLpMsg { | |
return &LpMsg, false | |
} | |
oldRpMsg := (*oldData)[other.Rp] | |
if RpMsg != oldRpMsg { | |
return &RpMsg, false | |
} | |
} | |
valid, b := this.Left.Valid(other.Left, oldData, newData) | |
if !b { | |
return valid, b | |
} | |
node, b2 := this.Right.Valid(other.Right, oldData, newData) | |
if !b2 { | |
return node, b2 | |
} | |
return nil, true | |
} |
其实就是个二分,我们只需要递归找到最后一个出错的节点就可以了,但是这个代码有个问题,不知大家有没有发现?
比如有这样的节点
B没有问题,C也没有问题,但是客户端在传输的过程中,A出现了错误怎么办?
所以这里我们还需要对当前节点进行校验
最后的代码如下
if b && b2 && !this.Equals(other) { | |
return nil, false | |
} |
这里我们就不反回哪个节点报错了,因为没有意义。
如果需要返回的话,自行进行一次接口抽象吧,哈哈,最后的代码
func (this *MerkalNode) Valid(other *MerkalNode, oldData *[]Message, newData *[]Message) (*Message, bool) { | |
if this.Equals(other) { | |
return nil, true | |
} | |
if this.Left != nil && other.Left == nil { | |
return nil, false | |
} | |
if (this.Right != nil && other.Right == nil) || (this.Right == nil && other.Right != nil) { | |
return nil, false | |
} | |
if this.Left == nil && other.Left != nil { | |
return nil, true | |
} | |
if this.Left == nil && this.Right == nil && this.Rp != -1 && this.Lp != -1 { | |
LpMsg := (*newData)[other.Lp] | |
if this.Lp != other.Lp { | |
return &LpMsg, false | |
} | |
RpMsg := (*newData)[other.Rp] | |
if this.Rp != other.Rp { | |
return &RpMsg, false | |
} | |
oldLpMsg := (*oldData)[other.Lp] | |
if LpMsg != oldLpMsg { | |
return &LpMsg, false | |
} | |
oldRpMsg := (*oldData)[other.Rp] | |
if RpMsg != oldRpMsg { | |
return &RpMsg, false | |
} | |
} | |
valid, b := this.Left.Valid(other.Left, oldData, newData) | |
if !b { | |
return valid, b | |
} | |
node, b2 := this.Right.Valid(other.Right, oldData, newData) | |
if !b2 { | |
return node, b2 | |
} | |
if b && b2 && !this.Equals(other) { | |
return nil, false | |
} | |
return nil, true | |
} |
一些基本的CRUD和默克尔树的生成
这些就轻轻松松了,我这上面就全部写在一起,具体的源代码可以去看我的github仓库,嘿嘿
BlockChain
func (this *BlockChain) String() string { | |
marshal, _ := json.Marshal(this) | |
return string(marshal) | |
} | |
func NewBlockChain(treeSize int64) *BlockChain { | |
return &BlockChain{TreeSize: treeSize, Version: "V1.0.0"} | |
} | |
func hash(node BlockNode) string { | |
sum256 := sha256.Sum256([]byte(node.String())) | |
return fmt.Sprintf("0x%x", sum256) | |
} | |
func (this *BlockChain) AddMsg(message *merkal.Message) { | |
nodeLen := 0 | |
Lock.Lock() | |
nodeLen = len(this.Nodes) | |
defer Lock.Unlock() | |
if nodeLen == 0 { | |
this.AddNode(NewBlockNode("head", this.Version, merkal.MerkalTree{})) | |
nodeLen = len(this.Nodes) | |
} | |
prev := this.Nodes[nodeLen-1] | |
useSize := prev.RootTree.Size() | |
if useSize < this.TreeSize { | |
prev.RootTree.AddNode(message) | |
return | |
} | |
tree := new(merkal.MerkalTree) | |
tree.AddNode(message) | |
node := NewBlockNode(hash(*prev), this.Version, *tree) | |
this.AddNode(node) | |
} | |
func (this *BlockChain) AddNode(node *BlockNode) { | |
defer func() { | |
a := recover() | |
if a != nil { | |
fmt.Println(a) | |
} | |
}() | |
if node.Version != this.Version { | |
panic("版本号不对") | |
} | |
nodeLen := len(this.Nodes) | |
if nodeLen == 0 { | |
this.Nodes = append(this.Nodes, NewBlockNode("head", this.Version, merkal.MerkalTree{})) | |
nodeLen = len(this.Nodes) | |
} | |
prev := this.Nodes[nodeLen-1] | |
node.PrevHash = hash(*prev) | |
this.Nodes = append(this.Nodes, node) | |
} |
BlockNode
func NewBlockNode(prevHash string, version string, rootTree merkal.MerkalTree) *BlockNode { | |
node := &BlockNode{PrevHash: prevHash, | |
Version: version, | |
Datestamp: long(time.Now().UnixMilli()), | |
RootTree: rootTree, | |
Nonce: getNonce(), | |
} | |
node.Build() | |
return node | |
} | |
func getNonce() string { | |
return string("test") | |
} | |
func (this *BlockNode) Build() { | |
this.RootTreeCode = this.RootTree.RootHash | |
this.LocalTreeSize = this.RootTree.Size() | |
} | |
func (this *BlockNode) Valid(node *BlockNode) bool { | |
defer func() bool { | |
err := recover() | |
if err != nil { | |
log.Println(err) | |
return false | |
} | |
return true | |
}() | |
this.Build() | |
node.Build() | |
localVersion, localSize := node.Version, node.LocalTreeSize | |
if this.Version != localVersion || this.LocalTreeSize != localSize { | |
return false | |
} | |
valid, b := this.RootTree.Valid(node.RootTree) | |
if b { | |
return true | |
} else { | |
panic(valid.String()) | |
} | |
} | |
func (this *BlockNode) String() string { | |
this.LocalTreeSize = this.RootTree.Size() | |
jsonBytes, _ := json.Marshal(this) | |
return string(jsonBytes) | |
} |
MerkalNode
func (this *MerkalNode) String() string { | |
marshal, _ := json.Marshal(this) | |
return string(marshal) | |
} | |
func (this *MerkalNode) Equals(other *MerkalNode) bool { | |
if other == nil { | |
return false | |
} | |
return reflect.DeepEqual(this, other) | |
} | |
func NewMerkalNode(Left *MerkalNode, Right *MerkalNode, Hash string, Lp int, Rp int) *MerkalNode { | |
res := &MerkalNode{Left: Left, Right: Right, Hash: Hash, Lp: Lp, Rp: Rp} | |
//fmt.Println(res) | |
return res | |
} |
MerkalTree
func getSize(node *MerkalNode, size int64) int64 { | |
if node == nil { | |
return size | |
} | |
size = getSize(node.Left, size) | |
size++ | |
size = getSize(node.Right, size) | |
return size | |
} | |
func (this MerkalTree) Size() int64 { | |
return getSize(this.RootNode, 0) | |
} | |
func (this *MerkalTree) AddNode(data *Message) { | |
if this.DataSet == nil { | |
this.DataSet = (new([]Message)) | |
} | |
messages := append((*this.DataSet), *data) | |
this.DataSet = (&messages) | |
this.Detail() | |
} | |
func (this *MerkalTree) String() string { | |
res, _ := json.Marshal(this) | |
return string(res) | |
} | |
func (this *MerkalTree) binaryDetail(data *[]Message, l int, r int) (resNode *MerkalNode) { | |
if r == l { | |
resNode = NewMerkalNode(nil, nil, hash(&(*data)[l], nil), l, r) | |
return resNode | |
} | |
if r-l == 1 { | |
resNode = NewMerkalNode(nil, nil, hash(&(*data)[l], &(*data)[r]), l, r) | |
return resNode | |
} | |
mid := (l-r)>>1 + r | |
left := this.binaryDetail(data, l, mid) | |
right := this.binaryDetail(data, mid+1, r) | |
resNode = NewMerkalNode(left, right, hash(left, right), -1, -1) | |
return resNode | |
} | |
func (this *MerkalTree) Detail() { | |
data := this.DataSet | |
l := 0 | |
r := len(*data) - 1 | |
detail := this.binaryDetail(data, l, r) | |
this.RootNode = detail | |
this.RootHash = (hash(detail, nil)) | |
} | |
func NewMerkalTree(dataSet *[]Message) (res *MerkalTree) { | |
res = &MerkalTree{DataSet: dataSet} | |
res.RootHash = hash(res, nil) | |
return res | |
} |
Message
func (this *Message) String() string { | |
res, _ := json.Marshal(this) | |
return string(res) | |
} | |
func (this *Message) Equals(other Message) bool { | |
return reflect.DeepEqual(this, other) | |
} |
公共父接口
type Object interface { | |
String() string | |
} |
总结
以上内容便是本期的分享,关于Raft和bftp我将在以后进行实践后再发文,大家也可以去看看etcd来扩展下见识
源代码github地址:https://github.com/Karosown/blockLink.git
目前raft和bftp算法暂未实现,同时也欢迎大家一起来完善这个项目