[Blockchain_Ethereum] 从零开始开发区块链浏览器

本篇使用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插件实现的组件代码和样式的按需加载。

  1. 安装 babel-plugin-import: npm install babel-plugin-import --save
  2. 在上述弹出的配置项文件中进行相应更改
    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启动项目,页面结果如下图所示:

Home组件界面

接下来我们借助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的文档进一步学习,完善浏览器的更多功能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容