go语言 实现简单区块链
Posted 二向箔区块链开发
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go语言 实现简单区块链相关的知识,希望对你有一定的参考价值。
简介
本教程是从这篇优秀的文章改编的,关于使用javascript编写基本区块链。我们已将它移植到Go并添加了一些额外的好东西,如在Web浏览器中查看您的区块链。
本教程中的数据示例将基于您的心跳。:-)为了好玩,在整个教程中记下你的脉搏一分钟(每分钟节拍)并记住这个数字。
世界上几乎每个开发商都听说过区块链,但大多数仍然不知道它是如何工作的。他们可能只知道比特币,因为他们听说过智能合约等事情。这篇文章试图通过帮助您在Go中编写自己的简单区块链,使用少于200行代码来揭开区块链的神秘面纱!到本教程结束时,您将能够在本地运行并写入区块链,并在Web浏览器中查看它。
了解区块链比创建自己的区块更好?
你将能够做什么
创建您自己的区块链
了解哈希如何保持区块链的完整性
了解如何添加新块
看看当多个节点生成块时,tiebreakers如何解决
在网页浏览器中查看您的区块链
编写新的块
获取区块链的基础知识,以便您可以决定您的知识旅行将带您从这里出发!
为了保持这篇文章的简单性,我们不会处理更高级的共识概念,例如工作证明与股权证明。网络交互将被模拟,以便您可以查看您的区块链并查看已添加的区块,但网络广播将留作未来的帖子。
让我们开始吧!
建立
既然我们要用Go编写我们的代码,我们假设你已经有了Go的一些经验。在安装和配置Go之后,我们还需要获取以下软件包:
$ go get github.com/davecgh/go-spew/spew
Spew
使我们能够在控制台中查看structs并slices清晰地格式化。这很好。
$ go get github.com/gorilla/mux
Gorilla / mux
是编写Web处理程序的流行软件包。我们需要这个。
$ go get github.com/joho/godotenv
Gotdotenv
让我们从.env我们保存在我们目录根目录的文件中读取 数据,这样我们就不必像我们的http端口那样硬编码了。我们也需要这个。
在的项目目录的根目录中创建一个.env
文件,定义将提供http请求的端口。只需在该文件中添加一行:ADDR=8080
创建一个main.go
文件。从现在开始的所有内容都将写入此文件,并且少于200行代码。让我们来编码!
import
我们来写这些main.go,首先是import 和 声明:
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"os"
"time"
"github.com/davecgh/go-spew/spew" //在控制台中查看structs清晰地格式化
"github.com/gorilla/mux" //编写Web处理程序包
"github.com/joho/godotenv" //读取.env 端口
)
数据结构
我们来定义struct组成区块链的每个区块。
type Block struct {
Index int //区块号
Timestamp string //时间戳
BPM int //心率,演示写入区块链的数据
Hash string //是代表数据记录的SHA256标识符,哈希值
PrevHash string //上一区块的哈希值
}
每个Block数据都包含将写入区块链的数据,并将你的BPM心率写入区块中。
我们还模拟出了blockchain本身,这是一个简单的slice的Block:
var Blockchain []Block
那么哈希如何适合区块和区块链呢?我们使用散列来确定并保持块的顺序。通过确保PrevHash每一个Block与Hash前一个相同,Block我们知道构成链的块的正确顺序。
哈希和生成新区块
那么为什么我们需要散列哈希呢?我们散列数据有两个主要原因:
为了节省空间。哈希值来自块上的所有数据。在我们的例子中,我们只有一些数据点,但想象我们有来自数百,数千或数百万个先前块的数据。将这些数据散列到单个SHA256字符串或散列散列比将前面的块中的所有数据一遍又一遍地复制更为有效。
保持区块链的完整性。通过存储之前的哈希,就像我们在上图中所做的那样,我们能够确保区块链中的区块按照正确的顺序排列。如果一个恶意方进入并试图操纵这些数据(例如,为了改变我们的心率来修复人寿保险价格),哈希将会迅速改变,链条会“断裂”,并且每个人都会知道不信任那个恶意链。
我们来编写一个函数,它接收我们的Block数据并创建它的SHA256哈希值。
func calculateHash(block Block) string {
record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}
这个calculateHash
函数需要Index
,Timestamp
,BPM
,PrevHash
的Block数据我们提供作为参数,并返回SHA256哈希
为一个字符串。现在我们可以Block用一个新的generateBlock
函数生成一个新的元素,并带有我们需要的所有元素 我们需要为它提供前一个块,以便我们可以在BPM中获得散列和脉搏率。不要担心BPM int通过的参数,我们稍后会解决。
func generateBlock(oldBlock Block, BPM int) (Block, error) {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.BPM = BPM
newBlock.PrevHash = oldBlock.Hash
newBlock.Hash = calculateHash(newBlock)
return newBlock, nil
}
请注意,当前时间将自动写入块中time.Now()
。另外请注意,我们之前的calculateHash
函数被调用。PrevHash
从前一个块的散列复制而来。Index
从前一个块的索引增加。
区块的验证
现在我们需要编写一些函数来确保块没有被篡改。我们通过检查Index来确保它们按预期递增。我们也检查以确保我们PrevHash
的确与Hash
前一个区块相同。最后,我们想通过在当前块上calculateHash
再次运行该函数来检查当前块的散列。让我们写一个isBlockValid
函数来完成所有这些事情并返回一个bool
。true如果它通过我们所有的验证,它会返回:
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 replaceChain(newBlocks []Block) {
if len(newBlocks) > len(Blockchain) {
Blockchain = newBlocks
}
}
如果你已经做到了这一步,请拍背!我们基本上已经用我们需要的所有各种功能写下了我们区块链的胆量。
现在我们想要一个方便的方式来查看我们的区块链并写入它,最好在网络浏览器中,以便我们可以向我们的哥们儿展示!
网络
我们假设您已经熟悉Web服务器的工作方式,并且有一些经验将它们连接到Go中。我们现在将引导您完成整个过程。
我们将使用您之前下载的Gorilla / mux
软件包来为我们完成繁重的工作。
让run我们用稍后调用的函数创建我们的服务器。
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
}
请注意,我们选择的端口来自我们.env
之前创建的 文件。我们给自己一个快速的控制台信息,log.Println
让我们知道服务器已启动并正在运行。我们稍后配置服务器ListenAndServe。漂亮的标准去东西。
现在我们需要编写makeMuxRouter
定义所有处理程序的函数。要在浏览器中查看和写入我们的区块链,我们只需要2条路线,我们会将它们保持简单。如果我们发送GET请求,localhost我们会查看我们的区块链。如果我们向POST它发送请求,我们可以写信给它。
func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
return muxRouter
}
这是我们的GET处理程序:
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))
}
我们只是回写完整的JSON格式
的区块链,我们可以通过访问浏览任何浏览器localhost:8080
。我们ADDR在我们的.env
文件中将变量设置为8080,所以如果您更改它,请确保访问您的正确端口。
我们的POST要求有点复杂,但不是太多。首先,我们需要一个新的Message struct。我们将在第二秒解释为什么我们需要它。
type Message struct {
BPM int
}
这是编写新块的处理程序的代码。阅读完之后,我们会带你通读它。
func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
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()
newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
if err != nil {
respondWithJSON(w, r, http.StatusInternalServerError, m)
return
}
if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
newBlockchain := append(Blockchain, newBlock)
replaceChain(newBlockchain)
spew.Dump(Blockchain)
}
respondWithJSON(w, r, http.StatusCreated, newBlock)
}
我们使用一个单独的Message结构
的原因是需要我们用来写入新块的JSON POST请求的请求主体。这使我们可以简单地发送一个POST请求到下面的主体,我们的处理程序会为我们填充剩下的块:{"BPM":50}
这50是一个以每分钟为单位的示例心率。通过将该整数更改为您自己的脉率来使用您自己的。
在完成将请求体解码到我们的var m Message
结构之后,我们通过将前一个块和新的心率传入generateBlock
我们之前编写的函数来创建一个新块。这是该功能创建新块所需的一切。我们做一个快速检查,确保使用isBlockValid
我们之前创建的函数来确定新块是否为验证的区块。
spew.Dump
是一个方便的功能,可以将我们的结构打印到控制台中。这对调试很有用。
为了测试POST请求,我们喜欢使用Postman。curl如果你不能远离终端,那么它也可以工作得很好。
当我们的POST请求成功或失败时,我们希望相应地收到警报。我们使用一个小包装函数respondWithJSON
让我们知道发生了什么。请记住,在Go中,不要忽略错误。优雅地处理它们。
func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
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)
}
几乎完成! main
让我们用一个简短的干净的main函数
连接所有这些不同的区块链功能,Web处理程序和Web服务器:
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
go func() {
t := time.Now()
genesisBlock := Block{0, t.String(), 0, "", ""}
spew.Dump(genesisBlock)
Blockchain = append(Blockchain, genesisBlock)
}()
log.Fatal(run())
}
那么这里发生了什么?godotenv.Load()
允许我们从.env
我们放在目录根目录的文件中读取像我们的端口号这样的变量,所以我们不必在整个应用程序中对它们进行硬编码。genesisBlock
是该功能中最重要的部分main。我们需要为我们的区块链提供一个初始块,否则一个新块将无法将其先前的散列与任何东西进行比较,因为之前的散列不存在。
我们将创世区块划分为自己的去程序,这样我们就可以从区块链逻辑和我们的网络服务器逻辑中分离出关注点。这可以在没有日常工作的情况下工作,但这种方式更简单。
让我们试试看:-)
从终端使用中启动程序
go run main.go
在终端中,我们看到网络服务器已启动并正在运行,并且我们打印了我们的创始块。
现在,请访问localhost
您的端口号,这对我们来说是8080
.正如预期的那样,我们看到了相同的区块。
现在,让我们发送一些POST请求
来添加块。使用Postman
(谷歌应用下载,图标是小飞人),我们将添加一些新的块与各种BPM。
让我们刷新我们的浏览器。瞧,现在我们看到我们区块链里的所有新区块PrevHash
我们的新区块相匹配Hash
,就像我们预期的一样!
下一步
!!您只需使用适当的散列和块验证来编写自己的区块链。您现在应该能够控制自己的区块链旅程,并探索更复杂的内容,如工作证明,股权证明,智能合约,Dapps,侧链等。
本教程未解决的是如何使用工作证明开采新区块。这将是一个单独的教程,但没有工作证明机制存在大量的区块链。此外,网络广播目前通过在网络服务器中编写和查看区块链来模拟。本教程中没有P2P组件。
日后随着 学习打怪的不断深入,会解锁其他的高级教程。
期待 吗? 那就关注下面 公众号 随时锁定区块链开发学习教程进度 :)
`
以上是关于go语言 实现简单区块链的主要内容,如果未能解决你的问题,请参考以下文章