區塊鏈(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;
}