From 20bf9246877430adba150b06e00325ae91ee078a Mon Sep 17 00:00:00 2001 From: Anthony Laibe Date: Thu, 2 Aug 2018 14:40:13 +0100 Subject: [PATCH] Adding transactions explorer --- embark-ui/src/actions/index.js | 24 ++++++ embark-ui/src/api/index.js | 4 + embark-ui/src/components/ExplorerLayout.js | 10 +++ embark-ui/src/components/Transactions.js | 46 ++++++++++ embark-ui/src/containers/BlocksContainer.js | 1 - .../src/containers/TransactionsContainer.js | 63 ++++++++++++++ embark-ui/src/reducers/accountsReducer.js | 2 +- embark-ui/src/reducers/index.js | 4 +- embark-ui/src/reducers/transactionsReducer.js | 12 +++ embark-ui/src/sagas/index.js | 20 ++++- lib/modules/blockchain_connector/index.js | 84 ++++++++++++------- 11 files changed, 235 insertions(+), 35 deletions(-) create mode 100644 embark-ui/src/components/Transactions.js create mode 100644 embark-ui/src/containers/TransactionsContainer.js create mode 100644 embark-ui/src/reducers/transactionsReducer.js diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index bf5e07d5d..7104d037e 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -10,6 +10,10 @@ export const RECEIVE_PROCESSES_ERROR = 'RECEIVE_PROCESSES_ERROR'; export const FETCH_BLOCKS = 'FETCH_BLOCKS'; export const RECEIVE_BLOCKS = 'RECEIVE_BLOCKS'; export const RECEIVE_BLOCKS_ERROR = 'RECEIVE_BLOCKS_ERROR'; +// Transactions +export const FETCH_TRANSACTIONS = 'FETCH_TRANSACTIONS'; +export const RECEIVE_TRANSACTIONS = 'RECEIVE_TRANSACTIONS'; +export const RECEIVE_TRANSACTIONS_ERROR = 'RECEIVE_TRANSACTIONS_ERROR'; export function fetchAccounts() { return { @@ -68,3 +72,23 @@ export function receiveBlocksError() { type: RECEIVE_BLOCKS_ERROR }; } + +export function fetchTransactions(blockFrom) { + return { + type: FETCH_TRANSACTIONS, + blockFrom + }; +} + +export function receiveTransactions(transactions) { + return { + type: RECEIVE_TRANSACTIONS, + transactions + }; +} + +export function receiveTransactionsError() { + return { + type: RECEIVE_TRANSACTIONS_ERROR + }; +} diff --git a/embark-ui/src/api/index.js b/embark-ui/src/api/index.js index 968f99f00..704e1baa3 100644 --- a/embark-ui/src/api/index.js +++ b/embark-ui/src/api/index.js @@ -10,6 +10,10 @@ export function fetchBlocks(from) { return axios.get(`${BASE_URL}/blockchain/blocks`, {params: {from}}); } +export function fetchTransactions(blockFrom) { + return axios.get(`${BASE_URL}/blockchain/transactions`, {params: {blockFrom}}); +} + export function fetchProcesses() { return axios.get(`${BASE_URL}/processes`); } diff --git a/embark-ui/src/components/ExplorerLayout.js b/embark-ui/src/components/ExplorerLayout.js index b4b70f988..8c9d1483d 100644 --- a/embark-ui/src/components/ExplorerLayout.js +++ b/embark-ui/src/components/ExplorerLayout.js @@ -8,6 +8,7 @@ import { import AccountsContainer from '../containers/AccountsContainer'; import BlocksContainer from '../containers/BlocksContainer'; +import TransactionsContainer from '../containers/TransactionsContainer'; const ExplorerLayout = () => ( @@ -31,6 +32,14 @@ const ExplorerLayout = () => ( > Blocks + + Transactions + @@ -38,6 +47,7 @@ const ExplorerLayout = () => ( + diff --git a/embark-ui/src/components/Transactions.js b/embark-ui/src/components/Transactions.js new file mode 100644 index 000000000..257aa2740 --- /dev/null +++ b/embark-ui/src/components/Transactions.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { + Page, + Grid, + Card, + Table +} from "tabler-react"; +import PropTypes from 'prop-types'; + +const Transactions = ({transactions}) => ( + + + + + { + return ([ + {content: transaction.blockNumber}, + {content: transaction.from}, + {content: transaction.to}, + {content: transaction.to ? "Contract Call" : "Contract Creation"}, + {content: transaction.hash} + ]); + }) + } + /> + + + + +); + +Transactions.propTypes = { + transactions: PropTypes.arrayOf(PropTypes.object) +}; + +export default Transactions; diff --git a/embark-ui/src/containers/BlocksContainer.js b/embark-ui/src/containers/BlocksContainer.js index b2a5cb17a..c939dbd7a 100644 --- a/embark-ui/src/containers/BlocksContainer.js +++ b/embark-ui/src/containers/BlocksContainer.js @@ -8,7 +8,6 @@ import Loading from '../components/Loading'; import LoadMore from '../components/LoadMore'; class BlocksContainer extends Component { - componentDidMount() { if (!this.props.blocks.data) { this.props.fetchBlocks(); diff --git a/embark-ui/src/containers/TransactionsContainer.js b/embark-ui/src/containers/TransactionsContainer.js new file mode 100644 index 000000000..9298fc9f9 --- /dev/null +++ b/embark-ui/src/containers/TransactionsContainer.js @@ -0,0 +1,63 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import PropTypes from 'prop-types'; + +import {fetchTransactions} from '../actions'; +import Transactions from '../components/Transactions'; +import Loading from '../components/Loading'; +import LoadMore from '../components/LoadMore'; + +class TransactionsContainer extends Component { + componentDidMount() { + if (!this.props.transactions.data) { + this.props.fetchTransactions(); + } + } + + loadMore() { + this.props.fetchTransactions(this.loadMoreFrom()); + } + + loadMoreFrom() { + let transactions = this.props.transactions.data; + return transactions[transactions.length - 1].blockNumber - 1; + } + + render() { + const {transactions} = this.props; + if (!transactions.data) { + return ; + } + + if (transactions.error) { + return ( +

+ Error API... +

+ ); + } + + return ( + + + {(this.loadMoreFrom() > 0) ? this.loadMore()} /> : } + + ); + } +} + +function mapStateToProps(state) { + return {transactions: state.transactions}; +} + +TransactionsContainer.propTypes = { + transactions: PropTypes.object, + fetchTransactions: PropTypes.func +}; + +export default connect( + mapStateToProps, + { + fetchTransactions + }, +)(TransactionsContainer); diff --git a/embark-ui/src/reducers/accountsReducer.js b/embark-ui/src/reducers/accountsReducer.js index a89cde482..c1f76a738 100644 --- a/embark-ui/src/reducers/accountsReducer.js +++ b/embark-ui/src/reducers/accountsReducer.js @@ -3,7 +3,7 @@ import {RECEIVE_ACCOUNTS, RECEIVE_ACCOUNTS_ERROR} from "../actions"; export default function accounts(state = {}, action) { switch (action.type) { case RECEIVE_ACCOUNTS: - return Object.assign({}, state, {data: action.accounts.data}); + return {...state, data: [...state.data || [], ...action.accounts.data]}; case RECEIVE_ACCOUNTS_ERROR: return Object.assign({}, state, {error: true}); default: diff --git a/embark-ui/src/reducers/index.js b/embark-ui/src/reducers/index.js index 2ecb959e4..3f4250d4a 100644 --- a/embark-ui/src/reducers/index.js +++ b/embark-ui/src/reducers/index.js @@ -2,11 +2,13 @@ import {combineReducers} from 'redux'; import processesReducer from './processesReducer'; import accountsReducer from './accountsReducer'; import blocksReducer from './blocksReducer'; +import transactionsReducer from './transactionsReducer'; const rootReducer = combineReducers({ accounts: accountsReducer, processes: processesReducer, - blocks: blocksReducer + blocks: blocksReducer, + transactions: transactionsReducer }); export default rootReducer; diff --git a/embark-ui/src/reducers/transactionsReducer.js b/embark-ui/src/reducers/transactionsReducer.js new file mode 100644 index 000000000..36b6a4b7c --- /dev/null +++ b/embark-ui/src/reducers/transactionsReducer.js @@ -0,0 +1,12 @@ +import {RECEIVE_TRANSACTIONS, RECEIVE_TRANSACTIONS_ERROR} from "../actions"; + +export default function transactions(state = {}, action) { + switch (action.type) { + case RECEIVE_TRANSACTIONS: + return {...state, data: [...state.data || [], ...action.transactions.data]}; + case RECEIVE_TRANSACTIONS_ERROR: + return Object.assign({}, state, {error: true}); + default: + return state; + } +} diff --git a/embark-ui/src/sagas/index.js b/embark-ui/src/sagas/index.js index 7cb1d010d..c2ad5da73 100644 --- a/embark-ui/src/sagas/index.js +++ b/embark-ui/src/sagas/index.js @@ -2,6 +2,19 @@ import * as actions from '../actions'; import * as api from '../api'; import {all, call, fork, put, takeEvery} from 'redux-saga/effects'; +export function *fetchTransactions(payload) { + try { + const transactions = yield call(api.fetchTransactions, payload.blockFrom); + yield put(actions.receiveTransactions(transactions)); + } catch (e) { + yield put(actions.receiveTransactionsError()); + } +} + +export function *watchFetchTransactions() { + yield takeEvery(actions.FETCH_TRANSACTIONS, fetchTransactions); +} + export function *fetchBlocks(payload) { try { const blocks = yield call(api.fetchBlocks, payload.from); @@ -42,5 +55,10 @@ export function *watchFetchProcesses() { } export default function *root() { - yield all([fork(watchFetchAccounts), fork(watchFetchProcesses), fork(watchFetchBlocks)]); + yield all([ + fork(watchFetchAccounts), + fork(watchFetchProcesses), + fork(watchFetchBlocks), + fork(watchFetchTransactions) + ]); } diff --git a/lib/modules/blockchain_connector/index.js b/lib/modules/blockchain_connector/index.js index 1c287ca91..7e56172ec 100644 --- a/lib/modules/blockchain_connector/index.js +++ b/lib/modules/blockchain_connector/index.js @@ -336,39 +336,61 @@ class BlockchainConnector { (req, res) => { let from = parseInt(req.query.from, 10); let limit = req.query.limit || 10; - let results = []; - async.waterfall([ - function(callback) { - if (!isNaN(from)) { - return callback(); - } - self.getBlockNumber((err, blockNumber) => { - if (err) { - self.logger.error(err); - from = 0; - } else { - from = blockNumber; - } - callback(); - }); - }, - function(callback) { - async.times(limit, function(n, next) { - self.web3.eth.getBlock(from - n, function(err, block) { - if (err){ - self.logger.error(err); - return next(); - } - results.push(block); - next(); - }); - }, callback); - } - ], function () { - res.send(results); - }); + self.getBlocks(from, limit, false, res.send.bind(res)); } ); + + plugin.registerAPICall( + 'get', + '/embark-api/blockchain/transactions', + (req, res) => { + let blockFrom = parseInt(req.query.blockFrom, 10); + let blockLimit = req.query.blockLimit || 10; + self.getTransactions(blockFrom, blockLimit, res.send.bind(res)); + } + ); + } + + getTransactions(blockFrom, blockLimit, callback) { + this.getBlocks(blockFrom, blockLimit, true, (blocks) => { + let transactions = blocks.reduce((acc, block) => acc.concat(block.transactions), []); + callback(transactions); + }); + } + + getBlocks(from, limit, returnTransactionObjects, callback) { + let self = this; + let blocks = []; + async.waterfall([ + function(next) { + if (!isNaN(from)) { + return next(); + } + self.getBlockNumber((err, blockNumber) => { + if (err) { + self.logger.error(err); + from = 0; + } else { + from = blockNumber; + } + next(); + }); + }, + function(next) { + async.times(limit, function(n, eachCb) { + self.web3.eth.getBlock(from - n, returnTransactionObjects, function(err, block) { + if (err){ + self.logger.error(err); + return eachCb(); + } + blocks.push(block); + eachCb(); + }); + }, next); + } + ], function () { + callback(blocks); + }); }