区块链技术学习(微信号:Blockchain1024)翻译
原文链接:
https://medium.com/@mycoralhealth/code-your-own-blockchain-mining-algorithm-in-go-82c6a71aba1f
随着最近比特币和以太坊采矿热潮的兴起,大家就好奇这到底是什么。对于这个领域的新人来说,他们听到的都是一些疯狂的故事:人们用gpu填满仓库,每个月用加密货币赚取数百万美元。什么是加密货币挖掘?它是如何工作的?如何尝试编写自己的挖掘算法?
在这篇文章中,我们将逐一介绍这些问题,最后是关于如何编写自己的挖掘算法的教程。我们将要展示的算法称之为工作证明,它是比特币和以太坊的基础,这是两种最流行的加密货币。别担心,我们很快就会解释它是如何工作的。
什么是加密货币挖掘?
加密货币需要稀缺才能有价值。如果任何人都能在任何时候随心所欲地生产比特币,那么比特币作为一种货币将毫无价值。比特币算法每10分钟向其网络中获胜的成员发布一些比特币,在大约122年内将达到最大供应量。这一发布时间表也在一定程度上控制了通货膨胀,因为整个固定的供应量在开始时并没有发布。随着时间的推移,更多的比特币会慢慢发布。
决定胜利者并给予比特币的过程要求胜利者完成一些“工作”,并与其他也在做这些工作的人竞争。这个过程被称为采矿,因为它类似于金矿开采者花费一段时间做工作,最终(并且希望)找到一点黄金。
比特币算法强制参与者或节点完成这项工作,并相互竞争,以确保比特币不会发布得太快。
采矿是如何进行的?
谷歌快速搜索“比特币采矿是如何工作的?”用大量的页面填充你的结果,解释比特币采矿要求一个节点(你或你的电脑)解决一个难题。虽然在技术上是正确的,但简单地称它为一个“数学”问题是非常繁琐和陈腐的。如何在引擎盖下开采是很有趣的理解。我们需要了解一些密码学和哈希技术来了解采矿是如何工作的。
加密哈希的简要介绍
单向密码学接受人类可读的输入,如“Hello world”,并对其应用一个函数(即数学问题),以产生一个无法解释的输出。这些函数(或算法)的性质和复杂性各不相同。算法越复杂,逆向工程就越难。因此,密码算法在保护用户密码和军用代码等方面非常强大。
让我们看一个SHA-256的例子,这是一种流行的密码算法。这个哈希网站让您轻松计算SHA-256哈希。我们来哈希一下“Hello world”,看看我们得到了什么:
试着一遍又一遍地哈希“Hello world”。每次都得到相同的哈希值。在编程中,给定相同的输入一次又一次地得到相同的结果称为幂等性。
密码算法的一个基本特性是,反向工程很难找到输入,但是非常容易验证输出。例如,使用上面的SHA-256散列,对于其他人来说,将SHA-256散列算法应用于“Hello world”以检查它是否确实生成相同的结果散列是很简单的事情,但是从中获取结果散列并获得“hello world”应该是非常困难的。这就是为什么这种密码学被称为单向密码学。
比特币使用双SHA-256,它只是再次将SHA-256应用于“Hello world”的SHA-256散列。对于本教程中的示例,我们将使用SHA-256。
采矿
既然我们已经了解了什么是密码学,我们就可以回到加密货币挖掘了。比特币需要找到一些方法来让想要赚取比特币的参与者“工作”,这样比特币就不会太快被发布。比特币通过让参与者哈希许多字母和数字的组合来实现这一点,直到得到的哈希包含一个特定的前导“0”的数字。
例如,回到Hash网站并哈希“886”。它产生一个以3个零作为前缀的哈希。
但是我们怎么知道“886”产生了3个0呢?这就是重点。在写这个博客之前,我们没有这样做。理论上,我们必须通过一大堆字母和数字的组合来测试结果,直到得到一个符合3个零要求的结果。举个简单的例子,我们已经提前实现了“886”产生3个前导零散列。
任何人都可以很容易地检查“886”生成的结果中有3个前导零。
事实上,任何人都可以很快速地检查“886”是否产生3个前导零的结果,为了得到这个结果,我们做了大量艰苦的工作,测试和检查了大量字母和数字的组合。因此,如果我是第一个得到这个结果的人,我就可以通过证明我做了这项工作来获得比特币,证明任何人都可以快速检查“886”产生了我声称的零的数量。这就是为什么比特币共识算法被称为工作证明的原因。
但如果我很幸运,第一次就得到了3个前导零呢?这是几乎不可能发生的,偶尔有节点在第一次尝试时就成功地挖掘了一个块(证明它们做了工作)的可能性非常低,相反其他数百万个节点需要做更多的工作才能找到所需的散列,你可以尝试一下,在哈希网站中随便输入一些字母和数字的组合。我们打赌你不会得到3个前导零。
比特币的约束条件要比这复杂一点(更多的前导零!),它能够动态调整难度,以确保所需的工作不是太容易或太难。记住,它的目标是每10分钟发布一次比特币,所以如果有太多的人在挖矿,它需要让加大工作量证明难度。实现难度的动态调整。就我们的目的而言,调整难度意味着需要更多的前导零。
所以,你可以明白比特币的共识算法比单纯“解决数学问题”要有趣的多!
开始编程
既然我们现在已经有了所需的背景知识,那么让我们用Proof-of-Work算法构建我们自己的区块链程序。我们选择用Go语言来实现它,坦白说,这门语言真的非常棒。
在此之前,我们建议阅读我们早前发布的博客文章,《用200行Go代码实现自己的区块链》,这不是必需的,但是下面的一些例子我们将很快地浏览一遍。如果你需要更多的细节,请参考前面的帖子。如果您已经熟悉了,接下来就继续看吧。
整体架构如下:
我们需要有一个Go服务器,为了简单起见,我们将把所有代码放在一个main.go文件中。该文件将为我们提供所需的所有区块链逻辑(包括Proof of Work),并包含REST API对应的所有处理程序。区块链数据是不可变的,我们只需要GET和POST请求。我们通过浏览器发起GET请求查看数据,并使用Postman发布新块(curl也可以)。
导入依赖
首先,我们从需要导入一些库。请使用go get获取以下包:
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"
如果你阅读看过前面那篇文章,你就知道这个图。我们将块中的previous hash与前一块的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 <20,
}
if err := s.ListenAndServe(); err != nil {
return err
}
return nil
}
func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
return muxRouter
}
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.StatusInternalServerError)
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.StatusInternalServerError)
w.Write([]byte
("HTTP 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.StatusBadRequest, r.Body)
return
}
defer r.Body.Close()
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.StatusCreated, newBlock)
}
请注意代码中mutex的lock 和 unlock 操作,我们需要在编写新块之前lock 它,否则多次写入将创建一个数据竞争。敏锐的读者会注意到GenerateBlock函数。这是处理工作证明的关键函数。我们很快就会讲到。
基本的区块链函数
在介绍Proof of Work之前,我们先整理下基本的区块链函数。 我们需要添加一个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)
}
Proof of Work
接下来,我们来介绍一下挖矿算法,也就是 Proof of Work。 在我们允许将新块添加到区块链之前,我们要确保 Proof of Work 任务已经完成。我们先从一个简单的函数开始,该函数可以检查Proof of Work期间生成的哈希是否满足我们设置的约束条件。
我们的约束条件如下:
1、Proof of Work生成的哈希必须以特定数量的零开始
2、零的数量由我们在程序开始时定义的difficulty常量难度决定(在我们的例子中是1)
3、我们可以通过增加难度值,使Proof of Work提高难度
首先,构建这个函数,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"