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

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

内容简介:在本教程中,我将尝试通过帮助你在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构建一个简单的区块链


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

查看所有标签

猜你喜欢:

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

Rework

Rework

Jason Fried、David Heinemeier Hansson / Crown Business / 2010-3-9 / USD 22.00

"Jason Fried and David Hansson follow their own advice in REWORK, laying bare the surprising philosophies at the core of 37signals' success and inspiring us to put them into practice. There's no jarg......一起来看看 《Rework》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换