從零開始構建一個區塊鏈(三): API
一、挖礦獎勵
在開始改造我們的程式碼之前,我們先來看一下什麼是挖礦獎勵。
上一篇專欄講解了挖礦的原理並且實現了POW演算法,可是伺服器為什麼願意去耗費自己的CPU資源來打包區塊呢?答案就是挖礦時有一個獎勵機制。礦工在打包一個時間段的交易後,會在區塊的第一筆交易的位置建立一筆新的交易。這筆交易沒有傳送人,接收人可以設為任何人(一般就是自己啦),獎勵的數額是多少呢?目前比特幣中是12。5個BTC。這筆獎勵交易是由系統保證的,可以透過任何一個其他節點的驗證。
這裡面幾個問題。首先是獎勵金額的問題。比特幣剛開始發行時,每個區塊的獎勵是50BTC,其後每隔四年時間減半,今年剛剛減半到12。5個了。另外一個是礦工能否建立多比獎勵交易或者加大獎勵金額?礦工當然可以這麼幹,但是這麼做以後廣播出去的區塊是無法透過其它節點驗證的,因此其他節點收到區塊後會丟棄該區塊,而該區塊最終也不會被新增到
區塊鏈
中。
二、程式碼重構
為了把我們當前的程式碼改造成適合透過API對外提供的形式,我們需要做幾個處理:
1。 在Blockchain類中新增屬性currentTransactions,由於收集最新交易,並且準備打包到
下一個
區塊中。
constructor() {
this。chain = [this。createGenesisBlock()];
this。difficulty = 3;
this。currentTransactions = [];
}
2。 把Block類中的addNewTransaction方法移到Blockchain類裡。
3。 把Block類和Blockchain類export出去,重新命名app。js為
blockchain。js
。
最後的blockchain。js應該為:
const SHA256 = require(‘
crypto-js
/sha256’);
class Block {
constructor(index, timestamp) {
this。index = index;
this。timestamp = timestamp;
this。transactions = [];
this。previousHash = ‘’;
this。hash = this。calculateHash();
this。nonce = 0;
}
calculateHash() {
return SHA256(this。index + this。previousHash + this。timestamp + JSON。stringify(this。transactions) + this。nonce)。toString();
}
mineBlock(difficulty) {
console。log(`Mining block ${this。index}`);
while (this。hash。substring(0, difficulty) !== Array(difficulty + 1)。join(“0”)) {
this。nonce++;
this。hash = this。calculateHash();
}
console。log
(“BLOCK MINED: ” + this。hash);
}
getTransactions() {
return this。transactions;
}
}
class Blockchain {
constructor() {
this。chain = [this。createGenesisBlock()];
this。difficulty = 3;
this。currentTransactions = [];
}
addNewTransaction(sender, recipient, amount) {
this。currentTransactions。push({
sender,
recipient,
amount
})
}
createGenesisBlock() {
const genesisBlock = new Block(0, “01/10/2017”);
genesisBlock。previousHash = ‘0’;
genesisBlock。transactions。push({
sender: ‘Leo’,
recipient: ‘Janice’,
amount: 520
})
return genesisBlock;
}
getLatestBlock() {
return this。chain[this。chain。length - 1];
}
addBlock(newBlock) {
newBlock。previousHash = this。getLatestBlock()。hash;
newBlock。mineBlock(this。difficulty);
this。chain。push(newBlock);
}
isChainValid() {
for (let i = 1; i < this。chain。length; i++){
const currentBlock = this。chain[i];
const previousBlock = this。chain[i - 1];
if(currentBlock。hash !== currentBlock。calculateHash()){
return false;
}
if(currentBlock。previousHash !== previousBlock。hash){
return false;
}
}
return true;
}
}
module。exports = {
Block,
Blockchain
}
注意上面順便修改了Blockchain裡的
方法createGenesisBloc
k的程式碼。
三、 使用Express提供API服務
為了能夠提供API服務,這裡我們採用Node。js中最流行的Express框架,對外提供三個介面:
/transactions/new 新增新的交易,格式為JSON;
/mine 打包目前的交易到新的區塊
/chain 返回當前的區塊鏈
基礎程式碼如下:
const express = require(‘express’);
const uuidv4 = require(‘uuid/v4’);
const Blockchain = require(‘。/blockchain’)。Blockchain;
const port = process。env。PORT || 3000;
const app = express();
const nodeIdentifier = uuidv4();
const testCoin = new Blockchain();
app。get(‘/mine’, (req, res) => {
res。send(“We‘ll mine a new block。”);
});
app。post(’/transactions/new‘, (req, res) => {
res。send(“We’ll add a new transaction。”)
});
app。get(‘/chain’, (req, res) => {
const response = {
chain: testCoin。chain,
length: testCoin。chain。length
}
res。send(response);
})
app。listen(port, () => {
console。log(`Server is up on port ${port}`);
});
下面我們完善路由‘/mine’以及‘/transactions/new’,並新增一些日誌的功能(非必需)。
先來看路由/transactions/new,在這個介面中,我們接受一個JSON格式的交易,內容為
{
“sender”: “my address”,
“recipient”: “someone else‘s address”,
“amount”: 5
}
然後把該交易新增到當前區塊鏈的currentTransactions中。這裡會利用到
body-parser
模組,最後的程式碼為:
const bodyParser = require(“body-parser”);
const jsonParser = bodyParser。json();
app。post(’/transactions/new‘, jsonParser, (req, res) => {
const newTransaction = req。body;
testCoin。addNewTransaction(newTransaction)
res。send(`The transaction ${JSON。stringify(newTransaction)} is successfully added to the blockchain。`);
});
接下來是路由/mine。該介面實現的功能是收集當前未被打包的交易,打包到一個新的區塊中;新增獎勵交易(這裡設定為50,接收地址為uuid);進行符合難度要求的挖礦,返回新區塊資訊。程式碼實現如下:
app。get(’/mine‘, (req, res) => {
const latestBlockIndex = testCoin。chain。length;
const newBlock = new Block(latestBlockIndex, new Date()。toString());
newBlock。transactions = testCoin。currentTransactions;
// Get a reward for mining
the new block
newBlock。transactions。unshift({
sender: ’0‘,
recipient: nodeIdentifier,
amount: 50
})
testCoin。addBlock(newBlock);
testCoin。currentTransactions = [];
res。send(`Mined new block ${JSON。stringify(newBlock, undefined, 2)}`);
});
至此程式碼基本完成,最後我們新增一個記錄日誌的中介軟體
app。use((req, res, next) => {
var now = new Date()。toString();
var log = `${now}: ${req。method} ${req。url}`;
console。log(log);
fs。appendFile(’server。log‘, log + ’\n‘, (err) => {
if (err) console。log(err);
});
next();
})
完整程式碼請參考Github liangpeili/testcoin
四、 測試API
使用node server。js啟動應用,我們使用Postman來對當前的API進行測試。
在啟動應用後,當前區塊鏈應該只有一個創世區塊,我們使用/chain來獲取當前區塊鏈資訊;
可以看到當前區塊鏈只有一個區塊。那怎麼新增新的交易呢?我們使用以下方式:
把交易以JSON的形式新增到請求的body中,應該會返回以下結果:
接下來我們可以進行挖礦了:把當前的交易打包到新的區塊,並給自己分配獎勵。這次我們使用/mine介面。
返回結果如下:
可以看到交易已經被打包到新的區塊中了。新的區塊中包含一筆獎勵交易,難度也符合要求(連續3個0)。
至此三個介面全部工作正常,我們也可以繼續新增交易、挖礦,一直進行下去。
有人會問:如果不新增交易是否可以挖礦呢?答案是Yes!一般在一個區塊鏈專案的早期,交易的數量可能一天也沒有幾筆。但是挖礦的工作是要一直進行下去的,只不過每個區塊除了獎勵交易再沒有其他了,這種區塊一般被成為“空塊”。在我們這裡也可以實現,不新增交易,直接呼叫mine介面:
此時再檢視區塊鏈資訊,就可以看到剛剛的兩個區塊了。
參考:
https://
hackernoon。com/learn-bl
ockchains-by-building-one-117428612f46