转自:https://mp.weixin.qq.com/s/2daFH9Ej5fVlWmpsN5HZzw

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

在之前的文章中,我们已经知道了怎么编写PoW也知道了IPFS怎么工作, 但是有一个致命的缺点,我们的服务都是中心化的,这篇文章会教你怎么实现一个简单的完全去中心化的P2P网络。

背景知识

什么是P2P网络

在真正的P2P架构中,不需要中心化的服务来维护区块链的状态。例如,当你给朋友发送比特币时,比特币区块链的“状态”应该更新,这样你朋友的余额就会增加,你的余额就会减少。

在这个网络中,不存在一个权力高度中心化的机构来维护状态(银行就是这样的中心化机构)。对于比特币网络来说,每个节点都会维护一份完整的区块链状态,当交易发生时,每个节点的区块链状态都会得到更新。这样,只要网络中51%的节点对区块链的状态达成一致,那么区块链网络就是安全可靠的,具体可以阅读这篇一致性协议文章。

本文将继续之前的工作,200行Go代码实现区块链, 并加入P2P网络架构。在继续之前,强烈建议你先阅读该篇文章,它会帮助你理解接下来的代码。

开始实现

编写P2P网络可不是开开玩笑就能简单视线的,有很多边边角角的情况都要覆盖到,而且需要你拥有很多工程学的知识,这样的P2P网络才是可扩展、高可靠的。有句谚语说得好:站在巨人肩膀上做事,那么我们先看看巨人们提供了哪些工具吧。

喔,看看,我们发现了什么!一个用Go语言实现的P2P库go-libp2p!如果你对新技术足够敏锐,就会发现这个库的作者和IPFS的作者是同一个团队。如果你还没看过我们的IPFS教程,可以看看这里, 你可以选择跳过IPFS教程,因为对于本文这不是必须的。

警告

目前来说, go-libp2p主要有两个缺点:

  1. 安装设置比较痛苦,它使用gx作为包管理工具,怎么说呢,不咋好用,但是凑活用吧

  2. 目前项目还没有成熟,正在紧密锣鼓的开发中,当使用这个库时,可能会遇到一些数据竞争(data race)

对于第一点,不必担心,有我们呢。第二点是比较大的问题,但是不会影响我们的代码。假如你在使用过程中发现了数据竞争问题,记得给项目提一个issue,帮助它更好的成长!

总之,目前开源世界中,现代化的P2P库是非常非常少的,因为我们要多给 go-libp2p一些耐心和包容,而且就目前来说,它已经能很好的满足我们的目标了。

安装设置

最好的环境设置方式是直接clone libp2p库,然后在这个库的代码中直接开发。你也可以在自己的库中,调用这个库开发,但是这样就需要用到 gx了。这里我们使用简单的方式,假设你已经安装了Go:

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

  • 进入 go-libp2p文件夹

  • make

  • make deps

这里会通过gx包管理工具下载所有需要的包和依赖,再次申明,我们不喜欢gx,因为它打破了Go语言的很多惯例,但是为了这个很棒的库,认怂吧。

这里,我们在 examples子目录下进行开发,因此在 go-libp2p的examples下创建一个你自己的目录

  • mkdir ./examples/p2p

然后进入到p2p文件夹下,创建 main.go文件,后面所有的代码都会在该文件中。

你的目录结构是这样的:

好了,勇士们,拔出你们的剑,哦不,拔出你们的 main.go,开始我们的征途吧!

导入相关库

这里申明我们需要用的库,大部分库是来自于 go-libp2p本身的,在教程中,你会学到怎么去使用它们。

  1. package main

  2. import (

  3.    "bufio"

  4.    "context"

  5.    "crypto/rand"

  6.    "crypto/sha256"

  7.    "encoding/hex"

  8.    "encoding/json"

  9.    "flag"

  10.    "fmt"

  11.    "io"

  12.    "log"

  13.    mrand "math/rand"

  14.    "os"

  15.    "strconv"

  16.    "strings"

  17.    "sync"

  18.    "time"

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

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

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

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

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

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

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

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

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

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

  29. )

spew包可以很方便、优美的打印出我们的区块链,因此记得安装它:

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

区块链结构

记住,请先阅读200行Go代码实现区块链, 这样,下面的部分就会简单很多。

先来申明全局变量:

  1. // Block represents each 'item' in the blockchain

  2. type Block struct {

  3.    Index     int

  4.    Timestamp string

  5.    BPM       int

  6.    Hash      string

  7.    PrevHash  string

  8. }

  9. // Blockchain is a series of validated Blocks

  10. var Blockchain []Block

  11. var mutex = &sync.Mutex{}

  • 我们是一家健康看护公司,因此Block中存着的是用户的脉搏速率BPM

  • Blockchain是我们的"状态",或者严格的说:最新的Blockchain,它其实就是Block的切片(slice)

  • mutex是为了防止资源竞争出现

下面是Blockchain相关的特定函数:

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

  2. func isBlockValid(newBlock, oldBlock Block) bool {

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

  4.        return false

  5.    }

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

  7.        return false

  8.    }

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

  10.        return false

  11.    }

  12.    return true

  13. }

  14. // SHA256 hashing

  15. func calculateHash(block Block) string {

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

  17.    h := sha256.New()

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

  19.    hashed := h.Sum(nil)

  20.    return hex.EncodeToString(hashed)

  21. }

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

  23. func generateBlock(oldBlock Block, BPM int) Block {

  24.    var newBlock Block

  25.    t := time.Now()

  26.    newBlock.Index = oldBlock.Index + 1

  27.    newBlock.Timestamp = t.String()

  28.    newBlock.BPM = BPM

  29.    newBlock.PrevHash = oldBlock.Hash

  30.    newBlock.Hash = calculateHash(newBlock)

  31.    return newBlock

  32. }

  • isBlockValid检查Block的hash是否合法

  • calculateHash使用 sha256来对原始数据做hash

  • generateBlock创建一个新的Block区块,然后添加到区块链Blockchain上,同时会包含所需的事务

P2P结构

下面我们快接近核心部分了,首先我们要写出创建主机的逻辑。当一个节点运行我们的程序时,它可以作为一个主机,被其它节点连接。下面一起看看代码:-)

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

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

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

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

  5.    // deterministic randomness source to make generated keys stay the same

  6.    // across multiple runs

  7.    var r io.Reader

  8.    if randseed == 0 {

  9.        r = rand.Reader

  10.    } else {

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

  12.    }

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

  14.    // to obtain a valid host ID.

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

  16.    if err != nil {

  17.        return nil, err

  18.    }

  19.    opts := []libp2p.Option{

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

  21.        libp2p.Identity(priv),

  22.    }

  23.    if !secio {

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

  25.    }

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

  27.    if err != nil {

  28.        return nil, err

  29.    }

  30.    // Build host multiaddress

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

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

  33.    // by encapsulating both addresses:

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

  35.    fullAddr := addr.Encapsulate(hostAddr)

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

  37.    if secio {

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

  39.    } else {

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

  41.    }

  42.    return basicHost, nil

  43. }

makeBasicHost函数有3个参数,同时返回一个host结构体

  • listenPort是主机监听的端口,其它节点会连接该端口

  • secio表明是否开启数据流的安全选项,最好开启,因此它代表了"安全输入/输出"

  • randSeed是一个可选的命令行标识,可以允许我们提供一个随机数种子来为我们的主机生成随机的地址。这里我们不会使用

函数的第一个 if语句针对随机种子生成随机key,接着我们生成公钥和私钥,这样能保证主机是安全的。 opts部分开始构建网络地址部分,这样其它节点就可以连接进来。

!secio部分可以绕过加密,但是我们准备使用加密,因此这段代码不会被触发。

接着,创建了主机地址,这样其他节点就可以连接进来。 log.Printf可以用来在控制台打印出其它节点的连接信息。最后我们返回生成的主机地址给调用方函数。

流处理

之前的主机需要能处理进入的数据流。当另外一个节点连接到主机时,它会想要提出一个新的区块链,来覆盖主机上的区块链,因此我们需要逻辑来判定是否要接受新的区块链。

同时,当我们往本地的区块链添加区块后,也要把相关信息广播给其它节点,这里也需要实现相关逻辑。

先来创建流处理的基本框架吧:

  1. func handleStream(s net.Stream) {

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

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

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

  5.    go readData(rw)

  6.    go writeData(rw)

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

  8. }

这里创建一个新的 ReadWriter,为了能支持数据读取和写入,同时我们启动了一个单独的Go协程来处理相关读写逻辑。

读取数据

首先创建 readData函数:

  1. func readData(rw *bufio.ReadWriter) {

  2.    for {

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

  4.        if err != nil {

  5.            log.Fatal(err)

  6.        }

  7.        if str == "" {

  8.            return

  9.        }

  10.        if str != "\n" {

  11.            chain := make([]Block, 0)

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

  13.                log.Fatal(err)

  14.            }

  15.            mutex.Lock()

  16.            if len(chain) > len(Blockchain) {

  17.                Blockchain = chain

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

  19.                if err != nil {

  20.                    log.Fatal(err)

  21.                }

  22.                // Green console color:     \x1b[32m

  23.                // Reset console color:     \x1b[0m

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

  25.            }

  26.            mutex.Unlock()

  27.        }

  28.    }

  29. }

该函数是一个无限循环,因为它需要永不停歇的去读取外面进来的数据。首先,我们使用 ReadString解析从其它节点发送过来的新的区块链(JSON字符串)。

然后检查进来的区块链的长度是否比我们本地的要长,如果进来的链更长,那么我们就接受新的链为最新的网络状态(最新的区块链)。

同时,把最新的区块链在控制台使用一种特殊的颜色打印出来,这样我们就知道有新链接受了。

如果在我们主机的本地添加了新的区块到区块链上,那就需要把本地最新的区块链广播给其它相连的节点知道,这样这些节点机会接受并更新到我们的区块链版本。这里使用 writeData函数:

  1. func writeData(rw *bufio.ReadWriter) {

  2.    go func() {

  3.        for {

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

  5.            mutex.Lock()

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

  7.            if err != nil {

  8.                log.Println(err)

  9.            }

  10.            mutex.Unlock()

  11.            mutex.Lock()

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

  13.            rw.Flush()

  14.            mutex.Unlock()

  15.        }

  16.    }()

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

  18.    for {

  19.        fmt.Print("> ")

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

  21.        if err != nil {

  22.            log.Fatal(err)

  23.        }

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

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

  26.        if err != nil {

  27.            log.Fatal(err)

  28.        }

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

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

  31.            mutex.Lock()

  32.            Blockchain = append(Blockchain, newBlock)

  33.            mutex.Unlock()

  34.        }

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

  36.        if err != nil {

  37.            log.Println(err)

  38.        }

  39.        spew.Dump(Blockchain)

  40.        mutex.Lock()

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

  42.        rw.Flush()

  43.        mutex.Unlock()

  44.    }

  45. }

首先是一个单独协程中的函数,每5秒钟会将我们的最新的区块链状态广播给其它相连的节点。它们收到后,如果发现我们的区块链比它们的要短,就会直接把我们发送的区块链信息丢弃,继续使用它们的区块链,反之则使用我们的区块链。总之,无论哪种方法,所有的节点都会定期的同步本地的区块链到最新状态。

这里我们需要一个方法来创建一个新的Block区块,包含之前提到过的脉搏速率(BPM)。为了简化实现,我们不会真的去通过物联网设备读取脉搏,而是直接在终端控制台上输入一个脉搏速率数字。

首先要验证输入的BPM是一个整数类型,然后使用之前的 generateBlock来生成区块,接着使用 spew.Dump输入到终端控制台,最后我们使用 rw.WriteString把最新的区块链广播给相连的其它节点。

牛逼了我的哥,现在我们完成了区块链相关的函数以及大多数P2P相关的函数。在前面,我们创建了流处理,因此可以读取和写入最新的区块链状态;创建了状态同步函数,这样节点之间可以互相同步最新状态。

剩下的就是实现我们的 main函数了:

  1. func main() {

  2.    t := time.Now()

  3.    genesisBlock := Block{}

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

  5.    Blockchain = append(Blockchain, genesisBlock)

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

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

  8.    // all loggers with:

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

  10.    // Parse options from the command line

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

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

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

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

  15.    flag.Parse()

  16.    if *listenF == 0 {

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

  18.    }

  19.    // Make a host that listens on the given multiaddress

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

  21.    if err != nil {

  22.        log.Fatal(err)

  23.    }

  24.    if *target == "" {

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

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

  27.        // a user-defined protocol name.

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

  29.        select {} // hang forever

  30.        /**** This is where the listener code ends ****/

  31.    } else {

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

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

  34.        // given multiaddress

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

  36.        if err != nil {

  37.            log.Fatalln(err)

  38.        }

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

  40.        if err != nil {

  41.            log.Fatalln(err)

  42.        }

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

  44.        if err != nil {

  45.            log.Fatalln(err)

  46.        }

  47.        // Decapsulate the /ipfs/<peerID> part from the target

  48.        // /ip4/<a.b.c.d>/ipfs/<peer> becomes /ip4/<a.b.c.d>

  49.        targetPeerAddr, _ := ma.NewMultiaddr(

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

  51.        targetAddr := ipfsaddr.Decapsulate(targetPeerAddr)

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

  53.        // so LibP2P knows how to contact it

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

  55.        log.Println("opening stream")

  56.        // make a new stream from host B to host A

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

  58.        // we use the same /p2p/1.0.0 protocol

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

  60.        if err != nil {

  61.            log.Fatalln(err)

  62.        }

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

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

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

  66.        go writeData(rw)

  67.        go readData(rw)

  68.        select {} // hang forever

  69.    }

  70. }

首先是创建一个创世区块(如果你读了200行Go代码实现你的区块链,这里就不会陌生)。

其次我们使用 go-libp2pSetAllLoggers日志函数来记录日志。

接着,设置了所有的命令行标识:

  • secio之前有提到,是用来加密数据流的。在我们的程序中,一定要打开该标识

  • target指明当前节点要连接到的主机地址

  • listenF是当前节点的监听主机地址,这样其它节点就可以连接进来,记住,每个节点都有两个身份:主机和客户端, 毕竟P2P不是白叫的

  • seed是随机数种子,用来创建主机地址时使用

然后,使用 makeBasicHost函数来创建一个新的主机地址,如果我们只想做主机不想做客户端(连接其它的主机),就使用 if*target==“”

接下来的几行,会从 target解析出我们要连接到的主机地址。然后把 peerID和主机目标地址 targetAddr添加到"store"中,这样就可以持续跟踪我们跟其它主机的连接信息,这里使用的是 ha.Peerstore().AddAddr函数。

接着我们使用 ha.NewStream连接到想要连接的节点上,同时为了能接收和发送最新的区块链信息,创建了 ReadWriter,同时使用一个Go协程来进行 readDatawriteData

哇哦

终于完成了,写文章远比写代码累!我知道之前的内容有点难,但是相比P2P的复杂性来说,你能通过一个库来完成P2P网络,已经很牛逼了,所以继续加油!

完整代码

mycoralhealth/blockchain-tutorial

运行结果

现在让我们来试验一下,首先打开3个独立的终端窗口做为独立节点。

开始之前,请再次进入 go-libp2p的根目录运行一下 make deps,确保所有依赖都正常安装。

回到你的工作目录 examples/p2p,打开第一个终端窗口,输入 go run main.go-l10000-secio

细心的读者会发现有一段话"Now run…",那还等啥,继续跟着做吧,打开第二个终端窗口运行: go run main.go-l10001-d<given addressinthe instructions>-secio

这是你会发现第一个终端窗口检测到了新连接!

接着打开第三个终端窗口,运行: go run main.go-l10002-d<given addressinthe instructions>-secio

检查第二终端,又发现了新连接

接着,该我们输入BPM数据了,在第一个终端窗口中输入"70",等几秒中,观察各个窗口的打印输出。

来看看发生了什么:

  • 终端1向本地的区块链添加了一个新的区块Block

  • 终端1向终端2广播该信息

  • 终端2将新的区块链跟本地的对比,发现终端1的更长,因此使用新的区块链替代了本地的区块链,然后将新的区块链广播给终端3

  • 同上,终端3也进行更新

所有的3个终端节点都把区块链更新到了最新版本,同时没有使用任何外部的中心化服务,这就是P2P网络的力量!

我们再往终端2的区块链中添加一个区块试试看,在终端2中输入"80"

结果忠诚的记录了我们的正确性,再一次欢呼吧!

下一步

先享受一下自己的工作,你刚用了区区几百行代码就实现了一个全功能的P2P网络!这不是开玩笑,P2P编程时非常复杂的,为什么之前没有相关的教程,就是因为太难了。

但是,这里也有几个可以改进的地方,你可以挑战一下自己:

  • 之前提到过, go-libp2p是存在数据竞争的Bug的,因此如果你要在生产环境使用,需要格外小心。一旦发现Bug,请反馈给作者团队知道

  • 尝试将本文的P2P网络跟之前的共识协议结合,例如之前的文章PoW 和PoS (PoS是中文译文)

  • 添加持久化存储。截止目前,为了简化实现,我们没有实现持久化存储,因此节点关闭,数据就丢失了

  • 本文的代码没有在大量节点的环境下测试过,试着写一个脚本运行大量节点,看看性能会怎么变化。如果发现Bug记得给我们提交

  • 学习一下节点发现技术。新节点是怎么发现已经存在的节点的?这篇文章是一个很好的起点

使用Go语言编写区块链P2P网络(译)(转)的更多相关文章

  1. 详解区块链P2P网络

    根据前一篇文章<从微观到宏观理解区块链>我们已经了解到,微观上,区块链本质就是一种不可篡改且可追踪溯源的哈希链条:宏观上,还具备了另外三个基本特征:分布式存储.P2P 网络和共识机制.分布 ...

  2. Go语言开发区块链只需180行代码

    区块链开发用什么语言?通过本文你将使用Go语言开发自己的区块链(或者说用go语言搭建区块链).理解哈希函数是如何保持区块链的完整性.掌握如何用Go语言编程创造并添加新的块.实现多个节点通过竞争生成块. ...

  3. 用Hyperledger Fabric(超级账本)来构建Java语言开发区块链的环境

    面向 Java 开发人员的链代码简介 您或许听说过区块链,但可能不确定它对 Java™ 开发人员有何用.本教程将帮助大家解惑.我将分步展示如何使用 Hyperledger Fabric v0.6 来构 ...

  4. 比原链CTO James | Go语言成为区块链主流开发语言的四点理由

    11月24日,比原链CTO James参加了Go中国举办的Gopher Meetup杭州站活动,与来自阿里.网易的技术专家带来Kubernetes.区块链.日志采集.云原生等话题的分享.James向大 ...

  5. 001/Go语言构建区块链(mooc)

    1.区块链发展与现状 视频地址:https://www.imooc.com/video/17452 注意: 比特币与以太坊最大的区别在于: 以太坊引入了对图灵完美智能合约的支持,人们可以将任何业务逻辑 ...

  6. 用Java为Hyperledger Fabric(超级账本)编写区块链智能合约链代码

    编写第一个 Java 链代码程序 在上一节中,您已经熟悉了如何构建.运行.部署和调用链代码,但尚未编写任何 Java 代码. 在本节中,将会使用 Eclipse IDE.一个用于 Eclipse 的 ...

  7. 只用120行Java代码写一个自己的区块链-2网络

    已经看完第一章的内容了吗,欢迎回来. 上一章我们介绍了关于怎么去编写自己的区块链,完成哈希和新块的校验.但是它只是在一个终端(结点)上跑.我们怎么样来连接其他结点以及贡献新的块呢,怎么样广播到其他结点 ...

  8. [GO]用go语言实现区块链工作原理

    基本原理这里就不写了,只写一个简单demo的实现 首先得有一个区块用来存储区块头和区块体 type Block struct { Version int64 PreBlockHash []byte H ...

  9. [GO]go语言实现区块链工作证明(pow)原理

    package main import ( "math/big" "bytes" "math" "crypto/sha256&qu ...

随机推荐

  1. Win 7/10 安装Oracle 11g

    两个系统安装oracle的过程基本一致,注意安装时选桌面类(没有试过server,只有server类的操作系统选择server类) 安装过程:https://jingyan.baidu.com/alb ...

  2. pytorch错误:Missing key(s) in state_dict、Unexpected key(s) in state_dict解决

    版权声明:本文为博主原创文章,欢迎转载,并请注明出处.联系方式:460356155@qq.com 在模型训练时加上: model = nn.DataParallel(model)cudnn.bench ...

  3. JS 获取某个容器控件中id包含制定字符串的控件id列表

    //获取某容器控件中id包含某字符串的控件id列表 //参数:容器控件.要查找的控件的id关键字 function GetIdListBySubKey(container,subIdKey) { va ...

  4. Django(七)缓存、信号、Form

    大纲 一.缓存 1.1.五种缓存配置 1.2配置 2.1.三种应用(全局.视图函数.模板) 2.2 应用多个缓存时生效的优先级 二.信号 1.Django内置信号 2.自定义信号 三.Form 1.初 ...

  5. A Base Class pointer can point to a derived class object. Why is the vice-versa not true?

    问题转载自:https://stackoverflow.com/questions/4937180/a-base-class-pointer-can-point-to-a-derived-class- ...

  6. vue cli使用融云实现聊天

    公司有个项目要实现一个聊天功能,需求如下图,略显随意 公司最终选择融云这个吊炸天的即时通信,文档详细的一匹,刚开始看文档感觉很详细实现起来也不麻烦,有很多开源的demo可以在线演示和下载 不过我们的项 ...

  7. AWS Step Function Serverless Applications

    Installing VS Components To follow along with this article, you must have an AWS account and install ...

  8. SqlMapConfig.xml 的配置

    jdbc.properties :数据库连接的配置 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://192.168.181.135:33 ...

  9. Linux下find命令用法详解

    Linux下find命令用法详解   学神VIP烟火 学神IT教育:XueGod-IT   最负责任的线上直播教育平台   本文作者为VIP学员 烟火   第一部分:根据文件名查找   1.在当前目录 ...

  10. Flask 构建微电影视频网站(八)

    评论收藏及弹幕 实现电影评论添加及列表.数据查询实现统计播放量和评论量.jquery ajax实现收藏电影,flask结合redis消息队列实现电影弹幕,bug处理等功能. 电影评论-统计 class ...