1. 链一财经首页
  2. 资讯

【技术篇】区块链之网络架构详解(下)

我们继续区块链网络下部分内容,不知道大家对上半部分理解如何?

微信图片_20180507164013.jpg

getblocks

type getblocks struct {

    AddrFrom string}

getblocks 意为 “给我看一下你有什么区块”(在比特币中,这会更加复杂)。注意,它并没有说“把你全部的区块给我”,而是请求了一个块哈希的列表。这是为了减轻网络负载,因为区块可以从不同的节点下载,并且我们不想从一个单一节点下载数十 GB 的数据。

处理命令十分简单:

func handleGetBlocks(request []byte, bc *Blockchain) {

    …

    blocks := bc.GetBlockHashes()

    sendInv(payload.AddrFrom, “block”, blocks)}

在我们简化版的实现中,它会返回 所有块哈希。

inv

type inv struct {

    AddrFrom string

    Type     string

    Items    [][]byte}

比特币使用 inv 来向其他节点展示当前节点有什么块和交易。再次提醒,它没有包含完整的区块链和交易,仅仅是哈希而已。Type 字段表明了这是块还是交易。

处理 inv 稍显复杂:

func handleInv(request []byte, bc *Blockchain) {

    …

    fmt.Printf(“Recevied inventory with %d %sn”, len(payload.Items), payload.Type)

    if payload.Type == “block” {

        blocksInTransit = payload.Items

        blockHash := payload.Items[0]

        sendGetData(payload.AddrFrom, “block”, blockHash)

        newInTransit := [][]byte{}

        for _, b := range blocksInTransit {

            if bytes.Compare(b, blockHash) != 0 {

                newInTransit = append(newInTransit, b)

            }

        }

        blocksInTransit = newInTransit    }

    if payload.Type == “tx” {

        txID := payload.Items[0]

        if mempool[hex.EncodeToString(txID)].ID == nil {

            sendGetData(payload.AddrFrom, “tx”, txID)

        }

    }}


如果收到块哈希,我们想要将它们保存在 blocksInTransit 变量来跟踪已下载的块。这能够让我们从不同的节点下载块。在将块置于传送状态时,我们给 inv 消息的发送者发送 getdata 命令并更新 blocksInTransit。在一个真实的 P2P 网络中,我们会想要从不同节点来传送块。

在我们的实现中,我们永远也不会发送有多重哈希的 inv。这就是为什么当 payload.Type == "tx" 时,只会拿到第一个哈希。然后我们检查是否在内存池中已经有了这个哈希,如果没有,发送 getdata 消息。

【技术篇】区块链之网络架构详解(下)getdata

type getdata struct {

    AddrFrom string

    Type     string

    ID       []byte}


getdata 用于某个块或交易的请求,它可以仅包含一个块或交易的 ID。

func handleGetData(request []byte, bc *Blockchain) {

    …

    if payload.Type == “block” {

        block, err := bc.GetBlock([]byte(payload.ID))

        sendBlock(payload.AddrFrom, &block)

    }

    if payload.Type == “tx” {

        txID := hex.EncodeToString(payload.ID)

        tx := mempool[txID]

        sendTx(payload.AddrFrom, &tx)

    }}

这个处理器比较地直观:如果它们请求一个块,则返回块;如果它们请求一笔交易,则返回交易。注意,我们并不检查实际上是否已经有了这个块或交易。这是一个缺陷 🙂

【技术篇】区块链之网络架构详解(下)block和tx

type block struct {

    AddrFrom string

    Block    []byte}type tx struct {

    AddFrom     string

    Transaction []byte}

实际完成数据转移的正是这些消息。

处理 block 消息十分简单:

    }}

当接收到一个新块时,我们把它放到区块链里面。如果还有更多的区块需要下载,我们继续从上一个下载的块的那个节点继续请求。当最后把所有块都下载完后,对 UTXO 集进行重新索引。

TODO:并非无条件信任,我们应该在将每个块加入到区块链之前对它们进行验证。

TODO: 并非运行 UTXOSet.Reindex(), 而是应该使用 UTXOSet.Update(block),因为如果区块链很大,它将需要很多时间来对整个 UTXO 集重新索引。

处理 tx 消息是最困难的部分:

func handleTx(request []byte, bc *Blockchain) {

    …

    txData := payload.Transaction

    tx := DeserializeTransaction(txData)

    mempool[hex.EncodeToString(tx.ID)] = tx    if nodeAddress == knownNodes[0] {

        for _, node := range knownNodes {

            if node != nodeAddress && node != payload.AddFrom {

                sendInv(node, “tx”, [][]byte{tx.ID})

            }

        }

    } else {

        if len(mempool) >= 2 && len(miningAddress) > 0 {

        MineTransactions:

            var txs []*Transaction            for id := range mempool {

                tx := mempool[id]

                if bc.VerifyTransaction(&tx) {

                    txs = append(txs, &tx)

                }

            }

            if len(txs) == 0 {

                fmt.Println(“All transactions are invalid! Waiting for new ones…”)

                return

            }

            cbTx := NewCoinbaseTX(miningAddress, “”)

            txs = append(txs, cbTx)

            newBlock := bc.MineBlock(txs)

            UTXOSet := UTXOSet{bc}

            UTXOSet.Reindex()

            fmt.Println(“New block is mined!”)

            for _, tx := range txs {

                txID := hex.EncodeToString(tx.ID)

                delete(mempool, txID)

            }

            for _, node := range knownNodes {

                if node != nodeAddress {

                    sendInv(node, “block”, [][]byte{newBlock.Hash})

                }

            }

            if len(mempool) > 0 {

                goto MineTransactions            }

        }

    }}

首先要做的事情是将新交易放到内存池中(再次提醒,在将交易放到内存池之前,必要对其进行验证)。下个片段:

if nodeAddress == knownNodes[0] {

    for _, node := range knownNodes {

        if node != nodeAddress && node != payload.AddFrom {

            sendInv(node, “tx”, [][]byte{tx.ID})

        }

    }}


检查当前节点是否是中心节点。在我们的实现中,中心节点并不会挖矿。它只会将新的交易推送给网络中的其他节点。

下一个很大的代码片段是矿工节点“专属”。让我们对它进行一下分解:

if len(mempool) >= 2 && len(miningAddress) > 0 {

miningAddress 只会在矿工节点上设置。如果当前节点(矿工)的内存池中有两笔或更多的交易,开始挖矿:

for id := range mempool {

    tx := mempool[id]

    if bc.VerifyTransaction(&tx) {

        txs = append(txs, &tx)

    }}if len(txs) == 0 {

    fmt.Println(“All transactions are invalid! Waiting for new ones…”)

    return}

首先,内存池中所有交易都是通过验证的。无效的交易会被忽略,如果没有有效交易,则挖矿中断。

cbTx := NewCoinbaseTX(miningAddress, “”)txs = append(txs, cbTx)newBlock := bc.MineBlock(txs)UTXOSet := UTXOSet{bc}UTXOSet.Reindex()fmt.Println(“New block is mined!”)

验证后的交易被放到一个块里,同时还有附带奖励的 coinbase 交易。当块被挖出来以后,UTXO 集会被重新索引。

TODO: 提醒,应该使用 UTXOSet.Update 而不是 UTXOSet.Reindex.

for _, tx := range txs {

    txID := hex.EncodeToString(tx.ID)

    delete(mempool, txID)}for _, node := range knownNodes {

    if node != nodeAddress {

        sendInv(node, “block”, [][]byte{newBlock.Hash})

    }}if len(mempool) > 0 {

    goto MineTransactions}

当一笔交易被挖出来以后,就会被从内存池中移除。当前节点所连接到的所有其他节点,接收带有新块哈希的 inv 消息。在处理完消息后,它们可以对块进行请求。

【技术篇】区块链之网络架构详解(下)结果

让我们来回顾一下上面定义的场景。

首先,在第一个终端窗口中将 NODE_ID 设置为 3000(export NODE_ID=3000)。为了让你知道什么节点执行什么操作,我会使用像 NODE 3000 或 NODE 3001 进行标识。

NODE 3000

创建一个钱包和一个新的区块链:

$ blockchain_go createblockchain -address CENTREAL_NODE

(为了简洁起见,我会使用假地址。)

然后,会生成一个仅包含创世块的区块链。我们需要保存块,并在其他节点使用。创世块承担了一条链标识符的角色(在 Bitcoin Core 中,创世块是硬编码的)

$ cp blockchain_3000.db blockchain_genesis.db

NODE 3001

接下来,打开一个新的终端窗口,将 node ID 设置为 3001。这会作为一个钱包节点。通过 blockchain_go createwallet 生成一些地址,我们把这些地址叫做 WALLET_1, WALLET_2, WALLET_3.

NODE 3000

向钱包地址发送一些币:

$ blockchain_go send -from CENTREAL_NODE -to WALLET_1 -amount 10 -mine

$ blockchain_go send -from CENTREAL_NODE -to WALLET_2 -amount 10 -mine


-mine 标志指的是块会立刻被同一节点挖出来。我们必须要有这个标志,因为初始状态时,网络中没有矿工节点。

启动节点:

$ blockchain_go startnode

这个节点会持续运行,直到本文定义的场景结束。

NODE 3001

启动上面保存创世块节点的区块链:

$ cp blockchain_genesis.db blockchain_3001.db

运行节点:

$ blockchain_go startnode

它会从中心节点下载所有区块。为了检查一切正常,暂停节点运行并检查余额:

$ blockchain_go getbalance -address WALLET_1

Balance of ‘WALLET_1’: 10

$ blockchain_go getbalance -address WALLET_2

Balance of ‘WALLET_2’: 10

你还可以检查 CENTRAL_NODE 地址的余额,因为 node 3001 现在有它自己的区块链:

$ blockchain_go getbalance -address CENTRAL_NODE

Balance of ‘CENTRAL_NODE’: 10

NODE 3002

打开一个新的终端窗口,将它的 ID 设置为 3002,然后生成一个钱包。这会是一个矿工节点。初始化区块链:

$ cp blockchain_genesis.db blockchain_3002.db

启动节点:

$ blockchain_go startnode -miner MINER_WALLET

NODE 3001

发送一些币:

$ blockchain_go send -from WALLET_1 -to WALLET_3 -amount 1

$ blockchain_go send -from WALLET_2 -to WALLET_4 -amount 1

NODE 3002

迅速切换到矿工节点,你会看到挖出了一个新块!同时,检查中心节点的输出。

NODE 3001

切换到钱包节点并启动:

$ blockchain_go startnode

它会下载最近挖出来的块!

暂停节点并检查余额:

$ blockchain_go getbalance -address WALLET_1

Balance of ‘WALLET_1’: 9

$ blockchain_go getbalance -address WALLET_2

Balance of ‘WALLET_2’: 9

$ blockchain_go getbalance -address WALLET_3

Balance of ‘WALLET_3’: 1

$ blockchain_go getbalance -address WALLET_4

Balance of ‘WALLET_4’: 1

$ blockchain_go getbalance -address MINER_WALLET

Balance of ‘MINER_WALLET’: 10


就是这么多了!


文章原标题:[区块链研究实验室]区块链之网络架构详解(下)  原作者:链三丰

根据国家《关于防范代币发行融资风险的公告》,大家应警惕代币发行融资与交易的风险隐患。

本文来自LIANYI转载,不代表链一财经立场,转载请联系原作者。

发表评论

登录后才能评论

联系我们

微信:kkyves

邮件:kefu@lianyi.com

时间:7x24,节假日bu休息

QR code