NodeJS區塊鏈實踐(1)Nodejs搭建簡易區塊鏈
區塊鏈的目的之一是讓我們所需要的“有價值”的資訊得以儲存且不可更改,這些資訊都儲存在一個叫做“區塊(block)”的結構中。以比特幣為例,被認為是有價值的資訊是“交易”,所有的交易儲存在區塊中,通過區塊的hash、時間戳等實現資訊的可追溯以及不可更改性。
我們這裡首先實現的是一個簡易的區塊鏈,並不是像比特幣這樣成熟的區塊鏈,暫不涉及交易結構、驗證以及UTXO。
一、區塊(block)
首先,建立一個區塊(Block)類。我們的區塊僅包含了一部分關鍵資訊,它的結構如下:
class Block{ constructor(data){ this.hash = "", // 當前區塊的Hash值 this.height = 0, // 當前區塊的高度 this.body = data, // 區塊實際儲存的有效資訊 this.time = 0, // 時間戳,即區塊建立的時間 this.previousBlockHash = ""。// 前一個區塊的Hash值 } }
這裡,hash、height、time和previousBlockHash在比特幣規範中屬於區塊頭(header),區塊頭是一個單獨的資料結構,而body部分用於儲存交易資訊或者其他資料。
二、區塊鏈(blockchain)
首先,我們要把區塊存在哪裡?我們可以把區塊直接放進一個數組(let blockchain = arr[]),但是這顯然不是我們想要的。這裡我們選擇leveldb作為底層的資料儲存方案。
const level = require('level'); const chainDB = './.data/blockchain'; const db = level(chainDB);
然後,我們要思考一下,一個blockchain例項需要具有哪些功能?
1、getBlockHeight():獲取當前區塊的高度,即該區塊鏈的高度;
2、getBlockByHeight(height):通過區塊高度來獲取區塊;
3、getBlockByHash(hash):通過區塊雜湊來獲取區塊;
4、validateBlock(height):驗證某高度的區塊是否有效;
5、validateChain():驗證整條鏈的每個區塊是否均有效;
通過上面幾個函式,我們定義了對一個區塊鏈(blockchain)的基本描述,我們通過當前區塊的高度獲取當前區塊的hash和前一個區塊的hash,進而可以向前回溯出整個區塊鏈。
下面我們看看怎麼實現?
首先,Blockchain要包括如下的方法:
class Blockchain {
addBlock();
getBlockHeight();
getBlockByHeight();
getBlockByHash();
validateBlock();
validateChain();
}
我們要保證系統中只有一個Blockchain例項,可以使用ES6語法class的static關鍵詞來設定一個靜態方法來儲存Blockchain例項。在ES6的class中,加了static關鍵詞的方法不會被例項所繼承,只能通過class來呼叫,如Blockchain.getInstance()。
static getInstance() {
if (!Blockchain.instance) {
Blockchain.instance = new Blockchain();
return Blockchain.instance.getBlockHeight()
.then((height) => {
if (height === 0) {
const initialBlock = new Block('First block of the blockchain');
return Blockchain.instance.addBlock(initialBlock);
}
})
.then(() => Blockchain.instance)
}
return Promise.resolve(Blockchain.instance);
}
getBlockHeight()
通過遞迴呼叫count函式,從創始區塊一直計數到最後一個區塊,得到待增新區塊的高度。
getBlockHeight() {
let count = function (key) {
return db.get(key)
.then(() => count(key + 1))
.catch(() => key);
};
return count(0);
}
getBlockByHeight()
getBlockByHeight(){
return db.get(height).then((value) => JSON.parse(value));
}
addBlock(newBlock)
新區塊的previousBlockHash應等於前一個區塊的Hash,這樣就將兩個區塊連結到了一起。
addBlock(newBlock) {
return this.getBlockHeight()
.then((height) => {
let PrevBlock;
newBlock.height = height; // 新區塊的高度
newBlock.time = new Date().getTime().toString().slice(0, -3); // 新區塊時間戳
// 得到新區塊的previousBlockHash
if (height > 0) {
PrevBlock = this.getBlock(height - 1)
.then((previousBlock) => {
newBlock.previousBlockHash = previousBlock.hash;
});
}
return Promise.all([PrevBlock])
.then(() => {
newBlock.hash = SHA256(JSON.stringify(newBlock)).toString();
return db.put(height, JSON.stringify(newBlock));
});
})
.then(() => Blockchain.instance);
}
getBlockByHash()
可以通過類似於getBlockHeight()的思路進行遞迴,比較該區塊的hash於輸入的引數hash是否相等。然而,我們還有另外一種選擇。leveldb給我提供了db.createReadStream()方法來逐個讀取資料庫中的條目。
getBlockByHash(hash){
return new Promise((resolve, reject) => {
let block;
db.createReadStream()
.on("data", (data) => {
if(data.key != 'height'){
let value = JSON.parse(data.value);
let blockHash = value.hash;
if (blockHash == hash) {
block = value;
}
}
})
.on("error", (err) => {
reject(err);
})
.on("close", ()=>{
resolve(block); // 如果沒有滿足條件的,則block值為undefined,在下一步就會丟擲錯誤
})
})
}
validateBlock(blockHeight)
驗證一個區塊,就是要重新生成該區塊的hash,比較該hash與區塊本身hash屬性的值是否相等。
validateBlock(blockHeight){
return db.get(blockHeight).then(function(value){
let block = JSON.parse(value);
let blockHash = block.hash;
// remove block hash to test block integrity
block.hash = '';
// generate block hash
let validBlockHash = SHA256(JSON.stringify(block)).toString();
// Compare
if (blockHash === validBlockHash) {
return true
} else {
console.log('Block #'+blockHeight+' invalid hash:\n'+blockHash+'<>'+validBlockHash);
return false
}
}).catch(function(err){
console.log('Not found!', err);
})
}
validateChain()
驗證整個鏈的所有區塊,這就肯定要用到for迴圈。
validateChain(){
let errorLog = [];
this.getBlockHeight().then(height =>{
for (var i = 0; i <= height; i++) {
this.getBlock(i).then(block => {
// validate block
let h = block.height
this.validateBlock(h).then(val => {
if(!val) errorLog.push(h)
})
// compare blocks hash link
let hash = block.hash;
let n = block.height + 1;
if( n <= value ) {
db.get(n, (err, val) => {
let nextBlock = JSON.parse(val)
let preHash = nextBlock.previousBlockHash
if(hash !== preHash) {
errorLog.push(n-1);
}
if(n == value) {
if(errorLog.length>0){
console.log('\n-------Errors Detected!-------\n')
console.log('Block errors = ' + errorLog.length);
console.log('Blocks: '+ errorLog);
} else {
console.log('No errors detected');
}
}
})
}
})
}
}).catch(function(err){
console.log('Not found!', err);
})
}
總結
通過上面簡單的程式碼,我們實現了一個簡單的區塊鏈結構,這是一個private chain,沒有p2p網路,沒有挖礦需要的共識演算法。我們關注的是基本的區塊鏈的儲存結構,即有效資料儲存在block中,每一個block通過hash值關聯成一條鏈,我們通過區塊的高度即可從資料庫leveldb中讀取區塊的資料。