简易区块链实现V1(golang)
前言
最近看了下go语言,然后找了一个区块链的教学视频来看,视频中准备由易到难分几个版本来逐步实现区块链,这里也来跟着实现一下
这里V1基本上是只实现了一个区块链的基本架构,很多参数都没有用到,比较简单吧
由于也是刚开始看go,所以很多东西可能写的都不是很准确与优美,望各位大佬能多指正。
代码和分析
这里来实现最简单的区块链
首先是区块部分
区块主要由区块头和区块体构成
区块头:版本,父区块的哈希值,Merkle根,时间戳,难度目标,Nonce(这里为了简化难度,会多存一个本块的哈嘻值)
区块体:就记账内容
把上面的区块结构用一个结构体来表示
type Block struct {
Version int64
PrevHash []byte
Hash []byte
MerkleRoot []byte
TimeStamp int64
Difficulty int64
Nonce int64
Data []byte
}
然后构造创建区块的方法
func Newblock(data string, prevhash []byte) *Block{
var newblock Block
newblock.Version = 1
newblock.PrevHash = prevhash
newblock.MerkleRoot = []byte{}
newblock.TimeStamp = time.Now().Unix()
newblock.Difficulty = 1
newblock.Nonce = 5
newblock.Data = []byte(data)
CalcHsah(&newblock)
return &newblock
}
可以看到上面的函数,我们需要传入内容和父区块哈希,返回这个区块结构的地址
这里主要是定义一个Block的结构体,并给里面的元素赋值
Version,Difficulty,Nonce,MerkleRoot这里因为是测试,就随意设置了些值,时间戳就用当前时间,父区块Hash和Data都是传入的值
但问题是这里当前区块的Hash值,这个值是我们额外添加的,用来存储当前区块的值,所以我们设置好其它的值之后,再来用建一个函数来修改掉结构体中当前区块的值
func CalcHsah(newblock *Block){
tmp := [][]byte{
Inttobyte(newblock.Version),
newblock.PrevHash,
newblock.MerkleRoot,
Inttobyte(newblock.TimeStamp),
Inttobyte(newblock.Difficulty),
Inttobyte(newblock.Nonce),
newblock.Data,
}
data := bytes.Join(tmp,[]byte{})
hash := sha256.Sum256(data)
newblock.Hash = hash[:]
}
这里计算hash的时候我们需要传入[]byte类型数据,我们需要用bytes.Join来将结构体中其它元素拼接起来,这里我门创建一个[][]byte变量,里面的元素全部为[]byte类型,也就是上面代码中的tmp,结构体中主要由[]byte和int64两种类型元素。[]byte类型的很好不需要转换,但int64的元素需要转为[]byte类型,所以再创建一个函数用于int64和[]byte类型的转换
func Inttobyte(num int64)[]byte{
var buf = make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(num))
return buf
}
这里int64与[]byte的转换除了上面的方法,也可以用下面的
s1 := make([]byte, 0)
buf := bytes.NewBuffer(s1)
// 数字转 []byte, 网络字节序为大端字节序
binary.Write(buf, binary.BigEndian, i1)
fmt.Println(buf.Bytes())
// 数字转 []byte, 小端字节序
buf.Reset()
binary.Write(buf, binary.LittleEndian, i1)
fmt.Println(buf.Bytes())
然后我们通过bytes.Join进行拼接,第二个参数指要已什么元素来进行拼接,这里我们必须是用空来进行拼接[]byte{}。
我们得到了整个块的[]byte值后就可以用sha256的算法来求去哈希值了
这里sha256.Sum256返回的是一个[32]byte的数组,后面用[:]取到所有元素
这里我们区块部分就算实现好了
然后我们来创建链式结构
这里是最简单的实现也不用用到数据库,这里我们设计一个结构体
type BlockChain struct {
Blocks []*Block
}
里面是一个切片保存已经上链区块的地址
然后创建一个函数用于初始一个结构体和和生成第一个区块
func NewBlockchain(data string) *BlockChain{
return &BlockChain{[]*Block{Newblock(data,[]byte{})}}
}
创建一个组合函数,用于向区块链上添加区块
func (bc *BlockChain)AddBlock(data string){
if len(bc.Blocks) <= 0{
os.Exit(1)
}
prehash := bc.Blocks[len(bc.Blocks)-1].Hash
newblock := Newblock(data, prehash)
bc.Blocks = append(bc.Blocks, newblock)
}
因为我们这里为了方便在每个区块中都多存了一个当前块的哈希值,所以这里取父哈希就不需要计算了
这里我们写个main函数,简单的添加两个区块,打印看一下
func main(){
bc := NewBlockchain("first block")
bc.AddBlock("root to kid 2")
bc.AddBlock("root to he 1")
for i,block := range bc.Blocks{
fmt.Printf("第%d个区块\n",i)
fmt.Printf("区块版本:%d\n",block.Version)
fmt.Printf("前区块Hash:%x\n",block.PrevHash)
fmt.Printf("区块Hash:%x\n",block.Hash)
fmt.Printf("区块MerkleRoot:%x\n",block.MerkleRoot)
fmt.Printf("区块时间戳:%d\n",block.TimeStamp)
fmt.Printf("区块难度:%d\n",block.Difficulty)
fmt.Printf("区块Nonce:%d\n",block.Nonce)
fmt.Printf("区块内容:%s\n",string(block.Data))
fmt.Println("==========================================")
}
}
这样我们最简单的区块链V1版本就算完成了…
代码
block.go
package main
import (
"bytes"
"crypto/sha256"
"time"
)
type Block struct {
Version int64
PrevHash []byte
Hash []byte
MerkleRoot []byte
TimeStamp int64
Difficulty int64
Nonce int64
Data []byte
}
func Newblock(data string, prevhash []byte) *Block{
var newblock Block
newblock.Version = 1
newblock.PrevHash = prevhash
newblock.MerkleRoot = []byte{}
newblock.TimeStamp = time.Now().Unix()
newblock.Difficulty = 1
newblock.Nonce = 5
newblock.Data = []byte(data)
CalcHsah(&newblock)
return &newblock
}
func CalcHsah(newblock *Block){
tmp := [][]byte{
Inttobyte(newblock.Version),
newblock.PrevHash,
newblock.MerkleRoot,
Inttobyte(newblock.TimeStamp),
Inttobyte(newblock.Difficulty),
Inttobyte(newblock.Nonce),
newblock.Data,
}
data := bytes.Join(tmp,[]byte{})
hash := sha256.Sum256(data)
newblock.Hash = hash[:]
}
blockchain.go
package main
import "os"
type BlockChain struct {
Blocks []*Block
}
func NewBlockchain(data string) *BlockChain{
return &BlockChain{[]*Block{Newblock(data,[]byte{})}}
}
func (bc *BlockChain)AddBlock(data string){
if len(bc.Blocks) <= 0{
os.Exit(1)
}
prehash := bc.Blocks[len(bc.Blocks)-1].Hash
newblock := Newblock(data, prehash)
bc.Blocks = append(bc.Blocks, newblock)
}
utils.go
package main
import "encoding/binary"
func Inttobyte(num int64)[]byte{
var buf = make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(num))
return buf
}
main.go
package main
import "fmt"
func main(){
bc := NewBlockchain("first block")
bc.AddBlock("root to kid 2")
bc.AddBlock("root to he 1")
for i,block := range bc.Blocks{
fmt.Printf("第%d个区块\n",i)
fmt.Printf("区块版本:%d\n",block.Version)
fmt.Printf("前区块Hash:%x\n",block.PrevHash)
fmt.Printf("区块Hash:%x\n",block.Hash)
fmt.Printf("区块MerkleRoot:%x\n",block.MerkleRoot)
fmt.Printf("区块时间戳:%d\n",block.TimeStamp)
fmt.Printf("区块难度:%d\n",block.Difficulty)
fmt.Printf("区块Nonce:%d\n",block.Nonce)
fmt.Printf("区块内容:%s\n",string(block.Data))
fmt.Println("==========================================")
}
}
总结
因为也是刚开始学go,虽然代码没什么难度,但写起来也费了大半天…
和python相比感觉个人写代码的话,数据类型就显的麻烦,要不断考虑类型转换设定等…而动态语言就很方便,特别是鸭子类型这一特点的方便性,感觉写go的时候就能充分的感受到…
这里实现了一个最简单的区块链结构,后面会逐步完善,V2会增加工作量证明。