用Go构建一个简单的区块链

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

内容简介:在本教程中,我将尝试通过帮助你在Go中编写简单的区块链来揭开区块链的广义概念。在本教程中,你应该能够:

在本教程中,我将尝试通过帮助你在 Go 中编写简单的区块链来揭开区块链的广义概念。

用Go构建一个简单的区块链

在本教程中,你应该能够:

  • 理解区块链术语。
  • 创建自己的简单区块链。
  • 了解什么是区块以及如何创建块。
  • 了解如何维护区块链的完整性。

区块链:一种数字分类帐,以较小的集合排列,称为块。这些块通过加密hash相互链接。每个块包含指向前一个块的哈希。区块链对于加密货币很有用,因为它具有去中心化的特性,这意味着存储的数据不在一个位置,而是每个人都可以访问,同时也是任何人都不可信的。

构建一个简单的区块链

在本教程中,我们将为图书馆系统创建一个示例区块链。我们的区块链将存储包含图书结账活动数据的区块。此实现的流程如下:

  • 添加一本新书。
  • 为书创建Genesis块。
  • 将Checkout数据添加到区块链。

这是一个单节点,非复杂的区块链,在运行时将所有内容存储在内存中。

在区块链中,块存储有价值的信息。此信息可以是实现区块链的系统所需的交易或一些其他信息,例如交易时间戳或来自前一个块的哈希。我们将继续为每个块定义数据模型,以及构成区块链的结账信息:

package main

// Block contains data that will be written to the blockchain.
type Block struct {
  Pos       int
  Data      BookCheckout
  Timestamp string
  Hash      string
  PrevHash  string
}

// BookCheckout contains data for a checked out book
type BookCheckout struct {
  BookID       string `json:"book_id"`
  User         string `json:"user"`
  CheckoutDate string `json:"checkout_date"`
  IsGenesis    bool   `json:"is_genesis"`
}

// Book contains data for a sample book
type Book struct {
  ID          string `json:"id"`
  Title       string `json:"title"`
  Author      string `json:"author"`
  PublishDate string `json:"publish_date"`
  ISBN        string `json:"isbn:`
}

Block 结构中, Pos 保持链中数据的位置。数据是块中包含的有价值信息(在这种情况下是结帐项目)。时间戳保存块创建的当前时间戳。哈希是块的生成哈希。 PrevHash 存储前一个块的哈希值。

在定义了 Block 结构的情况下,我们需要考虑对块进行哈希。哈希用于以正确的顺序识别和保持块。计算哈希值是区块链的一个非常重要的特征。计算哈希值是一项困难的操作(计算方面)。创建哈希的难度是经过深思熟虑的体系结构设计决策,因为它会增加新块的难度,防止在添加后进行可变操作。

哈希和生成块

我们将从一个简单的哈希方法开始,并编写一个函数 calculateHash 来连接块字段并创建一个SHA-256哈希:

func (b *Block) generateHash() {
  // get string val of the Data
  bytes, _ := json.Marshal(b.Data)
  // concatenate the dataset
  data := string(b.Pos) + b.Timestamp + string(bytes) + b.PrevHash
  hash := sha256.New()
  hash.Write([]byte(data))
  b.Hash = hex.EncodeToString(hash.Sum(nil))
}

接下来,让我们编写另一个函数 CreateBlock 来创建一个新块。

func CreateBlock(prevBlock *Block, checkoutItem BookCheckout) *Block {
  block := &Block{}
  block.Pos = prevBlock.Pos + 1
  block.Timestamp = time.Now().String()
  block.Data = checkoutItem
  block.PrevHash = prevBlock.Hash
  block.generateHash()
  
  return block
}

CreateBlock 函数完全按照它的状态执行,它创建一个新的Block。它需要两个参数才能实现,前一个块和要添加的结帐项目。如果你已经注意到,我们不会对参数进行任何形式的检查,只是为了简单起见。

创建区块链

我们已经为Block创建了 struct ,并创建了一个创建一个的函数。我们将实现一个区块链来保存这些区块的列表,以及一个将区块链添加到区块链的功能。

// Blockchain is an ordered list of blocks
type Blockchain struct {
  blocks []*Block
}

// BlockChain is a global variable that'll return the mutated Blockchain struct
var BlockChain *Blockchain

// AddBlock adds a Block to a Blockchain
func (bc *Blockchain) AddBlock (data BookCheckout) {
  // get previous block
  prevBlock := bc.blocks[len(bc.blocks)-1]
  // create new block
  block := CreateBlock(prevBlock, data)
  bc.blocks = append(bc.blocks, block)
}

Genesis块

在区块链中,Genesis Block是链中的第一个项目。要添加新块,我们必须首先检查现有块。如果没有,则创建Genesis Block。让我们编写一个函数来创建一个新的Genesis Block。

func GenesisBlock() *Block {
  return CreateBlock(&Block{}, BookCheckout{IsGenesis: true})
}
We also need to write a function to create a new blockchain:
func NewBlockchain() *Blockchain {
  return &Blockchain{[]*Block{GenesisBlock()}}
}

NewBlockchain 函数返回带有Genesis Block的Blockchain结构。由于我们没有考虑区块链的测量数据持久性,因为这将超出本教程的范围,所以我们总是从一个新的组开始,通过在程序运行时生成Genesis Block。

验证

在我们运行区块链应用程序之前,我们需要以某种方式实现验证,以便在已经发生变异时不保存块。我们将创建一个辅助函数 validBlock 并在附加到Blockchain结构的 AddBlock 方法中使用它:

func validBlock(block, prevBlock *Block) bool {
  // Confirm the hashes
  if prevBlock.Hash != block.PrevHash {
    return false
  }
  // confirm the block's hash is valid
  if !block.validateHash(block.Hash) {
    return false
  }
  // Check the position to confirm its been incremented
  if prevBlock.Pos+1 != block.Pos {
    return false
  }
  return true
}

func (b *Block) validateHash(hash string) bool {
  b.generateHash()
  if b.Hash != hash {
    return false
  }
  return true
}

我们的 AddBlock 方法应如下所示:

func (bc *Blockchain) AddBlock (data BookCheckout) {
  // get previous block
  prevBlock := bc.blocks[len(bc.blocks)-1]
  // create new block
  block := CreateBlock(prevBlock, data)
  // validate integrity of blocks 
  if validBlock(block, prevBlock) {
    bc.blocks = append(bc.blocks, block)
  }
}

到目前为止,我们已经编写了区块链的主要部分!让我们创建一个Web服务器,以便我们可以与区块链进行通信并进行测试。

在我们的main函数中,我们将编写创建Web服务器所需的代码,并注册与区块链方法通信的路由。我们将使用Gorilla Mux来路由和创建服务器mux:

func main() {
  // register router
  r := mux.NewRouter()
  r.HandleFunc("/", getBlockchain).Methods("GET")
  r.HandleFunc("/", writeBlock).Methods("POST")
  r.HandleFunc("/new", newBook).Methods("POST")
  
  log.Println("Listening on port 3000")
  
  log.Fatal(http.ListenAndServe(":3000", r))
}

在我们的主要功能中,我们有一个路由器和三个路由、处理程序定义。我们现在将创建这些处理程序。

getBlockchain 处理程序将简单地将区块链作为JSON字符串写回浏览器:

func getBlockchain(w http.ResponseWriter, r *http.Request) {
  jbytes, err := json.MarshalIndent(BlockChain.blocks, "", " ")
  if err != nil {
    w.WriteHeader(http.StatusInternalServerError)
    json.NewEncoder(w).Encode(err)
    return
  }
  // write JSON string
  io.WriteString(w, string(jbytes))
}

writeBlock处理程序添加一个包含已发送数据的新块。

func writeBlock(w http.ResponseWriter, r *http.Request) {
  var checkoutItem BookCheckout
  if err := json.NewDecoder(r.Body).Decode(&checkoutItem); err != nil {
    w.WriteHeader(http.StatusInternalServerError)
    log.Printf("could not write Block: %v", err)
    w.Write([]byte("could not write block"))
    return
  }
  // create block 
  BlockChain.AddBlock(checkoutItem)
  resp, err := json.MarshalIndent(checkoutItem, "", " ")
  if err != nil {
    w.WriteHeader(http.StatusInternalServerError)
    log.Printf("could not marshal payload: %v", err)
    w.Write([]byte("could not write block"))
    return
  }
  w.WriteHeader(http.StatusOK)
  w.Write(resp)
}

我们将编写最后一个处理程序 newBook ,它创建新的Book数据,因此我们将使用生成的ID作为块添加。记住流程:

  • 添加一本新书。
  • 为书创建Genesis块。
  • 将Checkout数据添加到区块链。
func newBook(w http.ResponseWriter, r *http.Request) {
  var book Book
  if err := json.NewDecoder(r.Body).Decode(&book); err != nil {
    w.WriteHeader(http.StatusInternalServerError)
    log.Printf("could not create: %v", err)
    w.Write([]byte("could not create new Book"))
    return
  }
  // We'll create an ID, concatenating the ISDBand publish date
  // This isn't an efficient way but it serves for this tutorial
  h := md5.New()
  io.WriteString(h, book.ISBN+book.PublishDate)
  book.ID = fmt.Sprintf("%x", h.Sum(nil))

  // send back payload
  resp, err := json.MarshalIndent(book, "", " ")
  if err != nil {
    w.WriteHeader(http.StatusInternalServerError)
    log.Printf("could not marshal payload: %v", err)
    w.Write([]byte("could not save book data"))
    return
  }
  w.WriteHeader(http.StatusOK)
  w.Write(resp)
}

编写了所有三个处理程序后,让我们清理我们的主要功能。我们的主要功能应如下所示:

func main() {
  // initialize the blockchain and store in var
  BlockChain = NewBlockchain()
  
  // register router
  r := mux.NewRouter()
  r.HandleFunc("/", getBlockchain).Methods("GET")
  r.HandlerFunc("/", writeBlock).Methods("POST")
  r.HandlerFunc("/new", newBook).Methods("POST")
  
  // dump the state of the Blockchain to the console
  go func() {
    for _, block := range BlockChain.blocks {
      fmt.Printf("Prev. hash: %x\n", block.PrevHash)
      bytes, _ := json.MarshalIndent(block.Data, "", " ")
      fmt.Printf("Data: %v\n", string(bytes))
      fmt.Printf("Hash: %x\n", block.Hash)
      fmt.Println()
    }
  }()
  log.Println("Listening on port 3000")

  log.Fatal(http.ListenAndServe(":3000", r))
}

几乎完成了!

使用我们更新的代码,让我们启动我们的应用程序: go run main.go

用Go构建一个简单的区块链

转到 http://localhost:3000 。你会看到Genesis Block显示:

用Go构建一个简单的区块链

让我们添加一本新书,这样我们就可以在添加块中使用ID。我将从终端使用cURL。Postman也是一个很好的工具:

$ curl -X POST http://localhost:3000/new \
  -H "Content-Type: application/json" \
  -d '{"title": "Sample Book", "author":"John Doe", 
"isbn":"909090","publish_date":"2018-05-26"}'

创建新书后,我们将使用生成的ID获取有效负载。请注意,因为它总是需要创建块。要添加一个将存储在区块链中的结帐记录,我们会向根端点 http://localhost:3000 发送一个POST请求,其中包含结帐项的有效负载:

$ curl -X POST http://localhost:3000 \
  -H "Content-Type: application/json" \
  -d '{"book_id": "generated_id", "user": "Mary Doe", 
"checkout_date":"2018-05-28"}'

刷新浏览器,我们将看到添加了自己的哈希的新项目:

用Go构建一个简单的区块链

我们做到了!!

恭喜,伙计!你走了很长的路。你刚刚写了第一个Blockchain原型!值得注意的是,与我们上面的实现相比,实际的区块链要复杂得多。本教程中的实现使得添加新块变得非常容易,而实际情况并非如此。添加新块需要一些繁重的计算(如工作量证明)。

通过解释的概念,你应该更好地理解区块链。还有其他主题是了解区块链基础的先决条件,如股权证明,工作量证明,智能合约,DApps等。

你可以在此 GitHub Repo 上获取本教程的源代码。

======================================================================

分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:

  • EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • java以太坊开发教程,主要是针对 java 和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对 python 工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用 php 进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、 mongodb 、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • tendermint区块链开发详解 ,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是原文 用Go构建一个简单的区块链


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

Effective STL中文版

Effective STL中文版

[美]Scott Meyers / 潘爱民、陈铭、邹开红 / 清华大学出版社 / 2006-1 / 30.00元

STL是C++标准库的一部分。本书是针对STL的经验总结,书中列出了50个条款,绝大多数条款都解释了在使用STL时应该注意的某一个方面的问题,并且详尽地分析了问题的来源、解决方案的优劣。一起来看看 《Effective STL中文版》 这本书的介绍吧!

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

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具