golang实现比特币公链进阶V1:基本数据结构
2018-06-10 本文已影响22人
ccup区块链
golang实现比特币公链进阶:V1
本文为作者加强练习手敲代码,可直接运行到本地,先创建V1文件夹,然后分别创建以下文件即可。
本文借鉴https://github.com/Jeiwan/blockchain_go
- 区块整体结构
字节 | 字段 | 说明 |
---|---|---|
4 | 区块大小 | 用字节表示的该字段之后的区块大小 |
80 | 区块头 | 组成区块头的几个字段 |
1-9 | 交易计数器 | 该区块包含的交易数量,包含coinbase交易 |
不定 | 交易 | 记录在区块里的交易信息,使用原生的交易信息格式,并且交易在数据流中的位置必须与Merkle树的叶子节点顺序一致 |
- 区块头结构(6个字段)
字节 | 字段 | 说明 |
---|---|---|
4 | 版本 | 区块版本号,表示本区块遵守的验证规则 |
32 | 父区块头哈希值 | 前一区块的哈希值,使用SHA256(SHA256(父区块头))计算 |
32 | Merkle根 | 该区块中交易的Merkle树根的哈希值,同样采用SHA256(SHA256())计算 |
4 | 时间戳 | 该区块产生的近似时间,精确到秒的UNIX时间戳,必须严格大于前11个区块时间的中值,同时全节点也会拒绝那些超出自己2个小时时间戳的区块 |
4 | 难度目标 | 该区块工作量证明算法的难度目标,已经使用特定算法编码 |
4 | Nonce | 为了找到满足难度目标所设定的随机数,为了解决32位随机数在算力飞升的情况下不够用的问题,规定时间戳和coinbase交易信息均可更改,以此扩展nonce的位数 |
block.go
定义区块block结构体,并且实现创建区块,设置哈希的功能
package main
import (
"time"
"crypto/sha256"
"bytes"
)
type Block struct {
//区块头
Version int64
PrevBlockHash []byte
Hash []byte //为了方便实现,做的临时的简化,正常比特币区块是不包含自己的哈希值
TimeStamp int64
TargetBits int64
Nonce int64
MerKelRoot []byte
Data []byte //区块体,为了简化,临时用,实际应该是交易信息
}
func NewBlock(data string, preBlockHash []byte) *Block {
//初始化区块各个字段,此处暂时是随便写的
block := &Block{
Version: 1,
PrevBlockHash: preBlockHash,
//Hash:,后面会计算
TimeStamp: time.Now().Unix(),
TargetBits: 10,
Nonce: 5,
MerKelRoot: []byte{},
Data: []byte(data),
}
block.SetHash()
return block
}
func (block *Block) SetHash() {
temp := [][]byte{
//int 转byte方法
IntToByte(block.Version),
block.PrevBlockHash,
IntToByte(block.TimeStamp),
block.MerKelRoot,
IntToByte(block.Nonce),
block.Data,
}
//将区块的各个字段连接成一个切片
data := bytes.Join(temp, []byte{})
//对区块进行sha256哈希算法,返回值为数组,不是切片
hash := sha256.Sum256(data)
block.Hash = hash[:]//由数组转化为切片
}
//创世区块,即第一个区块,它的前一个区块哈希值为空
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}
utils.go
提取的公共方法,以后期重复使用。
package main
import (
"bytes"
"encoding/binary"
"fmt"
"os"
)
func IntToByte(num int64)[]byte {
var buffer bytes.Buffer
//二进制写法
err:=binary.Write(&buffer,binary.BigEndian,num)
CheckErr(err)
return buffer.Bytes()
}
func CheckErr(err error) {
if err!=nil {
fmt.Println("IntToByte err occur :",err)
os.Exit(1)
}
}
blockchain.go
创建区块链结构体,此处仅以数组作为简易模仿链的操作,并且实现创建区块链和添加区块的操作
package main
import "os"
type BlockChain struct {
//构造区块链结构,使用数组存储区块
blocks []*Block
}
//创建区块链实例,并且添加第一个创世块
func NewBlockChain()*BlockChain {
return &BlockChain{[]*Block{NewGenesisBlock()}}
}
//添加区块的到链上
func (bc *BlockChain)AddBlock(data string) {
//避免越界访问
if len(bc.blocks)<=0 {
os.Exit(1)
}
//取出链上的最后一个区块,使用其哈希值,构造新区块
prevBlockHash:=bc.blocks[len(bc.blocks)-1].Hash
newBlock:=NewBlock(data,prevBlockHash)
//添加到链数组中
bc.blocks=append(bc.blocks,newBlock)
}
main.go
简单测试操作区块链,添加区块,读取链数据的功能
package main
import "fmt"
func main() {
bc := NewBlockChain()
bc.AddBlock("bob send 1btc to Alice")
bc.AddBlock("bob send 1btc to Alice")
for i, block := range bc.blocks {
fmt.Println("=============block num:", i)
fmt.Printf("data:%s\n", string(block.Data))
fmt.Println("Version:", block.Version)
fmt.Printf("PreHash:%x\n", block.PrevBlockHash)
fmt.Printf("Hash:%x\n", block.Hash)
fmt.Printf("Time:%d\n", block.TimeStamp)
fmt.Printf("Nonce:%d\n", block.Nonce)
}
}
执行
在终端中,进入V1文件目录下,
1.编译
go build *.go
2.执行,编译后的可执行文件
./block