用go语言撸一个简易版的区块链(代码片段)

lucasma.eth lucasma.eth     2022-11-28     112

关键词:

用go撸一个简易版的区块链

引言

这个最初的版本时多年以前学习go的时候,自己撸的一个简易版本的区块链。不过麻雀虽小,五脏俱全。通过这个代码你了解区块链内部的大概运行机制时没有问题的。

比特币底层区块链的代码非常复杂,但是我们可以从中梳理几个核心的概念,然后对应进行简单的实现。通过这些简易版本的实现我们可以以小窥大。下面我们先来梳理下几个核心的概念。

交易

拿比特币举例,A给B转账,这是一笔交易。更广义的概念,交易可以形容数据库网络中发生的每一次改变,可以是一笔转账、一个事件通知、或一段信息。交易中通常包含发送者的信息,接收者的信息,交易金额等信息。

这是交易的概念。

区块

区块链 是一个共享的、不可篡改的账本,旨在促进业务网络中的交易记录和资产跟踪流程。拿比特币交易举例,比如A向B转账,这笔交易会在区块链公网进行广播,并在各节点间共享这一信息。每十分钟左右,挖矿者会将这些交易收集到一个新区块中。

这是区块的概念。

把一个个验证后的合法的区块连在一起,形成的就是链。

哈希(hash)

哈希函数(Hash Function),也称为散列函数,给定一个输入x,它会算出相应的输出H(x)。只要输入变化,输出的哈希结果必然也会变化。

区块里面除了包含交易的信息之外,还会将一个加密哈希附加到新区块的最后。如果修改了区块的内容,则该哈希值也将更改,这将提供一种检测数据篡改的方式。

这是哈希的概念。

工作量证明

就是我们俗称的挖矿。矿工(就是区块链节点)创建区块,如果想把区块加入区块链,矿工需要不断的在区块中加入一个随机数并计算一个哈希值,只有这个值小于的某个目标值才能加入链中。这个目标值决定了挖矿的难度。

源码解析

有了上面的知识储备后,我们来分析下源码。

注意我只会贴出来关键的代码,会把不影响核心逻辑的都去掉。 全部的源码再文章最后的链接给出。

整个工程的目录结构时这样的,

pkg里面存放的是核心业务代码,也就是区块链的核心逻辑。同时也包括对应的单元测试程序。cmd目录下存放的是程序的运行入口,也就是我们的main函数。

我们从入口开始,采用从全局到细节的流程来剖析源码。

func main() 

	port := flag.String("port", "8001", "use -port <port number>")
	flag.Parse()

	fmt.Printf("port is:%s\\n", *port)

	http.HandleFunc("/mine", MineHandler)
	http.HandleFunc("/transactions/new", NewTransactionHandler)
	http.HandleFunc("/nodes/register", RegisterNodesHandler)
	http.HandleFunc("/nodes/resolve", ConsensusHandler)
	http.HandleFunc("/chain", ChainHandler)

	http.ListenAndServe(fmt.Sprintf(":%s", *port), nil)

main函数没有业务逻辑,就是通过http的方式给我们提供了测试的入口,让我们可以通过http请求(get或者post)发起对应的功能。

首先我们来看看,注册节点的方法RegisterNodesHandler,所谓的节点就是矿工的意思。我们可以通过命令行的方式(./goblockchain -port 8001)来启动一个节点,同时通过RegisterNodesHandler把其他节点加入进来。

func RegisterNodesHandler(w http.ResponseWriter, req *http.Request) 

	type Nodes_ST struct 
		Nodes []string
	

	type ResponseJsonBean struct 
		Message string   `json:"message"`
		Data    []string `json:"total_nodes"`
	

	nodeGroup := Nodes_ST
	result := ResponseJsonBean


	req.ParseForm()

	b, _ := ioutil.ReadAll(req.Body)

	if req.Method == "POST" 

		json.Unmarshal([]byte(b), &nodeGroup)

		result.Data = make([]string, 0)

		for _, node := range nodeGroup.Nodes 

				fmt.Printf("node:%s\\n", node)
				goblockchain.Register_Node(node)
				result.Data = append(result.Data, node)
		

		code = http.StatusCreated
		result.Message = "New nodes have been added"

	

	bytes, _ := json.Marshal(result)
	w.WriteHeader(code)
	fmt.Fprintf(w, string(bytes))

这部分的逻辑就是解析post请求的节点数,然后循环调用(循环次数是节点的数量)调用Register_Node方法注册节点,请求的数据类型下面这样的格式:


    "nodes": [
        "http://127.0.0.1:8002",
        "http://127.0.0.1:8003"
    ]

继续来看下Register_Node方法,

func (bc *Blockchain) Register_Node(address string) 
	u, err := url.Parse(address)
	bc.nodes = append(bc.nodes, u.Host)

其实就是加入了一个blockchain实例的nodes元素的数组里,nodes的定义如下:

type Blockchain struct 
	current_transactions []Transaction
	chain                []block
	nodes                []string

然后我们看看在某个节点上创建一笔交易的流程。入口函数是NewTransactionHandler

func NewTransactionHandler(w http.ResponseWriter, req *http.Request) 

    //交易的元数据
	type Transaction_ST struct 
		Sender    string `json:sender`
		Recipient string `json:recipient`
		Amount    int    `json:amount`
	

	type ResponseJsonBean struct 
		Message string `json:"message"`
	

	transaction := Transaction_ST
	result := ResponseJsonBean

	req.ParseForm()
	b, _ := ioutil.ReadAll(req.Body)

	if req.Method == "POST" 

		json.Unmarshal([]byte(b), &transaction)

		index := goblockchain.New_Transaction(transaction.Sender, transaction.Recipient, transaction.Amount)

		code = http.StatusCreated
		result.Message = fmt.Sprintf("New nodes have been added to Block %d", index)

	

	bytes, _ := json.Marshal(result)
	w.WriteHeader(code)
	fmt.Fprintf(w, string(bytes))


解析post提交的交易数据,然后请求New_Transaction方法创建交易。请求的数据示例如下:


  "sender": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
  "recipient": "1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55",
  "amount": 1000

New_Transaction方法也比较简单,

func (bc *Blockchain) New_Transaction(sender string, recipient string, amount int) int 

	var trans Transaction

	trans.Sender = sender
	trans.Recipient = recipient
	trans.Amount = amount

	bc.current_transactions = append(bc.current_transactions, trans)
	block := bc.Last_block()
	return block.Index + 1


创建交易,放到blockchain实例的交易数组里,这里有个Last_block方法也注意下,它的实现如下:

func (bc *Blockchain) Last_block() block 
	height := len(bc.chain)
	block := bc.chain[height-1]
	return block

这个方法就是返回了当前区块链的最后一个区块的实例。

然后,就可以挖矿了,哈哈。入口函数是MineHandler

func MineHandler(w http.ResponseWriter, req *http.Request) 

    //区块的数据结构
	type Block_ST struct 
		Index         int           `json:index`
		Message       string        `json:message`
		Transactions  []Transaction `json:transactions`
		Proof         int           `json:proof`
		Previous_hash string        `json:previous_hash`
	

    
	result := Block_ST

	req.ParseForm()


	last_block := goblockchain.Last_block()
	last_proof := last_block.Proof
	previous_hash := last_block.Previous_hash

    //开始挖坑
	proof := goblockchain.Proof_of_work(last_proof)

	var trans_reward Transaction
	trans_reward.Sender = "0"
	trans_reward.Recipient = "random address"
	trans_reward.Amount = 1

    //挖坑成功,形成新的区块并加入链
	block := goblockchain.New_Block(proof, previous_hash)

	code = http.StatusOK

	result.Message = "New Block Forged"
	result.Index = block.Index
	result.Proof = block.Proof
	result.Transactions = block.Transactions
	result.Previous_hash = block.Previous_hash

	bytes, _ := json.Marshal(result)
	w.WriteHeader(code)
	fmt.Fprintf(w, string(bytes))



这个代码稍长,不过也很好理解。首先是获取链上最后一个区块的证明值(last_proof),之所以要获取它是因为计算新区块的哈希需要使用上一个区块的证明值。这样能保证整个链上的区块都是互相关联的。

工作量证明的方法是Proof_of_work

func (bc *Blockchain) Proof_of_work(last_proof int) int 
	proof := 0

	for !(valid_proof(last_proof, proof)) 
		proof++
	

	return proof


这里的proof就是新区块一直在尝试的随机值,然后用valid_proof验证是否满足条件(也就是挖矿是否成功),源码如下:

func valid_proof(last_proof int, proof int) bool 

	str_last_proorf := []byte(strconv.FormatInt(int64(last_proof), 10))
	str_proof := []byte(strconv.FormatInt(int64(proof), 10))

	str_data := bytes.Join([][]bytestr_last_proorf, str_proof, []byte)

	guess_hash := sha256.Sum256(str_data)
	return bytes.Equal(guess_hash[:2], []byte("00"))


这里可以看到,计算哈希的时候需要把链上最后一个区块的证明值作为其中一个输入。

挖矿成功后,就可以形成新的区块放入链上了,方法是New_Block,源码如下:

func (bc *Blockchain) New_Block(proof int, previous_hash string) *block 


	blockinstance := &block

	blockinstance.Index = len(bc.chain) + 1
	blockinstance.timestamp = time.Now().Unix()
	blockinstance.Transactions = bc.current_transactions
	blockinstance.Proof = proof
	blockinstance.Previous_hash = previous_hash

	trans_len := len(bc.current_transactions)

	fmt.Printf("trans_len:%d \\n", trans_len)
	fmt.Printf("index:%d \\n", blockinstance.Index)

	bc.current_transactions = bc.current_transactions[trans_len:] //clear
	bc.chain = append(bc.chain, *blockinstance)

	return blockinstance


这里的逻辑也很简单,生成一个新的区块实例,加入交易的信息,工作量证明值,上一个区块的哈希,然后链接在区块链的最后即可。

测试

首先编译下源码,进入cmd目录,执行:

$ go build -o goblockchain

然后开启三个命令行窗口,分别执行下面的命令,启动三个节点(矿工)

./goblockchain -port 8001
./goblockchain -port 8002
./goblockchain -port 8003

然后我们在node1执行添加节点的命令,

可以看到执行成功了。

接着我们创建一笔交易,

可以看到创建成功了。

然后执行挖矿

可以看到挖矿成功了。

然后我们可以查看下当前区块链的信息:

curl 127.0.0.1:8001/chain

返回的结果是:

Index:1 timestamp:1655738834 Transactions:[] Proof:1 Previous_hash:1 --> Index:2 timestamp:1655738903 Transactions:[Sender:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa Recipient:1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55 Amount:1000] Proof:9561 Previous_hash:1 --> 

可以看到是有两个区块,第一个是默认生成的不包含交易信息,第二个区块就是我们自己挖的。


源码地址如下:

https://github.com/pony-maggie/blockchain

用go语言撸一个简易版的区块链(代码片段)

用go撸一个简易版的区块链引言这个最初的版本时多年以前学习go的时候,自己撸的一个简易版本的区块链。不过麻雀虽小,五脏俱全。通过这个代码你了解区块链内部的大概运行机制时没有问题的。比特币底层区块链的... 查看详情

[go]用go语言实现区块链工作原理(代码片段)

基本原理这里就不写了,只写一个简单demo的实现首先得有一个区块用来存储区块头和区块体typeBlockstructVersionint64PreBlockHash[]byteHash[]byte//区块体内是不存储HASH值的,这是网络中某个节点在计算时存储在息本地的,这里是为了方便... 查看详情

只用200行go代码写一个自己的区块链!(代码片段)

...想了解这一切是如何工作的。这篇文章就是帮助你使用Go语言来实现一个简单的区块链,用不到200行代码来揭示区块链的原理!高可用架构也会持续推出更多区块链方面文章,欢迎点击上方蓝色『高可用架构』关注。“用不到200... 查看详情

xuperchain百度区块链简易学习帖(代码片段)

...XuperChain基于Ubuntu系统从零开始到部署简单合约环境配置go语言安装git安装客户端安装启动私链基本操作命令1.创建普通用户2.创建合约账号3.查询余额4.转账5.查询交易信息6.查询block信息网络部署(选择性阅读)1.创建网络... 查看详情

用go构建一个区块链----part1:基本原型(代码片段)

...且还有很多潜力尚未显现出来。本质上,区块链只是一个分布式数据库而已。不过,使它独一无二的是,区块链是一个公开的数据库,而不是一个私人数据库,也就是说,每个使用它的人都有一个完整或部... 查看详情

用go构建一个区块链--part7:网络(代码片段)

...密货币成为可能的,是网络(network)。如果实现的这样一个区块链仅仅运行在单一节点上,有什么用呢?如果只有一个用户,那么这些基于密码学的特性,又有什么用呢?正是由于网络,才使得整个机 查看详情

用go构建一个区块链--part6:交易(代码片段)

引言在这个系列文章的一开始,我们就提到了,区块链是一个分布式数据库。不过在之前的文章中,我们选择性地跳过了“分布式”这个部分,而是将注意力都放到了“数据库”部分。到目前为止,我们几乎已经实现了一个区块... 查看详情

基于go语言构建区块链:part1(代码片段)

Golang语言和区块链理论学习完毕后,快速入门方法无疑是项目实战。本文将参考https://jeiwan.cc/tags/blockchain/教程,学习如何基于Go语言构建区块链。1、编程环境设置编程工具使用GoLand,前文已介绍软件安装经验。软件安装完成后... 查看详情

用go构建一个区块链--part7:网络(代码片段)

翻译的系列文章我已经放到了GitHub上:blockchain-tutorial,后续如有更新都会在GitHub上,可能就不在这里同步了。如果想直接运行代码,也可以cloneGitHub上的教程仓库,进入src目录执行make即可。引言到目前为止... 查看详情

区块链技术go语言——变量篇(代码片段)

/**@Author:mrtao*@Date:2018-08-1110:56:38*@LastModifiedby:mrtao*@LastModifiedtime:2018-08-1211:04:04*/packagemainimport"fmt"funcmain() varaint a=20 fmt.Println("a=& 查看详情

区块链技术go语言——数组篇(代码片段)

/**@Author:mrtao*@Date:2018-08-1414:48:25*@LastModifiedby:mrtao*@LastModifiedtime:2018-08-1415:55:51*/packagemainimport"fmt"funcmain() //指针 varp*int a:=10 p=&a //p& 查看详情

go语言凭什么是区块链的首选语言(代码片段)

GO语言凭什么是区块链的首选语言区块链的火热也带动了GO语言开发者的突增,那凭什么GO语言会成为最主要的区块链构建语言之一。我认为这得益于GO语言的性能、抽象度、简单性和现代性。并且,当下最成功、最流行的... 查看详情

用go构建一个区块链--part3:持久化和命令行接口(代码片段)

...目录执行make即可。引言到目前为止,我们已经构建了一个有工作量证明机制的区块链。有了工作量证明ÿ 查看详情

200行go语言代码自建一个区块链体验挖矿乐趣(代码片段)

谈谈区块链:挖矿的目的:通过挖矿证明算力,防止他人作弊,自己又能获得奖励【给自己加钱】。挖矿的过程:将网上别的合法且最新的用户交易同步过来,加入到区块,然后加随机数哈希后与系统给出... 查看详情

用go构建一个区块链--part2:工作量证明(代码片段)

...进入src目录执行make即可。在前面一文中,我们构造了一个非常简单的数据结构,这个数据结构也是整个区 查看详情

区块链基础语言——go语言变量(代码片段)

...性和可维护性,多采用统一的、可读性高的命名方式。Go语言变量名由一个或多个字母、数字、下划线组成的序列,但第一个字符必须是字母或下划线,不能是数字,且区分大小写。例如:1a不能作为变量名,myname和myName是不同... 查看详情

c语言实现简易区块链(代码片段)

C语言实现简易区块链总结:C语言真不是我这种菜鸡所能驾驭的…无奈哈希函数太麻烦,就采用base64替代下哈希函数吧,其他符合区块链理论#include<stdio.h>#include<string.h>#include<time.h>#include<stdlib.h>//base6... 查看详情

cpp区块链模拟示例序列化(代码片段)

...币中是使用的是谷歌出品、c++编写的 LevelDB数据库,go语言示例中使用的是BoltDB。我本来考虑使 查看详情