前面我们搭建了一个简单的私链,这一节我们看看如何与区块链进行交互。
我们这里使用Express来搭建我们的服务端应用。
Express是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。
使用 Express 可以快速地搭建一个完整功能的网站。
const bodyParser = require('body-parser'); //HTTP请求体解析中间件
const express = require('express')
const app = express()
app.use(bodyParser.json());
app.listen(8000, () => console.log('app listening on:localhost:8000...\nblockchain is running...'))
这是一个基于node.js的基本服务框架,下面用它来链接我们的区块链。
// import block and blockchain object
let Block = require("./block").block
let Blockchain = require("./blockchain").blockchain
// create blockchain
let blockchain = Blockchain().instance;
第一步、获取权限
在我们要在改变区块链上的数据之前,首先要获得账户权限。此时,我们需要post我们的地址,用来获得许可。这个许可不是永久的,需要在一定时间内完成上传数据,否则需要重新申请权限。
// story requests in app locals
// validateReq的key上address,value上数组,数组的第一个元素是认证窗口剩余时间,第二个元素是时间戳
app.locals.validateReq = {};
// 对validateReq的每个address的时间窗口进行倒计时
let count = function(obj) {
let countDown = function(){
Object.keys(obj).forEach(key => { // 返回一个所有元素为字符串的数组
obj[key][0]--;
if(obj[key][0] == 0) {
delete obj[key]; // 删除对象属性
}
});
}
setInterval(countDown, 1000);
}
// auto run count
count(app.locals.validateReq);
app.post('/requestValidation',(req,res) => {
let { validateReq } = req.app.locals; // 中间件使用locals可以使用req.app.locals
let { address } = req.body;
let validationWindow;
let timeStamp;
if(validateReq[address]) {
validationWindow = validateReq[address][0];
timeStamp = validateReq[address][1];
} else {
validationWindow = 300; // 初始化认证窗口为300秒
timeStamp = new Date().getTime().toString().slice(0,-3);
validateReq[address] = [validationWindow, timeStamp]
}
res.send({
"address":address,
"requestTimeStamp":timeStamp,
"message":`${address}:${timeStamp}:starRegistry`,
"validationWindow":validationWindow
})
})
第二步,验证信息签名
这里我们需要用到比特币里的签名和验证的方法,所以需要引入bitcoinjs-lib和bitcoin-message两个库。上传数据时,我们要使用bitcoin-core客户端来生成签名,我们的web app会验证我们上传的签名是否正确。如果正确,才能上传数据,添加新区块。
const bitcoin = require("bitcoinjs-lib");
const bitcoinMessage = require("bitcoinjs-message");
// story requests in app locals
app.locals.validateReq = {};
app.locals.validateAddress = {};
app.post('/message-signature/validate', (req, res) => {
let { validateReq, validateAddress } = req.app.locals;
let { address, signature } = req.body; // 申请验证时需要有签名信息
if(validateReq[address]) {
let timeStamp = validateReq[address][1];
let message = `${address}:${timeStamp}:starRegistry`;
let validationWindow = validateReq[address][0];
// 对签名进行验证,成功则标记为valid
let registerStar = bitcoinMessage.verify(message, address, signature);
let messageSignature;
if(registerStar){
messageSignature = 'valid';
validateAddress[address] = true;
} else {
messageSignature = 'invalid'
}
res.send({
"registerStar": registerStar,
"status": {
"address": address,
"requestTimeStamp": timeStamp,
"message": message,
"validationWindow": validationWindow,
"messageSignature": messageSignature
}
})
} else {
res.send( `Address ${address} not found`)
}
})
第三步,添加新区块
我们通过web服务来添加新的区块,我们这里的区块body储存的数据结构如下:
{
"address": "142BDCeSGbXjWKaAnYXbMpZ6sbrSAo3DpZ",
"star": {
"dec": "14° 20'\'' 12.2",
"ra": "13h 34m 1.3s",
"story": "a test star"
}
上面数据包括上传的账户地址以及star的坐标以及story,现在让我们通过一个post请求来上传数据。
app.post('/block', (req, res) => {
let { address, star } = req.body;
let { validateAddress } = req.app.locals;
// 匹配所有双字节字符
let reg = /[\x00-\xff]+/g;
if(star && star.ra && star.dec && reg.test(star.story) && Buffer.byteLength(star.story) <= 500) {
if (validateAddress[address]) {
let { ra, dec } = star;
let story = Buffer(star.story).toString('hex'); //strTohex(star.story)
let body = {
address,
star:{ra, dec, story}
}
let newBlock = new Block(body)
blockchain.addBlock(newBlock).then(()=>{
blockchain.getBlockHeight().then((value) => {
blockchain.getBlockByHeight(value).then((value) => {
res.send(value)
})
})
});
// 删除认证的账号
delete validateAddress[address];
delete validateReq[address];
} else {
res.send(`Address ${address} not found in validated addresses`)
}
} else {
res.send('incorret star contents')
}
})
现在,我们可以实现通过height来获取区块,即访问http://localhost:8000/block/{height}来获得区块数据的返回值。
app.get('/block/:height', (req, res) => {
let { height } = req.params;
blockchain.getBlock(height).then((value) => {
if(value.height != 0){
value.body.star.storyDecoded = Buffer(value.body.star.story, 'hex').toString(); //hexTostring(value.body.star.story)
res.send(value)
} else {
res.send(value)
}
}).catch(err => res.send(`The current block height is less than ${height}`))
})
然后通过hash来获取区块,即访问http://localhost:8000/block/{hash}来获得区块数据的返回值。
app.get('/block/:hash', (req, res) => {
let { hash } = req.params;
blockchain.getBlockByHash(hash).then((value) => {
if(value.body.star){
value.body.star.storyDecoded = Buffer(value.body.star.story, 'hex').toString();
res.send(value)
} else {
res.send(value)
}
}).catch(err => {res.send( `hash ${hash} not found in blockchain` )})
})
至此,我们实现了在web端与区块链的交互。