专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
程序员的那些事  ·  快!快!快!DeepSeek 满血版真是快 ·  昨天  
OSC开源社区  ·  RAG市场的2024:随需而变,从狂热到理性 ·  昨天  
程序员的那些事  ·  清华大学:DeepSeek + ... ·  3 天前  
OSC开源社区  ·  2024: 大模型背景下知识图谱的理性回归 ·  4 天前  
51好读  ›  专栏  ›  SegmentFault思否

用 Go 构建一个区块链——Part 1: 基本原型

SegmentFault思否  · 公众号  · 程序员  · 2018-02-01 08:00

正文

翻译的系列文章我已经放到了 GitHub 上:blockchain-tutorial(https://github.com/liuchengxu/blockchain-tutorial),后续如有更新都会在 GitHub 上,可能就不在这里同步了。如果想直接运行代码,也可以 clone GitHub 上的教程仓库,进入 src 目录执行 make 即可。

引言

区块链是 21 世纪最具革命性的技术之一,它仍然处于不断成长的阶段,而且还有很多潜力尚未显现出来。 本质上,区块链只是一个分布式数据库而已。 不过,使它独一无二的是,区块链是一个公开的数据库,而不是一个私人数据库,也就是说,每个使用它的人都有一个完整或部分的副本。 只有经过其他数据库管理员的同意,才能向数据库中添加新的记录。 此外,也正是由于区块链,才使得加密货币和智能合约成为现实。

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

区块

让我们从 “区块链” 中的 “区块” 谈起。在区块链中,存储有效信息的是区块。比如,比特币区块存储的有效信息,就是比特币交易,交易信息也是所有加密货币的本质。除此以外,区块还包含了一些技术信息,比如版本,当前时间戳和前一个区块的哈希。

在本文中,我们并不会实现一个像比特币技术规范所描述的区块链,而是实现一个简化版的区块链,它仅包含了一些关键信息。看起来就像是这样:

  1. type Block struct {

  2.    Timestamp     int64

  3.    Data          []byte

  4.    PrevBlockHash []byte

  5.    Hash          []byte

  6. }

说明:

  • Timestamp 是当前时间戳,也就是区块创建的时间。

  • Data 是区块存储的实际有效的信息。

  • PrevBlockHash 存储的是前一个块的哈希。

  • Hash 是当前块的哈希。

在比特币技术规范中, Timestamp , PrevBlockHash , Hash 是区块头(block header),区块头是一个单独的数据结构。而交易,也就是这里的 Data , 是另一个单独的数据结构。为了简便起见,我把这两个混合在了一起。

那么,我们要如何计算哈希呢?如何计算哈希,是区块链一个非常重要的部分。正是由于这个特性,才使得区块链是安全的。计算一个哈希,是在计算上非常困难的一个操作。即使在高速电脑上,也要花费不少时间 (这就是为什么人们会购买 GPU 来挖比特币) 。这是一个有意为之的架构设计,它故意使得加入新的区块十分困难,因此可以保证区块一旦被加入以后,就很难再进行修改。在本系列未来几篇文章中,我们将会讨论和实现这个机制。

目前,我们仅取了 Block 结构的一些字段(Timestamp, Data 和 PrevBlockHash),并将它们相互连接起来,然后在连接后的结果上计算一个 SHA-256 的哈希. 让我们在 SetHash 方法中完成这个任务:

  1. func (b *Block) SetHash() {

  2.    timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))

  3.    headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})

  4.    hash := sha256.Sum256 (headers)

  5.    b.Hash = hash[:]

  6. }

接下来,按照 Golang 的惯例,我们会实现一个用于简化创建一个区块的函数:

  1. func NewBlock(data string, prevBlockHash []byte) *Block {

  2.    block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}

  3.    block.SetHash()

  4.    return block

  5. }

这就是区块部分的全部内容了!

区块链

下面让我们来实现一个区块链。本质上,区块链仅仅是一个有着特定结构的数据库,是一个有序,后向连接的列表。这也就是说,区块按照插入的顺序进行存储,每个块都被连接到前一个块。这样的结构,能够让我们快速地获取链上的最新块,并且高效地通过哈希来检索一个块。

在 Golang 中,可以通过一个 array 和 map 来实现这个结构:array 存储有序的哈希(Golang 中 array 是有序的),map 存储 hask -> block 对(Golang 中, map 是无序的)。 但是在基本的原型阶段,我们只用到了 array,因为现在还不需要通过哈希来获取块。

  1. type Blockchain struct {

  2.    blocks []*Block

  3. }

这就是我们的第一个区块链!我从来没有想过它会是这么容易。

现在,让我们能够给它添加一个块:

  1. func (bc *Blockchain) AddBlock(data string) {

  2.    prevBlock := bc.blocks[len(bc.blocks)-1]

  3.    newBlock := NewBlock(data, prevBlock.Hash)

  4.    bc.blocks = append(bc.blocks, newBlock)

  5. }

完成!不过,真的就这样了吗?

为了加入一个新的块,我们必须要有一个已有的块,但是,现在我们的链是空的,一个块都没有!所以,在任何一个区块链中,都必须至少有一个块。这样的块,也就是链中的第一个块,通常叫做创世块( genesis block ). 让我们实现一个方法来创建一个创世块:

  1. func NewGenesisBlock() *Block {

  2.    return NewBlock("Genesis Block", []byte{})

  3. }

现在,我们可以实现一个函数来创建有创世块的区块链:

  1. func NewBlockchain() *Blockchain {

  2.    return &Blockchain{[]*Block{NewGenesisBlock()}}

  3. }

来检查一个我们的区块链是否如期工作:

  1. func main() {

  2.    bc := NewBlockchain()

  3.    bc.AddBlock("Send 1 BTC to Ivan")

  4.    bc.AddBlock("Send 2 more BTC to Ivan")

  5.    for _, block := range bc.blocks {

  6.        fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)

  7.        fmt.Printf("Data: %s\n", block.Data)

  8.        fmt.Printf("Hash: %x\n"







请到「今天看啥」查看全文