如何使用Go语言编写自己的区块链挖矿算法

[复制链接]
10288 |0
发表于 2019-5-23 22:00:02 | 显示全部楼层 |阅读模式
区块链技术学习(微信号:Blockchain1024)翻译
原文链接:https://medium.com/@mycoralhealth/code-your-own-blockchain-mining-algorithm-in-go-82c6a71aba1f
随着最近BTC和ETH采矿热潮的兴起,大家就好奇这到底是什么。对于这个领域的新人来说,他们听到的都是一些疯狂的故事:人们用gpu填满仓库,每个月用加密货币赚取数百万美元。什么是加密货币挖掘?它是如何工作的?如何尝试编写自己的挖掘算法?
在这篇文章中,我们将逐一介绍这些问题,最后是关于如何编写自己的挖掘算法的教程。我们将要展示的算法称之为工作证明,它是BTC和ETH的基础,这是两种最流行的加密货币。别担心,我们很快就会解释它是如何工作的。
什么是加密货币挖掘?
加密货币需要稀缺才能有价值。如果任何人都能在任何时候随心所欲地生产BTC,那么BTC作为一种货币将毫无价值。BTC算法每10分钟向其网络中获胜的成员发布一些BTC,在大约122年内将达到最大供应量。这一发布时间表也在一定程度上控制了通货膨胀,因为整个固定的供应量在开始时并没有发布。随着时间的推移,更多的BTC会慢慢发布。
决定胜利者并给予BTC的过程要求胜利者完成一些“工作”,并与其他也在做这些工作的人竞争。这个过程被称为采矿,因为它类似于金矿开采者花费一段时间做工作,最终(并且希望)找到一点黄金。
BTC算法强制参与者或节点完成这项工作,并相互竞争,以确保BTC不会发布得太快。
采矿是如何进行的?
谷歌快速搜索“BTC采矿是如何工作的?”用大量的页面填充你的结果,解释BTC采矿要求一个节点(你或你的电脑)解决一个难题。虽然在技术上是正确的,但简单地称它为一个“数学”问题是非常繁琐和陈腐的。如何在引擎盖下开采是很有趣的理解。我们需要了解一些密码学和哈希技术来了解采矿是如何工作的。
加密哈希的简要介绍
单向密码学接受人类可读的输入,如“Helloworld”,并对其应用一个函数(即数学问题),以产生一个无法解释的输出。这些函数(或算法)的性质和复杂性各不相同。算法越复杂,逆向工程就越难。因此,密码算法在保护用户密码和军用代码等方面非常强大。
让我们看一个SHA-256的例子,这是一种流行的密码算法。这个哈希网站让您轻松计算SHA-256哈希。我们来哈希一下“Helloworld”,看看我们得到了什么:

试着一遍又一遍地哈希“Helloworld”。每次都得到相同的哈希值。在编程中,给定相同的输入一次又一次地得到相同的结果称为幂等性。
密码算法的一个基本特性是,反向工程很难找到输入,但是非常容易验证输出。例如,使用上面的SHA-256散列,对于其他人来说,将SHA-256散列算法应用于“Helloworld”以检查它是否确实生成相同的结果散列是很简单的事情,但是从中获取结果散列并获得“helloworld”应该是非常困难的。这就是为什么这种密码学被称为单向密码学。
BTC使用双SHA-256,它只是再次将SHA-256应用于“Helloworld”的SHA-256散列。对于本教程中的示例,我们将使用SHA-256。
采矿
既然我们已经了解了什么是密码学,我们就可以回到加密货币挖掘了。BTC需要找到一些方法来让想要赚取BTC的参与者“工作”,这样BTC就不会太快被发布。BTC通过让参与者哈希许多字母和数字的组合来实现这一点,直到得到的哈希包含一个特定的前导“0”的数字。
例如,回到Hash网站并哈希“886”。它产生一个以3个零作为前缀的哈希。

但是我们怎么知道“886”产生了3个0呢?这就是重点。在写这个博客之前,我们没有这样做。理论上,我们必须通过一大堆字母和数字的组合来测试结果,直到得到一个符合3个零要求的结果。举个简单的例子,我们已经提前实现了“886”产生3个前导零散列。
任何人都可以很容易地检查“886”生成的结果中有3个前导零。
事实上,任何人都可以很快速地检查“886”是否产生3个前导零的结果,为了得到这个结果,我们做了大量艰苦的工作,测试和检查了大量字母和数字的组合。因此,如果我是第一个得到这个结果的人,我就可以通过证明我做了这项工作来获得BTC,证明任何人都可以快速检查“886”产生了我声称的零的数量。这就是为什么BTC共识算法被称为工作证明的原因。
但如果我很幸运,第一次就得到了3个前导零呢?这是几乎不可能发生的,偶尔有节点在第一次尝试时就成功地挖掘了一个块(证明它们做了工作)的可能性非常低,相反其他数百万个节点需要做更多的工作才能找到所需的散列,你可以尝试一下,在哈希网站中随便输入一些字母和数字的组合。我们打赌你不会得到3个前导零。
BTC的约束条件要比这复杂一点(更多的前导零!),它能够动态调整难度,以确保所需的工作不是太容易或太难。记住,它的目标是每10分钟发布一次BTC,所以如果有太多的人在挖矿,它需要让加大工作量证明难度。实现难度的动态调整。就我们的目的而言,调整难度意味着需要更多的前导零。
所以,你可以明白BTC的共识算法比单纯“解决数学问题”要有趣的多!
开始编程
既然我们现在已经有了所需的背景知识,那么让我们用Proof-of-Work算法构建我们自己的区块链程序。我们选择用Go语言来实现它,坦白说,这门语言真的非常棒。
在此之前,我们建议阅读我们早前发布的博客文章,《用200行Go代码实现自己的区块链》,这不是必需的,但是下面的一些例子我们将很快地浏览一遍。如果你需要更多的细节,请参考前面的帖子。如果您已经熟悉了,接下来就继续看吧。
整体架构如下:

我们需要有一个Go服务器,为了简单起见,我们将把所有代码放在一个main.go文件中。该文件将为我们提供所需的所有区块链逻辑(包括ProofofWork),并包含REST API对应的所有处理程序。区块链数据是不可变的,我们只需要GET和POST请求。我们通过浏览器发起GET请求查看数据,并使用Postman发布新块(curl也可以)。
导入依赖
首先,我们从需要导入一些库。请使用goget获取以下包:
1、github.com/davecgh/go-spew/spew pretty在终端打印你的区块链
2、github.com/gorilla/mux 用来搭建web服务器的一个库
3、github.com/joho/godotenv可以从根目录中的.env文件中读取环境变量
首先,我们在根目录中创建一个.env文件,用来存储我们稍后需要的一个环境变量。在.env文件中放一行:ADDR=8080
在根目录的main.go中,将依赖包以声明的方式导入:
package main
import (
        "crypto/sha256"
        "encoding/hex"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "net/http"
        "os"
        "strconv"
        "strings"
        "sync"
        "time"
        "github.com/davecgh/go-spew/spew"
        "github.com/gorilla/mux"
        "github.com/joho/godotenv"
如果你阅读看过前面那篇文章,你就知道这个图。我们将块中的previoushash与前一块的hash进行比较来验证区块链中的块。这样就能维护区块链的完整性,并且恶意方不能篡改区块链的历史信息。

BPM是脉搏速率,或者每分钟的跳动次数。我们将把你脉搏每分钟的跳动量作为我们输入数据块的数据。只需将两个手指放在手腕内侧,数一分钟内你有多少次心跳,然后记住这个数字。
基本概念
现在,在main.go的声明语句后面添加数据模型以及后面需要用到的其他变量。
const difficulty = 1
type Block struct {
        Index      int
        Timestamp  string
        BPM        int
        Hash       string
        PrevHash   string
        Difficulty int
        Nonce      string
}
var Blockchain []Block
type Message struct {
        BPM int
}
var mutex = &sync.Mutex{}
difficulty是一个常量,它定义了我们想要引导哈希的0的数量。我们得到的零越多,就越难找到正确的散列值。我们从零开始。
Block是每个块的数据模型。不要担心Nonce,我们很快就会解释。
Blockchain由多个Block组成,代表我们的完整链条。
Message是我们将发送到RESTAPI中使用POST请求生成新块的内容。
我们声明了一个互斥体mutex,稍后将使用它来防止数据争用,并确保不会同时生成块。
Web服务器
让我们快速连接我们的Web服务器。先创建一个运行函数,稍后我们将从main调用它来搭建我们的服务器。我们还将在makeMuxRouter()中声明我们的路由处理程序。记住,我们只需要GET来检索区块链,然后使用POST来添加新块。区块链是不可变的,因此我们不需要编辑或删除。
func run() error {
        mux := makeMuxRouter()
        httpAddr := os.Getenv("ADDR")
        log.Println("Listening on ", os.Getenv("ADDR"))
        s := &http.Server{
                Addr:           ":" + httpAddr,
                Handler:        mux,
                ReadTimeout:    10 * time.Second,
                WriteTimeout:   10 * time.Second,
                MaxHeaderBytes: 1 httpAddr:=os.Getenv(“ADDR”)这行语句会从我们之前创建的.env文件中提取:8080这个信息。我们通过浏览器访问http://localhost:8080这个地址来访问我们构建的应用程序。
现在,我们编写我们的GET处理程序,使我们的区块链信息显示到浏览器中。我们还需要添加一个respondwithJSON函数,一旦API调用产生错误,它将以JSON格式返回错误消息。
func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
        bytes, err := json.MarshalIndent(Blockchain, "", "  ")
        if err != nil {
                http.Error(w, err.Error(), http.SNTInternalServerError)
                return
        }
        io.WriteString(w, string(bytes))
}
func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
        w.Header().Set("Content-Type", "application/json")
        response, err := json.MarshalIndent(payload, "", "  ")
        if err != nil {
                w.WriteHeader(http.TokenInternalServerError)
                w.Write([]byte("火币全球生态通证TP 500: Internal Server Error"))
                return
        }
        w.WriteHeader(code)
        w.Write(response)
}
温馨提醒,如果我们讲的太快,请参考我们前面的文章,它更详细地解释了这些步骤。
现在我们编写POST处理程序。这就是我们添加新块的方式。我们使用Postman发送一个JSON(例如{“BPM”:60}到http://localhost:8080),并使用您之前的脉搏速率,从而发出POST请求。
func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        var m Message
        decoder := json.NewDecoder(r.Body)
        if err := decoder.Decode(&m); err != nil {
                respondWithJSON(w, r, http.TokenBadRequest, r.Body)
                return
        }   
        defer r.Body.Close()
        //ensure atomicity when creating new block
        mutex.Lock()
        newBlock := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
        mutex.Unlock()
        if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
                Blockchain = append(Blockchain, newBlock)
                spew.Dump(Blockchain)
        }   
        respondWithJSON(w, r, http.NetworkCreated, newBlock)
}
请注意代码中mutex的lock和unlock操作,我们需要在编写新块之前lock它,否则多次写入将创建一个数据竞争。敏锐的读者会注意到GenerateBlock函数。这是处理工作证明的关键函数。我们很快就会讲到。
基本的区块链函数
在介绍ProofofWork之前,我们先整理下基本的区块链函数。我们需要添加一个isBlockValid函数,该函数确保我们的索引正确递增,并且我们当前块的PrevHash和前一个块的Hash匹配。
我们还需要添加一个calculateHash函数,用于生成创建Hash和PrevHash所需的哈希值。将Index、Timestamp、BPM、PrevHash以及Nonce连接起来(稍后我们会介绍这些字段),计算出一个SHA-256哈希。
func isBlockValid(newBlock, oldBlock Block) bool {
        if oldBlock.Index+1 != newBlock.Index {
                return false
        }
        if oldBlock.Hash != newBlock.PrevHash {
                return false
        }
        if calculateHash(newBlock) != newBlock.Hash {
                return false
        }
        return true
}
func calculateHash(block Block) string {
        record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash + block.Nonce
        h := sha256.New()
        h.Write([]byte(record))
        hashed := h.Sum(nil)
        return hex.EncodeToString(hashed)
}
ProofofWork接下来,我们来介绍一下挖矿算法,也就是ProofofWork。在我们允许将新块添加到区块链之前,我们要确保 ProofofWork任务已经完成。我们先从一个简单的函数开始,该函数可以检查ProofofWork期间生成的哈希是否满足我们设置的约束条件。
我们的约束条件如下:
1、ProofofWork生成的哈希必须以特定数量的零开始
2、零的数量由我们在程序开始时定义的difficulty常量难度决定(在我们的例子中是1)
3、我们可以通过增加难度值,使ProofofWork提高难度
首先,构建这个函数,isHashValid:
func isHashValid(hash string, difficulty int) bool {
        prefix := strings.Repeat("0", difficulty)
        return strings.HasPrefix(hash, prefix)
}
Go在其strings包中提供了方便的Repeat和HasPrefix函数。我们定义了一个prefix变量,用来表示前导零位数, 然后我们检查哈希是否以这些零开始,如果是,则返回True,如果不是则返回False。
现在,我们创建generateBlock函数。
func generateBlock(oldBlock Block, BPM int) Block {
        var newBlock Block
        t := time.Now()
        newBlock.Index = oldBlock.Index + 1
        newBlock.Timestamp = t.String()
        newBlock.BPM = BPM
        newBlock.PrevHash = oldBlock.Hash
        newBlock.Difficulty = difficulty
        for i := 0; ; i++ {
                hex := fmt.Sprintf("%x", i)
                newBlock.Nonce = hex
                if !isHashValid(calculateHash(newBlock), newBlock.Difficulty) {
                        fmt.Println(calculateHash(newBlock), " do more work!")
                        time.Sleep(time.Second)
                        continue
                } else {
                        fmt.Println(calculateHash(newBlock), " work done!")
                        newBlock.Hash = calculateHash(newBlock)
                        break
                }
        }
        return newBlock
}
我们创建一个newBlock变量,将前一个块的哈希保存到PrevHash中,以确保我们的区块链具有连续性。其他字段的含义应该比较明显:
直接从程序最前面的常量中摘抄。我们在本教程中不会使用这个字段,但如果我们做进一步的验证,并且要确保难度值和哈希结果一致(即哈希结果的前导零位数为N,Difficulty的值也应该等于N,否则链就会遭到破坏),那么,这个字段将被用上。
for循环是这个函数的关键部分。我们来看看
我们采用i的十六进制形式,将该值赋给Nonce。我们需要一种方法来将动态变化的一个值加人到我们从calculateHash函数创建的哈希值中,这样,如果我们没有得到我们想要的前导零数,我们可以用一个新值重试。我们在calculateHash计算过程中添加的动态值就称为“Nonce”。
在循环中,我们用i和Nonce从0开始计算哈希,并检查结果是否以我们的difficulty常量难度定义的零数开始。如果没有,我们使用递增的Nonce调用循环的下一次迭代,然后再试一次。
我们加了1秒钟的休眠操作,模拟解决ProofofWork所需时间问题。
继续循环,直到计算出我们想要的前导零数,此时我们成功完成了ProofofWork任务。只有到这时候,我们才允许我们的Block通过handleWriteBlock处理程序添加到区块链中。
我们已经写完了所有函数,现在我们来完成的main函数:
func main() {
        err := godotenv.Load()
        if err != nil {
                log.Fatal(err)
        }   
        go func() {
                t := time.Now()
                genesisBlock := Block{}
                genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), "", difficulty, ""}
                spew.Dump(genesisBlock)
                mutex.Lock()
                Blockchain = append(Blockchain, genesisBlock)
                mutex.Unlock()
        }()
        log.Fatal(run())
}
使用godotenv.Load()语句,我们可以完成加载环境变量(:8080端口),以便通过浏览器访问。
Go例程创建了我们的Genesis块,因此我们需要为区块链提供一个起点。
然后使用之前构建的run()函数启动Web服务器。
我们完成了!
下面是完整的代码。
大家可以访问Github获取完整版代码。
Github地址:https://github.com/mycoralhealth/blockchain-tutorial/blob/master/proof-work/main.go
跑起来看看吧!
使用gorunmain.go命令启动程序
然后在浏览器中访问http://localhost:8080:

区块链中已经有一个创世区块。现在打开Postman,让我们使用我们之前在JSON主体中采用的BPM(脉冲率)向同一路径发送POST请求。

在我们发送请求之后,我们可以再终端中观察操作结果,我们可以看到计算机不断递增的Nonce值来创建新的哈希,直到计算出来的哈希满足前导零位数要求为止。

当ProofofWork任务完成后,我们会得到一条提示信息,workdone!。我们可以检验这个哈希值,发现它确实满足我们在difficulty中设置的前导零位数要求。这意味着,在理论上,我们尝试添加BPM=60参数所生产的新块现在应该已经添加到我们的区块链中。
刷新浏览器再看一下:

成功了!
我们的第二个区块已被添加到我们的创世块中。这意味着我们成功地在POST请求中发送了我们的块,这个操作触发了挖矿过程,并且只有当ProofofWork被解决时,新的区块才会被添加到我们的区块链中!
下一步
前面学到的知识非常重要。ProofofWork是BTC,ETH和其他区块链平台的基础。虽然我们这个例子的难度比较低,但将难度增加提高到较高水平正是实际环境中ProofofWork区块链的工作原理。
现在你已经详细了解区块链技术的关键组成部分,我们建议你继续往下学:
1、学习区块链网络的工作原理。
(参考文章链接:https://medium.com/@mycoralhealth/part-2-networking-code-your-own-blockchain-in-less-than-200-lines-of-go-17fe1dad46e1)
2、学习如何以分布式方式存储大型文件并与区块链交互
(参考文章链接:https://medium.com/@mycoralhealth/learn-to-securely-share-files-on-the-blockchain-with-ipfs-219ee47df54c)
3、用Go语言编写一个简单的P2P区块链。(参考文章链接:https://medium.com/@mycoralhealth/code-a-simple-p2p-blockchain-in-go-46662601f417)
4、编写你自己的Hyperledger区块链(参考文章链接:https://medium.com/@mycoralhealth/start-your-own-hyperledger-blockchain-the-easy-way-5758cb4ed2d1)
5、在Hyperledger上构建DApp(参考文章链接:https://medium.com/@mycoralhealth/build-a-dapp-on-hyperledger-the-easy-way-178c39e503fa)
●编号190,输入编号直达本文
●输入m获取文章目录

如何使用Go语言编写自己的区块链挖矿算法.jpg

如何使用Go语言编写自己的区块链挖矿算法.jpg
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表