英文源地址

简介

区块链技术是21世纪最具变革型的技术之一,它仍处于成长阶段, 其潜力尚未完全实现.从本质上说, 区块链是一个分布式的记账数据库.但它的独特之处在于它不是一个私有数据库,而是一个公共数据库, 也就是说, 每个使用它的人都有它的完整或部分副本.而且,只有在征得其他数据库维护者的同意的情况下, 才能添加新记录.此外, 正是区块链使加密货币和智能合约成为可能.
在本系列文章中, 我们将构建一个基于简单区块链实现的简易的数字加密货币.

Block区块

让我们从’区块链’的区块部分开始. 在区块链中, 它是存储有价值信息的块.例如, 比特币区块存储交易, 这是任何加密货币的本质. 除此之外, 一个块还包含一些技术信息, 比如它的版本, 当前时间戳和前一个块的哈希值.
在本文中, 我们不打算实现区块链或比特币规范中描述的区块, 相反, 我们将使用它的简化版本, 其中只包含重要信息.它看起来是这样的.

type Block struct {Timestamp int64Data []bytePrevBlockHash []byteHash []byte}

Timestamp是当前时间戳(区块创建的时间), Data时区块中包含的实际有价值的信息, PrevBlockHash存储前一个区块的哈希值, Hash是当前区块的哈希值.在比特币规范中, Timestamp, PrevBlockHash和Hash是区块头部, 它们形成一个独立的数据结构, 而transactions(我们例子中的Data)是一个独立的数据结构.为了简单起见, 我们把它们放在一起.
那么我们如何计算哈希呢?哈希值的计算方式是区块链非常重要的特征, 正式这个特点使得区块链安全.问题是计算哈希是一项计算密集型操作, 即使在高速计算机上也需要耗费一些时间(这就是为什么人们购买强大的GPU来挖矿比特币). 这是一个有意义的设计, 这使得添加新区块变得困难, 从而阻止了它们在添加后被修改.我们将在以后的文章中讨论并实现该机制.
现在, 我们将获取区块字段, 将它们连接起来, 并在连接的组合上计算SHA-256哈希值.让我们在SetHash方法中实现这一点:

func (b *Block) SetHash() {timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))header := 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}

这就是整个区块!

区块链

现在让我们实现一个区块链. 从本质上讲, 区块链只是一个具有一定结构的数据库: 它是一个有序的反向链表. 这意味着数据块是按插入顺序存储的, 并且每个数据块都链接到前一个数据块.这种结构允许快速获取链中的最新区块, 并(有效地)通过其散列值获取区块.
在Golang中, 这种结构可以通过使用数组和字典来实现.数组保持有序地哈希值(在Go中数组是有序的), 而字典将保持哈希值->区块对(字典是无序的). 但是对于我们的区块链原型, 我们将只使用一个数组, 因为我们现在不需要通过它们的哈希值来获取区块.

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

就是这样了吗?
要添加一个新区块, 我们需要一个现有的区块, 但是我们的区块链中没有区块!因此, 在任何区块链中, 必须至少有一个区块, 而这样的区块, 即链的第一个区块, 被称为创世区块(gensis block).让我们实现一个创建这样一个区块的方法.

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 BlockHash: fc5e6a0666636c732f11b6d159081d0838ad126026ffef5d98b9a0a8cbf2f21ePrev. hash: fc5e6a0666636c732f11b6d159081d0838ad126026ffef5d98b9a0a8cbf2f21eData: Send 1 BTC to IvanHash: 4028898ea2a9907266de11ad1a770fb78163c05bb7b7a3d712ee59ea1473253ePrev. hash: 4028898ea2a9907266de11ad1a770fb78163c05bb7b7a3d712ee59ea1473253eData: Send 2 more BTC to IvanHash: 5571f6b8ad167ad209b1effe129dc139934d7487999am85bf081e83116c12a5

就是这样!

总结

我们构建了一个简易的区块链原型: 它只是一个区块数组, 每个区块都于前一个区块有链接.然而, 实际的区块链要复杂的多.在我们的区块链中添加新区块是简单而快速的, 但在真正的区块链中添加新区块需要一些工作: 在获得添加新区块的许可之前, 必须执行一些繁重的计算任务(这种机制成为工作量证明).此外, 区块链是一个分布式数据库, 没有单一的决策者.因此, 一个新的区块必须得到网络中其他参与者的确认和批准(这种机制成为共识consensus).我们的区块链中还没有事务.
在以后的文章中, 我们将介绍这些特性.

Links:

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