使用 go 实现一个简单的区块链
介绍
本质上来说,区块链就是一个分布式的数据库。它的独特之处在于,它不是一个私有的数据库,而是一个公共的数据库,每个人都可以使用它,也可以复制它。但是如果要添加数据,需要每个数据库的使用者或者说维护者都同意,才能往里添加数据。
Block
区块链是两个词的组合,一个是区块,一个链。在区块链中,块是用于存储有价值的信息的。例如: BTC 的块记录交易,这是任何加密货币的本质。除此之外,一个块包含一些技术信息,如其版本,当前时间戳和上一个块的哈希值。
这是一个最简单的 Block:
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
}
Timestamp
当该块被创建时的时间戳Data
记录块中实际的信息PrevBlockHash
记录前一个块的 hash 值Hash
该块的 hash 值
我们这里的 Timestamp
, PrevBlockHash
, Hash
在比特币的技术规范中属于区块头部(block header
),头部是一个单独的数据结构。完整的比特币区块头部(block header)如下:
Field | Purpose | Updated when... | Size (Bytes) |
---|---|---|---|
Version | Block version number | You upgrade the software and it specifies a new version | 4 |
hashPrevBlock | 256-bit hash of the previous block header | A new block comes in | 32 |
hashMerkleRoot | 256-bit hash based on all of the transactions in the block | A transaction is accepted | 32 |
Time | Current timestamp as seconds since 1970-01-01T00:00 UTC | Every few seconds | 4 |
Bits | Current target in compact format | The difficulty is adjusted | 4 |
Nonce | 32-bit number (starts at 0) | A hash is tried (increments) | 4 |
如果用 go 实现 BTC header 的规范
type BlockHeader struct {
// Version of the block.
Version int32
// Hash of previous block in the blcok chain.
PrevBlock chainhash.Hash
// Merkle tree reference to hash of all transactions for the block.
MarkleRoot chainhash.Hash
// Time the block was created. This is, unfortunately, encoded as a uint32 on the wire and therefore is limited to 2106.
Timestamp time.Time
// Difficulty target for the block.
Bits uint32
// Nonce used to generate the block.
Nonce uint32
}
我们还需要计算 hash 。这个是用来保证区块链安全的。这是一个架构上有意为之的设计,它故意使得加入新的区块十分困难,继而保证区块一旦被加入以后,就很难再进行修改。
使用 Block
里面的部分字段(PrevBlockHash
, Data
, timestamp
),将他们连接,然后计算 SHA-256
就得到了 Hash
。
编写一个 SetHash()
函数,用来完成这些操作
func (b *Block) SetHash() {
// int64 2 []byte
timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
hash := sha256.Sum256(headers)
b.Hash = hash[:]
}
实现一个用于简化创建 Block
的函数 NewBlock
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), []byte(prevBlockHash), []byte{}}
block.SetHash()
return block
}
Blockchain
现在有了区块,要让区块形成链。实际上,区块链就是一个有着特定结构的数据库,每个区块的最前面都放着前一个区块,像链表一样,顺序相连。这种结构,可以快速获取到最新的区块。
定义一个区块链的结构体
type Blockchain struct {
Blocks []*Block
}
实际上就是一个 Block
数组。
再写一个添加区块的函数 AddBlock
func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.Blocks[len(bc.blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.Blocks = append(bc.Blocks, newBlock)
}
添加一个新的区块需要原本就有一个区块,因此我们需要写一个类似于初始化一个区块的方法,这个块也就是整个区块链中的第一个区块,这个初始化的区块通常被叫做创世块(genesis blcok) 。
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}
有了创世块,也需要为创世块创建链
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
写一个 main 函数来测试
func main() {
bc := blockchain.NewBlockchain()
bc.AddBlock("Send 1")
bc.AddBlock("Send 2")
for _, block := range bc.Blocks {
fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
fmt.Println()
}
}
输出
Prev. hash:
Data: Genesis Block
Hash: aff955a50dc6cd2abfe81b8849eab15f99ed1dc333d38487024223b5fe0f1168
Prev. hash: aff955a50dc6cd2abfe81b8849eab15f99ed1dc333d38487024223b5fe0f1168
Data: Send 1 BTC to Ivan
Hash: d75ce22a840abb9b4e8fc3b60767c4ba3f46a0432d3ea15b71aef9fde6a314e1
Prev. hash: d75ce22a840abb9b4e8fc3b60767c4ba3f46a0432d3ea15b71aef9fde6a314e1
Data: Send 2 more BTC to Ivan
Hash: 561237522bb7fcfbccbc6fe0e98bbbde7427ffe01c6fb223f7562288ca2295d1