page contents

区块链的原理与Golang实现例子

什么是区块链 区块链是 21 世纪最具革命性的技术之一,它仍然处于不断成长的阶段,而且还有很多潜力尚未显现出来。 本质上,区块链只是一个分布式数据库而已。 不过,使它独一无二的是,区块...

attachments-2021-07-6N8awTPP60eba492293e3.png

什么是区块链

区块链是 21 世纪最具革命性的技术之一,它仍然处于不断成长的阶段,而且还有很多潜力尚未显现出来。 本质上,区块链只是一个分布式数据库而已。 不过,使它独一无二的是,区块链是一个公开的数据库,而不是一个私人数据库,也就是说,每个使用它的人都有一个完整或部分的副本。 只有经过其他数据库管理员的同意,才能向数据库中添加新的记录。 此外,也正是由于区块链,才使得加密货币和智能合约成为现实。

综而述之,用一个形象的比如:区块链就是一个去中心化、分布式”记账本”。

区块链原理

1.区块

让我们从 “区块链” 中的 “区块” 谈起。在区块链中,存储有效信息的是区块。

比如,比特币区块存储的有效信息,就是比特币交易,交易信息也是所有加密货币的本质。除此以外,区块还包含了一些技术信息,比如版本,当前时间戳和前一个区块的哈希。

这里,我们并不会实现一个像比特币技术规范所描述的区块链,而是实现一个简化版的区块链,它仅包含了一些关键信息。看起来就像是这样:

type Block struct {
      Timestamp     int64
      Data          []byte
      PrevBlockHash []byte
      Hash          []byte
}
      - Timestamp 是当前时间戳,也就是区块创建的时间。
      - Data 是区块存储的实际有效的信息。
      -  PrevBlockHash 存储的是前一个块的哈希。
      -  Hash 是当前块的哈希。

在比特币技术规范中,Timestamp, PrevBlockHash, Hash 是区块头(block header),区块头是一个单独的数据结构。而交易,也就是这里的 Data, 是另一个单独的数据结构。为了简便起见,我把这两个混合在了一起。

那么,我们要如何计算哈希呢?如何计算哈希,是区块链一个非常重要的部分。正是由于这个特性,才使得区块链是安全的。计算一个哈希,是在计算上非常困难的一个操作。即使在高速电脑上,也要花费不少时间 (这就是为什么人们会购买 GPU 来挖比特币) 。这是一个有意为之的架构设计,它故意使得加入新的区块十分困难,因此可以保证区块一旦被加入以后,就很难再进行修改。

目前,我们仅取了 Block 结构的一些字段(Timestamp, Data 和 PrevBlockHash),并将它们相互连接起来,然后在连接后的结果上计算一个 SHA-256 的哈希. 让我们在 SetHash 方法中完成这个任务:

func (b *Block) SetHash() {
    timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
    headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
    hash := sha256.Sum256(headers)
    b.Hash = hash[:]
}

接下来,按照 Golang 的惯例,我们会实现一个用于简化创建一个区块的函数:

func NewBlock(data string, prevBlockHash []byte) *Block {
    block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
    block.SetHash()
    return block
}

这就是区块部分的全部内容了!

2.链

下面让我们来实现一个区块链。本质上,区块链仅仅是一个有着特定结构的数据库,是一个有序,后向连接的列表。

这也就是说,区块按照插入的顺序进行存储,每个块都被连接到前一个块。这样的结构,能够让我们快速地获取链上的最新块,并且高效地通过哈希来检索一个块。

Golang 中,可以通过一个 array 和 map 来实现这个结构:array 存储有序的哈希(Golang 中 array 是有序的),map 存储 hask -> block 对(Golang 中, map 是无序的)。 但是在基本的原型阶段,我们只用到了 array,因为现在还不需要通过哈希来获取块。

type Blockchain struct {
    blocks []*Block
}

这就是我们的第一个区块链!我从来没有想过它会是这么容易。

现在,让我们能够给它添加一个块:

func (bc *Blockchain) AddBlock(data string) {
    prevBlock := bc.blocks[len(bc.blocks)-1]
    newBlock := NewBlock(data, prevBlock.Hash)
    bc.blocks = append(bc.blocks, newBlock)
}

完成!不过,真的就这样了吗?

为了加入一个新的块,我们必须要有一个已有的块,但是,现在我们的链是空的,一个块都没有!所以,在任何一个区块链中,都必须至少有一个块。这样的块,也就是链中的第一个块,通常叫做创世块(genesis block). 让我们实现一个方法来创建一个创世块:

func NewGenesisBlock() *Block {
    return NewBlock("Genesis Block", []byte{})
}

现在,我们可以实现一个函数来创建有创世块的区块链:

func NewBlockchain() *Blockchain {
    return &Blockchain{[]*Block{NewGenesisBlock()}}
}

来检查一个我们的区块链是否如期工作:

func main() {
    bc := NewBlockchain()
    bc.AddBlock("Send 1 BTC to Ivan")
    bc.AddBlock("Send 2 more BTC to Ivan")
    for _, block := range bc.blocks {
        fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
        fmt.Printf("Data: %s\n", block.Data)
        fmt.Printf("Hash: %x\n", block.Hash)
        fmt.Println()
    }
}

一个完整的代码

package main
import (
    "crypto/sha256"
    "encoding/json"
    "flag"
    "fmt"
    "io"
    "log"
    "net/http"
    "sort"
    "strings"
    "time"
    "websocket"
    //"golang.org/x/net/websocket"
)
const (
    queryLatest = iota
    queryAll
    responseBlockchain
)
var genesisBlock = &Block{
    Index:        0,
    PreviousHash: "0",
    Timestamp:    1465154705,
    Data:         "my genesis block!!",
    Hash:         "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7",
}
var (
    sockets      []*websocket.Conn
    blockchain   = []*Block{genesisBlock}
    httpAddr     = flag.String("api", ":3001", "api server address.")
    p2pAddr      = flag.String("p2p", ":6001", "p2p server address.")
    initialPeers = flag.String("peers", "ws://localhost:6001", "initial peers")
)
type Block struct {
    Index        int64  `json:"index"`
    PreviousHash string `json:"previousHash"`
    Timestamp    int64  `json:"timestamp"`
    Data         string `json:"data"`
    Hash         string `json:"hash"`
}
func (b *Block) String() string {
    return fmt.Sprintf("index: %d,previousHash:%s,timestamp:%d,data:%s,hash:%s", b.Index, b.PreviousHash, b.Timestamp, b.Data, b.Hash)
}
type ByIndex []*Block
func (b ByIndex) Len() int           { return len(b) }
func (b ByIndex) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
func (b ByIndex) Less(i, j int) bool { return b[i].Index < b[j].Index }
type ResponseBlockchain struct {
    Type int    `json:"type"`
    Data string `json:"data"`
}
func errFatal(msg string, err error) {
    if err != nil {
        log.Fatalln(msg, err)
    }
}
func connectToPeers(peersAddr []string) {
    for _, peer := range peersAddr {
        if peer == "" {
            continue
        }
        ws, err := websocket.Dial(peer, "", peer)
        if err != nil {
            log.Println("dial to peer", err)
            continue
        }
        initConnection(ws)
    }
}
func initConnection(ws *websocket.Conn) {
    go wsHandleP2P(ws)
    log.Println("query latest block.")
    ws.Write(queryLatestMsg())
}
func handleBlocks(w http.ResponseWriter, r *http.Request) {
    bs, _ := json.Marshal(blockchain)
    w.Write(bs)
}
func handleMineBlock(w http.ResponseWriter, r *http.Request) {
    var v struct {
        Data string `json:"data"`
    }
    decoder := json.NewDecoder(r.Body)
    defer r.Body.Close()
    err := decoder.Decode(&v)
    if err != nil {
        w.WriteHeader(http.StatusGone)
        log.Println("[API] invalid block data : ", err.Error())
        w.Write([]byte("invalid block data. " + err.Error() + "\n"))
        return
    }
    block := generateNextBlock(v.Data)
    addBlock(block)
    broadcast(responseLatestMsg())
}
func handlePeers(w http.ResponseWriter, r *http.Request) {
    var slice []string
    for _, socket := range sockets {
        if socket.IsClientConn() {
            slice = append(slice, strings.Replace(socket.LocalAddr().String(), "ws://", "", 1))
        } else {
            slice = append(slice, socket.Request().RemoteAddr)
        }
    }
    bs, _ := json.Marshal(slice)
    w.Write(bs)
}
func handleAddPeer(w http.ResponseWriter, r *http.Request) {
    var v struct {
        Peer string `json:"peer"`
    }
    decoder := json.NewDecoder(r.Body)
    defer r.Body.Close()
    err := decoder.Decode(&v)
    if err != nil {
        w.WriteHeader(http.StatusGone)
        log.Println("[API] invalid peer data : ", err.Error())
        w.Write([]byte("invalid peer data. " + err.Error()))
        return
    }
    connectToPeers([]string{v.Peer})
}
func wsHandleP2P(ws *websocket.Conn) {
    var (
        v    = &ResponseBlockchain{}
        peer = ws.LocalAddr().String()
    )
    sockets = append(sockets, ws)
    for {
        var msg []byte
        err := websocket.Message.Receive(ws, &msg)
        if err == io.EOF {
            log.Printf("p2p Peer[%s] shutdown, remove it form peers pool.\n", peer)
            break
        }
        if err != nil {
            log.Println("Can't receive p2p msg from ", peer, err.Error())
            break
        }
        log.Printf("Received[from %s]: %s.\n", peer, msg)
        err = json.Unmarshal(msg, v)
        errFatal("invalid p2p msg", err)
        switch v.Type {
        case queryLatest:
            v.Type = responseBlockchain
            bs := responseLatestMsg()
            log.Printf("responseLatestMsg: %s\n", bs)
            ws.Write(bs)
        case queryAll:
            d, _ := json.Marshal(blockchain)
            v.Type = responseBlockchain
            v.Data = string(d)
            bs, _ := json.Marshal(v)
            log.Printf("responseChainMsg: %s\n", bs)
            ws.Write(bs)
        case responseBlockchain:
            handleBlockchainResponse([]byte(v.Data))
        }
    }
}
func getLatestBlock() (block *Block) { return blockchain[len(blockchain)-1] }
func responseLatestMsg() (bs []byte) {
    var v = &ResponseBlockchain{Type: responseBlockchain}
    d, _ := json.Marshal(blockchain[len(blockchain)-1:])
    v.Data = string(d)
    bs, _ = json.Marshal(v)
    return
}
func queryLatestMsg() []byte { return []byte(fmt.Sprintf("{\"type\": %d}", queryLatest)) }
func queryAllMsg() []byte    { return []byte(fmt.Sprintf("{\"type\": %d}", queryAll)) }
func calculateHashForBlock(b *Block) string {
    return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d%s%d%s", b.Index, b.PreviousHash, b.Timestamp, b.Data))))
}
func generateNextBlock(data string) (nb *Block) {
    var previousBlock = getLatestBlock()
    nb = &Block{
        Data:         data,
        PreviousHash: previousBlock.Hash,
        Index:        previousBlock.Index + 1,
        Timestamp:    time.Now().Unix(),
    }
    nb.Hash = calculateHashForBlock(nb)
    return
}
func addBlock(b *Block) {
    if isValidNewBlock(b, getLatestBlock()) {
        blockchain = append(blockchain, b)
    }
}
func isValidNewBlock(nb, pb *Block) (ok bool) {
    if nb.Hash == calculateHashForBlock(nb) &&
        pb.Index+1 == nb.Index &&
        pb.Hash == nb.PreviousHash {
        ok = true
    }
    return
}
func isValidChain(bc []*Block) bool {
    if bc[0].String() != genesisBlock.String() {
        log.Println("No same GenesisBlock.", bc[0].String())
        return false
    }
    var temp = []*Block{bc[0]}
    for i := 1; i < len(bc); i++ {
        if isValidNewBlock(bc[i], temp[i-1]) {
            temp = append(temp, bc[i])
        } else {
            return false
        }
    }
    return true
}
func replaceChain(bc []*Block) {
    if isValidChain(bc) && len(bc) > len(blockchain) {
        log.Println("Received blockchain is valid. Replacing current blockchain with received blockchain.")
        blockchain = bc
        broadcast(responseLatestMsg())
    } else {
        log.Println("Received blockchain invalid.")
    }
}
func broadcast(msg []byte) {
    for n, socket := range sockets {
        _, err := socket.Write(msg)
        if err != nil {
            log.Printf("peer [%s] disconnected.", socket.RemoteAddr().String())
            sockets = append(sockets[0:n], sockets[n+1:]...)
        }
    }
}
func handleBlockchainResponse(msg []byte) {
    var receivedBlocks = []*Block{}
    err := json.Unmarshal(msg, &receivedBlocks)
    errFatal("invalid blockchain", err)
    sort.Sort(ByIndex(receivedBlocks))
    latestBlockReceived := receivedBlocks[len(receivedBlocks)-1]
    latestBlockHeld := getLatestBlock()
    if latestBlockReceived.Index > latestBlockHeld.Index {
        log.Printf("blockchain possibly behind. We got: %d Peer got: %d", latestBlockHeld.Index, latestBlockReceived.Index)
        if latestBlockHeld.Hash == latestBlockReceived.PreviousHash {
            log.Println("We can append the received block to our chain.")
            blockchain = append(blockchain, latestBlockReceived)
        } else if len(receivedBlocks) == 1 {
            log.Println("We have to query the chain from our peer.")
            broadcast(queryAllMsg())
        } else {
            log.Println("Received blockchain is longer than current blockchain.")
            replaceChain(receivedBlocks)
        }
    } else {
        log.Println("received blockchain is not longer than current blockchain. Do nothing.")
    }
}
func main() {
    flag.Parse()
    connectToPeers(strings.Split(*initialPeers, ","))
    http.HandleFunc("/blocks", handleBlocks)
    http.HandleFunc("/mine_block", handleMineBlock)
    http.HandleFunc("/peers", handlePeers)
    http.HandleFunc("/add_peer", handleAddPeer)
    go func() {
        log.Println("Listen HTTP on", *httpAddr)
        errFatal("start api server", http.ListenAndServe(*httpAddr, nil))
    }()
    http.Handle("/", websocket.Handler(wsHandleP2P))
    log.Println("Listen P2P on ", *p2pAddr)
    errFatal("start p2p server", http.ListenAndServe(*p2pAddr, nil))
}

更多相关技术内容咨询欢迎前往并持续关注六星社区了解详情。

程序员编程交流QQ群:805358732

如果你想用Python开辟副业赚钱,但不熟悉爬虫与反爬虫技术,没有接单途径,也缺乏兼职经验
关注下方微信公众号:Python编程学习圈,获取价值999元全套Python入门到进阶的学习资料以及教程,还有Python技术交流群一起交流学习哦。

attachments-2022-06-tvh0xM4q62ad373728faf.jpeg

  • 发表于 2021-07-12 10:11
  • 阅读 ( 457 )
  • 分类:Golang

0 条评论

请先 登录 后评论
轩辕小不懂
轩辕小不懂

2403 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1324 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章