區塊鏈筆記(4)用JS寫個簡單的區塊鏈原型

介紹了一些關於比特幣的概念與機制,爲了加深理解,本文基於JavaScript來實現一個簡單的區塊鏈原型,後續再對其進行不斷豐富。

1. 概述

如前所述區塊鏈模型的組成部分,包括區塊,區塊構成的區塊鏈,以及保存區塊鏈的數據持久層等。一個超簡單的UML類圖如下:
圖片描述
由於我是前端的,業餘看了這麼久區塊鏈的理論,還是手癢癢謝謝代碼,把這個類用JavaScript實現一下。寫完之後發現目前階段,對於區塊鏈原型來說還是太過簡單,不過如果說用來做前端面試題,考察下面向對象和Promise等知識點倒是挺接洽。

2. 定義區塊數據模型

摘取比特幣區塊的詳情進行修改,去除所有多餘信息,只留下能描述區塊最基本的信息,聲明區塊類如下:

class Block {
    constructor(data) {
        // 區塊的屬性值
        this.hash = "";
        this.height = 0;
        this.body = data;
        this.time = 0;
        this.previousBlockHash = "";
    }
}

module.exports.Block = Block;

3. 數據持久層

其實用數組實現區塊鏈是最簡單的原型方案,但每次重啓數組都會被清空,數據並不持久。所以這裏引入levelDB數據庫作爲持久層來保存數據,相關操作可參考level。由於直接調用API,對於應用層來說過於麻煩,所以在此聲明一個數據操作類LevelSandbox,該類不像傳統的關係型數據庫具有增、刪、改、查等全部功能,由於區塊鏈上數據的不可更改性,此類只包含增和查的操作。

3.1 根據key從數據庫中獲取數據

本文如下相關異步實現,都採用Promise的方式而非回調,其中好處作爲前端工程師此處就不多介紹了,有需要了解的可異步Promise介紹,自行擴展閱讀。

getLevelDBData(key) {
    let self = this;
    return new Promise(function(resolve, reject) {
        self.db.get(key)
            .then(value => {
                console.log('Value = ' + value);
                resolve(value)
            })
            .catch(err => {
                console.log('Not found!');
                reject(err)
            })
    });
}

3.2 將key/value數據插入數據庫中

key/value的方式在數據庫中存儲,其key值得選取,這裏考慮使用區塊類中聲明的height字段,該字段標識一個區塊在鏈中的位序,同時也具有唯一性,非常合適。

addLevelDBData(key, value) {
    let self = this;
    return new Promise(function(resolve, reject) {
        self.db.put(key, value)
            .then(() => resolve())
            .catch((err) => {
                console.log('Block ' + key + ' submission failed');
                reject(err)
            })
    });
}

3.3 獲取數據庫中區塊總數

createReadStream()方法創建一個讀取數據庫的流,這裏的作用是爲了遍歷整庫以獲取存儲的區塊總數,另外此方法還可通過傳參,設置遍歷次序,詳情可參閱文檔。

getBlocksCount() {
    let self = this;
    return new Promise(function(resolve, reject){
        let height = 0;
        self.db.createReadStream()
            .on('data', function () {
                height++;
            })
            .on('error', function (error) {
                reject('Unable to read data stream!', error);
            })
            .on('close', function () {
                resolve(height);
            });
    });
}

4. 區塊鏈類

該類主要負責將新創建的區塊添加進區塊鏈,並驗證鏈中各個區塊的數據完整性。這個過程中少不了對區塊數據的哈希處理,爲方便起見,採用第三方庫crypto-js實現的SHA256方法。

構想該類中的主要方法包括:

  • createGenesisBlock():生成起始區塊
  • getBlockHeight():獲取區塊鏈長度
  • getBlock(height):獲取指定區塊
  • addBlock(block):將一個新區塊加入區塊鏈中
  • validateBlock(block):驗證某個區塊
  • validateChain():驗證區塊鏈

如下便實現其中主要的幾個方法:

4.1 增加新區塊

各個區塊通過previousBlockHash屬性,依次指向前一個區塊來連接成鏈的,除首區塊該屬性爲空外。

addBlock(block) {
    return this.getBlockHeight()
        .then(height => {
            區塊高度
            block.height = height;
            // UTC 時間戳
            block.time = new Date().getTime().toString().slice(0, -3);
            if (height > 0) {
                this.getBlock(height - 1)
                    .then(preBlock => {
                        // 前一個區塊的哈希值
                        block.previousBlockHash = preBlock.hash;
                        // 對區塊進行哈希處理
                        block.hash = SHA256(JSON.stringify(block)).toString();
                        // 將新區快存入庫中
                        this.bd.addLevelDBData(height, JSON.stringify(block));
                    })
                    .catch(error => console.log(error));
            } else {
                block.hash = SHA256(JSON.stringify(block)).toString();
                this.bd.addLevelDBData(height, JSON.stringify(block));
            }
        })
        .catch( error => console.log(error));
}

4.2 驗證單個區塊完整性

驗證方法就是應用了hash算法的性質:相同的數據經過hash後會生成相同的hash值。

validateBlock(height) {
        // 獲取區塊的值
        return this.getBlock(height)
            .then(block => {
                const objBlock = JSON.parse(block);
                let blockHash = objBlock.hash;
                objBlock.hash = '';
                // 重新生成區塊的哈希值
                let validBlockHash = SHA256(JSON.stringify(objBlock)).toString();
                objBlock.hash = blockHash;
                // 比較以驗證完整性
                if (blockHash === validBlockHash) {
                    return Promise.resolve({isValidBlock: true, block: objBlock});
                } else {
                    console.log('Block #'+blockHeight+' invalid hash:\n'+blockHash+'<>'+validBlockHash);
                    return Promise.resolve({isValidBlock: false, block: objBlock});
                }
            })
    }

4.3 驗證整個區塊鏈

通過依次校驗每個區塊以驗證整條鏈的完整性。

validateChain() {
    let errorLog = [];
    let previousHash = '';
    this.getBlockHeight()
        .then(height => {
            for (let i = 0; i < height; i++) {
                this.getBlock(i)
                    .then(block => this.validateBlock(block.height))
                    .then(({isValidBlock, block}) => {
                        if (!isValidBlock) errorLog.push(i);
                        if (block.previousBlockHash !== previousHash) errorLog.push(i);
                        previousHash = block.hash;
                        if (i === height - 1) {
                            if (errorLog.length > 0) {
                                console.log(`Block errors = ${errorLog.length}`)
                                console.log(`Blocks: ${errorLog}`)
                            } else {
                                console.log('No errors detected')
                            }
                        }
                    })
            }
        })
}

5. 生成測試數據

(function theLoop (i) {
    setTimeout(function () {
        let blockTest = new Block.Block("Test Block - " + (i + 1));
        myBlockChain.addBlock(blockTest).then((result) => {
            console.log(result);
            i++;
            if (i < 10) theLoop(i);
        });
    }, 10000);
})(0);

作爲一個區塊鏈原型的樣子算是初見端倪,但就目前的功能來說還非常簡陋,說是原型都算擡舉了,不過後面慢慢再豐富吧。這裏也只算是對之前的一個實踐性的小節。

文中以列出主要代碼片段,整體實現其實不難,沒貼出所有代碼主要是爲了表述思路更清晰些,若有朋友實現過程中有問題,可文下留言交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章