第一章:最小可行區塊鏈

概覽

區塊鏈的基礎概念很是簡單, 說白了就是一個維護着一個持續增加的有序數據記錄列表的這麼一個分佈式數據庫。在此章節中咱們將實現一個簡單的玩具版的區塊鏈。此章節結束時,咱們的區塊鏈將實現如下功能:html

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

區塊數據結構

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

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

image

相應代碼大體以下:git

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,確定不能重複),因此絕對不會出現同樣的哈希值。 根據如下的代碼來計算哈希值:github

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

須要注意的是,在這個階段,區塊的哈希值與挖礦沒有任何關係,由於還未有 POW(工做量證實) 問題須要解決。咱們使用區塊哈希值來保證區塊的完整性,同時也使用它來回溯前一個區塊。web

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

如下的例子描述了這個特性。若是將第44區塊的數據從「DESERT」修改爲「STREET」,全部後續區塊的哈希值也必須被修改。這是因爲區塊的哈希值是經過對區塊的內容計算哈希獲得的,而內容中包含了 previousHash 這個表明了前一個區塊的哈希的值。shell

image

這個特性在咱們後面章節中引入的工做量證實機制來講尤爲重要。一個區塊在區塊鏈中的位置越深(即越靠前),要修改它的難度就越大,由於須要同時修改它自己以及它後續的全部區塊。數據庫

創世塊

創世塊是區塊鏈中的第一個區塊。它是惟一一個沒有 previousHash 的區塊,由於這個區塊比較特別,咱們在代碼裏會將創世區塊進行硬編碼處理:express

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

建立區塊

建立一個新的區塊,須要得到上一個區塊的哈希值,並建立其餘必須的內容( index, hash, data 和 timestamp)。區塊的數據(data字段)由用戶提供,其餘的參數使用如下代碼生成:npm

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

相關文章
相關標籤/搜索