简易区块链实现V1(golang)

版权声明:本文为博主原创文章,未经博主允许不得转载。如有问题,欢迎指正。 https://blog.csdn.net/qq_26406447/article/details/91038242

简易区块链实现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会增加工作量证明。

猜你喜欢

转载自blog.csdn.net/qq_26406447/article/details/91038242
v1