国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

用 Go 構(gòu)建一個(gè)區(qū)塊鏈 -- Part 7: 網(wǎng)絡(luò)

lingdududu / 1597人閱讀

摘要:盡管我們不會(huì)實(shí)現(xiàn)一個(gè)真實(shí)的網(wǎng)絡(luò),但是我們會(huì)實(shí)現(xiàn)一個(gè)真是,也是比特幣最常見(jiàn)最重要的用戶(hù)場(chǎng)景。不過(guò),這并不是處于禮貌用于找到一個(gè)更長(zhǎng)的區(qū)塊鏈。意為給我看一下你有什么區(qū)塊在比特幣中,這會(huì)更加復(fù)雜。

翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHub 上的教程倉(cāng)庫(kù),進(jìn)入 src 目錄執(zhí)行 make 即可。


引言

到目前為止,我們所構(gòu)建的原型已經(jīng)具備了區(qū)塊鏈所有的關(guān)鍵特性:匿名,安全,隨機(jī)生成的地址;區(qū)塊鏈數(shù)據(jù)存儲(chǔ);工作量證明系統(tǒng);可靠地存儲(chǔ)交易。盡管這些特性都不可或缺,但是仍有不足。能夠使得這些特性真正發(fā)光發(fā)熱,使得加密貨幣成為可能的,是網(wǎng)絡(luò)(network)。如果實(shí)現(xiàn)的這樣一個(gè)區(qū)塊鏈僅僅運(yùn)行在單一節(jié)點(diǎn)上,有什么用呢?如果只有一個(gè)用戶(hù),那么這些基于密碼學(xué)的特性,又有什么用呢?正是由于網(wǎng)絡(luò),才使得整個(gè)機(jī)制能夠運(yùn)轉(zhuǎn)和發(fā)光發(fā)熱。

你可以將這些區(qū)塊鏈特性認(rèn)為是規(guī)則(rule),類(lèi)似于人類(lèi)在一起生活,繁衍生息建立的規(guī)則,一種社會(huì)安排。區(qū)塊鏈網(wǎng)絡(luò)就是一個(gè)程序社區(qū),里面的每個(gè)程序都遵循同樣的規(guī)則,正是由于遵循著同一個(gè)規(guī)則,才使得網(wǎng)絡(luò)能夠長(zhǎng)存。類(lèi)似的,當(dāng)人們都有著同樣的想法,就能夠?qū)⑷^攥在一起構(gòu)建一個(gè)更好的生活。如果有人遵循著不同的規(guī)則,那么他們就將生活在一個(gè)分裂的社區(qū)(州,公社,等等)中。同樣的,如果有區(qū)塊鏈節(jié)點(diǎn)遵循不同的規(guī)則,那么也會(huì)形成一個(gè)分裂的網(wǎng)絡(luò)。

重點(diǎn)在于:如果沒(méi)有網(wǎng)絡(luò),或者大部分節(jié)點(diǎn)都不遵守同樣的規(guī)則,那么規(guī)則就會(huì)形同虛設(shè),毫無(wú)用處!

聲明:不幸的是,我并沒(méi)有足夠的時(shí)間來(lái)實(shí)現(xiàn)一個(gè)真實(shí)的 P2P 網(wǎng)絡(luò)原型。本文我會(huì)展示一個(gè)最常見(jiàn)的場(chǎng)景,這個(gè)場(chǎng)景涉及不同類(lèi)型的節(jié)點(diǎn)。繼續(xù)改進(jìn)這個(gè)場(chǎng)景,將它實(shí)現(xiàn)為一個(gè) P2P 網(wǎng)絡(luò),對(duì)你來(lái)說(shuō)是一個(gè)很好的挑戰(zhàn)和實(shí)踐!除了本文的場(chǎng)景,我也無(wú)法保證在其他場(chǎng)景將會(huì)正常工作。抱歉!

本文的代碼實(shí)現(xiàn)變化很大,請(qǐng)點(diǎn)擊 這里 查看所有的代碼更改。

區(qū)塊鏈網(wǎng)絡(luò)

區(qū)塊鏈網(wǎng)絡(luò)是去中心化的,這意味著沒(méi)有服務(wù)器,客戶(hù)端也不需要依賴(lài)服務(wù)器來(lái)獲取或處理數(shù)據(jù)。在區(qū)塊鏈網(wǎng)絡(luò)中,有的是節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)是網(wǎng)絡(luò)的一個(gè)完全(full-fledged)成員。節(jié)點(diǎn)就是一切:它既是一個(gè)客戶(hù)端,也是一個(gè)服務(wù)器。這一點(diǎn)需要牢記于心,因?yàn)檫@與傳統(tǒng)的網(wǎng)頁(yè)應(yīng)用非常不同。

區(qū)塊鏈網(wǎng)絡(luò)是一個(gè) P2P(Peer-to-Peer,端到端)的網(wǎng)絡(luò),即節(jié)點(diǎn)直接連接到其他節(jié)點(diǎn)。它的拓?fù)涫潜馄降模驗(yàn)樵诠?jié)點(diǎn)的世界中沒(méi)有層級(jí)之分。下面是它的示意圖:

Business vector created by Dooder - Freepik.com

要實(shí)現(xiàn)這樣一個(gè)網(wǎng)絡(luò)節(jié)點(diǎn)更加困難,因?yàn)樗鼈儽仨殘?zhí)行很多操作。每個(gè)節(jié)點(diǎn)必須與很多其他節(jié)點(diǎn)進(jìn)行交互,它必須請(qǐng)求其他節(jié)點(diǎn)的狀態(tài),與自己的狀態(tài)進(jìn)行比較,當(dāng)狀態(tài)過(guò)時(shí)時(shí)進(jìn)行更新。

節(jié)點(diǎn)角色

盡管節(jié)點(diǎn)具有完備成熟的屬性,但是它們也可以在網(wǎng)絡(luò)中扮演不同角色。比如:

礦工
這樣的節(jié)點(diǎn)運(yùn)行于強(qiáng)大或?qū)S玫挠布ū热?ASIC)之上,它們唯一的目標(biāo)是,盡可能快地挖出新塊。礦工是區(qū)塊鏈中唯一可能會(huì)用到工作量證明的角色,因?yàn)橥诘V實(shí)際上意味著解決 PoW 難題。在權(quán)益證明 PoS 的區(qū)塊鏈中,沒(méi)有挖礦。

全節(jié)點(diǎn)
這些節(jié)點(diǎn)驗(yàn)證礦工挖出來(lái)的塊的有效性,并對(duì)交易進(jìn)行確認(rèn)。為此,他們必須擁有區(qū)塊鏈的完整拷貝。同時(shí),全節(jié)點(diǎn)執(zhí)行路由操作,幫助其他節(jié)點(diǎn)發(fā)現(xiàn)彼此。對(duì)于網(wǎng)絡(luò)來(lái)說(shuō),非常重要的一段就是要有足夠多的全節(jié)點(diǎn)。因?yàn)檎沁@些節(jié)點(diǎn)執(zhí)行了決策功能:他們決定了一個(gè)塊或一筆交易的有效性。

SPV
SPV 表示 Simplified Payment Verification,簡(jiǎn)單支付驗(yàn)證。這些節(jié)點(diǎn)并不存儲(chǔ)整個(gè)區(qū)塊鏈副本,但是仍然能夠?qū)灰走M(jìn)行驗(yàn)證(不過(guò)不是驗(yàn)證全部交易,而是一個(gè)交易子集,比如,發(fā)送到某個(gè)指定地址的交易)。一個(gè) SPV 節(jié)點(diǎn)依賴(lài)一個(gè)全節(jié)點(diǎn)來(lái)獲取數(shù)據(jù),可能有多個(gè) SPV 節(jié)點(diǎn)連接到一個(gè)全節(jié)點(diǎn)。SPV 使得錢(qián)包應(yīng)用成為可能:一個(gè)人不需要下載整個(gè)區(qū)塊鏈,但是仍能夠驗(yàn)證他的交易。

網(wǎng)絡(luò)簡(jiǎn)化

為了在目前的區(qū)塊鏈原型中實(shí)現(xiàn)網(wǎng)絡(luò),我們不得不簡(jiǎn)化一些事情。因?yàn)槲覀儧](méi)有那么多的計(jì)算機(jī)來(lái)模擬一個(gè)多節(jié)點(diǎn)的網(wǎng)絡(luò)。當(dāng)然,我們可以使用虛擬機(jī)或是 Docker 來(lái)解決這個(gè)問(wèn)題,但是這會(huì)使一切都變得更復(fù)雜:你將不得不先解決可能出現(xiàn)的虛擬機(jī)或 Docker 問(wèn)題,而我的目標(biāo)是將全部精力都放在區(qū)塊鏈實(shí)現(xiàn)上。所以,我們想要在一臺(tái)機(jī)器上運(yùn)行多個(gè)區(qū)塊鏈節(jié)點(diǎn),同時(shí)希望它們有不同的地址。為了實(shí)現(xiàn)這一點(diǎn),我們將使用端口號(hào)作為節(jié)點(diǎn)標(biāo)識(shí)符,而不是使用 IP 地址,比如將會(huì)有這樣地址的節(jié)點(diǎn):127.0.0.1:3000127.0.0.1:3001127.0.0.1:3002 等等。我們叫它端口節(jié)點(diǎn)(port node) ID,并使用環(huán)境變量 NODE_ID 對(duì)它們進(jìn)行設(shè)置。故而,你可以打開(kāi)多個(gè)終端窗口,設(shè)置不同的 NODE_ID 運(yùn)行不同的節(jié)點(diǎn)。

這個(gè)方法也需要有不同的區(qū)塊鏈和錢(qián)包文件。它們現(xiàn)在必須依賴(lài)于節(jié)點(diǎn) ID 進(jìn)行命名,比如 blockchain_3000.db, blockchain_30001.db and wallet_3000.db, wallet_30001.db 等等。

實(shí)現(xiàn)

所以,當(dāng)你下載 Bitcoin Core 并首次運(yùn)行時(shí),到底發(fā)生了什么呢?它必須連接到某個(gè)節(jié)點(diǎn)下載最新?tīng)顟B(tài)的區(qū)塊鏈。考慮到你的電腦并沒(méi)有意識(shí)到所有或是部分的比特幣節(jié)點(diǎn),那么連接到的“某個(gè)節(jié)點(diǎn)”到底是什么?

在 Bitcoin Core 中硬編碼一個(gè)地址,已經(jīng)被證實(shí)是一個(gè)錯(cuò)誤:因?yàn)楣?jié)點(diǎn)可能會(huì)被攻擊或關(guān)機(jī),這會(huì)導(dǎo)致新的節(jié)點(diǎn)無(wú)法加入到網(wǎng)絡(luò)中。在 Bitcoin Core 中,硬編碼了 DNS seeds。雖然這些并不是節(jié)點(diǎn),但是 DNS 服務(wù)器知道一些節(jié)點(diǎn)的地址。當(dāng)你啟動(dòng)一個(gè)全新的 Bitcoin Core 時(shí),它會(huì)連接到一個(gè)種子節(jié)點(diǎn),獲取全節(jié)點(diǎn)列表,隨后從這些節(jié)點(diǎn)中下載區(qū)塊鏈。

不過(guò)在我們目前的實(shí)現(xiàn)中,無(wú)法做到完全的去中心化,因?yàn)闀?huì)出現(xiàn)中心化的特點(diǎn)。我們會(huì)有三個(gè)節(jié)點(diǎn):

一個(gè)中心節(jié)點(diǎn)。所有其他節(jié)點(diǎn)都會(huì)連接到這個(gè)節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)會(huì)在其他節(jié)點(diǎn)之間發(fā)送數(shù)據(jù)。

一個(gè)礦工節(jié)點(diǎn)。這個(gè)節(jié)點(diǎn)會(huì)在內(nèi)存池中存儲(chǔ)新的交易,當(dāng)有足夠的交易時(shí),它就會(huì)打包挖出一個(gè)新塊。

一個(gè)錢(qián)包節(jié)點(diǎn)。這個(gè)節(jié)點(diǎn)會(huì)被用作在錢(qián)包之間發(fā)送幣。但是與 SPV 節(jié)點(diǎn)不同,它存儲(chǔ)了區(qū)塊鏈的一個(gè)完整副本。

場(chǎng)景

本文的目標(biāo)是實(shí)現(xiàn)如下場(chǎng)景:

中心節(jié)點(diǎn)創(chuàng)建一個(gè)區(qū)塊鏈。

一個(gè)其他(錢(qián)包)節(jié)點(diǎn)連接到中心節(jié)點(diǎn)并下載區(qū)塊鏈。

另一個(gè)(礦工)節(jié)點(diǎn)連接到中心節(jié)點(diǎn)并下載區(qū)塊鏈。

錢(qián)包節(jié)點(diǎn)創(chuàng)建一筆交易。

礦工節(jié)點(diǎn)接收交易,并將交易保存到內(nèi)存池中。

當(dāng)內(nèi)存池中有足夠的交易時(shí),礦工開(kāi)始挖一個(gè)新塊。

當(dāng)挖出一個(gè)新塊后,將其發(fā)送到中心節(jié)點(diǎn)。

錢(qián)包節(jié)點(diǎn)與中心節(jié)點(diǎn)進(jìn)行同步。

錢(qián)包節(jié)點(diǎn)的用戶(hù)檢查他們的支付是否成功。

這就是比特幣中的一般流程。盡管我們不會(huì)實(shí)現(xiàn)一個(gè)真實(shí)的 P2P 網(wǎng)絡(luò),但是我們會(huì)實(shí)現(xiàn)一個(gè)真是,也是比特幣最常見(jiàn)最重要的用戶(hù)場(chǎng)景。

版本

節(jié)點(diǎn)通過(guò)消息(message)進(jìn)行交流。當(dāng)一個(gè)新的節(jié)點(diǎn)開(kāi)始運(yùn)行時(shí),它會(huì)從一個(gè) DNS 種子獲取幾個(gè)節(jié)點(diǎn),給它們發(fā)送 version 消息,在我們的實(shí)現(xiàn)看起來(lái)就像是這樣:

type version struct {
    Version    int
    BestHeight int
    AddrFrom   string
}

由于我們僅有一個(gè)區(qū)塊鏈版本,所以 Version 字段實(shí)際并不會(huì)存儲(chǔ)什么重要信息。BestHeight 存儲(chǔ)區(qū)塊鏈中節(jié)點(diǎn)的高度。AddFrom 存儲(chǔ)發(fā)送者的地址。

接收到 version 消息的節(jié)點(diǎn)應(yīng)該做什么呢?它會(huì)響應(yīng)自己的 version 消息。這是一種握手?:如果沒(méi)有事先互相問(wèn)候,就不可能有其他交流。不過(guò),這并不是處于禮貌:version 用于找到一個(gè)更長(zhǎng)的區(qū)塊鏈。當(dāng)一個(gè)節(jié)點(diǎn)接收到 version 消息,它會(huì)檢查本節(jié)點(diǎn)的區(qū)塊鏈?zhǔn)欠癖?BestHeight 的值更大。如果不是,節(jié)點(diǎn)就會(huì)請(qǐng)求并下載缺失的塊。

為了接收消息,我們需要一個(gè)服務(wù)器:

var nodeAddress string
var knownNodes = []string{"localhost:3000"}

func StartServer(nodeID, minerAddress string) {
    nodeAddress = fmt.Sprintf("localhost:%s", nodeID)
    miningAddress = minerAddress
    ln, err := net.Listen(protocol, nodeAddress)
    defer ln.Close()

    bc := NewBlockchain(nodeID)

    if nodeAddress != knownNodes[0] {
        sendVersion(knownNodes[0], bc)
    }

    for {
        conn, err := ln.Accept()
        go handleConnection(conn, bc)
    }
}

首先,我們對(duì)中心節(jié)點(diǎn)的地址進(jìn)行硬編碼:因?yàn)槊總€(gè)節(jié)點(diǎn)必須知道從何處開(kāi)始初始化。minerAddress 參數(shù)指定了接收挖礦獎(jiǎng)勵(lì)的地址。代碼片段:

if nodeAddress != knownNodes[0] {
    sendVersion(knownNodes[0], bc)
}

這意味著如果當(dāng)前節(jié)點(diǎn)不是中心節(jié)點(diǎn),它必須向中心節(jié)點(diǎn)發(fā)送 version 消息來(lái)查詢(xún)是否自己的區(qū)塊鏈已過(guò)時(shí)。

func sendVersion(addr string, bc *Blockchain) {
    bestHeight := bc.GetBestHeight()
    payload := gobEncode(version{nodeVersion, bestHeight, nodeAddress})

    request := append(commandToBytes("version"), payload...)

    sendData(addr, request)
}

我們的消息,在底層就是字節(jié)序列。前 12 個(gè)字節(jié)指定了命令名(比如這里的 version),后面的字節(jié)會(huì)包含 gob 編碼的消息結(jié)構(gòu),commandToBytes 看起來(lái)是這樣:

func commandToBytes(command string) []byte {
    var bytes [commandLength]byte

    for i, c := range command {
        bytes[i] = byte(c)
    }

    return bytes[:]
}

它創(chuàng)建一個(gè) 12 字節(jié)的緩沖區(qū),并用命令名進(jìn)行填充,將剩下的字節(jié)置為空。下面一個(gè)相反的函數(shù):

func bytesToCommand(bytes []byte) string {
    var command []byte

    for _, b := range bytes {
        if b != 0x0 {
            command = append(command, b)
        }
    }

    return fmt.Sprintf("%s", command)
}

當(dāng)一個(gè)節(jié)點(diǎn)接收到一個(gè)命令,它會(huì)運(yùn)行 bytesToCommand 來(lái)提取命令名,并選擇正確的處理器處理命令主體:

func handleConnection(conn net.Conn, bc *Blockchain) {
    request, err := ioutil.ReadAll(conn)
    command := bytesToCommand(request[:commandLength])
    fmt.Printf("Received %s command
", command)

    switch command {
    ...
    case "version":
        handleVersion(request, bc)
    default:
        fmt.Println("Unknown command!")
    }

    conn.Close()
}

下面是 version 命令處理器:

func handleVersion(request []byte, bc *Blockchain) {
    var buff bytes.Buffer
    var payload verzion

    buff.Write(request[commandLength:])
    dec := gob.NewDecoder(&buff)
    err := dec.Decode(&payload)

    myBestHeight := bc.GetBestHeight()
    foreignerBestHeight := payload.BestHeight

    if myBestHeight < foreignerBestHeight {
        sendGetBlocks(payload.AddrFrom)
    } else if myBestHeight > foreignerBestHeight {
        sendVersion(payload.AddrFrom, bc)
    }

    if !nodeIsKnown(payload.AddrFrom) {
        knownNodes = append(knownNodes, payload.AddrFrom)
    }
}

首先,我們需要對(duì)請(qǐng)求進(jìn)行解碼,提取有效信息。所有的處理器在這部分都類(lèi)似,所以我們會(huì)下面的代碼片段中略去這部分。

然后節(jié)點(diǎn)將從消息中提取的 BestHeight 與自身進(jìn)行比較。如果自身節(jié)點(diǎn)的區(qū)塊鏈更長(zhǎng),它會(huì)回復(fù) version 消息;否則,它會(huì)發(fā)送 getblocks 消息。

getblocks
type getblocks struct {
    AddrFrom string
}

getblocks 意為 “給我看一下你有什么區(qū)塊”(在比特幣中,這會(huì)更加復(fù)雜)。注意,它并沒(méi)有說(shuō)“把你全部的區(qū)塊給我”,而是請(qǐng)求了一個(gè)塊哈希的列表。這是為了減輕網(wǎng)絡(luò)負(fù)載,因?yàn)閰^(qū)塊可以從不同的節(jié)點(diǎn)下載,并且我們不想從一個(gè)單一節(jié)點(diǎn)下載數(shù)十 GB 的數(shù)據(jù)。

處理命令十分簡(jiǎn)單:

func handleGetBlocks(request []byte, bc *Blockchain) {
    ...
    blocks := bc.GetBlockHashes()
    sendInv(payload.AddrFrom, "block", blocks)
}

在我們簡(jiǎn)化版的實(shí)現(xiàn)中,它會(huì)返回 所有塊哈希

inv
type inv struct {
    AddrFrom string
    Type     string
    Items    [][]byte
}

比特幣使用 inv 來(lái)向其他節(jié)點(diǎn)展示當(dāng)前節(jié)點(diǎn)有什么塊和交易。再次提醒,它沒(méi)有包含完整的區(qū)塊鏈和交易,僅僅是哈希而已。Type 字段表明了這是塊還是交易。

處理 inv 稍顯復(fù)雜:

func handleInv(request []byte, bc *Blockchain) {
    ...
    fmt.Printf("Recevied inventory with %d %s
", 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 變量來(lái)跟蹤已下載的塊。這能夠讓我們從不同的節(jié)點(diǎn)下載塊。在將塊置于傳送狀態(tài)時(shí),我們給 inv 消息的發(fā)送者發(fā)送 getdata 命令并更新 blocksInTransit。在一個(gè)真實(shí)的 P2P 網(wǎng)絡(luò)中,我們會(huì)想要從不同節(jié)點(diǎn)來(lái)傳送塊。

在我們的實(shí)現(xiàn)中,我們永遠(yuǎn)也不會(huì)發(fā)送有多重哈希的 inv。這就是為什么當(dāng) payload.Type == "tx" 時(shí),只會(huì)拿到第一個(gè)哈希。然后我們檢查是否在內(nèi)存池中已經(jīng)有了這個(gè)哈希,如果沒(méi)有,發(fā)送 getdata 消息。

getdata
type getdata struct {
    AddrFrom string
    Type     string
    ID       []byte
}

getdata 用于某個(gè)塊或交易的請(qǐng)求,它可以?xún)H包含一個(gè)塊或交易的 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)
    }
}

這個(gè)處理器比較地直觀(guān):如果它們請(qǐng)求一個(gè)塊,則返回塊;如果它們請(qǐng)求一筆交易,則返回交易。注意,我們并不檢查實(shí)際上是否已經(jīng)有了這個(gè)塊或交易。這是一個(gè)缺陷 :)

block 和 tx
type block struct {
    AddrFrom string
    Block    []byte
}

type tx struct {
    AddFrom     string
    Transaction []byte
}

實(shí)際完成數(shù)據(jù)轉(zhuǎn)移的正是這些消息。

處理 block 消息十分簡(jiǎn)單:

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

    blockData := payload.Block
    block := DeserializeBlock(blockData)

    fmt.Println("Recevied a new block!")
    bc.AddBlock(block)

    fmt.Printf("Added block %x
", block.Hash)

    if len(blocksInTransit) > 0 {
        blockHash := blocksInTransit[0]
        sendGetData(payload.AddrFrom, "block", blockHash)

        blocksInTransit = blocksInTransit[1:]
    } else {
        UTXOSet := UTXOSet{bc}
        UTXOSet.Reindex()
    }
}

當(dāng)接收到一個(gè)新塊時(shí),我們把它放到區(qū)塊鏈里面。如果還有更多的區(qū)塊需要下載,我們繼續(xù)從上一個(gè)下載的塊的那個(gè)節(jié)點(diǎn)繼續(xù)請(qǐng)求。當(dāng)最后把所有塊都下載完后,對(duì) UTXO 集進(jìn)行重新索引。

TODO:并非無(wú)條件信任,我們應(yīng)該在將每個(gè)塊加入到區(qū)塊鏈之前對(duì)它們進(jìn)行驗(yàn)證。

TODO: 并非運(yùn)行 UTXOSet.Reindex(), 而是應(yīng)該使用 UTXOSet.Update(block),因?yàn)槿绻麉^(qū)塊鏈很大,它將需要很多時(shí)間來(lái)對(duì)整個(gè) 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
            }
        }
    }
}

首先要做的事情是將新交易放到內(nèi)存池中(再次提醒,在將交易放到內(nèi)存池之前,必要對(duì)其進(jìn)行驗(yàn)證)。下個(gè)片段:

if nodeAddress == knownNodes[0] {
    for _, node := range knownNodes {
        if node != nodeAddress && node != payload.AddFrom {
            sendInv(node, "tx", [][]byte{tx.ID})
        }
    }
}

檢查當(dāng)前節(jié)點(diǎn)是否是中心節(jié)點(diǎn)。在我們的實(shí)現(xiàn)中,中心節(jié)點(diǎn)并不會(huì)挖礦。它只會(huì)將新的交易推送給網(wǎng)絡(luò)中的其他節(jié)點(diǎn)。

下一個(gè)很大的代碼片段是礦工節(jié)點(diǎn)“專(zhuān)屬”。讓我們對(duì)它進(jìn)行一下分解:

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

miningAddress 只會(huì)在礦工節(jié)點(diǎn)上設(shè)置。如果當(dāng)前節(jié)點(diǎn)(礦工)的內(nèi)存池中有兩筆或更多的交易,開(kāi)始挖礦:

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
}

首先,內(nèi)存池中所有交易都是通過(guò)驗(yàn)證的。無(wú)效的交易會(huì)被忽略,如果沒(méi)有有效交易,則挖礦中斷。

cbTx := NewCoinbaseTX(miningAddress, "")
txs = append(txs, cbTx)

newBlock := bc.MineBlock(txs)
UTXOSet := UTXOSet{bc}
UTXOSet.Reindex()

fmt.Println("New block is mined!")

驗(yàn)證后的交易被放到一個(gè)塊里,同時(shí)還有附帶獎(jiǎng)勵(lì)的 coinbase 交易。當(dāng)塊被挖出來(lái)以后,UTXO 集會(huì)被重新索引。

TODO: 提醒,應(yīng)該使用 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
}

當(dāng)一筆交易被挖出來(lái)以后,就會(huì)被從內(nèi)存池中移除。當(dāng)前節(jié)點(diǎn)所連接到的所有其他節(jié)點(diǎn),接收帶有新塊哈希的 inv 消息。在處理完消息后,它們可以對(duì)塊進(jìn)行請(qǐng)求。

結(jié)果

讓我們來(lái)回顧一下上面定義的場(chǎng)景。

首先,在第一個(gè)終端窗口中將 NODE_ID 設(shè)置為 3000(export NODE_ID=3000)。為了讓你知道什么節(jié)點(diǎn)執(zhí)行什么操作,我會(huì)使用像 NODE 3000NODE 3001 進(jìn)行標(biāo)識(shí)。

NODE 3000

創(chuàng)建一個(gè)錢(qián)包和一個(gè)新的區(qū)塊鏈:

$ blockchain_go createblockchain -address CENTREAL_NODE

(為了簡(jiǎn)潔起見(jiàn),我會(huì)使用假地址。)

然后,會(huì)生成一個(gè)僅包含創(chuàng)世塊的區(qū)塊鏈。我們需要保存塊,并在其他節(jié)點(diǎn)使用。創(chuàng)世塊承擔(dān)了一條鏈標(biāo)識(shí)符的角色(在 Bitcoin Core 中,創(chuàng)世塊是硬編碼的)

$ cp blockchain_3000.db blockchain_genesis.db 
NODE 3001

接下來(lái),打開(kāi)一個(gè)新的終端窗口,將 node ID 設(shè)置為 3001。這會(huì)作為一個(gè)錢(qián)包節(jié)點(diǎn)。通過(guò) blockchain_go createwallet 生成一些地址,我們把這些地址叫做 WALLET_1, WALLET_2, WALLET_3.

NODE 3000

向錢(qián)包地址發(fā)送一些幣:

$ 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 標(biāo)志指的是塊會(huì)立刻被同一節(jié)點(diǎn)挖出來(lái)。我們必須要有這個(gè)標(biāo)志,因?yàn)槌跏紶顟B(tài)時(shí),網(wǎng)絡(luò)中沒(méi)有礦工節(jié)點(diǎn)。

啟動(dòng)節(jié)點(diǎn):

$ blockchain_go startnode

這個(gè)節(jié)點(diǎn)會(huì)持續(xù)運(yùn)行,直到本文定義的場(chǎng)景結(jié)束。

NODE 3001

啟動(dòng)上面保存創(chuàng)世塊節(jié)點(diǎn)的區(qū)塊鏈:

$ cp blockchain_genesis.db blockchain_3001.db

運(yùn)行節(jié)點(diǎn):

$ blockchain_go startnode

它會(huì)從中心節(jié)點(diǎn)下載所有區(qū)塊。為了檢查一切正常,暫停節(jié)點(diǎn)運(yùn)行并檢查余額:

$ blockchain_go getbalance -address WALLET_1
Balance of "WALLET_1": 10

$ blockchain_go getbalance -address WALLET_2
Balance of "WALLET_2": 10

你還可以檢查 CENTRAL_NODE 地址的余額,因?yàn)?node 3001 現(xiàn)在有它自己的區(qū)塊鏈:

$ blockchain_go getbalance -address CENTRAL_NODE
Balance of "CENTRAL_NODE": 10
NODE 3002

打開(kāi)一個(gè)新的終端窗口,將它的 ID 設(shè)置為 3002,然后生成一個(gè)錢(qián)包。這會(huì)是一個(gè)礦工節(jié)點(diǎn)。初始化區(qū)塊鏈:

$ cp blockchain_genesis.db blockchain_3002.db

啟動(dòng)節(jié)點(diǎn):

$ blockchain_go startnode -miner MINER_WALLET
NODE 3001

發(fā)送一些幣:

$ blockchain_go send -from WALLET_1 -to WALLET_3 -amount 1
$ blockchain_go send -from WALLET_2 -to WALLET_4 -amount 1
NODE 3002

迅速切換到礦工節(jié)點(diǎn),你會(huì)看到挖出了一個(gè)新塊!同時(shí),檢查中心節(jié)點(diǎn)的輸出。

NODE 3001

切換到錢(qián)包節(jié)點(diǎn)并啟動(dòng):

$ blockchain_go startnode

它會(huì)下載最近挖出來(lái)的塊!

暫停節(jié)點(diǎn)并檢查余額:

$ 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

就是這么多了!

總結(jié)

這是本系列的最后一篇文章了。我本可以就實(shí)現(xiàn)一個(gè)真實(shí)的 P2P 網(wǎng)絡(luò)原型繼續(xù)展開(kāi),但是我真的沒(méi)有這么多時(shí)間。我希望本文已經(jīng)回答了關(guān)于比特幣技術(shù)的一些問(wèn)題,也給讀者提出了一些問(wèn)題,這些問(wèn)題你可以自行尋找答案。在比特幣技術(shù)中還有隱藏著很多有趣的事情!好運(yùn)!

后記:你可以從實(shí)現(xiàn) addr 消息來(lái)開(kāi)始改進(jìn)網(wǎng)絡(luò),正如比特幣網(wǎng)絡(luò)協(xié)議中所描述的(鏈接可以下方找到)那樣。這是一個(gè)非常重要的消息,因?yàn)樗试S節(jié)點(diǎn)來(lái)互相發(fā)現(xiàn)彼此。我已經(jīng)開(kāi)始實(shí)現(xiàn)了,不過(guò)還沒(méi)有完成!

鏈接:

Source codes

Bitcoin protocol documentation

Bitcoin network

原文:Building Blockchain in Go. Part 7: Network

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/23934.html

相關(guān)文章

  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 6: 交易(2)

    摘要:到目前為止,我們幾乎已經(jīng)實(shí)現(xiàn)了一個(gè)區(qū)塊鏈數(shù)據(jù)庫(kù)的所有元素。使用根據(jù)在區(qū)塊鏈中找到一筆交易。是一個(gè)比特幣輕節(jié)點(diǎn),它不需要下載整個(gè)區(qū)塊鏈,也不需要驗(yàn)證區(qū)塊和交易。到目前為止,我們只是將一個(gè)塊里面的每筆交易哈希連接了起來(lái),將在上面應(yīng)用了算法。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果...

    spacewander 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 ---- Part 1: 基本原型

    摘要:在區(qū)塊鏈中,存儲(chǔ)有效信息的是區(qū)塊。存儲(chǔ)的是前一個(gè)塊的哈希。正是由于這個(gè)特性,才使得區(qū)塊鏈?zhǔn)前踩摹_@樣的結(jié)構(gòu),能夠讓我們快速地獲取鏈上的最新塊,并且高效地通過(guò)哈希來(lái)檢索一個(gè)塊。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHub 上的教程倉(cāng)...

    ZoomQuiet 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 2: 工作量證明

    摘要:哈希函數(shù)被廣泛用于檢測(cè)數(shù)據(jù)的一致性。在區(qū)塊鏈中,哈希被用于保證一個(gè)塊的一致性。比特幣使用,一個(gè)最初用來(lái)防止垃圾郵件的工作量證明算法。下面是與前面例子哈希的形式化比較第一個(gè)哈希基于計(jì)算比目標(biāo)要大,因此它并不是一個(gè)有效的工作量證明。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)...

    suxier 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 3: 持久化和命令行接口

    摘要:引言到目前為止,我們已經(jīng)構(gòu)建了一個(gè)有工作量證明機(jī)制的區(qū)塊鏈。在今天的內(nèi)容中,我們會(huì)將區(qū)塊鏈持久化到一個(gè)數(shù)據(jù)庫(kù)中,然后會(huì)提供一個(gè)簡(jiǎn)單的命令行接口,用來(lái)完成一些與區(qū)塊鏈的交互操作。這同樣也意味著,一個(gè)也就是區(qū)塊鏈的一種標(biāo)識(shí)符。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼...

    felix0913 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 4: 交易(1)

    摘要:引言交易是比特幣的核心所在,而區(qū)塊鏈的唯一目的,也正是為了能夠安全可靠地存儲(chǔ)交易。比特幣使用了一個(gè)更加復(fù)雜的技術(shù)它將一個(gè)塊里面包含的所有交易表示為一個(gè),然后在工作量證明系統(tǒng)中使用樹(shù)的根哈希。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHu...

    graf 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<