简易区块链实现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("==========================================")
    }
}

1

这样我们最简单的区块链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会增加工作量证明。


路漫漫其修远兮,吾将上下而求索