开发环境:Go语言
本教程是学习Jeiwan的博客后的学习笔记,代码实现也参考它的为主,精简了叙述并在适当位置添加了一些必备的小知识和适当的代码注释,如介绍哈希。
本教程是为了逐步教你设计一款简化的区块链原型币。通过我们不断添加功能,完成一个可交易的原型币。
本节我们设计一个单机版的仅支持保存信息的原型币。
- 单机版,仅支持保存信息
区块:
区块链中的最基本单位,一个区块包含区块头和区块体。
区块头
包含这个区块的相关信息。
如:
- 区块产生时间
- 本区块哈希值(保证本区块不篡改)
- 上一个区块哈希值(通过该哈希找到上一个区块,能串起来区块链)
区块体
真正包含数据的是区块体。
对于原型币,为了简便设计,将区块体和区块头合二为一。
请看该区块的代码
type Block struct {
Timestamp int64 //区块产生时间时的当前系统时间,从1970开始的毫秒数
PrevHash []byte //上一个区块哈希值,指向上一个区块
Hash []byte //本区块哈希值,根据本区块的字节经过哈希算法算的
Data []byte //区块体的信息,自己填写希望保存的信息
}
注意这里都使用byte数组,区块的底层实现其实都是字节byte。
哈希
在我们的区块中,唯一不明确的是,这个区块的哈希要如何计算呢?
先普及一下哈希,哈希是一个单向计算,将原信息通过哈希函数计算出哈希值,即hash=HASH(bytes);得到一个固定字节大小的字符串hash,可以用来代表原来的bytes数据,我们常见的MD5就是一种哈希算法。
哈希具有三大特性。
- 无碰撞(Collision-free),可认为hash和bytes一一对应,若bytes被篡改,则hash就变了。
- 隐藏信息(Hiding),通过hash字符串不能反推原信息。
- 谜题友好(puzzle-friendly),意思是若要从hash字符串反推原信息,只能遍历尝试每一个原信息。
我们在区块链中就是应用了无碰撞的特性,保证了区块不被串改信息。
本节的原型币的哈希函数采用SHA256,公式如下:
Hash = SHA256(PrevBlockHash + Timestamp + Data)
实现代码如下:
func (b *Block) SetHash() {
timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
//由于Timestamp是int64,需要转化为bytes,再和其他信息拼接,这里暂时没有要求用什么方式转,你可以直接按int64的字符串意义转成byte,也可以按int64的底层byte表示转成byte,这里取前者。只要还原成int64时按照同一个方式即可。
headers := bytes.Join([][]byte{b.PrevHash, b.Data, timestamp}, []byte{})
hash := sha256.Sum256(headers)
b.Hash = hash[:]
}
了解了哈希的计算,我们就知道如何制造一个区块了。
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevHash, []byte{}}
block.SetHash()
return block
}
区块链
区块链的本质就是串起来的区块,是一种特殊的数据库。通过每一个区块中的上一个区块的哈希值,将区块一个一个的串起来。
为了方便起见,我们单机版的币就用数组,按顺序存储。
代码如下:
type Blockchain struct {
Blocks []*Block
}
给区块链添加区块的方法如下:
func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.blocks[len(bc.blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.Blocks = append(bc.blocks, newBlock)
}
但是,特殊的是第一个创世区块,需要我们手动制造一下,中本聪也是这么做的。
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}
据此,我们可以创建创世区块链,
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
最后,让我们来运行一下我们的原型币。
func main() {
bc := datastruct.GenesisBlockchain()
bc.AddBlock("Send 1 BTC to Lin")
bc.AddBlock("Send 2 BTC to Lin")
for _, block := range bc.Blocks {
fmt.Printf("Prev. hash: %x\n", block.PrevHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
fmt.Println()
}
}
所得结果,你的运行结果和我的都会不一样,因为你的区块产生时间是不一样的:
Prev. hash:
Data: Genesis Block
Hash: 0a6a60a1def38b806d5e42410468d63d626171f75717a6a99ca7a88c98d69a70
Prev. hash: 0a6a60a1def38b806d5e42410468d63d626171f75717a6a99ca7a88c98d69a70
Data: Send 1 BTC to Lin
Hash: a2daba78c1c635c7b5570cac0eaa699455b940acaefc904f394e199dc2e22dee
Prev. hash: a2daba78c1c635c7b5570cac0eaa699455b940acaefc904f394e199dc2e22dee
Data: Send 2 BTC to Lin
Hash: cf17fc4e346328c957b97ef6e92cf91c7b3cd9a798363afbd84fc7bae9657273
参考:
源码
https://github.com/linxinzhe/go-simple-coin/tree/1_prototype_coin
下一节:
全系列:
关于我:
linxinzhe,全栈工程师,目前供职于某500强通信企业,人工智能,区块链爱好者。
GitHub:https://github.com/linxinzhe
欢迎留言讨论,也欢迎关注我,收获更多区块链开发相关的知识,我也会关注你的哦!
网友评论