使用Go語言編寫區(qū)塊鏈P2P網(wǎng)絡(luò)(譯)

2018-04-20?Chain God?程序新視界

外文發(fā)表日期: 2018-04-14?

外文鏈接:https://medium.com/coinmonks/code-a-simple-p2p-blockchain-in-go-46662601f417

在之前的文章中,我們已經(jīng)知道了怎么編寫PoW也知道了IPFS怎么工作, 但是有一個致命的缺點,我們的服務(wù)都是中心化的,這篇文章會教你怎么實現(xiàn)一個簡單的完全去中心化的P2P網(wǎng)絡(luò)。

背景知識

什么是P2P網(wǎng)絡(luò)

在真正的P2P架構(gòu)中,不需要中心化的服務(wù)來維護區(qū)塊鏈的狀態(tài)。例如,當(dāng)你給朋友發(fā)送比特幣時,比特幣區(qū)塊鏈的“狀態(tài)”應(yīng)該更新,這樣你朋友的余額就會增加,你的余額就會減少。

在這個網(wǎng)絡(luò)中,不存在一個權(quán)力高度中心化的機構(gòu)來維護狀態(tài)(銀行就是這樣的中心化機構(gòu))。對于比特幣網(wǎng)絡(luò)來說,每個節(jié)點都會維護一份完整的區(qū)塊鏈狀態(tài),當(dāng)交易發(fā)生時,每個節(jié)點的區(qū)塊鏈狀態(tài)都會得到更新。這樣,只要網(wǎng)絡(luò)中51%的節(jié)點對區(qū)塊鏈的狀態(tài)達成一致,那么區(qū)塊鏈網(wǎng)絡(luò)就是安全可靠的,具體可以閱讀這篇一致性協(xié)議文章。

本文將繼續(xù)之前的工作,200行Go代碼實現(xiàn)區(qū)塊鏈, 并加入P2P網(wǎng)絡(luò)架構(gòu)。在繼續(xù)之前,強烈建議你先閱讀該篇文章,它會幫助你理解接下來的代碼。

開始實現(xiàn)

編寫P2P網(wǎng)絡(luò)可不是開開玩笑就能簡單視線的,有很多邊邊角角的情況都要覆蓋到,而且需要你擁有很多工程學(xué)的知識,這樣的P2P網(wǎng)絡(luò)才是可擴展、高可靠的。有句諺語說得好:站在巨人肩膀上做事,那么我們先看看巨人們提供了哪些工具吧。

喔,看看,我們發(fā)現(xiàn)了什么!一個用Go語言實現(xiàn)的P2P庫go-libp2p!如果你對新技術(shù)足夠敏銳,就會發(fā)現(xiàn)這個庫的作者和IPFS的作者是同一個團隊。如果你還沒看過我們的IPFS教程,可以看看這里, 你可以選擇跳過IPFS教程,因為對于本文這不是必須的。

警告

目前來說,go-libp2p主要有兩個缺點:

安裝設(shè)置比較痛苦,它使用gx作為包管理工具,怎么說呢,不咋好用,但是湊活用吧

目前項目還沒有成熟,正在緊密鑼鼓的開發(fā)中,當(dāng)使用這個庫時,可能會遇到一些數(shù)據(jù)競爭(data race)

對于第一點,不必擔(dān)心,有我們呢。第二點是比較大的問題,但是不會影響我們的代碼。假如你在使用過程中發(fā)現(xiàn)了數(shù)據(jù)競爭問題,記得給項目提一個issue,幫助它更好的成長!

總之,目前開源世界中,現(xiàn)代化的P2P庫是非常非常少的,因為我們要多給go-libp2p一些耐心和包容,而且就目前來說,它已經(jīng)能很好的滿足我們的目標(biāo)了。

安裝設(shè)置

最好的環(huán)境設(shè)置方式是直接clonelibp2p庫,然后在這個庫的代碼中直接開發(fā)。你也可以在自己的庫中,調(diào)用這個庫開發(fā),但是這樣就需要用到gx了。這里我們使用簡單的方式,假設(shè)你已經(jīng)安裝了Go:

go?get-d github.com/libp2p/go-libp2p/…

進入?go-libp2p文件夾

make

make deps

這里會通過gx包管理工具下載所有需要的包和依賴,再次申明,我們不喜歡gx,因為它打破了Go語言的很多慣例,但是為了這個很棒的庫,認(rèn)慫吧。

這里,我們在examples子目錄下進行開發(fā),因此在go-libp2p的examples下創(chuàng)建一個你自己的目錄

mkdir?./examples/p2p

然后進入到p2p文件夾下,創(chuàng)建main.go文件,后面所有的代碼都會在該文件中。

你的目錄結(jié)構(gòu)是這樣的:

好了,勇士們,拔出你們的劍,哦不,拔出你們的main.go,開始我們的征途吧!

導(dǎo)入相關(guān)庫

這里申明我們需要用的庫,大部分庫是來自于go-libp2p本身的,在教程中,你會學(xué)到怎么去使用它們。

package main

import (

? ?"bufio"

? ?"context"

? ?"crypto/rand"

? ?"crypto/sha256"

? ?"encoding/hex"

? ?"encoding/json"

? ?"flag"

? ?"fmt"

? ?"io"

? ?"log"

? ?mrand "math/rand"

? ?"os"

? ?"strconv"

? ?"strings"

? ?"sync"

? ?"time"

? ?"github.com/davecgh/go-spew/spew"

? ?golog "github.com/ipfs/go-log"

? ?libp2p "github.com/libp2p/go-libp2p"

? ?crypto "github.com/libp2p/go-libp2p-crypto"

? ?host "github.com/libp2p/go-libp2p-host"

? ?net "github.com/libp2p/go-libp2p-net"

? ?peer "github.com/libp2p/go-libp2p-peer"

? ?pstore "github.com/libp2p/go-libp2p-peerstore"

? ?ma "github.com/multiformats/go-multiaddr"

? ?gologging "github.com/whyrusleeping/go-logging"

)

spew包可以很方便、優(yōu)美的打印出我們的區(qū)塊鏈,因此記得安裝它:

go?getgithub.com/davecgh/go-spew/spew

區(qū)塊鏈結(jié)構(gòu)

記住,請先閱讀200行Go代碼實現(xiàn)區(qū)塊鏈, 這樣,下面的部分就會簡單很多。

先來申明全局變量:

// Block represents each 'item' in the blockchain

type Block struct {

? ?Index ? ? int

? ?Timestamp string

? ?BPM ? ? ? int

? ?Hash ? ? ?string

? ?PrevHash ?string

}

// Blockchain is a series of validated Blocks

var Blockchain []Block

var mutex = &sync.Mutex{}

我們是一家健康看護公司,因此Block中存著的是用戶的脈搏速率BPM

Blockchain是我們的"狀態(tài)",或者嚴(yán)格的說:最新的Blockchain,它其實就是Block的切片(slice)

mutex是為了防止資源競爭出現(xiàn)

下面是Blockchain相關(guān)的特定函數(shù):

// make sure block is valid by checking index, and comparing the hash of the previous block

func isBlockValid(newBlock, oldBlock Block) bool {

? ?if oldBlock.Index+1 != newBlock.Index {

? ? ? ?return false

? ?}

? ?if oldBlock.Hash != newBlock.PrevHash {

? ? ? ?return false

? ?}

? ?if calculateHash(newBlock) != newBlock.Hash {

? ? ? ?return false

? ?}

? ?return true

}

// SHA256 hashing

func calculateHash(block Block) string {

? ?record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash

? ?h := sha256.New()

? ?h.Write([]byte(record))

? ?hashed := h.Sum(nil)

? ?return hex.EncodeToString(hashed)

}

// create a new block using previous block's hash

func generateBlock(oldBlock Block, BPM int) Block {

? ?var newBlock Block

? ?t := time.Now()

? ?newBlock.Index = oldBlock.Index + 1

? ?newBlock.Timestamp = t.String()

? ?newBlock.BPM = BPM

? ?newBlock.PrevHash = oldBlock.Hash

? ?newBlock.Hash = calculateHash(newBlock)

? ?return newBlock

}

isBlockValid檢查Block的hash是否合法

calculateHash使用?sha256來對原始數(shù)據(jù)做hash

generateBlock創(chuàng)建一個新的Block區(qū)塊,然后添加到區(qū)塊鏈Blockchain上,同時會包含所需的事務(wù)

P2P結(jié)構(gòu)

下面我們快接近核心部分了,首先我們要寫出創(chuàng)建主機的邏輯。當(dāng)一個節(jié)點運行我們的程序時,它可以作為一個主機,被其它節(jié)點連接。下面一起看看代碼:-)

// makeBasicHost creates a LibP2P host with a random peer ID listening on the

// given multiaddress. It will use secio if secio is true.

func makeBasicHost(listenPort int, secio bool, randseed int64) (host.Host, error) {

? ?// If the seed is zero, use real cryptographic randomness. Otherwise, use a

? ?// deterministic randomness source to make generated keys stay the same

? ?// across multiple runs

? ?var r io.Reader

? ?if randseed == 0 {

? ? ? ?r = rand.Reader

? ?} else {

? ? ? ?r = mrand.New(mrand.NewSource(randseed))

? ?}

? ?// Generate a key pair for this host. We will use it

? ?// to obtain a valid host ID.

? ?priv, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)

? ?if err != nil {

? ? ? ?return nil, err

? ?}

? ?opts := []libp2p.Option{

? ? ? ?libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", listenPort)),

? ? ? ?libp2p.Identity(priv),

? ?}

? ?if !secio {

? ? ? ?opts = append(opts, libp2p.NoEncryption())

? ?}

? ?basicHost, err := libp2p.New(context.Background(), opts...)

? ?if err != nil {

? ? ? ?return nil, err

? ?}

? ?// Build host multiaddress

? ?hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", basicHost.ID().Pretty()))

? ?// Now we can build a full multiaddress to reach this host

? ?// by encapsulating both addresses:

? ?addr := basicHost.Addrs()[0]

? ?fullAddr := addr.Encapsulate(hostAddr)

? ?log.Printf("I am %s\n", fullAddr)

? ?if secio {

? ? ? ?log.Printf("Now run \"go run main.go -l %d -d %s -secio\" on a different terminal\n", listenPort+1, fullAddr)

? ?} else {

? ? ? ?log.Printf("Now run \"go run main.go -l %d -d %s\" on a different terminal\n", listenPort+1, fullAddr)

? ?}

? ?return basicHost, nil

}

makeBasicHost函數(shù)有3個參數(shù),同時返回一個host結(jié)構(gòu)體

listenPort是主機監(jiān)聽的端口,其它節(jié)點會連接該端口

secio表明是否開啟數(shù)據(jù)流的安全選項,最好開啟,因此它代表了"安全輸入/輸出"

randSeed是一個可選的命令行標(biāo)識,可以允許我們提供一個隨機數(shù)種子來為我們的主機生成隨機的地址。這里我們不會使用

函數(shù)的第一個if語句針對隨機種子生成隨機key,接著我們生成公鑰和私鑰,這樣能保證主機是安全的。opts部分開始構(gòu)建網(wǎng)絡(luò)地址部分,這樣其它節(jié)點就可以連接進來。

!secio部分可以繞過加密,但是我們準(zhǔn)備使用加密,因此這段代碼不會被觸發(fā)。

接著,創(chuàng)建了主機地址,這樣其他節(jié)點就可以連接進來。log.Printf可以用來在控制臺打印出其它節(jié)點的連接信息。最后我們返回生成的主機地址給調(diào)用方函數(shù)。

流處理

之前的主機需要能處理進入的數(shù)據(jù)流。當(dāng)另外一個節(jié)點連接到主機時,它會想要提出一個新的區(qū)塊鏈,來覆蓋主機上的區(qū)塊鏈,因此我們需要邏輯來判定是否要接受新的區(qū)塊鏈。

同時,當(dāng)我們往本地的區(qū)塊鏈添加區(qū)塊后,也要把相關(guān)信息廣播給其它節(jié)點,這里也需要實現(xiàn)相關(guān)邏輯。

先來創(chuàng)建流處理的基本框架吧:

func handleStream(s net.Stream) {

? ?log.Println("Got a new stream!")

? ?// Create a buffer stream for non blocking read and write.

? ?rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))

? ?go readData(rw)

? ?go writeData(rw)

? ?// stream 's' will stay open until you close it (or the other side closes it).

}

這里創(chuàng)建一個新的ReadWriter,為了能支持?jǐn)?shù)據(jù)讀取和寫入,同時我們啟動了一個單獨的Go協(xié)程來處理相關(guān)讀寫邏輯。

讀取數(shù)據(jù)

首先創(chuàng)建readData函數(shù):

func readData(rw *bufio.ReadWriter) {

? ?for {

? ? ? ?str, err := rw.ReadString('\n')

? ? ? ?if err != nil {

? ? ? ? ? ?log.Fatal(err)

? ? ? ?}

? ? ? ?if str == "" {

? ? ? ? ? ?return

? ? ? ?}

? ? ? ?if str != "\n" {

? ? ? ? ? ?chain := make([]Block, 0)

? ? ? ? ? ?if err := json.Unmarshal([]byte(str), &chain); err != nil {

? ? ? ? ? ? ? ?log.Fatal(err)

? ? ? ? ? ?}

? ? ? ? ? ?mutex.Lock()

? ? ? ? ? ?if len(chain) > len(Blockchain) {

? ? ? ? ? ? ? ?Blockchain = chain

? ? ? ? ? ? ? ?bytes, err := json.MarshalIndent(Blockchain, "", " ?")

? ? ? ? ? ? ? ?if err != nil {

? ? ? ? ? ? ? ? ? ?log.Fatal(err)

? ? ? ? ? ? ? ?}

? ? ? ? ? ? ? ?// Green console color: ? ? \x1b[32m

? ? ? ? ? ? ? ?// Reset console color: ? ? \x1b[0m

? ? ? ? ? ? ? ?fmt.Printf("\x1b[32m%s\x1b[0m> ", string(bytes))

? ? ? ? ? ?}

? ? ? ? ? ?mutex.Unlock()

? ? ? ?}

? ?}

}

該函數(shù)是一個無限循環(huán),因為它需要永不停歇的去讀取外面進來的數(shù)據(jù)。首先,我們使用ReadString解析從其它節(jié)點發(fā)送過來的新的區(qū)塊鏈(JSON字符串)。

然后檢查進來的區(qū)塊鏈的長度是否比我們本地的要長,如果進來的鏈更長,那么我們就接受新的鏈為最新的網(wǎng)絡(luò)狀態(tài)(最新的區(qū)塊鏈)。

同時,把最新的區(qū)塊鏈在控制臺使用一種特殊的顏色打印出來,這樣我們就知道有新鏈接受了。

如果在我們主機的本地添加了新的區(qū)塊到區(qū)塊鏈上,那就需要把本地最新的區(qū)塊鏈廣播給其它相連的節(jié)點知道,這樣這些節(jié)點機會接受并更新到我們的區(qū)塊鏈版本。這里使用writeData函數(shù):

func writeData(rw *bufio.ReadWriter) {

? ?go func() {

? ? ? ?for {

? ? ? ? ? ?time.Sleep(5 * time.Second)

? ? ? ? ? ?mutex.Lock()

? ? ? ? ? ?bytes, err := json.Marshal(Blockchain)

? ? ? ? ? ?if err != nil {

? ? ? ? ? ? ? ?log.Println(err)

? ? ? ? ? ?}

? ? ? ? ? ?mutex.Unlock()

? ? ? ? ? ?mutex.Lock()

? ? ? ? ? ?rw.WriteString(fmt.Sprintf("%s\n", string(bytes)))

? ? ? ? ? ?rw.Flush()

? ? ? ? ? ?mutex.Unlock()

? ? ? ?}

? ?}()

? ?stdReader := bufio.NewReader(os.Stdin)

? ?for {

? ? ? ?fmt.Print("> ")

? ? ? ?sendData, err := stdReader.ReadString('\n')

? ? ? ?if err != nil {

? ? ? ? ? ?log.Fatal(err)

? ? ? ?}

? ? ? ?sendData = strings.Replace(sendData, "\n", "", -1)

? ? ? ?bpm, err := strconv.Atoi(sendData)

? ? ? ?if err != nil {

? ? ? ? ? ?log.Fatal(err)

? ? ? ?}

? ? ? ?newBlock := generateBlock(Blockchain[len(Blockchain)-1], bpm)

? ? ? ?if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {

? ? ? ? ? ?mutex.Lock()

? ? ? ? ? ?Blockchain = append(Blockchain, newBlock)

? ? ? ? ? ?mutex.Unlock()

? ? ? ?}

? ? ? ?bytes, err := json.Marshal(Blockchain)

? ? ? ?if err != nil {

? ? ? ? ? ?log.Println(err)

? ? ? ?}

? ? ? ?spew.Dump(Blockchain)

? ? ? ?mutex.Lock()

? ? ? ?rw.WriteString(fmt.Sprintf("%s\n", string(bytes)))

? ? ? ?rw.Flush()

? ? ? ?mutex.Unlock()

? ?}

}

首先是一個單獨協(xié)程中的函數(shù),每5秒鐘會將我們的最新的區(qū)塊鏈狀態(tài)廣播給其它相連的節(jié)點。它們收到后,如果發(fā)現(xiàn)我們的區(qū)塊鏈比它們的要短,就會直接把我們發(fā)送的區(qū)塊鏈信息丟棄,繼續(xù)使用它們的區(qū)塊鏈,反之則使用我們的區(qū)塊鏈。總之,無論哪種方法,所有的節(jié)點都會定期的同步本地的區(qū)塊鏈到最新狀態(tài)。

這里我們需要一個方法來創(chuàng)建一個新的Block區(qū)塊,包含之前提到過的脈搏速率(BPM)。為了簡化實現(xiàn),我們不會真的去通過物聯(lián)網(wǎng)設(shè)備讀取脈搏,而是直接在終端控制臺上輸入一個脈搏速率數(shù)字。

首先要驗證輸入的BPM是一個整數(shù)類型,然后使用之前的generateBlock來生成區(qū)塊,接著使用spew.Dump輸入到終端控制臺,最后我們使用rw.WriteString把最新的區(qū)塊鏈廣播給相連的其它節(jié)點。

牛逼了我的哥,現(xiàn)在我們完成了區(qū)塊鏈相關(guān)的函數(shù)以及大多數(shù)P2P相關(guān)的函數(shù)。在前面,我們創(chuàng)建了流處理,因此可以讀取和寫入最新的區(qū)塊鏈狀態(tài);創(chuàng)建了狀態(tài)同步函數(shù),這樣節(jié)點之間可以互相同步最新狀態(tài)。

剩下的就是實現(xiàn)我們的main函數(shù)了:

func main() {

? ?t := time.Now()

? ?genesisBlock := Block{}

? ?genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), ""}

? ?Blockchain = append(Blockchain, genesisBlock)

? ?// LibP2P code uses golog to log messages. They log with different

? ?// string IDs (i.e. "swarm"). We can control the verbosity level for

? ?// all loggers with:

? ?golog.SetAllLoggers(gologging.INFO) // Change to DEBUG for extra info

? ?// Parse options from the command line

? ?listenF := flag.Int("l", 0, "wait for incoming connections")

? ?target := flag.String("d", "", "target peer to dial")

? ?secio := flag.Bool("secio", false, "enable secio")

? ?seed := flag.Int64("seed", 0, "set random seed for id generation")

? ?flag.Parse()

? ?if *listenF == 0 {

? ? ? ?log.Fatal("Please provide a port to bind on with -l")

? ?}

? ?// Make a host that listens on the given multiaddress

? ?ha, err := makeBasicHost(*listenF, *secio, *seed)

? ?if err != nil {

? ? ? ?log.Fatal(err)

? ?}

? ?if *target == "" {

? ? ? ?log.Println("listening for connections")

? ? ? ?// Set a stream handler on host A. /p2p/1.0.0 is

? ? ? ?// a user-defined protocol name.

? ? ? ?ha.SetStreamHandler("/p2p/1.0.0", handleStream)

? ? ? ?select {} // hang forever

? ? ? ?/**** This is where the listener code ends ****/

? ?} else {

? ? ? ?ha.SetStreamHandler("/p2p/1.0.0", handleStream)

? ? ? ?// The following code extracts target's peer ID from the

? ? ? ?// given multiaddress

? ? ? ?ipfsaddr, err := ma.NewMultiaddr(*target)

? ? ? ?if err != nil {

? ? ? ? ? ?log.Fatalln(err)

? ? ? ?}

? ? ? ?pid, err := ipfsaddr.ValueForProtocol(ma.P_IPFS)

? ? ? ?if err != nil {

? ? ? ? ? ?log.Fatalln(err)

? ? ? ?}

? ? ? ?peerid, err := peer.IDB58Decode(pid)

? ? ? ?if err != nil {

? ? ? ? ? ?log.Fatalln(err)

? ? ? ?}

? ? ? ?// Decapsulate the /ipfs/ part from the target

? ? ? ?// /ip4//ipfs/ becomes /ip4/

? ? ? ?targetPeerAddr, _ := ma.NewMultiaddr(

? ? ? ? ? ?fmt.Sprintf("/ipfs/%s", peer.IDB58Encode(peerid)))

? ? ? ?targetAddr := ipfsaddr.Decapsulate(targetPeerAddr)

? ? ? ?// We have a peer ID and a targetAddr so we add it to the peerstore

? ? ? ?// so LibP2P knows how to contact it

? ? ? ?ha.Peerstore().AddAddr(peerid, targetAddr, pstore.PermanentAddrTTL)

? ? ? ?log.Println("opening stream")

? ? ? ?// make a new stream from host B to host A

? ? ? ?// it should be handled on host A by the handler we set above because

? ? ? ?// we use the same /p2p/1.0.0 protocol

? ? ? ?s, err := ha.NewStream(context.Background(), peerid, "/p2p/1.0.0")

? ? ? ?if err != nil {

? ? ? ? ? ?log.Fatalln(err)

? ? ? ?}

? ? ? ?// Create a buffered stream so that read and writes are non blocking.

? ? ? ?rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))

? ? ? ?// Create a thread to read and write data.

? ? ? ?go writeData(rw)

? ? ? ?go readData(rw)

? ? ? ?select {} // hang forever

? ?}

}

首先是創(chuàng)建一個創(chuàng)世區(qū)塊(如果你讀了200行Go代碼實現(xiàn)你的區(qū)塊鏈,這里就不會陌生)。

其次我們使用go-libp2p的SetAllLoggers日志函數(shù)來記錄日志。

接著,設(shè)置了所有的命令行標(biāo)識:

secio之前有提到,是用來加密數(shù)據(jù)流的。在我們的程序中,一定要打開該標(biāo)識

target指明當(dāng)前節(jié)點要連接到的主機地址

listenF是當(dāng)前節(jié)點的監(jiān)聽主機地址,這樣其它節(jié)點就可以連接進來,記住,每個節(jié)點都有兩個身份:主機和客戶端, 畢竟P2P不是白叫的

seed是隨機數(shù)種子,用來創(chuàng)建主機地址時使用

然后,使用makeBasicHost函數(shù)來創(chuàng)建一個新的主機地址,如果我們只想做主機不想做客戶端(連接其它的主機),就使用if*target==“”。

接下來的幾行,會從target解析出我們要連接到的主機地址。然后把peerID和主機目標(biāo)地址targetAddr添加到"store"中,這樣就可以持續(xù)跟蹤我們跟其它主機的連接信息,這里使用的是ha.Peerstore().AddAddr函數(shù)。

接著我們使用ha.NewStream連接到想要連接的節(jié)點上,同時為了能接收和發(fā)送最新的區(qū)塊鏈信息,創(chuàng)建了ReadWriter,同時使用一個Go協(xié)程來進行readData和writeData。

哇哦

終于完成了,寫文章遠比寫代碼累!我知道之前的內(nèi)容有點難,但是相比P2P的復(fù)雜性來說,你能通過一個庫來完成P2P網(wǎng)絡(luò),已經(jīng)很牛逼了,所以繼續(xù)加油!

完整代碼

mycoralhealth/blockchain-tutorial

運行結(jié)果

現(xiàn)在讓我們來試驗一下,首先打開3個獨立的終端窗口做為獨立節(jié)點。

開始之前,請再次進入go-libp2p的根目錄運行一下make deps,確保所有依賴都正常安裝。

回到你的工作目錄examples/p2p,打開第一個終端窗口,輸入go run main.go-l10000-secio

細心的讀者會發(fā)現(xiàn)有一段話"Now run…",那還等啥,繼續(xù)跟著做吧,打開第二個終端窗口運行:go run main.go-l10001-d-secio

這是你會發(fā)現(xiàn)第一個終端窗口檢測到了新連接!

接著打開第三個終端窗口,運行:go run main.go-l10002-d-secio

檢查第二終端,又發(fā)現(xiàn)了新連接

接著,該我們輸入BPM數(shù)據(jù)了,在第一個終端窗口中輸入"70",等幾秒中,觀察各個窗口的打印輸出。

來看看發(fā)生了什么:

終端1向本地的區(qū)塊鏈添加了一個新的區(qū)塊Block

終端1向終端2廣播該信息

終端2將新的區(qū)塊鏈跟本地的對比,發(fā)現(xiàn)終端1的更長,因此使用新的區(qū)塊鏈替代了本地的區(qū)塊鏈,然后將新的區(qū)塊鏈廣播給終端3

同上,終端3也進行更新

所有的3個終端節(jié)點都把區(qū)塊鏈更新到了最新版本,同時沒有使用任何外部的中心化服務(wù),這就是P2P網(wǎng)絡(luò)的力量!

我們再往終端2的區(qū)塊鏈中添加一個區(qū)塊試試看,在終端2中輸入"80"

結(jié)果忠誠的記錄了我們的正確性,再一次歡呼吧!

下一步

先享受一下自己的工作,你剛用了區(qū)區(qū)幾百行代碼就實現(xiàn)了一個全功能的P2P網(wǎng)絡(luò)!這不是開玩笑,P2P編程時非常復(fù)雜的,為什么之前沒有相關(guān)的教程,就是因為太難了。

但是,這里也有幾個可以改進的地方,你可以挑戰(zhàn)一下自己:

之前提到過,?go-libp2p是存在數(shù)據(jù)競爭的Bug的,因此如果你要在生產(chǎn)環(huán)境使用,需要格外小心。一旦發(fā)現(xiàn)Bug,請反饋給作者團隊知道

嘗試將本文的P2P網(wǎng)絡(luò)跟之前的共識協(xié)議結(jié)合,例如之前的文章PoW?和PoS?(PoS是中文譯文)

添加持久化存儲。截止目前,為了簡化實現(xiàn),我們沒有實現(xiàn)持久化存儲,因此節(jié)點關(guān)閉,數(shù)據(jù)就丟失了

本文的代碼沒有在大量節(jié)點的環(huán)境下測試過,試著寫一個腳本運行大量節(jié)點,看看性能會怎么變化。如果發(fā)現(xiàn)Bug記得給我們提交

學(xué)習(xí)一下節(jié)點發(fā)現(xiàn)技術(shù)。新節(jié)點是怎么發(fā)現(xiàn)已經(jīng)存在的節(jié)點的?這篇文章是一個很好的起點

更多資訊可關(guān)注微信公眾號:程序新視界

VIP一對一咨詢可關(guān)注知識星球(小密圈)

內(nèi)容轉(zhuǎn)載自公眾號

優(yōu)優(yōu)區(qū)塊鏈課堂

了解更多

投訴

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 江湖上,不會沒有人不知道沈無雙。 沈無雙是一個人。 一個救命的人。 江湖上都說,沈無雙是能從閻王手里搶人得人。 但...
    關(guān)門放狗閱讀 370評論 0 0
  • 轉(zhuǎn)眼已經(jīng)一周不寫記錄了,這一周過的很沒有成長感,每天不想學(xué)習(xí),不想看書,不想反思。有想做的事而每天都不想去做。有志...
    綠竹水心閱讀 202評論 0 0
  • 每日推薦: 每日一歌――周杰倫《星晴》 每周一影――鐘少雄《全城高考》 每日一詩――孟郊《登科后》 昔日齷齪不...
    薩拉芯雪閱讀 183評論 0 0

友情鏈接更多精彩內(nèi)容