区块链(Blockchain),是比特币的一个重要概念,它本质上是一个去中心化的数据库,同时作为比特币的底层技术,是一串使用密码学方法相关联产生的数据块,每一个数据块中包含了一批次比特币网络交易的信息,用于验证其信息的有效性(防伪)和生成下一个区块。(百度百科)
区块链本身的结构是非常简单的,其复杂的部分在于他的共识机制,加密等部分。我们可以将区块链看做一种数据结构,顾名思义,区块链就是把许多个区块(Block)链接在一起,成为一个链式结构(Chain)。所以我们要做的事情也很简单,就是首先创建出区块,然后把它们连起来。
1.区块的构建
首先,要确定的是区块里面要存什么。由于我们只是要构建一个最基础的区块链,所以区块里面的信息也比较简单。我们只往里存两个属性,前一个节点的哈希值(reviousHash)和交易信息(transaction)
type Block struct {
previousHash string
transaction []string
}
为了计算哈希值,我们要把Block结构体进行序列化。这里我选择使用protobuf,当然你也可以选择使用json或者直接转成[]byte。
在block包下新建一个pb包,然后新建block.proto文件。里面要写的内容很简单,只有一个message的定义。
syntax="proto3";
package blockpb;
message Block{
string previousHash = 1;
repeated string transaction = 2;
}
然后接下来编译proto文件,会自动生成block.pb.go文件(别忘了敲等号后面的小点)
protoc --go_out=. block.proto
有了block.pb.go,我们就可以很容易的计算每个区块的哈希值了。首先,写一个方法来把block转化为blockpb。
//把block转化为blockpb
func ToProto(block *Block) proto.Message {
return &blockpb.Block{
PreviousHash: block.previousHash,
Transaction: block.transaction,
}
}
接下来就是获取区块的哈希值,这个部分也很简单
func GetHash(block *Block) string{
serialBlock := ToProto(block)
byteBlock, _ := proto.Marshal(serialBlock)
hash := sha1.Sum(byteBlock)
return hex.EncodeToString(hash[:])
}
到这里所有关于区块的部分就已经结束了。接下来就是如何把这些区块给串起来,形成链式结构了。
2.区块的链接
我们创建的是一个非常简单基础的链,所以接下来的操作都会在main.go的main方法里进行。很显然,既然对于每个节点,我们都能计算出一个hash值,并且这个hash值是唯一的。那么们就可以通过块的hash值来定位到这个块的后一个节点是谁,因为我们每个块除了包含交易信息以外,还保存了上一个区块的hash值。
- 创世块的创建
创世块(Genesis Block)是一条区块链第一个创建的区块。这个区块的特殊之处在于,他并没有上一个区块,也就是说这个块的previousHash是空的。我们就用空字符来初始化他的上一个区块哈希值。
genesisBlock := block.CreateBlock("",[]string{"Hello","BlockChain","World"})
这个区块包含的交易信息是Hello BlockChain World。当然,在实际的区块中,这里会存放一些有意义的信息,而不是随手打的Hello World。
- 其他区块的创建
接下来创建其他区块,我们在这里就先创建三个块。注意,必须用上一个区块的哈希值来初始化本区块的previousHash,否则区块之间的联系就断了。
block1 := block.CreateBlock(block.GetHash(&genesisBlock),[]string{"first block"})
block2 := block.CreateBlock(block.GetHash(&block1),[]string{"second block"})
block3 := block.CreateBlock(block.GetHash(&block2),[]string{"third block"})
接下来我们打印出整个区块链的信息看一下:
假如我们修改创世块的信息,变成
genesisBlock := block.CreateBlock("",[]string{"Hello","Block","World"})
那么所有的区块的hash值都会发生很大的变化,这就是为什么在区块上作弊是非常困难的一件事情。
最后,附全部源码
- src/main/main.go
package main
import (
"block"
"fmt"
)
func main(){
genesisBlock := block.CreateBlock("",[]string{"Hello","Block","World"})
block1 := block.CreateBlock(block.GetHash(&genesisBlock),[]string{"first block"})
block2 := block.CreateBlock(block.GetHash(&block1),[]string{"second block"})
block3 := block.CreateBlock(block.GetHash(&block2),[]string{"third block"})
fmt.Println("genesisBlock:",block.GetHash(&genesisBlock))
fmt.Println("block1 :",block.GetHash(&block1))
fmt.Println("block2 :",block.GetHash(&block2))
fmt.Println("block3 :",block.GetHash(&block3))
}
- src/block/block.go
package block
import (
"block/pb"
"crypto/sha1"
"encoding/hex"
"github.com/gogo/protobuf/proto"
)
type Block struct {
previousHash string
transaction []string
}
func CreateBlock(preHash string, trans []string) Block{
b := Block{
preHash,trans,
}
return b
}
func ToProto(block *Block) proto.Message {
return &blockpb.Block{
PreviousHash: block.previousHash,
Transaction: block.transaction,
}
}
func GetHash(block *Block) string{
serialBlock := ToProto(block)
byteBlock, _ := proto.Marshal(serialBlock)
hash := sha1.Sum(byteBlock)
return hex.EncodeToString(hash[:])
}
- src/block/pb/block.pb.go
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: block.proto
package blockpb
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Block struct {
PreviousHash string `protobuf:"bytes,1,opt,name=previousHash,proto3" json:"previousHash,omitempty"`
Transaction []string `protobuf:"bytes,2,rep,name=transaction,proto3" json:"transaction,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Block) Reset() { *m = Block{} }
func (m *Block) String() string { return proto.CompactTextString(m) }
func (*Block) ProtoMessage() {}
func (*Block) Descriptor() ([]byte, []int) {
return fileDescriptor_8e550b1f5926e92d, []int{0}
}
func (m *Block) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Block.Unmarshal(m, b)
}
func (m *Block) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Block.Marshal(b, m, deterministic)
}
func (m *Block) XXX_Merge(src proto.Message) {
xxx_messageInfo_Block.Merge(m, src)
}
func (m *Block) XXX_Size() int {
return xxx_messageInfo_Block.Size(m)
}
func (m *Block) XXX_DiscardUnknown() {
xxx_messageInfo_Block.DiscardUnknown(m)
}
var xxx_messageInfo_Block proto.InternalMessageInfo
func (m *Block) GetPreviousHash() string {
if m != nil {
return m.PreviousHash
}
return ""
}
func (m *Block) GetTransaction() []string {
if m != nil {
return m.Transaction
}
return nil
}
func init() {
proto.RegisterType((*Block)(nil), "blockpb.Block")
}
func init() { proto.RegisterFile("block.proto", fileDescriptor_8e550b1f5926e92d) }
var fileDescriptor_8e550b1f5926e92d = []byte{
// 104 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4e, 0xca, 0xc9, 0x4f,
0xce, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x07, 0x73, 0x0a, 0x92, 0x94, 0x7c, 0xb9,
0x58, 0x9d, 0x40, 0x4c, 0x21, 0x25, 0x2e, 0x9e, 0x82, 0xa2, 0xd4, 0xb2, 0xcc, 0xfc, 0xd2, 0x62,
0x8f, 0xc4, 0xe2, 0x0c, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x14, 0x31, 0x21, 0x05, 0x2e,
0xee, 0x92, 0xa2, 0xc4, 0xbc, 0xe2, 0xc4, 0xe4, 0x92, 0xcc, 0xfc, 0x3c, 0x09, 0x26, 0x05, 0x66,
0x0d, 0xce, 0x20, 0x64, 0xa1, 0x24, 0x36, 0xb0, 0xf1, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff,
0x68, 0x7e, 0xfd, 0x82, 0x6d, 0x00, 0x00, 0x00,
}
- src/block/pb/block.proto
syntax="proto3";
package blockpb;
message Block{
string previousHash = 1;
repeated string transaction = 2;
}