<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 前言 本版本區塊鏈不會實現一個P2P網絡,但會實現基于網絡的、bitcoin的一個最常見的場景: 1. 中心節點創建一個區塊鏈。 2. 一個其他(錢包)節點連接到中心節點并下載區塊鏈。 3. 另一個(礦工)節點連接到中心節點并下載區塊鏈。 4. 錢包節點創建一筆交易。 5. 礦工節點接收交易,并將交易保存到內存池中。 6. 當內存池中有足夠的交易時,礦工開始挖一個新塊。 7. 當挖出一個新塊后,將其發送到中心節點。 8. 錢包節點與中心節點進行同步。 9. 錢包節點的用戶檢查他們的支付是否成功。 這就是比特幣中的一般流程,也是比特幣最常見最重要的用戶場景。 # 節點角色 盡管節點具有完備成熟的屬性,但是它們也可以在網絡中扮演不同角色。比如: 1. 礦工 這樣的節點運行于強大或專用的硬件(比如 ASIC)之上,它們唯一的目標是,盡可能快地挖出新塊。礦工是區塊鏈中唯一可能會用到工作量證明的角色,因為挖礦實際上意味著解決 PoW 難題。在權益證明 PoS 的區塊鏈中,沒有挖礦。 2. 全節點 這些節點驗證礦工挖出來的塊的有效性,并對交易進行確認。為此,他們必須擁有區塊鏈的完整拷貝。同時,全節點執行路由操作,幫助其他節點發現彼此。對于網絡來說,非常重要的一段就是要有足夠多的全節點。因為正是這些節點執行了決策功能:他們決定了一個塊或一筆交易的有效性。 3. SPV SPV 表示 Simplified Payment Verification,簡單支付驗證。這些節點并不存儲整個區塊鏈副本,但是仍然能夠對交易進行驗證(不過不是驗證全部交易,而是一個交易子集,比如,發送到某個指定地址的交易)。一個 SPV 節點依賴一個全節點來獲取數據,可能有多個 SPV 節點連接到一個全節點。SPV 使得錢包應用成為可能:一個人不需要下載整個區塊鏈,但是仍能夠驗證他的交易。 # 網絡簡化 我們將使用**端口號作為節點標識符**,而不是使用 IP 地址,比如將會有這樣地址的節點:**127.0.0.1:3000**,**127.0.0.1:3001**,**127.0.0.1:3002**等等。我們叫它端口節點(port node) ID,并使用環境變量`NODE_ID`對它們進行設置。故而,你可以打開多個終端窗口,設置不同的`NODE_ID`運行不同的節點。 這個方法也需要有不同的區塊鏈和錢包文件。它們現在必須依賴于節點 ID 進行命名,比如 blockchain_3000.db, blockchain_30001.db和wallet_3000.db, wallet_30001.db 等等。 # 實現 在 Bitcoin Core 中,硬編碼了[DNS seeds](https://link.jianshu.com/?t=https://bitcoin.org/en/glossary/dns-seed)。雖然這些并不是節點,但是 DNS 服務器知道一些節點的地址。當你啟動一個全新的 Bitcoin Core 時,它會連接到一個種子節點,獲取全節點列表,隨后從這些節點中下載區塊鏈。 不過在我們目前的實現中,無法做到完全的去中心化,因為會出現中心化的特點。我們會有三個節點: 1. 一個中心節點。所有其他節點都會連接到這個節點,這個節點會在其他節點之間發送數據。 2. 一個礦工節點。這個節點會在內存池中存儲新的交易,當有足夠的交易時,它就會打包挖出一個新塊。 3. 一個錢包節點。這個節點會被用作在錢包之間發送幣。但是與 SPV 節點不同,它存儲了區塊鏈的一個完整副本。 節點通過消息(message)進行交流:整個通訊就是:請求--回復--請求--回復...這么一個模式進行,第一個請求可能需要經過請求節點與被請求節點之間多次往返消息(一般為多個成對消息)才能完成。 當一個新的節點開始運行時,它會從一個 DNS 種子獲取幾個節點,給它們發送`version`消息,在我們的實現看起來就像是這樣: ~~~ type version struct { Version int//區塊鏈版本號 BestHeight int//存儲區塊鏈中節點的高度。 AddrFrom string//存儲發送消息者的地址 } ~~~ 接收到`version`消息的節點應該做什么呢?它會響應自己的`version`消息。這是一種握手??:如果沒有事先互相問候,就不可能有其他交流。`version`用于找到一個更長的區塊鏈。當一個節點接收到`version`消息,它會檢查本節點的區塊鏈是否比`BestHeight`的值更大。如果不是,節點就會請求并下載缺失的塊。 為了接收消息,我們需要一個服務器(每個節點都是服務器): ~~~ var nodeAddress string var knownNodes = []string{"localhost:3000"}//中心節點的地址數組 func StartServer(nodeID, minerAddress string) {//nodeID為當前節點 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)//非中心節點必須向中心節點發送`version`消息來查詢是否自己的區塊鏈已過時 } //優雅的Go語言 for {//開啟無限循環 conn, err := ln.Accept()//等待連接 go handleConnection(conn, bc)//異步,處理連接 } } ~~~ 非中心節點向中心節點發送消息,查詢自己的區塊鏈是否已經過時: ~~~ func sendVersion(addr string, bc *Blockchain) { bestHeight := bc.GetBestHeight() payload := gobEncode(version{nodeVersion, bestHeight, nodeAddress}) request := append(commandToBytes("version"), payload...)//“version”是命令,payload是gob編碼的消息結構 sendData(addr, request) } ~~~ `commandToBytes`看起來是這樣: ~~~ //創建一個 12 字節的緩沖區,并用命令名進行填充,將剩下的字節置為空 func commandToBytes(command string) []byte {//命令字符串轉為數組切片 var bytes [commandLength]byte//commandLength為12個字節 for i, c := range command { bytes[i] = byte(c) } return bytes[:]//返回切片 } ~~~ 解析命令的函數如下: ~~~ func bytesToCommand(bytes []byte) string { var command []byte for _, b := range bytes { if b != 0x0 { command = append(command, b) } } return fmt.Sprintf("%s", command) } ~~~ 當一個節點接收到一個命令,它會運行`bytesToCommand`來提取命令名,并選擇正確的處理器處理命令主體: ~~~ func handleConnection(conn net.Conn, bc *Blockchain) { request, err := ioutil.ReadAll(conn) command := bytesToCommand(request[:commandLength]) fmt.Printf("接收到 %s 命令\n", 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)//發送getblocks消息 } else if myBestHeight > foreignerBestHeight { sendVersion(payload.AddrFrom, bc)//回復version消息 } if !nodeIsKnown(payload.AddrFrom) {//如果中心節點不包含本地地址 knownNodes = append(knownNodes, payload.AddrFrom)//將本地地址加入到中心節點地址數組中 } } ~~~ 首先,我們需要對請求進行解碼,提取有效信息。所有的處理器在這部分都類似。 然后節點將從消息中提取的`BestHeight`與自身進行比較。如果自身節點的區塊鏈更長,它會回復`version`消息;否則,它會發送`getblocks`消息。 ## getblocks ~~~ type getblocks struct { AddrFrom string } ~~~ `getblocks`意為 “給我看一下你有什么區塊”(在比特幣中,這會更加復雜)。注意,它并沒有說“把你全部的區塊給我”,而是請求了一個塊哈希的列表。這是為了減輕網絡負載,因為區塊可以從不同的節點下載,并且我們不想從一個單一節點下載數十 GB 的數據。 處理命令十分簡單: ~~~ func handleGetBlocks(request []byte, bc *Blockchain) { ... blocks := bc.GetBlockHashes()//接收到getblocks命令的節點獲得本地的區塊哈希列表 sendInv(payload.AddrFrom, "block", blocks)//然后將本地的區塊哈希列表回復給請求者 } ~~~ 在我們簡化版的實現中,它會返回**所有塊哈希**。 ## inv ~~~ type inv struct { AddrFrom string Type string//payload的類型:block或tx Items [][]byte//當前節點的所有塊的哈希或交易的哈希 } ~~~ 比特幣使用`inv`來向其他節點展示當前節點有什么塊和交易。再次提醒,它沒有包含完整的區塊鏈和交易,僅僅是哈希而已。`Type`字段表明了這是塊還是交易。 處理`inv`稍顯復雜: ~~~ func handleInv(request []byte, bc *Blockchain) {//請求區塊者收到回復節點返回的blocks后進行處理 ... fmt.Printf("Recevied inventory with %d %s\n", len(payload.Items), payload.Type) if payload.Type == "block" {//塊哈希 blocksInTransit = payload.Items blockHash := payload.Items[0] //給回復節點發送getdata命令,請求該塊:哦,你有這么一個塊,麻煩給我 //注意,每次只能請求一個block,所以這次請求的是payload里面的第0個 sendGetData(payload.AddrFrom, "block", blockHash) newInTransit := [][]byte{} for _, b := range blocksInTransit {//迭代收到的區塊哈希列表 //如果b不等于收到的第0個哈希值(第0個塊在上面getdata命令中已經請求下載),加入到新的newInTransit(尚未下載該塊) if bytes.Compare(b, blockHash) != 0 {//第0個已經請求下載了啦,所以需要排除,下次不要再請求其他節點下載 newInTransit = append(newInTransit, b) } } //更新blocksInTransit,blocksInTransit用來跟蹤已下載的塊,以后會根據此表請求下載其他的區塊,直到全部下載完畢 blocksInTransit = newInTransit } if payload.Type == "tx" { txID := payload.Items[0] if mempool[hex.EncodeToString(txID)].ID == nil {//如果本地內存池中不存在這么一個交易 sendGetData(payload.AddrFrom, "tx", txID)//收到回復節點返回的tx,請求該交易:哦,你有這么一個交易,麻煩給我 } } } ~~~ ## 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`消息十分簡單: ~~~ 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\n", block.Hash) if len(blocksInTransit) > 0 {//迭代需要下載的區塊哈希列表 blockHash := blocksInTransit[0] sendGetData(payload.AddrFrom, "block", blockHash)//這里是從回復哈希列表的節點處,請求其一一給下區塊 blocksInTransit = blocksInTransit[1:]//排除第一個,更新待下載區塊列表 } else {//區塊下載完畢 UTXOSet := UTXOSet{bc} UTXOSet.Reindex()//最好不要調用Reindex,而是迭代下載的blocks,使用 UTXOSet.Update(block)更新數據庫的UTXO表 } } ~~~ 當接收到一個新塊時,我們把它放到區塊鏈里面。如果還有更多的區塊需要下載,我們繼續從上一個下載的塊的那個節點繼續請求。當最后把所有塊都下載完后,對 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 {//如果交易池中的交易大于或者等于2筆,并且當前是礦工節點 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("所有交易均非法,正在等待新的交易到來...") return } cbTx := NewCoinbaseTX(miningAddress, "")//coninbase交易,挖礦獎勵 txs = append(txs, cbTx) newBlock := bc.MineBlock(txs)//挖礦 UTXOSet := UTXOSet{bc} UTXOSet.Reindex()//最好不要重新索引,而是執行update fmt.Println("已挖到新區塊!") 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 } } } } ~~~ # 場景檢驗 ## 創建中心節點 在第一個終端窗口中將`NODE_ID`設置為 3000,作為中心節點,3001為錢包節點,3002為礦工節點: 讓我們來回顧一下上面定義的場景。 首先,在第一個終端窗口中將`NODE_ID`設置為 3000(`export NODE_ID=3000`)。為了讓你知道什么節點執行什么操作,我會使用像**NODE 3000**或**NODE 3001**進行標識。 ### NODE 3000 創建一個錢包和一個新的區塊鏈: ~~~ $ main 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 向錢包地址發送一些幣: ~~~ $ main send -from CENTREAL_NODE -to WALLET_1 -amount 10 -mine $ main send -from CENTREAL_NODE -to WALLET_2 -amount 10 -mine ~~~ `-mine`標志指的是塊會立刻被同一節點挖出來。我們必須要有這個標志,因為初始狀態時,網絡中沒有礦工節點。 啟動節點: ~~~ $ main startnode ~~~ 這個節點會持續運行,直到本文定義的場景結束。 ### NODE 3001 啟動上面保存創世塊節點的區塊鏈: ~~~ $ cp blockchain_genesis.db blockchain_3001.db ~~~ 運行節點: ~~~ $ main startnode ~~~ 它會從中心節點下載所有區塊。為了檢查一切正常,暫停節點運行并檢查余額: ~~~ $ main getbalance -address WALLET_1 Balance of 'WALLET_1': 10 $ main getbalance -address WALLET_2 Balance of 'WALLET_2': 10 ~~~ 你還可以檢查`CENTRAL_NODE`地址的余額,因為 node 3001 現在有它自己的區塊鏈: ~~~ $ main getbalance -address CENTRAL_NODE Balance of 'CENTRAL_NODE': 10 ~~~ ### NODE 3002 打開一個新的終端窗口,將它的 ID 設置為 3002,然后生成一個錢包。這會是一個礦工節點。初始化區塊鏈: ~~~ $ cp blockchain_genesis.db blockchain_3002.db ~~~ 啟動節點: ~~~ $ main startnode -miner MINER_WALLET ~~~ ### NODE 3001 發送一些幣: ~~~ $ main send -from WALLET_1 -to WALLET_3 -amount 1 $ main send -from WALLET_2 -to WALLET_4 -amount 1 ~~~ ### NODE 3002 迅速切換到礦工節點,你會看到挖出了一個新塊!同時,檢查中心節點的輸出。 ### NODE 3001 切換到錢包節點并啟動: ~~~ $ main startnode ~~~ 它會下載最近挖出來的塊! ![](https://img.kancloud.cn/bb/4b/bb4bc4f4ca70376fef356427bf6e5f8c_2130x1544.png)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看