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

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

内容简介:为什么说是最简单的区块链呢,因为根本写不出一个完整的区块链,甚至连区块链的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


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

查看所有标签

猜你喜欢:

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

伟大创意的诞生

伟大创意的诞生

史蒂文·约翰逊 (Steven Johnson) / 盛杨燕 / 浙江人民出版社 / 2014-8-1 / CNY 52.90

 大家都认得出好创意。印刷机、铅笔、抽水马桶、电池、互联网、GPS、Google——这些都是绝妙的创意。然而,它们是如何产生的?被喻为“科技界的达尔文”的作者,在《伟大创意的诞生》一书中,提供了深具启示意义以及有论证实据的答案。  作者史蒂文•约翰逊以富有感染力、包罗万象的风格游历于多重领域,从神经生物学、都市研究,到网络文化,找出了独特创新背后的7大关键模式,深入人类600年重要发明的......一起来看看 《伟大创意的诞生》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

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

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具