第一章:最小可行區塊鏈

  • 概覽
  • 區塊數據結構
  • 區塊哈希
  • 創世塊
  • 創建區塊
  • 保存區塊鏈
  • 驗證區塊完整性
  • 選擇最長鏈
  • 節點間通信
  • 操作節點
  • 架構
  • 運行測試
  • 小結

概覽

區塊鏈的基礎概念非常簡單, 說白了就是一個維護著一個持續增長的有序數據記錄列表的這么一個分布式數據庫。在此章節中我們將實現一個簡單的玩具版的區塊鏈。此章節結束時,我們的區塊鏈將實現以下功能:

  • 實現區塊和區塊鏈結構定義
  • 實現可以將包含任意數據的新區塊寫入到區塊鏈的方法
  • 實現可以與其他節點進行點到點溝通和同步區塊鏈數據的運行節點
  • 操作單個運行節點的簡單HTTP(Restful) API

區塊數據結構

我們首先會從區塊數據結構的定義開始。在當前階段,簡單起見,我們只會給每個區塊定義最關鍵的屬性。

  • index: 區塊在區塊鏈中的高度(即序號),因為每加一個區塊,該index就會加1,所以幣圈將其稱之為高度。
  • data: 任何需要包括在此區塊中的數據。本章節中可以是任何數據,到后面章節我們會用來記賬用。
  • timestamp: 時間戳。本章節中也是可以是任何數據,往后我們需要保證這個字段是正確的時間戳數據,用來防止攻擊等用。
  • hash: 根據區塊內容計算的哈希值(SHA256)。
  • previousHash: 前一個區塊的哈希值。通過這個屬性,我們能很方便回溯前面的區塊。

image

相應代碼大致如下:


class Block {public index: number;public hash: string;public previousHash: string;public timestamp: number;public data: string;constructor(index: number, hash: string, previousHash: string, timestamp: number, data: string) {this.index = index;this.previousHash = previousHash;this.timestamp = timestamp;this.data = data;this.hash = hash;}
}

區塊哈希

區塊哈希值是區塊中最重要的屬性之一。哈希值根據區塊中的所有數據計算而得,這意味著如果區塊中任何數據發生變化,原有的哈希值就不再有效。區塊哈希值也能被看成區塊的唯一性標識。比如說,兩個人同時挖礦成功,那就有可能出現兩個高度一致的區塊,但是因為要通過其他屬性值一起算哈希(往后我們會看到data屬性會存放交易數據,交易數據,特別是id,肯定不能重復),所以絕對不會出現一樣的哈希值。
根據以下的代碼來計算哈希值:

const calculateHash = (index: number, previousHash: string, timestamp: number, data: string): string =>CryptoJS.SHA256(index + previousHash + timestamp + data).toString();

需要注意的是,在這個階段,區塊的哈希值與挖礦沒有任何關系,因為還未有 POW(工作量證明) 問題需要解決。我們使用區塊哈希值來保證區塊的完整性,同時也使用它來回溯前一個區塊。

由以上對 hash 和 previousHash 屬性的處理機制,很容易得出區塊鏈的一個重要特性:區塊的內容不能被修改,除非同時修改它后續的所有區塊內容。

以下的例子描述了這個特性。如果將第44區塊的數據從“DESERT”修改成“STREET”,所有后續區塊的哈希值也必須被修改。這是由于區塊的哈希值是通過對區塊的內容計算哈希得到的,而內容中包含了 previousHash 這個代表了前一個區塊的哈希的值。

image

這個特性在我們后面章節中引入的工作量證明機制來說尤其重要。一個區塊在區塊鏈中的位置越深(即越靠前),要修改它的難度就越大,因為需要同時修改它本身以及它后續的所有區塊。

創世塊

創世塊是區塊鏈中的第一個區塊。它是唯一一個沒有 previousHash 的區塊,因為這個區塊比較特別,我們在代碼里會將創世區塊進行硬編碼處理:


const genesisBlock: Block = new Block(0, '816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7', null, 1465154705, 'my genesis block!!'
);

創建區塊

創建一個新的區塊,需要獲得上一個區塊的哈希值,并創建其他必須的內容( index, hash, data 和 timestamp)。區塊的數據(data字段)由用戶提供,其他的參數使用以下代碼生成:


const generateNextBlock = (blockData: string) => {const previousBlock: Block = getLatestBlock();const nextIndex: number = previousBlock.index + 1;const nextTimestamp: number = new Date().getTime() / 1000;const nextHash: string = calculateHash(nextIndex, previousBlock.hash, nextTimestamp, blockData);const newBlock: Block = new Block(nextIndex, nextHash, previousBlock.hash, nextTimestamp, blockData);return newBlock;
};

保存區塊鏈

目前我們使用 JavaScript 的數組,將區塊鏈保存在程序的運行內存中。這意味著當一個運行節點停止時,該節點上的區塊鏈數據不會被持久化。

const blockchain: Block[] = [genesisBlock];

驗證區塊完整性

為確保數據完整性,我們應想辦法做到可隨時對一個區塊,或者一條區塊鏈上的區塊進行有效性驗證。特別是當我們的節點從其他運行節點中接收到廣播過來的新區塊時,我們就需要驗證區塊的有效性,以便決定是否接受這些區塊。

驗證區塊的有效性,需要滿足以下所有條件:

  • 區塊的 index 需要比上一個區塊大1;
  • 區塊的 previousHash 屬性需要與上一個區塊的 hash 屬性一致;
  • 區塊自身的 hash 值需要有效。

以下代碼描述了整個驗證過程:


const isValidNewBlock = (newBlock: Block, previousBlock: Block) => {if (previousBlock.index + 1 !== newBlock.index) {console.log('invalid index');return false;} else if (previousBlock.hash !== newBlock.previousHash) {console.log('invalid previoushash');return false;} else if (calculateHashForBlock(newBlock) !== newBlock.hash) {console.log(typeof (newBlock.hash) + ' ' + typeof calculateHashForBlock(newBlock));console.log('invalid hash: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash);return false;}return true;
};

同時我們還必須驗證該區塊的結構是否正確,以避免其他節點廣播過來的帶有不正確格式的數據導致程序崩潰。

const isValidBlockStructure = (block: Block): boolean => {return typeof block.index === 'number'&& typeof block.hash === 'string'&& typeof block.previousHash === 'string'&& typeof block.timestamp === 'number'&& typeof block.data === 'string';
};

既然我們現在能夠驗證單個區塊的有效性,我們就可以進一步的對整個區塊鏈進行有效性驗證了。首先驗證鏈中的第一個區塊為創世區塊。然后,我們使用以上的方式來依次校驗鏈中的下一個區塊,以下為實現代碼:

const isValidChain = (blockchainToValidate: Block[]): boolean => {const isValidGenesis = (block: Block): boolean => {return JSON.stringify(block) === JSON.stringify(genesisBlock);};if (!isValidGenesis(blockchainToValidate[0])) {return false;}for (let i = 1; i < blockchainToValidate.length; i++) {if (!isValidNewBlock(blockchainToValidate[i], blockchainToValidate[i - 1])) {return false;}}return true;
};

選擇最長鏈

在任何時候,在區塊鏈系統中都應該只存在一條正確的鏈,但沖突還是在所難免的,我們需要有一個大家都認同的共識機制來確保沖突得以解決。在沖突發生的情況下(比如:主鏈在71這個塊的時候發生分叉,然后我緊鄰的節點在某一條鏈的基礎上挖出了第73個塊),則從中選擇包含更長區塊的鏈(比如我的節點啟動時會和其他節點請求區塊鏈狀態,發現有最后塊為72和73的兩條鏈,那么我們的節點將會在73這個鏈的基礎上繼續貢獻資源進行挖礦)。在以下的例子中,由于被更長的區塊鏈復寫,第72區塊: a350235b00 中的數據將不會被包括在區塊鏈中。

image

代碼實現如下:

const replaceChain = (newBlocks: Block[]) => {if (isValidChain(newBlocks)&& newBlocks.length > getBlockchain().length) {console.log('Received blockchain is valid. Replacing current blockchain with received blockchain');blockchain = newBlocks;broadcastLatest();} else {console.log('Received blockchain invalid');}
};

節點間通信

每個運行節點都必須能和其他節點廣播和同步區塊鏈數據。我們通過以下規則保證節點間能正確有效的同步:

  • 當一個節點生成新區塊時,該節點會將此區塊廣播至區塊鏈網絡中
  • 當一個節點和另外一個節點建立點對點連接時,該節點將會向另一個節點請求最新的區塊鏈信息
  • 當一個節點發現從其他節點過來的一個區塊的 index 比該節點中保留的區塊鏈的最后一個區塊的 index 大,根據兩個index之間相差的大小,該節點會有兩個選擇:如果只相差1,則將此區塊加到自身的區塊鏈中; 如果超過1,則需要向其他節點請求整條區塊鏈。

image

我們將會使用 WebSocket 技術來實現各個節點的點對點通信。各個節點的 socket 列表將保存在 const sockets: WebSocket[] 變量中。我們并沒有實現節點發現機制,所以新增加一個節點后,需要手動添加需要建立點對點連接的目標節點的地址。

操作節點

用戶需能夠以某種方式來操作節點。我們將通過實現相應的http服務端接口來提供相應功能。

const initHttpServer = ( myHttpPort: number ) => {const app = express();app.use(bodyParser.json());app.get('/blocks', (req, res) => {res.send(getBlockchain());});app.post('/mineBlock', (req, res) => {const newBlock: Block = generateNextBlock(req.body.data);res.send(newBlock);});app.get('/peers', (req, res) => {res.send(getSockets().map(( s: any ) => s._socket.remoteAddress + ':' + s._socket.remotePort));});app.post('/addPeer', (req, res) => {connectToPeers(req.body.peer);res.send();});app.listen(myHttpPort, () => {console.log('Listening http on port: ' + myHttpPort);});
};

根據以上代碼暴露出來的HTTP接口,用戶可以發送請求到節點進行以下操作:

  • 列出所有區塊
  • 由用戶指定相應內容來創建一個新區塊
  • 列出連接過來的節點的地址
  • 通過websocket url連接到指定節點

您可以通過Curl工具來對節點進行操作,當然您也可以通過postman等工具來操作:


#get all blocks from the node
> curl http://localhost:3001/blocks

架構

每個節點都對外暴露兩個web 服務: 一個是用戶來給用戶對節點進行操作(HTTP Server),一個是用來實現節點間的點對點通信(Websocket HTTP server)。

image

運行測試

安裝

npm install

運行

打開一個終端運行節點1. 節點1的http服務端端口為3001, p2p端口為6001。

npm run node1

建議打開另外一個終端運行節點2,以便能通過輸出查看兩個區塊鏈節點是怎么通信的。 節點1的http服務端端口為3002, p2p端口為6002。

npm run node2

ps: 節點2運行后,即可以通過addPeer這個api和節點1進行websocket連接。

生成一個區塊

curl -H "Content-type:application/json" --data '{"data" : "Some data to the first block"}' http://localhost:3001/mineBlock

返回結果示例:

{"index": 1,"previousHash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7","timestamp": 1561025398.834,"data": "Some data to the first block","hash": "979335f8383fa058c0abf5d342a232d345de51ea644756d3522eca5637e97a17"
}

獲取區塊鏈

curl http://localhost:3001/blocks

返回示例:

[
{
"index": 0,
"previousHash": "",
"timestamp": 1465154705,
"data": "my genesis block!!",
"hash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7"
},
{
"index": 1,
"previousHash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7",
"timestamp": 1561025398.834,
"data": "Some data to the first block",
"hash": "979335f8383fa058c0abf5d342a232d345de51ea644756d3522eca5637e97a17"
}
]

連接到一個節點

curl -H "Content-type:application/json" --data '{"peer" : "ws://localhost:6001"}' http://localhost:3002/addPeer

查詢連接的節點列表

curl http://localhost:3001/peers

返回示例:

["::ffff:127.0.0.1:54261"]

小結

到現在為止,我們實現了一個簡單的玩具版的區塊鏈。此外,本章節還為我們展示了如何用簡單扼要的方法來實現區塊鏈的一些基本原理。下一章節中我們將為naivecoin 加入工作量證明機制。

本章節的代碼請查看這里

第二章

本文由天地會珠海分舵編譯,轉載需授權,喜歡點個贊,吐槽請評論,如能給Github上的項目給個星,將不勝感激。

轉載于:https://www.cnblogs.com/techgogogo/p/11072536.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/393097.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/393097.shtml
英文地址,請注明出處:http://en.pswp.cn/news/393097.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Oracle Controlfile控制文件中記錄的信息片段sections

初學Oracle的朋友肯定對Controlfile控制文件中到底記錄了何種的信息記錄而感到好奇&#xff0c;實際上我們可以通過一個視圖v$controlfile_record_section來了解控制文件的信息片段&#xff1a; SQL> select type, record_size, records_total from v$controlfile_record_s…

linux 怎么禁止遍歷目錄,linux下遍歷目錄功能實現

/*編譯:dir:dir.cgcc -o $ $<*/#include #include #include #include #include int do_search_dir(char *path);int do_check_dir(char *fullpath, char* truefullpath);void usage(char *apps);int count 0;intmain(int argc,char **argv){char fullpath[…

leetcode面試題 16.26. 計算器(棧)

給定一個包含正整數、加()、減(-)、乘(*)、除(/)的算數表達式(括號除外)&#xff0c;計算其結果。 表達式僅包含非負整數&#xff0c;&#xff0c; - &#xff0c;*&#xff0c;/ 四種運算符和空格 。 整數除法僅保留整數部分。 示例 1: 輸入: “32*2” 輸出: 7 代碼 clas…

團隊項目電梯會議視頻

http://v.youku.com/v_show/id_XMjcyMjI3Mjk2NA.html?spma2hzp.8244740.userfeed.5!2~5~5~5!3~5~A轉載于:https://www.cnblogs.com/jingxiaopu/p/6749776.html

arduino服務器_如何使用Arduino檢查Web服務器的響應狀態

arduino服務器by Harshita Arora通過Harshita Arora 如何使用Arduino檢查Web服務器的響應狀態 (How to use Arduino to check your web server’s response status) Last year, I created Crypto Price Tracker (an app which was acquired by Redwood City Ventures this yea…

leetcode486. 預測贏家(dp)

給定一個表示分數的非負整數數組。 玩家 1 從數組任意一端拿取一個分數&#xff0c;隨后玩家 2 繼續從剩余數組任意一端拿取分數&#xff0c;然后玩家 1 拿&#xff0c;…… 。每次一個玩家只能拿取一個分數&#xff0c;分數被拿取之后不再可取。直到沒有剩余分數可取時游戲結束…

linux怎么看文件狀態,linux查看文件類型-file、狀態-stat

linux查看文件類型-file、狀態-stat首頁 計算機相關 linux命令 linux查看文件類型-file、狀態-statfile 命令可以用來查看文件類型-i mime type-s 讀取字符或塊設備文件最好指定[root192 tmp]# file freeclsfreecls: UTF-8 Unicode text[root192 tmp]# file -i freeclsfreecls:…

Linux課程筆記 Crond介紹

1. 定時任務比較及cron語法 Linux的任務調度可以分為兩類&#xff1a; 系統自身執行的任務用戶執行的工作Linux系統下另外兩種定時任務軟件&#xff1a; at&#xff1a;適合僅執行一次的調度任務&#xff0c;需要啟動一個名為atd的服務 anacron&#xff1a;這個命令主要用于非…

Python 學習日記第二篇 -- 列表,元組

一、列表 列表是一個可以包含所以數據類型的對象的位置有序集合&#xff0c;它是可以改變的。 1、列表的序列操作&#xff08;Python3&#xff09; 123456789101112131415161718192021222324>>> one_list [1,2,3,4]>>> two_list ["jonny","…

【Gamma】PhyLab 測試報告

PhyLab Gamma測試報告 測試中發現的bug Gamma階段新Bug Bug可能原因部分錯誤碼設置與原先拋異常的邏輯沖突原先代碼中使用了一些特殊的辦法處理異常Beta未發現Bug Bug可能原因控制臺新建實驗編號不能以0開頭后端處理編號會將其前導0去除&#xff0c;以數字形式存儲&#xff0c;…

如何使用Node.js,Express和MongoDB設置GraphQL服務器

by Leonardo Maldonado萊昂納多馬爾多納多(Leonardo Maldonado) 如何使用Node.js&#xff0c;Express和MongoDB設置GraphQL服務器 (How to set up a GraphQL Server using Node.js, Express & MongoDB) 從GraphQL和MongoDB開始的最直接的方法。 (The most straightforward…

leetcode954. 二倍數對數組(treemap)

給定一個長度為偶數的整數數組 A&#xff0c;只有對 A 進行重組后可以滿足 “對于每個 0 < i < len(A) / 2&#xff0c;都有 A[2 * i 1] 2 * A[2 * i]” 時&#xff0c;返回 true&#xff1b;否則&#xff0c;返回 false。 示例 1&#xff1a; 輸入&#xff1a;[3,1,…

linux文件內容打印成二進制,如何在二進制文件中只打印可打印字符(相當于Linux下的字符串)?...

在Python3中&#xff0c;以二進制模式打開文件會得到bytes的結果。迭代一個bytes對象可以得到0到255(包括0到255)的整數&#xff0c;而不是字符。從^{} documentation&#xff1a;While bytes literals and representations are based on ASCII text, bytes objects actually b…

1098 均分紙牌

1098 均分紙牌 2002年NOIP全國聯賽提高組 時間限制: 1 s 空間限制: 128000 KB 題目等級 : 黃金 Gold 題目描述 Description有 N 堆紙牌&#xff0c;編號分別為 1&#xff0c;2&#xff0c;…, N。每堆上有若干張&#xff0c;但紙牌總數必為 N 的倍數。可以在任一堆上取若于張紙…

輕松學習分布式|系列3|分布式數據庫。

我們繼續來講分布式&#xff0c;回到我們的創業游戲。 我們的業務規模上來了&#xff0c;客戶也越來越忠誠了。很多客戶都通過我們的訂票服務&#xff0c;來方便自己的行程。 那對這些老客戶&#xff0c;我們的宗旨是&#xff1a;要不斷超越客戶的期待。 所以&#xff0c;我們要…

量子運算 簡單通俗例子_什么是量子計算機? 用一個簡單的例子解釋。

量子運算 簡單通俗例子by YK Sugi由YK Sugi 什么是量子計算機&#xff1f; 用一個簡單的例子解釋。 (What is a quantum computer? Explained with a simple example.) Hi everyone!嗨&#xff0c;大家好&#xff01; The other day, I visited D-Wave Systems in Vancouver…

linux增加端口失敗,端口沒被占用,怎么會bind失敗呢?

今天在一個服務器上部署一個webserver的時候&#xff0c;提示我bind端口失敗&#xff0c;我習慣性的用netstat看了下&#xff0c;沒有被占用啊&#xff01;把問題分享出來后&#xff0c;給力的同事們搜索到了ip_local_port_range這個東西這個東西對應的是/proc/sys/net/ipv4/ip…

leetcode面試題 17.15. 最長單詞

給定一組單詞words&#xff0c;編寫一個程序&#xff0c;找出其中的最長單詞&#xff0c;且該單詞由這組單詞中的其他單詞組合而成。若有多個長度相同的結果&#xff0c;返回其中字典序最小的一項&#xff0c;若沒有符合要求的單詞則返回空字符串。 示例&#xff1a; 輸入&am…

Restful API 設計

1. 簡介 目前 "互聯網軟件"從用客戶端/服務端模式&#xff0c;建立在分布式體系上&#xff0c;通過互聯網通訊&#xff0c;具有高延時、高開發等特點。但是軟件開發和網絡是兩個不同的領域&#xff0c;交集很少。要使得兩個融合&#xff0c;就要考慮如何在互聯網環境…

sql行數少于10_如何用少于100行的代碼創建生成藝術

sql行數少于10by Eric Davidson埃里克戴維森(Eric Davidson) 如何用少于100行的代碼創建生成藝術 (How to Create Generative Art In Less Than 100 Lines Of Code) Generative art, like any programming topic, can be intimidating if you’ve never tried it before. I’v…