在本教程中,我将尝试通过帮助你在Go中编写简单的区块链来揭开区块链的广义概念。
在本教程中,你应该能够:
区块链:一种数字分类帐,以较小的集合排列,称为块。这些块通过加密hash相互链接。每个块包含指向前一个块的哈希。区块链对于加密货币很有用,因为它具有去中心化的特性,这意味着存储的数据不在一个位置,而是每个人都可以访问,同时也是任何人都不可信的。
在本教程中,我们将为图书馆系统创建一个示例区块链。我们的区块链将存储包含图书结账活动数据的区块。此实现的流程如下:
这是一个单节点,非复杂的区块链,在运行时将所有内容存储在内存中。
在区块链中,块存储有价值的信息。此信息可以是实现区块链的系统所需的交易或一些其他信息,例如交易时间戳或来自前一个块的哈希。我们将继续为每个块定义数据模型,以及构成区块链的结账信息:
// 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 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作为块添加。记住流程:
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
转到 http://localhost:3000
。你会看到Genesis Block显示:
让我们添加一本新书,这样我们就可以在添加块中使用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"}'
刷新浏览器,我们将看到添加了自己的哈希的新项目:
我们做到了!!
恭喜,伙计!你走了很长的路。你刚刚写了第一个Blockchain原型!值得注意的是,与我们上面的实现相比,实际的区块链要复杂得多。本教程中的实现使得添加新块变得非常容易,而实际情况并非如此。添加新块需要一些繁重的计算(如工作量证明)。
通过解释的概念,你应该更好地理解区块链。还有其他主题是了解区块链基础的先决条件,如股权证明,工作量证明,智能合约,DApps等。
你可以在此 GitHub Repo 上获取本教程的源代码。
======================================================================
分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:
汇智网原创翻译,转载请标明出处。这里是原文 用Go构建一个简单的区块链