#1: 一個(gè)小型可工作區(qū)塊鏈
概述
區(qū)塊鏈的基本概念很簡(jiǎn)單:一個(gè)分布式數(shù)據(jù)庫(kù),保持不斷增長(zhǎng)的命令列表記錄惧辈。在這一章里,我們將實(shí)現(xiàn)這樣的區(qū)塊鏈精簡(jiǎn)具版本帅容。最后一章我們將在區(qū)塊鏈中加入以下基本功能:
-
定義一個(gè)區(qū)塊休弃,并構(gòu)造區(qū)塊鏈
-
實(shí)現(xiàn)一個(gè)能帶任意數(shù)據(jù)并形成新區(qū)塊的方法
-
區(qū)塊鏈的節(jié)點(diǎn)之間的通信與同步
-
一個(gè)簡(jiǎn)單的HTTP API控制節(jié)點(diǎn)
本章中實(shí)現(xiàn)的完整代碼,可以在這里找到盅抚。
區(qū)塊的結(jié)構(gòu)
我們需要定義塊的基本結(jié)構(gòu)房待,這里只定義了一些最基本的屬性
-
index:區(qū)塊的高度
-
data:任何塊中包含的數(shù)據(jù)
-
timestamp:一個(gè)時(shí)間戳
-
hash:通過(guò)sha256加密塊的內(nèi)容得到的hash
-
previousHash:一個(gè)散列引用前面的塊怔球。 這個(gè)值顯式地定義了前面的塊
就像下面的代碼一樣:
class Block {
public index: number; //區(qū)塊的索引
public hash: string; //區(qū)塊的hash
public previousHash: string; //前一個(gè)區(qū)塊的hash
public timestamp: number; //生成區(qū)塊的時(shí)間戳
public data: string; //交易數(shù)據(jù)
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;
}
}
區(qū)塊的哈希(hash)
區(qū)塊的hash是重要的屬性之一嚼酝,hash是通過(guò)區(qū)塊的所有數(shù)據(jù)計(jì)算得到。這就意味著任何hash改變竟坛,原來(lái)的hash都會(huì)失效闽巩。hash也可以認(rèn)為是一個(gè)區(qū)塊的唯一標(biāo)識(shí)符。
我們通過(guò)下面的代碼計(jì)算得到hash
const calculateHash = (index: number, previousHash: string, timestamp: number, data: string): string =>
CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
應(yīng)該注意,hash還不能被挖掘,因?yàn)檫€沒(méi)有使用proof-of-work來(lái)解決担汤。我們用hash來(lái)保存完整的塊和顯式地引用前面的塊涎跨。
一個(gè)重要的一點(diǎn),一個(gè)區(qū)塊中==hash==和==previousHash==不能被改變崭歧,除非改變所有的區(qū)塊隅很。
在下面的例子中,如果第44區(qū)塊的數(shù)據(jù)從==desert==變?yōu)?=street==率碾,所有的區(qū)塊都應(yīng)該全部改變外构。這是由于==hash==依賴于==previoushash==普泡。(除此之外)
當(dāng)介紹POW(工作量證明)時(shí),這是一個(gè)重要的概念审编,區(qū)塊的高度越高撼班,就越難被修改,因?yàn)橐薷乃羞B續(xù)的區(qū)塊垒酬。
創(chuàng)世區(qū)塊
創(chuàng)世區(qū)塊是區(qū)塊鏈的第一條區(qū)塊砰嘁,這是唯一一個(gè)沒(méi)有previoushash的區(qū)塊。我們將通過(guò)編碼生成第一條區(qū)塊勘究。
const genesisBlock: Block = new Block(
0, '816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7', null, 1465154705, 'my genesis block!!'
);
生成一個(gè)區(qū)塊
生成一個(gè)區(qū)塊之前矮湘,我們先需要知道前一個(gè)區(qū)塊的hash和當(dāng)前區(qū)塊的索引、hash口糕、數(shù)據(jù)缅阳、時(shí)間戳。區(qū)塊的數(shù)據(jù)將由最終的用戶給定景描。代碼如下十办。
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;
};
區(qū)塊的存儲(chǔ)
現(xiàn)在我們將通過(guò)JavaScript的數(shù)組來(lái)保存區(qū)塊鏈,這就意味著當(dāng)節(jié)點(diǎn)結(jié)束時(shí)超棺,數(shù)據(jù)不會(huì)被保存向族。
const blockchain: Block[] = [genesisBlock];
驗(yàn)證區(qū)塊的完整性
在必要時(shí)候,我們驗(yàn)證一個(gè)區(qū)塊的完整性或者整個(gè)區(qū)塊鏈的完整性棠绘,我們就要從其他節(jié)點(diǎn)接受區(qū)塊件相,并選擇接受與否。
一個(gè)區(qū)塊的有效性必須滿足一下條件
- 塊的索引必須是一個(gè)數(shù)字且比前一個(gè)區(qū)塊的索引大
- 區(qū)塊的previousHash能匹配前一個(gè)塊的hash
- 區(qū)塊本身的hash必須是有效的
下面通過(guò)代碼演示這點(diǎn)
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;
};
我們還必須驗(yàn)證區(qū)塊的結(jié)構(gòu):
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';
};
到這里我們已經(jīng)驗(yàn)證了一個(gè)單獨(dú)的區(qū)塊氧苍,現(xiàn)在我們驗(yàn)證整個(gè)區(qū)塊鏈夜矗。首先我們要檢驗(yàn)第一個(gè)區(qū)塊==genesisBlock==,后面我們用同意的方法來(lái)驗(yàn)證后面所有連續(xù)的塊让虐。下面是給出的代碼侯养。
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;
};
選擇最長(zhǎng)的區(qū)塊鏈
在一定時(shí)間內(nèi),只有一條鏈?zhǔn)呛戏ǖ某胃伞T跊_突的情況下(例如,有兩個(gè)節(jié)點(diǎn)生成塊編號(hào)72)我們應(yīng)該選擇最長(zhǎng)的區(qū)塊鏈逛揩。 在以下示例中,區(qū)塊72:a350235b00不會(huì)區(qū)塊鏈中,因?yàn)樗鼘⒂砷L(zhǎng)鏈覆蓋。
以下是代碼實(shí)現(xiàn):
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');
}
};
節(jié)點(diǎn)間的通信
一個(gè)節(jié)點(diǎn)的基本功能是與其他節(jié)點(diǎn)共享并同最新的區(qū)塊鏈麸俘,下面的規(guī)則是用于同步辩稽。
- 當(dāng)一個(gè)節(jié)點(diǎn)生成一個(gè)新的塊,將它廣播到網(wǎng)絡(luò)中
- 當(dāng)一個(gè)節(jié)點(diǎn)連接到一個(gè)新的廣播時(shí)同步最新的區(qū)塊塊
-
當(dāng)一個(gè)節(jié)點(diǎn)遇到一塊索引大于當(dāng)前已知的塊,將放棄當(dāng)前的鏈并同步最新的鏈。
我們將使用websockets作為點(diǎn)對(duì)點(diǎn)通信从媚。 積極為每個(gè)節(jié)點(diǎn)存儲(chǔ)在套接字const sockets: WebSocket[]變量逞泄。 不使用自動(dòng)對(duì)等的發(fā)現(xiàn)。 同行的位置(= Websocket url)必須手動(dòng)添加。
控制節(jié)點(diǎn)
用戶必須通過(guò)一些方式控制節(jié)點(diǎn)喷众,這是設(shè)置一個(gè)http服務(wù)器各谚。
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);
});
};
可以看到,用戶可以通過(guò)節(jié)點(diǎn)實(shí)現(xiàn)以下功能
- 列出所有塊
- 創(chuàng)建一個(gè)新的塊內(nèi)容
- 列表或添加平行節(jié)點(diǎn)
最直接的方式通過(guò)curl來(lái)控制節(jié)點(diǎn)
#get all blocks from the node
> curl http://localhost:3001/blocks
體系結(jié)構(gòu)
應(yīng)該注意的是,實(shí)際上暴露了兩個(gè)web服務(wù)器的節(jié)點(diǎn):一個(gè)用于用戶控制的節(jié)點(diǎn)(HTTP服務(wù)器)和一個(gè)對(duì)等節(jié)點(diǎn)之間的通信到千。 (Websocket HTTP服務(wù)器)
[圖片上傳失敗...(image-ddda7f-1523368323591)]