本篇使用React框架结合Web.j3从头开始一步步开发一个区块链浏览器。区块链浏览器通常用于查询区块信息、交易信息等。虽名为“浏览器”,但区块链浏览器与常用的Chrome、Safari等网页浏览器中的“浏览器”并不是一个意思,只是可以运行在网页浏览器或Native APP里Webview中的一系列网页。代码已开源至https://github.com/yangj96/blockchain-explorer
常见的面向公有链的区块链浏览器有:
很多常见的区块链浏览器同时也提供查询公有链区块、交易等信息的第三方API,可以很方便地调用开发。与上述面向公网的区块链浏览器不同,本文的目标是开发构建一个面向自己的私有链的区块链浏览器,因此我们开发前需要先搭建一条用于启动服务的以太坊私有链。当然,如果不想使用本地以太坊节点也可以安装MetaMask插件使用其提供的测试网络。
搭建私有链的步骤首先需安装Geth客户端,定义自己的创世区块的json配置文件,然后初始化区块链将创世区块信息写入到区块链中。私有链创建成功后就可以在终端启动自己的私有链节点并进行创建区块、查询账户余额、挖矿、发送交易、查询区块和查询交易等操作。此外,还可以搭建多节点的私有链,如果你已经熟悉上述操作和步骤,接下来我们开发针对自己私有链的区块链浏览器,如果还不熟悉上述过程,可以参考以太坊私有链搭建的相关内容进行学习。
初始化项目
首先通过Facebook发布的create-react-app脚手架工具来快速初始化一个React项目。在终端的项目存放路径下执行如下命令,如果在过程中出现网络问题,请尝试配置代理或使用其他 npm registry。
npm install -g create-react-app
create-react-app blockchain-explorer
如果命令执行成功,将看到如下提示:
Success! Created blockchain-explorer at /Users/jingy/blockchain-explorer
Inside that directory, you can run several commands:
npm start
Starts the development server.
npm run build
Bundles the app into static files for production.
npm test
Starts the test runner.
npm run eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you can’t go back!
We suggest that you begin by typing:
cd blockchain-explorer
npm start
Happy hacking!
其中包括项目运行、打包、测试的命令,值得注意的是最后一个命令npm run eject
。它的作用是把项目打包所需要的配置文件弹(eject)出来,从而可以自定义修改这些配置文件。默认生成的项目中配置文件是被隐藏,默认项目目录结构如下:
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ └── logo.svg
为便于后续更自由地对项目配置进行修改扩展,我们切换到新建的项目路径下执行npm run eject
命令,该命令执行后不可恢复,项目目录结构变为:
├── README.md
├── package.json
├── config
│ ├── env.js
│ ├── paths.js
│ ├── webpack.config.dev.js
│ ├── webpack.config.prod.js
│ └── webpackDevServer.config.js
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── scripts
│ ├── build.js
│ ├── start.js
│ └── test.js
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ └── logo.svg
其中,增加了两个文件夹:config和scripts,里面存放着打包相关的配置文件和流程控制文件。至此,项目的初始化工作完成,接下来我们一步步完善它。
完善项目配置
添加对antd的支持
项目开发中必然会需要用到各类组件,引入第三方组件库可以节约开发成本,因此这里添加蚂蚁金服团队所开发的antd组件库。如果选择其它组件库,也可以自行参照其官方文档引入。
首先在项目根目录安装antd npm install antd --save
,但默认情况下项目运行时会加载全部的 antd 组件的样式,影响前端性能,因此使用 babel-plugin-import插件实现的组件代码和样式的按需加载。
- 安装 babel-plugin-import:
npm install babel-plugin-import --save
- 在上述弹出的配置项文件中进行相应更改
在npm run eject
弹出配置项后生成的config文件夹中包括dev模式和prod模式的配置文件,我们首先修改 webpack.config.dev.js,prod模式与dev模式的配置可采用同样的修改:
- 在webpack.config.dev.js文件的下列代码位置的plugins中:
// Process application JS with Babel.
// The preset includes JSX, Flow, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent: '@svgr/webpack?-prettier,-svgo![path]',
},
},
},
],
],
- 添加以下代码
plugins: [
['import', {
libraryName: 'antd',
style: 'css'
}]
]
路由配置
路由配置使用react-router完成。实际上根据react-router的最新版本的重构更新,路由同样变为React的组件,从而能够在组件渲染时进行路由的动态映射,已不再需要所谓的路由配置。但由于react-router版本从v3到v4的更新不只是简单的API改变,而是整个设计哲学的改变,同时也带了很多负面的声音,例如关于取消嵌套路由,完全放弃对服务器端渲染异步组件的支持等。因此,在本教程中,我们依然使用react-router的v3版本实现路由配置。在安装时指定版本3.2.1: npm install --save react-router@3.2.1
安装好react-router后,我们在新建三个组件Home,SearchBlock和SearchTransaction,然后修改App.js文件使得默认访问主域名的时候渲染Home组件,并声明嵌套路由,访问/searchBlocks和/searchTransaction时分别渲染SearchBlock和SearchTransaction组件以查询最新区块和交易信息。
import React, { Component } from 'react';
import { browserHistory, Route, Router } from 'react-router';
import Home from './components/Home';
import SearchBlock from "./components/SearchBlock";
import SearchTransaction from "./components/SearchTransaction";
class App extends Component {
render() {
return (
<Router history={browserHistory} >
<Route path="/" component={Home}>
<Route path="searchBlock" component={SearchBlock}/>
<Route path="searchTransaction" component={SearchTransaction}/>
</Route>
</Router>
);
}
}
至此,一个基本的项目框架搭建完成。在此基础上,我们结合Web3.js框架开始进行应用的具体组件的开发。
具体组件开发
React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样在页面中插入组件。本地组件的类和 HTML 标签使用大、小写的约定来区分,组件类的第一个字母必须大写。除此之外,与HTML属性不同的是,组件属性声明时,class 属性需要写成 className,for 属性需要写成 htmlFor,因为 class 和 for 是 JavaScript 的保留字。关于React的更多信息可以参考其官方文档进一步学习。
我们首先编写Home组件,Home组件使用antd组件库中『上-中-下』的布局 搭建项目主页的整体框架,具体代码如下:
import { Layout, Menu, Breadcrumb } from 'antd';
import * as React from 'react';
import { IndexLink } from 'react-router';
import './Home.css'
const { Header, Content, Footer } = Layout;
class Home extends React.Component<> {
render() {
return (
<Layout className="layout">
<Header>
<div className="logo" >
<span>区块链浏览器</span>
</div>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['1']}
style={{lineHeight: '64px'}}
>
<Menu.Item key="1">
<IndexLink to="/searchBlock">
<span >区块查询</span>
</IndexLink>
</Menu.Item>
<Menu.Item key="2">
<IndexLink to="/searchTransaction">
<span >交易查询</span>
</IndexLink>
</Menu.Item>
</Menu>
</Header>
<Content style={{padding: '0 50px'}}>
<Breadcrumb style={{margin: '16px 0'}}>
</Breadcrumb>
<div style={{background: '#fff', padding: 24, minHeight: 280}}>
{this.props.children}
</div>
</Content>
<Footer style={{textAlign: 'center'}}>
Blockchain Explorer ©2018 Created by JING
</Footer>
</Layout>
);
}
}
export default Home;
上述代码声明了一个水平的菜单栏使用两个菜单项分页展示前面创建的区块查询和交易查询两个组件。npm start
启动项目,页面结果如下图所示:
接下来我们借助Web3.js框架实现区块的查询。首先安装web3.js: npm install web3
,web3通过RPC调用可以与任何暴露了RPC接口的区块链节点通信,因此在使用Geth终端启动服务时需要开启rpc服务,为便于之后启动节点服务,在项目根目录下新建一个starthttp.sh文件,用来放置启动本地以太坊节点服务的命令:
geth --datadir data0 --networkid 1108 --rpc --rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain="*" console
之后运行前只需执行bash starthttp.sh命令即可。
在SearchBlock组件中与本地节点通信来查询区块的信息前首先要创建一个web3的实例,设置一个provider。为了保证不会覆盖已有的provider,比如使用Mist钱包时已经内置,需要先检查web3实例是否已存在。注意创建实例时要保证与本地节点启动服务的端口或MetaMask以太坊网络环境的设置相对应。
if (typeof web3 !== 'undefined'){
web3 = new Web3(web3.currentProvider);
} else {
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
}
web3提供了getBlockNumber方法获取当前的区块数量,以及getBlock方法根据block number或block hash返回对应的区块对象。Web3.js的API默认使用同步的HTTP的请求来与本地的RPC结点交互,如果想要发起异步的请求,可以使用回调函数来支持异步。区块对象中包括区块的hash、parentHash、nonce、logsBloom、stateRoot、miner、difficulty、extraData、size、gasLimit、gasUsed、timestamp等值,获取到返回的区块对象后我们构造区块数组并将其各属性与antd的Table组件的各列分别对应显示即可。综上,SearchBlock组件的完整代码如下:
class SearchBlock extends React.Component<> {
constructor(props) {
super(props);
this.state = {
loading: true,
blocks: [],
};
}
async componentDidMount() {
if (typeof web3 !== 'undefined'){
web3 = new Web3(web3.currentProvider);
} else {
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
}
let blockNumber = 0;
await web3.eth.getBlockNumber()
.then(res => blockNumber = res)
.catch(e => console.error(e));
console.log(blockNumber);
this.getBlocks(blockNumber);
}
async getBlocks(blockNumber) {
const blocks = this.state.blocks.slice();
let currentBlockObject;
let currentBlockNumber = blockNumber;
for (let i = 0; i < blockNumber; i++, currentBlockNumber--) {
await web3.eth.getBlock(currentBlockNumber)
.then(res => currentBlockObject = res);
console.log(currentBlockObject);
blocks.push(currentBlockObject);
}
this.setState({
blocks: blocks,
loading: false
});
}
render() {
return (
<div>
<div style={{ background: '#fff', padding: 0 }}>
</div>
<div style={{position: 'relative'}}>
<Table dataSource={this.state.blocks} loading={this.state.loading} >
<Table.Column dataIndex="number" title="Height"/>
<Table.Column dataIndex="hash" title="Hash"/>
<Table.Column dataIndex="size" title="Size"/>
<Table.Column dataIndex="timestamp" title="TimeStamp"/>
<Table.Column dataIndex="gasUsed" title="GasUsed"/>
<Table.Column dataIndex="gasLimit" title="GasLimit"/>
</Table>
</div>
</div>
);
}
}
export default SearchBlock;
Geth客户端开启节点服务,npm start
启动项目,页面显示如下图所示:
至此,我们完成了区块链浏览器整体框架的搭建并实现了区块信息查询的功能,当然网站还应该提供更多链上信息的公开,例如交易信息、算力信息等等。这些实现的思路无非借助web3.js的API访问链上信息或者对web3.js API获取到信息进行加工处理,从而得到web3.js API无法直接查询到的字段,与本文介绍的区块查询异曲同工,读者可根据Web3.js的文档进一步学习,完善浏览器的更多功能。