首先打开Goland开发工具
新建工程:mypublicchain
创建目录:day01_01_Base_Prototype
Block.go
在day01_01_Base_Prototype
目录下,再创建一个目录作为包命名为BLC。
在该目录下创建一个文件Block.go
。
在Block.go
文件中编写代码如下:
package BLC
import (
"time"
"strconv"
"bytes"
"crypto/sha256"
)
//step1:创建Block结构体
type Block struct {
//字段:
//高度Height:其实就是区块的编号,第一个区块叫创世区块,高度为0
Height int64
//上一个区块的哈希值ProvHash:
PrevBlockHash []byte
//交易数据Data:目前先设计为[]byte,后期是Transaction
Data [] byte
//时间戳TimeStamp:
TimeStamp int64
//哈希值Hash:32个的字节,64个16进制数
Hash []byte
}
//step2:创建新的区块
func NewBlock(data string,provBlockHash []byte,height int64) *Block{
//创建区块
block:=&Block{height,provBlockHash,[]byte(data),time.Now().Unix(),nil}
//设置哈希值
block.SetHash()
return block
}
//step3:设置区块的hash
func (block *Block) SetHash(){
//1.将高度转为字节数组
heightBytes:= IntToHex(block.Height)
//fmt.Println(heightBytes)
//2.时间戳转为字节数组
//timeBytes:=IntToHex(block.TimeStamp)
//转为二进制的字符串
//fmt.Println(block.TimeStamp)
//fmt.Printf("%x,%b\n",block.TimeStamp,block.TimeStamp)
timeString := strconv.FormatInt(block.TimeStamp,2)
//fmt.Println("timeString:",timeString)
timeBytes := [] byte(timeString)
//fmt.Println("timeStamp:",timeBytes)
//3.拼接所有的属性
blockBytes:= bytes.Join([][]byte{
heightBytes,
block.PrevBlockHash,
block.Data,
timeBytes},[]byte{})
//4.生成哈希值
hash:=sha256.Sum256(blockBytes)//数组长度32位
block.Hash = hash[:]
}
//step4:创建创世区块:
func CreateGenesisBlock(data string) *Block{
return NewBlock(data,make([] byte,32,32),0)
}
utils.go
在BLC包下,再创建一个go文件,命名为:utils.go
。里面编写所需要的各种工具方法。
编写代码如下:
package BLC
import (
"bytes"
"encoding/binary"
"log"
)
/*
将一个int64的整数:转为二进制后,每8bit一个byte。转为[]byte
*/
func IntToHex(num int64) []byte {
buff := new(bytes.Buffer)
//将二进制数据写入w
//func Write(w io.Writer, order ByteOrder, data interface{}) error
err := binary.Write(buff, binary.BigEndian, num)
if err != nil {
log.Panic(err)
}
//转为[]byte并返回
return buff.Bytes()
}
BlockChain.go
在BLC包下,再创建一个go文件,BlockChain.go
。
编写如下代码:
package BLC
//step5:创建区块链
type BlockChain struct {
Blocks []*Block //存储有序的区块
}
//step6:创建区块链,带有创世区块
func CreateBlockChainWithGenesisBlock(data string) *BlockChain{
//创建创世区块
genesisBlock := CreateGenesisBlock(data)
//返回区块链对象
return &BlockChain{[]*Block{genesisBlock}}
}
//step7:添加一个新的区块,到区块链中
func (bc *BlockChain) AddBlockToBlockChain(data string,height int64,prevHash [] byte){
//创建新区块
newBlock := NewBlock(data,prevHash,height)
//添加到切片中
bc.Blocks = append(bc.Blocks,newBlock)
}
main.go
在day01_01_Base_Prototype
目录下,直接创建main.go
。
并编写如下代码:
package main
import (
"./BLC"
"fmt"
)
func main() {
//1.测试Block
//block:=BLC.NewBlock("I am a block",make([]byte,32,32),1)
//fmt.Println(block)
//2.测试创世区块
//genesisBlock :=BLC.CreateGenesisBlock("Genesis Block..")
//fmt.Println(genesisBlock)
//3.测试区块链
//genesisBlockChain := BLC.CreateBlockChainWithGenesisBlock()
//fmt.Println(genesisBlockChain)
//fmt.Println(genesisBlockChain.Blocks)
//fmt.Println(genesisBlockChain.Blocks[0])
//4.测试添加新区块
blockChain:=BLC.CreateBlockChainWithGenesisBlock("Genesis Block..")
blockChain.AddBlockToBlockChain("Send 100RMB To Wangergou",blockChain.Blocks[len(blockChain.Blocks)-1].Height+1,blockChain.Blocks[len(blockChain.Blocks)-1].Hash)
blockChain.AddBlockToBlockChain("Send 300RMB To lixiaohua",blockChain.Blocks[len(blockChain.Blocks)-1].Height+1,blockChain.Blocks[len(blockChain.Blocks)-1].Hash)
blockChain.AddBlockToBlockChain("Send 500RMB To rose",blockChain.Blocks[len(blockChain.Blocks)-1].Height+1,blockChain.Blocks[len(blockChain.Blocks)-1].Hash)
fmt.Println(blockChain)
}
首先从 “区块” 谈起。在区块链中,真正存储有效信息的是区块(block)。而在比特币中,真正有价值的信息就是交易(transaction)。实际上,交易信息是所有加密货币的价值所在。除此以外,区块还包含了一些技术实现的相关信息,比如版本,当前时间戳和前一个区块的哈希。
不过,我们要实现的是一个简化版的区块链,而不是一个像比特币技术规范所描述那样成熟完备的区块链。所以在我们目前的实现中,区块仅包含了部分关键信息,它的数据结构如下(在Block.go
文件中):
创建Block.go
文件,并添加Block
的结构体,代码如下:
//step1:创建Block结构体
type Block struct {
//字段:
//高度Height:其实就是区块的编号,第一个区块叫创世区块,高度为0
Height int64
//上一个区块的哈希值ProvHash:
PrevBlockHash []byte
//交易数据Data:目前先设计为[]byte,后期是Transaction
Data [] byte
//时间戳TimeStamp:
TimeStamp int64
//哈希值Hash:32个的字节,64个16进制数
Hash []byte
}
说明一下:
字段 | 解释 |
---|---|
Height |
当前区块的高度,就是区块的编号 |
PrevBlockHash |
前一个块的哈希,即父哈希 |
Data |
区块存储的实际有效信息,也就是交易。随着项目的逐步完善后期会将Data修改为Transaction。 |
Timestamp |
当前时间戳,也就是区块创建的时间 |
Hash |
当前块的哈希 |
什么是创世区块?
区块链中的第一个区块,也就是高度Heigth为0的区块,往往叫做创世区块。
我们这里的 Timestamp
,PrevBlockHash
, Hash
,在比特币技术规范中属于区块头(block header
),区块头是一个单独的数据结构。
完整的比特币的区块头(block header
)结构如下:
Field | Purpose | Updated when... | Size (Bytes) |
---|---|---|---|
Version | Block version number | You upgrade the software and it specifies a new version | 4 |
hashPrevBlock | 256-bit hash of the previous block header | A new block comes in | 32 |
hashMerkleRoot | 256-bit hash based on all of the transactions in the block | A transaction is accepted | 32 |
Time | Current timestamp as seconds since 1970-01-01T00:00 UTC | Every few seconds | 4 |
Bits | Current target in compact format | The difficulty is adjusted | 4 |
Nonce | 32-bit number (starts at 0) | A hash is tried (increments) | 4 |
而我们的 Data
, 在比特币中对应的是交易,是另一个单独的数据结构。为了简便起见,目前将这两个数据结构放在了一起。在真正的比特币中,区块的数据结构如下:
Field | Description | Size |
---|---|---|
Magic no | value always 0xD9B4BEF9 | 4 bytes |
Blocksize | number of bytes following up to end of block | 4 bytes |
Blockheader | consists of 6 items | 80 bytes |
Transaction counter | positive integer VI = VarInt | 1 - 9 bytes |
transactions | the (non empty) list of transactions |
区块链的结构
在我们的简化版区块中,还有一个 Hash
字段,那么,要如何计算哈希呢?哈希计算,是区块链一个非常重要的部分。正是由于它,才保证了区块链的安全。计算一个哈希,是在计算上非常困难的一个操作。即使在高速电脑上,也要耗费很多时间 (这就是为什么人们会购买 GPU,FPGA,ASIC 来挖比特币) 。这是一个架构上有意为之的设计,它故意使得加入新的区块十分困难,继而保证区块一旦被加入以后,就很难再进行修改。在接下来的内容中,我们将会讨论和实现这个机制。
在本节,我们会讨论哈希计算。如果你已经熟悉了这个概念,可以直接跳过。
获得指定数据的一个哈希值的过程,就叫做哈希计算。一个哈希,就是对所计算数据的一个唯一表示。对于一个哈希函数,输入任意大小的数据,它会输出一个固定大小的哈希值。下面是哈希的几个关键特性:
所以说,hash
是一种算法, 是将文本转换成具有相同长度的,不可逆的杂凑字符串散列(很多资料也叫指纹或叫消息摘要Message Digest
)。本身词义"切碎,剁碎"。也可以说,hash
就是找到一种数据内容和数据存放地址之间的映射关系。
哈希函数被广泛用于检测数据的一致性。软件提供者常常在除了提供软件包以外,还会发布校验和。当下载完一个文件以后,你可以用哈希函数对下载好的文件计算一个哈希,并与作者提供的哈希进行比较,以此来保证文件下载的完整性。
Hash
算法有很多种,本文中只介绍了项目中使用的SHA256S算法。
安全散列算法SHA:
安全散列算法SHA(Secure Hash Algorithm)是美国国家安全局 (NSA) 设计,美国国家标准与技术研究院(NIST) 发布的一系列密码散列函数,包括 SHA-1、SHA-224、SHA-256、SHA-384 和 SHA-512 等变体。主要适用于数字签名标准(DigitalSignature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。SHA-1已经不是那么安全了,google和微软都已经弃用这个加密算法。为此,我们使用热门的比特币使用过的算法SHA-256作为实例。其它SHA算法,也可以按照这种模式进行使用。
著名的Hash算法:
- MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年设计的,MD 是 Message Digest 的缩写。它适用在32位字长的处理器上用快速软件实现–它是基于 32 位操作数的位操作来实现的。
- MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本号。它对输入仍以512位分组,其输出是4个32位字的级联,与 MD4 同样。MD5比MD4来得复杂,而且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好
拼接字段
目前,我们仅取了 Block
结构的部分字段(Height
,Timestamp
, Data
和 PrevBlockHash
),并将它们相互拼接起来,然后在拼接后的结果上计算一个 SHA-256,然后就得到了哈希.
因为Height
是一个整型数据,我们需要将它转为[]byte
utils.go
文件中:
/*
将一个int64的整数:转为二进制后,每8bit一个byte。转为[]byte
*/
func IntToHex(num int64) []byte {
buff := new(bytes.Buffer)
//将二进制数据写入w
//func Write(w io.Writer, order ByteOrder, data interface{}) error
err := binary.Write(buff, binary.BigEndian, num)
if err != nil {
log.Panic(err)
}
//转为[]byte并返回
return buff.Bytes()
}
说明:在比特币种并没有拼接
Heigth
这个字段属性。但是这不重要,我们拼接字段只是为了生成Hash
。
设置Hash
在 SetHash
方法中完成这些操作:
Block.go
文件中:
// 设置区块的hash
func (block *Block) SetHash(){
//1.将高度转为字节数组
heightBytes:= IntToHex(block.Height)
//fmt.Println(heightBytes)
//2.时间戳转为字节数组
//timeBytes:=IntToHex(block.TimeStamp)
//转为二进制的字符串
//fmt.Println(block.TimeStamp)
//fmt.Printf("%x,%b\n",block.TimeStamp,block.TimeStamp)
timeString := strconv.FormatInt(block.TimeStamp,2)
//fmt.Println("timeString:",timeString)
timeBytes := [] byte(timeString)
//fmt.Println("timeStamp:",timeBytes)
//3.拼接所有的属性
blockBytes:= bytes.Join([][]byte{
heightBytes,
block.PrevBlockHash,
block.Data,
timeBytes},[]byte{})
//4.生成哈希值
hash:=sha256.Sum256(blockBytes)//数组长度32位
block.Hash = hash[:]
}
接下来,我们会实现一个用于简化创建区块的函数 NewBlock()
:
Block.go
文件中:
//创建新的区块
func NewBlock(data string,provBlockHash []byte,height int64) *Block{
//创建区块
block:=&Block{height,provBlockHash,[]byte(data),time.Now().Unix(),nil}
//设置哈希值
block.SetHash()
return block
}
在main.go
文件中添加main函数,并进行测试:
func main() {
//1.测试Block
block:=BLC.NewBlock("I am a block",make([]byte,32,32),1)
fmt.Printf("Heigth:%x\n",block.Height)
fmt.Printf("Data:%s\n",block.Data)
}
进行测试:
打开终端:点击goland底部的Terminal:
输入命令:进入day01_01_Base_Prototype目录
hanru:mypublicchain ruby$ cd day01_01_Base_Prototype/
hanru:day01_01_Base_Prototype ruby$
查看该目录下的内容:
hanru:day01_01_Base_Prototype ruby$ ls
BLC main.go
执行程序:
hanru:day01_01_Base_Prototype ruby$ go run main.go
运行结果:
创世区块因为是区块链中的第一个区块,还是有一些特殊的,比如Heigth
固定为0,PrevBlockHash
固定为0。
//创建创世区块:
func CreateGenesisBlock(data string) *Block{
return NewBlock(data,make([] byte,32,32),0)
}
在main函数中进行测试:
func main() {
//1.测试Block
//block:=BLC.NewBlock("I am a block",make([]byte,32,32),1)
//fmt.Printf("Heigth:%x\n",block.Height)
//fmt.Printf("Data:%s\n",block.Data)
//2.测试创世区块
genesisBlock :=BLC.CreateGenesisBlock("Genesis Block..")
fmt.Printf("Heigth:%x\n",genesisBlock.Height)
fmt.Printf("PrevBlockHash:%x\n",genesisBlock.PrevBlockHash)
fmt.Printf("Data:%s\n",genesisBlock.Data)
}
运行结果:
有了区块,下面让我们来实现区块链。本质上,区块链就是一个有着特定结构的数据库,是一个有序,每一个块都连接到前一个块的链表。也就是说,区块按照插入的顺序进行存储,每个块都与前一个块相连。这样的结构,能够让我们快速地获取链上的最新块,并且高效地通过哈希来检索一个块。
在 Golang 中,可以通过一个 array
和 map
来实现这个结构:array
存储有序的哈希(Golang 中 array
是有序的),map
存储 hash -> block 对(Golang 中, map
是无序的)。 但是在基本的原型阶段,我们只用到了 array
,因为现在还不需要通过哈希来获取块。
BlockChain.go
文件中:
//定义区块链
type BlockChain struct {
Blocks []*Block //存储有序的区块
}
为了创建一个区块链,并且能够加入一个新的块,我们必须要有一个已有的块,因为新区块需要引用之前的区块Hash
作为PrevBlockHash
。但是,初始状态下,我们的链是空的,一个块都没有!所以,在任何一个区块链中,都必须至少有一个块。这个块,也就是链中的第一个块,通常叫做创世区块,简称叫创世块(genesis block),而创世区块的PrevBlockHash
固定为0。
接下来,我们提供一个方法,用于创建一个区块链,并且该区块链中包含了创世区块。
BlockChain.go
文件中:
//创建区块链,带有创世区块
func CreateBlockChainWithGenesisBlock(data string) *BlockChain{
//创建创世区块
genesisBlock := CreateGenesisBlock(data)
//返回区块链对象
return &BlockChain{[]*Block{genesisBlock}}
}
这就是我们的第一个区块链!是不是出乎意料地简单? 就是一个 Block
数组。
在main函数中进行测试:
func main() {
//1.测试Block
//block:=BLC.NewBlock("I am a block",make([]byte,32,32),1)
//fmt.Printf("Heigth:%x\n",block.Height)
//fmt.Printf("Data:%s\n",block.Data)
//2.测试创世区块
//genesisBlock :=BLC.CreateGenesisBlock("Genesis Block..")
//fmt.Printf("Heigth:%x\n",genesisBlock.Height)
//fmt.Printf("PrevBlockHash:%x\n",genesisBlock.PrevBlockHash)
//fmt.Printf("Data:%s\n",genesisBlock.Data)
//3.测试区块链
genesisBlockChain := BLC.CreateBlockChainWithGenesisBlock("Genesis Block..")
fmt.Println(genesisBlockChain)
fmt.Println(genesisBlockChain.Blocks)
fmt.Printf("Heigth:%x\n",genesisBlockChain.Blocks[0].Height)
fmt.Printf("PrevBlockHash:%x\n",genesisBlockChain.Blocks[0].PrevBlockHash)
fmt.Printf("Data:%s\n",genesisBlockChain.Blocks[0].Data)
}
运行结果:
现在,让我们能够给它添加一个区块:
BlockChain.go
文件中:
//添加一个新的区块,到区块链中
func (bc *BlockChain) AddBlockToBlockChain(data string,height int64,prevHash [] byte){
//创建新区块
newBlock := NewBlock(data,prevHash,height)
//添加到切片中
bc.Blocks = append(bc.Blocks,newBlock)
}
在main函数中进行测试:
func main() {
//4.测试添加新区块
blockChain:=BLC.CreateBlockChainWithGenesisBlock("Genesis Block..")
blockChain.AddBlockToBlockChain("Send 1BTC To Wangergou",blockChain.Blocks[len(blockChain.Blocks)-1].Height+1,blockChain.Blocks[len(blockChain.Blocks)-1].Hash)
blockChain.AddBlockToBlockChain("Send 3BTC To lixiaohua",blockChain.Blocks[len(blockChain.Blocks)-1].Height+1,blockChain.Blocks[len(blockChain.Blocks)-1].Hash)
blockChain.AddBlockToBlockChain("Send 5BTC To rose",blockChain.Blocks[len(blockChain.Blocks)-1].Height+1,blockChain.Blocks[len(blockChain.Blocks)-1].Hash)
for _, block := range blockChain.Blocks {
fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
fmt.Println()
}
}
运行结果:
我们创建了一个非常简单的区块链原型:它仅仅是一个数组构成的一系列区块,每个块都与前一个块相关联。真实的区块链要比这复杂得多。在我们的区块链中,加入新的块非常简单,也很快,但是在真实的区块链中,加入新的块需要很多工作:你必须要经过十分繁重的计算(这个机制叫做工作量证明),来获得添加一个新块的权力。并且,区块链是一个分布式数据库,并且没有单一决策者。因此,要加入一个新块,必须要被网络的其他参与者确认和同意(这个机制叫做共识(consensus))。