使用 Go 语言打造区块链(一)


第一部分:基础原型

简介

区块链是二十一世纪最具革命性的技术之一,它正在不断成熟,它的诸多潜力正在逐步实现中。本质上来看,区块链只不过是一个分布式的数据库。之所以区块链独特,是因为它并不是一个私有数据库,而是一个公开的数据库,即,每一个使用它的人拥有这个数据库的全部或者至少一部分。任何一个新的数据记录,只能在多数数据库持有者(维护者)的多数同意之后被加入数据库。正因如此,区块链使得加密货币以及智能合约成为可能。

在这个系列文章中,我们将打造一个简化版本的加密货币,它将基于一个简化版本的区块链实现。

区块(Block)

让我们先从区块开始。在区块链里,价值信息存储在区块之中。比如,比特币的区块存储交易记录,而交易记录是任何加密货币的核心。除此之外,区块里还包含有技术信息,比如它的版本号,当前的时间戳,以及上一个区块的哈希(Hash)。

在这篇文章中,我们所实现的并不是像比特币那样完整的区块链,而是一个简化版本的区块链,它只含有最基本的核心信息。差不多是这样:

1
2
3
4
5
6
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
}

在这里,Timestamp是当前的时间戳(即,区块被创建的时间),Data是区块中包含的价值信息,PrevBlockHash 存储的是上一个区块的哈希,而Hash 保存的是当前区块的哈希。在比特币的标配中,TimestampPrevBlockHashHash是区块的头部数据(Block headers),构成一个单独的数据结构;而交易记录(Transactions,在我们这个版本中就是 Data),是另外一个单独的数据结构。而我们在这里为了简化,把数据结构混在了一起。

那我们如何计算哈希呢?计算哈希的方式是区块链的重要特征之一,也正是这个特性使得区块链如此安全。关键在于,计算哈希是一个计算起来很困难的工作,它需要时间,哪怕是在很快的计算机上(这就是为什么人们要买比 CPU 计算能力更强悍 GPU 甚至专门的 ASIC 芯片做矿机的 原因)。这是故意如此设计的,这么做的结果是,往区块链(数据库)里添加新的区块(数据)有一定的困难,以此保证一旦新的数据被加入,往后很难篡改。以后的文章里会进一步讨论并实现这个机制。

现在呢,我们只需要罢区块里的各个字段关联起来,并在此基础上计算出一个 SHA-256 哈希。让我们调用一下 SetHash这个方法:

1
2
3
4
5
6
7
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[:]
}

接下来,依据 Golang 的常用方式,我们将实现一个函数,以便更简单地创建区块:

1
2
3
4
5
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
block.SetHash()
return block
}

就这么简单。

区块链(Blockchain)

现在,让我们来实现区块链。本质上来看,区块链只不过是一个特定结构的数据库,它是一个有序的,反向链接的列表(back-linked list)。这就意味着说,区块是按照插入的顺序排列的,每个区块都链接到上一个区块。这样的结构,使得使用者可以很快地在区块链中获得最新的区块,也可以很有效率地通过区块的哈希获得某个区块。

在 Golang 中,这种结构可以用数组(Array)与数图(Map) 实现:数组用来维护有序哈希(在 Go 语言中,数组是有序的);数图(Map) 用来维护 hash → block 对。不过,在我们的区块链原型中,我们只需要数组就可以了,因为我们暂时不需要通过哈希获取区块。

1
2
3
type Blockchain struct {
blocks []*Block
}

这就是我们的第一个区块链!我从来没想到竟然会这么简单!

现在,我们要想办法往区块链里添加区块了:

1
2
3
4
5
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 Block)。来,让我们实现一个方法去创建这个“创始块”:

1
2
3
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}

现在我们就可以创建一个函数,用来创建一个已含有“创始块”的区块链了:

1
2
3
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}

让我们来看看这区块链是否能用?

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
bc := NewBlockchain()
bc.AddBlock("Send 1 BTC to Ivan")
bc.AddBlock("Send 2 more BTC to Ivan")
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()
}
}

输出结果是:

1
2
3
4
5
6
7
8
9
10
11
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

(竟然)完工!

结论

我们创建了一个极简的区块链原型:它只不过是一个由区块构成的数组,每个区快链接指向上一个区块。当然,真正的区块链远比这个复杂的多。在我们的区块链里,添加一个新区块非常快,非常容易;但是在真正的区块链中添加一个新的区块需要更多的工作:在获得添加区块的允许之前要做很繁重的计算才行(这个过程被称为工“作证明机制”,即,“Proof-of-Work”,POW)。并且,区块链是一个没有主权的分布式的数据库。因此,任何一个新的区块在被加入之前,必须经过网络中其它参与者的确认与允许(这个机制被称为“共识机制”,“Consensus”)…… 还有,我们的区块链里,还没有任何交易记录呢!

未来的文章里,我们将进一步实现这些特性。


链接:

  1. Full source codes: https://github.com/Jeiwan/blockchain_go/tree/part_1
  2. Block hashing algorithm: https://en.bitcoin.it/wiki/Block_hashing_algorithm