翻译:使用go构建区块链(一)基本原型
原文:Building Blockchain in Go. Part 1: Basic Prototype
简介
区块链是21世纪最具革命性的技术之一,目前仍在逐步成熟,其潜能依然没有被完全挖掘。事实上,区块链本质上只是一个保存记录的分布式数据库,但唯一的不同就是,它是一个公开的数据库而不是私有的。换句话说,每个使用它的人都有其部分或者全部的副本。一条新纪录的添加必须得到这个数据库的其他使用者的同意才能成功。区块链技术也促使了加密数据货币和智能合约的诞生。
在这一系列的文章中,我们将会基于区块链的简单实现去构建一个精简型的加密货币。
区块
让我们先从"区块链"中的"区块"入手吧。区块在区块链的主要作用是存储一系列重要的信息。例如,比特币中存储了交易信息,这也是任何加密货币的本质。除此之外,一个区块还包含了一些技术性的信息,比如该区块的版本、当前时间戳以及上一个区块的hash值。
在本文中,我们将不会按照真实区块链或者比特币的标准去实现一个区块,相反,我们只会使用它的简化版本,里面只包含一些重要信息。如下:
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
}
Timestamp表示当前时间戳(区块的创建时间),Data表示区块中实际有用的信息,PrevBlockHash存储了上一个区块的hash值,Hash则存储了本区块的hash值。在比特币的标准中,Timestamp,PrevBlockHash和Hash都是属于区块头的属性,它们应该存储在一个单独的数据结构,和交易(也就是我们这里的Data)是分开的,但我们为了简单,这里就把他们混合存储在同一个结构体里面了。
我们应该如何去计算hash值呢?在区块链中,计算hash值是一个非常重要的特性,这个特性可以使得区块链变得更加的安全可靠。计算hash值是一个非常复杂和困难的操作,即使在高速的计算机上也需要花费一定的时间(这就是人们都要购买强大的GPU去挖矿的原因)。这个特性是刻意设计的,因为这样可以让添加一个区块变得非常困难,从而防止它们添加后被篡改。在后续的文章我们将会讨论这个计算的机制是如何实现的。
现在,我们只取出区块字段,把它们连接起来,并且在他们的连接组合上计算其SHA-256的hash值。让我们开始编写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[:]
}
接下来,遵循Golang的语法约定,我们将会实现一个简单的创建区块的函数:
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
block.SetHash()
return block
}
以上就是关于区块的代码了!
区块链
现在我们来实现区块链。本质上,区块链只是一个具备特定结构的数据库:它是一个有序的后向链表。这就意味着区块都是按照插入顺序存储,并且每一个区块都连接着上一个区块。这个数据结构可以快速地在链上获取最后一个区块和高效地通过hash值获取到一个区块。
在Golang中,我们可以使用一个数组和一个map去实现这个数据结构:数组可以保证hash是有序的(golang的数组是有序的),而map则可以保存hash -> block的键值对(map是无序的)。但根据我们的区块链原型,我们只需要一个数组就够了,因为我们目前还不需要根据他们的hash去获取一个区块。
type Blockchain struct {
blocks []*Block
}
这就是我们实现的第一个区块链!从未想过居然是如此简单😉
现在,我们来实现添加块的功能:
func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.blocks[len(bc.blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.blocks = append(bc.blocks, newBlock)
}
这样就可以了!?
为了添加一个新的区块,我们必须有一个存在的区块,但我们的区块链现在还没有区块呢!因此,每一个区块链都必须至少有一个区块,而第一个区块我们通常称为"创世区块"。下面我们实现一个方法去创建这个"创世区块":
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}
我们现在可以实现一个包含"创世区块"的创建区块链的函数了:
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
让我们来检查一下区块链是否能正常工作:
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()
}
}
输出:
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)。同样,区块链并不是一个只有单一决策者的分布式数据库。因此,一个区块必须要被网络节点中的其他参与者确认和认可方能被添加成功(这个机制也被称为共识机制)。还有,我们的区块链尚未引入任何交易!
接下来的文章,我们将逐一介绍这些功能。