写一个最简单的区块链——Yet another Go tutorial

栏目: Go · 发布时间: 7年前

内容简介:为什么说是最简单的区块链呢,因为根本写不出一个完整的区块链,甚至连区块链的Demo都算不上。本文充其量可以当做Go语言的一个入门教程,至少对我来说是这样。所以,即使读者没有任何区块链和Go语言的知识,也可以放心往下看。本文使用Go语言实现了区块链源自比特币,当年中本聪计划打造一个完全去中心化的电子货币交易系统,区块链应运而生。发明区块链的动机,大概是中本聪觉得,任何中心化的系统都不够安全,一旦把特权赋予某些人,就存在滥用职权和腐败的可能。只有在去中心化的系统中,才会存在绝对的安全。

前言

为什么说是最简单的区块链呢,因为根本写不出一个完整的区块链,甚至连区块链的Demo都算不上。本文充其量可以当做 Go 语言的一个入门教程,至少对我来说是这样。所以,即使读者没有任何区块链和Go语言的知识,也可以放心往下看。

本文使用Go语言实现了

  • 区块的定义和构建
  • 区块链的定义和构建
  • 添加交易
  • 查看区块链内容
  • 提供Go API和Web API两种方式

区块链的概念

区块链源自比特币,当年中本聪计划打造一个完全去中心化的电子货币交易系统,区块链应运而生。发明区块链的动机,大概是中本聪觉得,任何中心化的系统都不够安全,一旦把特权赋予某些人,就存在滥用职权和腐败的可能。只有在去中心化的系统中,才会存在绝对的安全。

去中心化其实很简单,直接让每一个节点都保存完整的交易信息,自然就不需要中心节点了。但这样会造成资源的极大浪费,也会造成通信的拥堵。不过比特币似乎就是这样干的,毕竟比特币的总量不算很多,交易量也不至于太大。

去中心化的另一个问题是需要共识机制。因为区块链是一个单向链表,如果两个节点同时想要向链表头部添加元素,势必造成链表的分叉。共识机制规定了整个网络中最长的那个链为有效链,任何节点一旦发现其它链比本地保存的链更长,就必须更换为那个最长的链。

此外,节点是不能随意向区块链中添加元素的,否则谁添加的最快,谁的区块链就最长,那岂不是成了速度竞赛。制约的方法是,一个节点产生的交易,必须由另一个节点记账,才能加入区块链中。为了鼓励人们积极为其他人记账,中本聪设计了“工作量证明”这一环节,也就是我们俗称的“挖矿”。成功为他人记账的节点,会收到若干个比特币的奖励。这样一来,人们蜂拥而至,争先为别人记账,但交易数量有限,多个人同时记账只会有一个人记账成功,其他人无功而返。为了使这一过程不受网速的影响,使大家能够公平竞争,中本聪规定,记账者必须得到若干个0开头的账单摘要才算记账成功。所谓账单摘要,就是把账单数据按照规定的组合方式,加上随机数,再经过某种哈希算法(比如SHA256)得到的固定长度的数据串。目前的规定是以18个0开头,每个0是一个16进制数字,也就相当于平均每尝试16 18 次才可能得到一个符合条件的账单摘要。这也是为什么挖矿需要消耗大量的计算资源,而且挖矿会越来越难(数字0开头的数量还会进一步增多)。

说了这么多理论,也该开始实践了。但说着容易做着难,我们没法把上面提到的所有特性都实现出来,只能实现最基础的功能。即使如此,对帮助大家直观地了解区块链也已经足够了。

Go语言实现区块链

如果手边有一台电脑,建议按照本节的流程亲自敲一遍代码。

0.配置开发环境

本来想把如何配置开发环境写一写,但实在太繁琐,又难以满足不同系统用户的需求,遂作罢。大家只能发挥自己的聪明才智搞一搞了。

1. 定义区块

// file: Block.go
package core

import (
    "crypto/sha256"
    "encoding/hex"
    "time"
)

type Block struct {

    // Block header
    Index int64
    Timestamp int64
    PrevBlockHash string
    Hash string

    // Block data
    Data string
}

func createBlock(prevBlock Block, data string) Block{
    newBlock := Block{}
    newBlock.Index = prevBlock.Index + 1
    newBlock.Timestamp = time.Now().Unix()
    newBlock.PrevBlockHash = prevBlock.Hash
    newBlock.Data = data
    newBlock.Hash = calculateHash(newBlock)
    return newBlock
}

func calculateHash(block Block) string {
    toBeHashed := string(block.Index) + string(block.Timestamp) + block.PrevBlockHash + block.Data
    hashInBytes := sha256.Sum256([]byte(toBeHashed))
    return hex.EncodeToString(hashInBytes[:])
}

这段代码定义了一个结构体 Block ,用来表示一个区块。区块包含区块头和数据两部分,其中,区块头包括序号 Index 、时间戳 Timestamp 、上一区块的摘要 PrevBlockHash 以及当前区块的摘要 Hash 。数据为了简单起见,直接用 string 类型表示。

下面两个私有函数分别用来创建区块和计算给定区块的摘要。之所以称为私有函数,是因为函数名以小写字母开头,Go编译器自动按照函数名首字母的大小写决定该函数的访问级别。在 calculateHash 函数中,我们把序号、时间戳、上一区块的摘要以及数据连接成一个长字符串,并计算该字符串的哈希值,作为当前区块的摘要。需要注意,Go语言中声明变量可以不显式指定类型,但需要用 := 符号来初始化。

2. 定义区块链

// file: BlockChain.go
package core

import "fmt"

type BlockChain struct {
    Blocks []*Block
}

func CreateBlockChain() BlockChain {
    genesisBlock := createBlock(Block{Index:-1}, "I am genesis block.")
    blockChain := BlockChain{}
    blockChain.Blocks = append(blockChain.Blocks, &genesisBlock)
    return blockChain
}

func (blockChain *BlockChain) AddTransaction(data string)  {
    block := createBlock(*blockChain.Blocks[len(blockChain.Blocks) - 1], data)
    blockChain.Blocks = append(blockChain.Blocks, █)
}

func (blockChain *BlockChain) Print()  {
    for _, block := range blockChain.Blocks {
        fmt.Printf("Index: %d\n", block.Index)
        fmt.Printf("Timestamp: %d\n", block.Timestamp)
        fmt.Printf("PreBlockHash: %s\n", block.PrevBlockHash)
        fmt.Printf("Hash: %s\n", block.Hash)
        fmt.Printf("Data: %s\n", block.Data)
        fmt.Println()
    }
}

这里,我们把区块链定义为另一个结构体,内部包含区块的数组切片。Go语言中的数组切片,其实就是变长数组,它的容量依赖于实际数组的长度。

我们提供了三个公有函数。 CreateBlockChain 用来创建一个新的区块链,在该函数中自动创建了一个区块,称为“创世区块”,该区块不含任何数据,Index为0,只用于标识区块链的起点。 AddTransaction 函数用来添加交易,内部会创建一个新的区块,并链接到区块链上。 Print 函数用来打印完整的区块链信息。

细心的话可以发现,这里的函数名前面增加了一些内容。在Go语言中,这种函数称为方法,方法名前面的部分是接收者,类似于C++中的this指针。 AddTransactionPrint 方法都声明了 BlockChain 类型的接收者,于是这两个方法可以当做 BlockChain 类型的成员函数来使用。

3. Go API Demo

以上已经实现了区块链的核心功能。我们现在写一个demo来测试一下效果。

// file: main.go
package main

import (
    "BlockChainDemo/core"
)

func main()  {
    blockChain := core.CreateBlockChain()
    blockChain.AddTransaction("Send 1 BTC to Faye.")
    blockChain.AddTransaction("Send 2 BTC to Liling.")
    blockChain.Print()
}

Go语言的主函数必须位于 main 包中,否则不能执行。运行该程序,输出结果为

Index: 0
Timestamp: 1538731505
PreBlockHash: 
Hash: f8970ec722193096998452516c709c1890323e5df5bd7cf1e139b9c592394f6d
Data: I am genesis block.

Index: 1
Timestamp: 1538731505
PreBlockHash: f8970ec722193096998452516c709c1890323e5df5bd7cf1e139b9c592394f6d
Hash: 12f8ca6f66b0b21e6d1ed7682265f849f46e4a4ff10e6ba794021eb25d0ab033
Data: Send 1 BTC to Faye.

Index: 2
Timestamp: 1538731505
PreBlockHash: 12f8ca6f66b0b21e6d1ed7682265f849f46e4a4ff10e6ba794021eb25d0ab033
Hash: 1c849f10a0da4f2aa45275f1585ddc700fa46c24cb8162484bf628a16aeac5a0
Data: Send 2 BTC to Liling.

可以看到,添加两次交易后,区块链的长度变为3,除了创世节点,后面每个节点表示一次交易。每个区块的 Hash 值与当前区块和上一区块都有关,因此任何人都无法篡改历史数据,任何微小的改动都会导致后面链条中所有数据的变化。

4. Web API Demo

最后一部分,发挥Go语言强大的Web编程能力,我们提供一个Web API供HTTP访问使用。

// file: server.go
package main

import (
    "BlockChainDemo/core"
    "encoding/json"
    "io"
    "net/http"
)

var blockChain core.BlockChain

func run()  {
    http.HandleFunc("/blockchain/get", blockchainGetHandler)
    http.HandleFunc("/blockchain/write", blockchainWriteHandler)
    http.ListenAndServe("localhost:8888", nil)
}

func blockchainGetHandler(writer http.ResponseWriter, request *http.Request) {
    bytes, error := json.MarshalIndent(blockChain, "", "\t")
    if error != nil {
        http.Error(writer, error.Error(), http.StatusInternalServerError)
        return
    }
    io.WriteString(writer, string(bytes))
}

func blockchainWriteHandler(writer http.ResponseWriter, request *http.Request)  {
    data := request.URL.Query().Get("data")
    blockChain.AddTransaction(data)
    blockchainGetHandler(writer, request)
}

func main()  {
    blockChain = core.CreateBlockChain()
    run()
}

实现方法非常简单,设置两个回调函数,对应两个URL,一个用来查询当前区块链,另一个用来向区块链中添加交易。运行此程序,然后打开浏览器,访问 http://localhost:8888/blockchain/get ,可以看到如下结果

写一个最简单的区块链——Yet another Go tutorial

get

此时只有创世区块。接下来添加一个交易

http://localhost:8888/blockchain/write?data=Send 1 BTC to Faye.

现在区块链变成了这样

写一个最简单的区块链——Yet another Go tutorial

write

每调用一次,区块链中就添加一个区块。

好了,到此为止,我们已经实现了预定的全部功能。

完整代码已上传GitHub,请点击 jingedawang/BlockChainDemo 下载。

总结

区块链并没有想象中那么神秘,也不是无所不能。最初版本的区块链有很大局限性,所以才有了区块链2.0、3.0,以及以太坊、智能合约的出现。想真正了解区块链,不深入到行业应用中是不行的,所以本文只是浅尝辄止,抛砖引玉而已。鉴于当下区块链工程师年薪百万,我想,是时候考虑转行了:-)

参考资料

区块链技术核心概念与原理讲解 慕课网

用GO语言构建自己的区块链 慕课网

Go 语言教程 菜鸟教程

golang 函数以及函数和方法的区别 D_Guco


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript Patterns

JavaScript Patterns

Stoyan Stefanov / O'Reilly Media, Inc. / 2010-09-21 / USD 29.99

What's the best approach for developing an application with JavaScript? This book helps you answer that question with numerous JavaScript coding patterns and best practices. If you're an experienced d......一起来看看 《JavaScript Patterns》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码