引言
本文及后续文章以通俗的方式讲解区块链技术,用go实现一个简单的区块链。
什么是区块链?
区块链是一种安全共享的去中心化的数据账本。 本质上,区块链只是一个分布式数据库而已。不过使他独一无二的事,区块链是一个公开的数据库,而不是一个私有的数据库,也就是说,每个使用它的人都有完整或部分的副本。 数据被细分为多个共享区块,并以加密哈希形式的唯一标识符链接在一起。 此外,也正是由于区块链,才使得加密货币和智能合约成为现实。
区块
首先我们先来了解什么叫做区块,区块就是真正存储有效信息的就是区块(block)。而在比特币中,真正存在价值的是transaction。实际上,交易信息是所有加密货币的价值所在。除此以外,区块还包含了一些技术实现的相关信息,比如版本,当前时间戳和前一个区块的哈希。
我们实现一个简易的区块链,里面只包含了关键信息,而不是像比特币那种完整的生态链。
// Block 区块结构体
type Block struct {
Timestamp int64 // 当前时间戳,也就是区块创建的时间
Data []byte // 前一个块的哈希,即父哈希
PrevBlockHash []byte // 当前块的哈希
Hash []byte // 区块存储的实际有效信息,也就是交易
}
这里的Timestamp
,PrevBlockHash
,Hash
,在比特币技术规范中属于区块头,区块头是一个单独的数据结构。下面是golang实现btcd的区块头定义:
// BlockHeader defines information about a block and is used in the bitcoin
// block (MsgBlock) and headers (MsgHeaders) messages.
type BlockHeader struct {
// Version of the block. This is not the same as the protocol version.
Version int32
// Hash of the previous block in the block chain.
PrevBlock chainhash.Hash
// Merkle tree reference to hash of all transactions for the block.
MerkleRoot 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
}
而我们简易版的Data
,在比特币中对应的就是transaction,是另一个单独的数据结构。为了方便起见,把两个数据结构放在了一起。
在我们的简化版中,还有一个Hash
字段,那么,要如何计算哈希呢?哈希计算是区块链中非常重要的部分,因为有了他才能保证区块的安全。为什么现在我们所说的挖矿非常困难了呢,那是因为计算一个哈希,是在计算上非常困难的一个操作。即使放在好一点的电脑上,也要耗费很多时间(这就是为什么人们会goumai一些GPU,FPGA,ASIC来挖比特币)。这是一个架构上有意为之的设计,它故意使得加入新的区块十分困难,继而保证区块一旦被加入以后,就很难再进行修改。在接下来的内容中,我们将会讨论和实现这个机制。
目前,我们仅取了 Block
结构的部分字段(Timestamp
, Data
和 PrevBlockHash
),并将它们相互拼接起来,然后在拼接后的结果上计算一个 SHA-256,然后就得到了哈希.
Hash = SHA256(PrevBlockHash + Timestamp + Data)
下面是在golang中实现哈希和创建一个简易的区块。
// setHash 设置当前块哈希
func (b *Block) setHash() {
timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
hash := sha256.Sum256(headers)
b.Hash = hash[:]
}
// NewBlock 创建简单的区块函数
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
block.setHash()
return block
}
区块链
有了区块,下一步就是实现区块链了。区块链其实就是把之前的区块按照插入的顺序进行存储,每一个块都与前一个块相连,其实就是一个特定的数据结构的数据库。这样的数据结构可以让我们快速获取链上的最新块,并且高效的通过哈希来检索一个块。
接下来我们来简单的实现一个区块链,结构非常简单,就是一个Block数组,组成的区块链。
type Blockchain struct {
blocks []*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)
}
不过不仅仅是这样的。为了加入一个新的块,我们必须要有一个已有的块,一开始我们的链是空的,所以就像链表一样需要一个头块。毕竟至少需要一个块才能组成区块链,这个块也就是链中的第一个块,通常叫做创世块。下面实现一个方法来创建创世块。
// NewGenesisBlock 创建创世块
func NewGenesisBlock() *Block {
return NewBlock("创世块", []byte{})
}
现在实现一个函数来创建有创世块的区块链:
// NewBlockchain 创建有创世块的区块链
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
测试区块函数
func main() {
bc := NewBlockchain()
bc.AddBlock("Send 1 ETH to Alex")
bc.AddBlock("Send 2 ETH to Alex")
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()
}
}
// console result
Prev. hash:
Data: Genesis Block
Hash: 1d4e96771da1e76bd946296039709860259a12e8031411f3c2644142dafc309f
Prev. hash: 1d4e96771da1e76bd946296039709860259a12e8031411f3c2644142dafc309f
Data: Send 1 BTC to Ivan
Hash: e1f60a364931dffa531aec919b4ca29513405d4d1f441b5c8e85f49b8c6d0aaf
Prev. hash: e1f60a364931dffa531aec919b4ca29513405d4d1f441b5c8e85f49b8c6d0aaf
Data: Send 2 more BTC to Ivan
Hash: 0c3062803bc312bd36dc39503ed02db040724e06bb4e257fd988d2432d72c405
可以看出我们已经完成了基础的区块链原型,建立了一个链,增加了区块。但是在真实的区块链中,加入一个新的块需要很多工作,需要经过十分繁重的计算,从而获得添加一个新块的权利。下一章我们将讲述工作量证明,为什么一个区块链添加区块需要繁琐的计算?