Go实现区块链导读

在本教程中,我将尝试通过帮助您以Go语言编写一个简单的区块链来揭开区块链的神秘面纱。
从本教程中,您应该能够:

  • 理解blockchain术语
  • 创建一个属于您自己的简单区块链
  • 了解什么是块以及如何创建块
  • 了解如何维护区块链的完整性
    您可以在GitHub Repo中找到本教程的源代码。

区块链【Blockchain】:一种记录的数字分类帐,并按块【block】排列。
这些块通过加密哈希相互链接, 每个块包含指向前一个块的哈希。
区块链对于加密货币很有用,因为它具有分散性,这意味着存储的数据不在一个位置,而是每个人都可以访问,同时,任何人都不能改变。

构建一个简单的区块链

在本教程中,我们将为图书馆系统创建一个示范性的区块链。我们的区块链将存储包含图书借阅数据信息的块【block】。该实现的流程如下:

  1. 添加一本新书
  2. 为一本书创建一个Genesis块
  3. 将借阅数据添加到区块链
    这是一个单节点,非复杂的区块链,在运行时将所有内容存储在内存中。

块【block】

在区块链中,一个块【block】中存储的是有价值的信息。这些信息可以是实现区块链的系统所需的事务或其他信息——例如,事务时间戳,或来自前一个块的散列哈希。

我们将继续为每个块【block】定义数据模型,以及组成区块链的借阅信息:

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保存的是数据在链中的位置。Data则是该块【block】中包含的有价值的信息(在本例中指的是借阅条目)。Timestamp包含块【block】创建时的当前时间戳。Hash是块【block】生成的散列哈希。PrevHash存储的是前一个块的散列哈希。

定义了Block结构体后,我们需要考虑如何散列哈希这些块【block】。散列哈希用于标识和保持块【block】的正确顺序。计算哈希是区块链的一个非常重要的特性。计算哈希是一项困难的操作(在计算方面)。让散列哈希创建变得困难,这是一个经过深思熟虑的架构设计决策,因为它使添加新块【block】变得困难,从而防止在添加新块【block】之后发生可变操作。

散列哈希和生成块

我们将从一个简单的哈希方法开始,编写一个calculateHash函数来连接块【block】中的属性字段,并创建一个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函数,用于创建一个新的块【block】:

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】②要添加的借阅项。您应该已经注意到,为了简单起见,我们没有对参数进行任何形式的检查。

创建区块链

我们已经为块【block】创建了结构体,并为创建块【block】编写了相应的函数。下面我们将实现一个区块链【blockchain】来保存关于这些块的列表,以及一个用于向区块链【blockchain】中添加块【block】的函数。

// 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 Block】

在区块链【Blockchain】中,创世纪块【Genesis Block】是链中的首项。要添加新块【block】,必须首先检查是否有块【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】结构体。由于我们没有考虑区块链【Blockchain】的数据持久性(这超出了本教程的范围),所以每当程序运行时,我们总是通过生成创世纪块【Genesis Block】来开启一个新的组。

Validation

在运行我们的区块链应用程序之前,我们需要以某种方式实现一个验证功能,这样在已经发生突变时(即有不法分子在书记借阅信息被保存的处理过程中,修改了借阅信息),区块就不会被保存。我们将创建一个工具函数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 来创建和路由我们的服务器:

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))
}

在我们的main函数中,我们定义了一个路由器【router】和三个路由【route】及其相应的处理程序【handler】。下面我们将着手创建这些处理程序:

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处理程序则使用传入的数据,向区块链中插入一个块【block】:

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数据:

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)
}

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

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

转到http://localhost:3000。你会看到创世纪块的显示:
在这里插入图片描述

让我们先添加一本新书,这样我们就可以使用该书的ID来向区块链中添加块【block】。 我将从终端使用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"}'

在创建一本新书之后,我们将得到一个响应,响应报文中带有新建的Boo的ID。该ID是创建块【block】时不可或缺的一部分

现在已经有书了,我们下面来添加一笔借阅记录,我们向根端点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"}'

刷新浏览器,我们就能在区块链信息中看到我们的借阅信息了:
在这里插入图片描述

祝贺你

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

通过对概念的解释,您应该对区块链有了更深入的理解。但还有一些其他的主题是理解区块链基础的先决条件,如股权证明(Proof of Stake ),工作证明(Proof of Work),智能合约(Smart Contracts),DApps等。

您可以在GitHub Repo上获得本教程的源代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值