在Go中构建区块链-1-基本原型

使用golang构建简单的区块链

本文译自 Building Blockchain in Go. Part 1: Basic Prototype

简介

区块链是21世纪最革命性的技术之一,仍然在不断发展,其潜力尚未充分实现。本质上,区块链只是一种分布式记录数据库。但使其独特的是,它不是私有数据库,而是公共数据库,即每个使用它的人都拥有其全部或部分副本。新的记录只能在数据库的其他维护者同意的情况下添加。此外,正是区块链使加密货币和智能合约成为可能。

在这一系列的文章中,我们将构建一个基于简单区块链实现的简化加密货币。

区块

让我们从“区块链”中的“区块”部分开始。在区块链中,存储有价值信息的是区块。例如,比特币区块存储交易,这是任何加密货币的本质。除此之外,一个区块还包含一些技术信息,如其版本、当前时间戳和前一区块的哈希值。 在本文中,我们不打算按照区块链或比特币规范实现区块,而是使用其简化版本,仅包含重要信息。以下是其结构:

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

Timestamp是当前时间戳(区块创建时的时间),Data是包含在区块中的实际有价值的信息,PrevBlockHash存储前一区块的哈希值,而Hash是区块的哈希值。在比特币规范中,Timestamp、PrevBlockHash和Hash构成区块头,形成一个独立的数据结构,而交易(在我们的情况中是Data)是另一个独立的数据结构。出于简化考虑,我们在这里将它们混合在一起。

那么我们如何计算哈希值呢?哈希值的计算方式是区块链的一个非常重要的特性,正是这个特性使得区块链具有安全性。问题在于计算哈希是一项计算上困难的操作,即使在高速计算机上也需要一些时间(这就是为什么人们购买强大的GPU来挖掘比特币的原因)。这是一种有意的架构设计,使得添加新区块变得困难,从而防止它们在添加后被修改。我们将在未来的文章中讨论并实现这一机制。

目前,我们将简单地取区块字段,连接它们,然后对连接的组合计算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
}

区块链

现在让我们来实现一个区块链。在其本质上,区块链只是一种具有特定结构的数据库:它是一个有序的、反向链接的列表。这意味着区块按照插入顺序存储,并且每个区块链接到前一个区块。这种结构允许快速获取链中的最新区块,并(高效地)通过其哈希获取区块。

在Golang中,可以通过使用数组和哈希表(map)来实现这种结构:数组将保持有序的哈希值(在Go中数组是有序的),而map(哈希表)将保持哈希值到区块的映射(map是无序的)。但对于我们的区块链原型,我们将只使用数组,因为目前我们不需要通过哈希值获取区块。

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)
}

就是这样!还是有点不够。。

要添加一个新区块,我们需要一个已经存在的区块,但是在我们的区块链中还没有区块!在任何区块链中,必须至少有一个区块(被称为创世块)。让我们实现一个创建这样一个区块的方法:

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)。此外,区块链是一个分布式数据库,没有单一的决策者。因此,新区块必须得到网络其他参与者的确认和批准(这个机制被称为共识)。而且,在我们的区块链中还没有交易!

在未来的文章中,我们将涵盖这些特性中的每一个。

本博客已稳定运行 小时 分钟
共发表 31 篇文章 · 总计 82.93 k 字
本站总访问量
Built with Hugo
主题 StackJimmy 设计