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

版权声明: https://blog.csdn.net/liwan2018/article/details/79377490

什么是区块链

区块链有多火,就不用我介绍了,你能通过搜索引擎跳转到这里,就证明你是区块链的fan了。既然进来了,就不会让你白来,no bb, 直接上干货!

(开场白,也不全是废话)区块链是 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))
}

链接: https://github.com/kofj/naivechain/blob/master/main.go


多少人忙得连写博客的时间都没有哟!

猜你喜欢

转载自blog.csdn.net/liwan2018/article/details/79377490