add contract logs

This commit is contained in:
Jonathan Rainville 2018-08-09 15:33:07 -04:00 committed by Pascal Precht
parent c7f9a374f2
commit 031ccc37ee
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
10 changed files with 245 additions and 102 deletions

View File

@ -76,6 +76,13 @@ export const processLogs = {
failure: (error) => action(PROCESS_LOGS[FAILURE], {error})
};
export const CONTRACT_LOGS = createRequestTypes('CONTRACT_LOGS');
export const contractLogs = {
request: () => action(CONTRACT_LOGS[REQUEST]),
success: (contractLogs) => action(CONTRACT_LOGS[SUCCESS], {contractLogs}),
failure: (error) => action(CONTRACT_LOGS[FAILURE], {error})
};
export const CONTRACTS = createRequestTypes('CONTRACTS');
export const contracts = {
request: () => action(CONTRACTS[REQUEST]),

View File

@ -56,6 +56,10 @@ export function fetchProcessLogs(payload) {
return get(`/process-logs/${payload.processName}`);
}
export function fetchContractLogs() {
return get(`/contracts/logs`);
}
export function fetchContracts() {
return get('/contracts');
}

View File

@ -8,6 +8,7 @@ import {
} from "tabler-react";
import ContractContainer from '../containers/ContractContainer';
import ContractLoggerContainer from '../containers/ContractLoggerContainer';
import ContractProfileContainer from '../containers/ContractProfileContainer';
const ContractLayout = (props) => (
@ -48,11 +49,20 @@ const ContractLayout = (props) => (
>
Profile
</List.GroupItem>
<List.GroupItem
className="d-flex align-items-center"
to={`/embark/contracts/${props.match.params.contractName}/logger`}
icon="chevrons-right"
RootComponent={withRouter(NavLink)}
>
Logger
</List.GroupItem>
</List.Group>
</div>
</Grid.Col>
<Grid.Col md={9}>
<Switch>
<Route exact path="/embark/contracts/:contractName/logger" component={ContractLoggerContainer} />
<Route exact path="/embark/contracts/:contractName/profiler" component={ContractProfileContainer} />
<ContractContainer />
</Switch>

View File

@ -0,0 +1,47 @@
import PropTypes from "prop-types";
import React from 'react';
import {
Page,
Grid, Table
} from "tabler-react";
const ContractLogger = ({contractName, contractLogs}) => (
<Page.Content title={contractName + ' Logger'}>
<Grid.Row>
<Grid.Col>
<Table
responsive
className="card-table table-vcenter text-nowrap"
headerItems={[
{content: "Function name"},
{content: "Params"},
{content: "Transaction hash"},
{content: "Gas Used"},
{content: "Block number"},
{content: "Status"}
]}
bodyItems={
contractLogs.map((log) => {
return ([
{content: log.functionName},
{content: log.paramString},
{content: log.transactionHash},
{content: log.gasUsed},
{content: log.blockNumber},
{content: log.status}
]);
})
}
/>
</Grid.Col>
</Grid.Row>
</Page.Content>
);
ContractLogger.propTypes = {
contractName: PropTypes.string.isRequired,
contractLogs: PropTypes.array.isRequired
};
export default ContractLogger;

View File

@ -0,0 +1,42 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {withRouter} from 'react-router-dom';
import {contractLogs as contractLogsAction} from '../actions';
import ContractLogger from '../components/ContractLogger';
// import DataWrapper from "../components/DataWrapper";
import {getContractLogsByContract} from "../reducers/selectors";
class ContractProfileContainer extends Component {
componentDidMount() {
if (this.props.contractLogs.length === 0) {
this.props.fetchContractLogs(this.props.match.params.contractName);
}
}
render() {
return (
<ContractLogger contractLogs={this.props.contractLogs} contractName={this.props.match.params.contractName}/>
);
}
}
function mapStateToProps(state, props) {
return {
contractLogs: getContractLogsByContract(state, props.match.params.contractName)
};
}
ContractProfileContainer.propTypes = {
contractLogs: PropTypes.array,
fetchContractLogs: PropTypes.func,
match: PropTypes.object
};
export default withRouter(connect(
mapStateToProps,
{
fetchContractLogs: contractLogsAction.request
}
)(ContractProfileContainer));

View File

@ -11,6 +11,7 @@ const entitiesDefaultState = {
processLogs: [],
contracts: [],
contractProfiles: [],
contractLogs: [],
commands: [],
messages: [],
messageChannels: [],
@ -27,6 +28,9 @@ const sorter = {
processLogs: function(a, b) {
return a.timestamp - b.timestamp;
},
contractLogs: function(a, b) {
return a.timestamp - b.timestamp;
},
messages: function(a, b) {
return a.time - b.time;
}

View File

@ -46,6 +46,11 @@ export function getProcessLogsByProcess(state, processName) {
return state.entities.processLogs.filter((processLog => processLog.name === processName));
}
export function getContractLogsByContract(state, contractName) {
return state.entities.contractLogs;
// return state.entities.processLogs.filter((processLog => processLog.name === processName));
}
export function getContracts(state) {
return state.entities.contracts;
}

View File

@ -4,7 +4,7 @@ import {eventChannel} from 'redux-saga';
import {all, call, fork, put, takeEvery, take} from 'redux-saga/effects';
const {account, accounts, block, blocks, transaction, transactions, processes, commands, processLogs,
contracts, contract, contractProfile, messageSend, messageVersion, messageListen} = actions;
contracts, contract, contractProfile, messageSend, messageVersion, messageListen, contractLogs} = actions;
function *doRequest(entity, apiFn, payload) {
const {response, error} = yield call(apiFn, payload);
@ -24,6 +24,7 @@ export const fetchTransactions = doRequest.bind(null, transactions, api.fetchTra
export const fetchProcesses = doRequest.bind(null, processes, api.fetchProcesses);
export const postCommand = doRequest.bind(null, commands, api.postCommand);
export const fetchProcessLogs = doRequest.bind(null, processLogs, api.fetchProcessLogs);
export const fetchContractLogs = doRequest.bind(null, contractLogs, api.fetchContractLogs);
export const fetchContracts = doRequest.bind(null, contracts, api.fetchContracts);
export const fetchContract = doRequest.bind(null, contract, api.fetchContract);
export const fetchContractProfile = doRequest.bind(null, contractProfile, api.fetchContractProfile);
@ -64,6 +65,10 @@ export function *watchFetchProcessLogs() {
yield takeEvery(actions.PROCESS_LOGS[actions.REQUEST], fetchProcessLogs);
}
export function *watchFetchContractLogs() {
yield takeEvery(actions.CONTRACT_LOGS[actions.REQUEST], fetchContractLogs);
}
export function *watchFetchContract() {
yield takeEvery(actions.CONTRACT[actions.REQUEST], fetchContract);
}
@ -146,6 +151,7 @@ export default function *root() {
fork(watchFetchAccount),
fork(watchFetchProcesses),
fork(watchFetchProcessLogs),
fork(watchFetchContractLogs),
fork(watchListenToProcessLogs),
fork(watchFetchBlock),
fork(watchFetchTransactions),

View File

@ -8,120 +8,122 @@ let receipts = {};
const {canonicalHost, defaultHost} = require('../utils/host');
const parseRequest = function(reqBody){
let jsonO;
try {
jsonO = JSON.parse(reqBody);
} catch(e){
return; // Request is not a json. Do nothing
}
if(jsonO.method === "eth_sendTransaction"){
commList[jsonO.id] = {
type: 'contract-log',
address: jsonO.params[0].to,
data: jsonO.params[0].data
};
} else if(jsonO.method === "eth_getTransactionReceipt"){
if(transactions[jsonO.params[0]]){
transactions[jsonO.params[0]].receiptId = jsonO.id;
receipts[jsonO.id] = transactions[jsonO.params[0]].commListId;
}
const parseRequest = function(reqBody) {
let jsonO;
try {
jsonO = JSON.parse(reqBody);
} catch (e) {
return; // Request is not a json. Do nothing
}
if (jsonO.method === "eth_sendTransaction") {
commList[jsonO.id] = {
type: 'contract-log',
address: jsonO.params[0].to,
data: jsonO.params[0].data
};
} else if (jsonO.method === "eth_getTransactionReceipt") {
if (transactions[jsonO.params[0]]) {
transactions[jsonO.params[0]].receiptId = jsonO.id;
receipts[jsonO.id] = transactions[jsonO.params[0]].commListId;
}
}
};
const parseResponse = function(ipc, resBody){
let jsonO;
try {
jsonO = JSON.parse(resBody);
} catch(e) {
return; // Response is not a json. Do nothing
const parseResponse = function(ipc, resBody) {
let jsonO;
try {
jsonO = JSON.parse(resBody);
} catch (e) {
return; // Response is not a json. Do nothing
}
if (commList[jsonO.id]) {
commList[jsonO.id].transactionHash = jsonO.result;
transactions[jsonO.result] = {commListId: jsonO.id};
} else if (receipts[jsonO.id] && jsonO.result && jsonO.result.blockNumber) {
commList[receipts[jsonO.id]].blockNumber = jsonO.result.blockNumber;
commList[receipts[jsonO.id]].gasUsed = jsonO.result.gasUsed;
commList[receipts[jsonO.id]].status = jsonO.result.status;
if (ipc.connected && !ipc.connecting) {
ipc.request('log', commList[receipts[jsonO.id]]);
} else {
ipc.connecting = true;
ipc.connect(() => {
ipc.connecting = false;
});
}
if(commList[jsonO.id]){
commList[jsonO.id].transactionHash = jsonO.result;
transactions[jsonO.result] = {commListId: jsonO.id};
} else if(receipts[jsonO.id] && jsonO.result && jsonO.result.blockNumber){
commList[receipts[jsonO.id]].blockNumber = jsonO.result.blockNumber;
commList[receipts[jsonO.id]].gasUsed = jsonO.result.gasUsed;
commList[receipts[jsonO.id]].status = jsonO.result.status;
if(ipc.connected && !ipc.connecting){
ipc.request('log', commList[receipts[jsonO.id]]);
} else {
ipc.connecting = true;
ipc.connect(() => {
ipc.connecting = false;
});
}
delete transactions[commList[receipts[jsonO.id]].transactionHash];
delete receipts[jsonO.id];
delete commList[jsonO.id];
}
delete transactions[commList[receipts[jsonO.id]].transactionHash];
delete receipts[jsonO.id];
delete commList[jsonO.id];
}
};
exports.serve = function(ipc, host, port, ws){
let proxy = httpProxy.createProxyServer({
target: {
host: canonicalHost(host),
port: port + constants.blockchain.servicePortOnProxy
},
ws: ws
exports.serve = function(ipc, host, port, ws) {
let proxy = httpProxy.createProxyServer({
target: {
host: canonicalHost(host),
port: port + constants.blockchain.servicePortOnProxy
},
ws: ws
});
proxy.on('error', function(e) {
console.error(__("Error forwarding requests to blockchain/simulator"), e.message);
});
proxy.on('proxyRes', (proxyRes) => {
let resBody = [];
proxyRes.on('data', (b) => resBody.push(b));
proxyRes.on('end', function() {
resBody = Buffer.concat(resBody).toString();
if (resBody) {
parseResponse(ipc, resBody);
}
});
});
proxy.on('error', function (e) {
console.error(__("Error forwarding requests to blockchain/simulator"), e.message);
});
proxy.on('proxyRes', (proxyRes) => {
let resBody = [];
proxyRes.on('data', (b) => resBody.push(b));
proxyRes.on('end', function () {
resBody = Buffer.concat(resBody).toString();
if(resBody){
parseResponse(ipc, resBody);
}
});
});
let server = http.createServer((req, res) => {
let reqBody = [];
req.on('data', (b) => { reqBody.push(b); })
.on('end', () => {
reqBody = Buffer.concat(reqBody).toString();
if(reqBody){
parseRequest(reqBody);
}
});
if(!ws){
proxy.web(req, res);
let server = http.createServer((req, res) => {
let reqBody = [];
req.on('data', (b) => {
reqBody.push(b);
})
.on('end', () => {
reqBody = Buffer.concat(reqBody).toString();
if (reqBody) {
parseRequest(reqBody);
}
});
if (!ws) {
proxy.web(req, res);
}
});
if (ws) {
const WsParser = require('simples/lib/parsers/ws'); // npm install simples
server.on('upgrade', function(req, socket, head) {
proxy.ws(req, socket, head);
});
if(ws){
const WsParser = require('simples/lib/parsers/ws'); // npm install simples
proxy.on('open', (proxySocket) => {
proxySocket.on('data', (data) => {
parseResponse(ipc, data.toString().substr(data.indexOf("{")));
});
});
server.on('upgrade', function (req, socket, head) {
proxy.ws(req, socket, head);
});
proxy.on('proxyReqWs', (proxyReq, req, socket) => {
var parser = new WsParser(0, false);
socket.pipe(parser);
parser.on('frame', function(frame) {
parseRequest(frame.data);
});
proxy.on('open', (proxySocket) => {
proxySocket.on('data', (data) => {
parseResponse(ipc, data.toString().substr(data.indexOf("{")));
});
});
});
}
proxy.on('proxyReqWs', (proxyReq, req, socket) => {
var parser = new WsParser(0, false);
socket.pipe(parser);
parser.on('frame', function (frame) {
parseRequest(frame.data);
});
});
}
server.listen(port, defaultHost);
return server;
server.listen(port, defaultHost);
return server;
};

View File

@ -2,10 +2,12 @@ const utils = require('../../utils/utils.js');
class ConsoleListener {
constructor(embark, options) {
this.embark = embark;
this.logger = embark.logger;
this.ipc = options.ipc;
this.events = embark.events;
this.addressToContract = [];
this.logs = [];
this.contractsConfig = embark.config.contractsConfig;
this.contractsDeployed = false;
this.outputDone = false;
@ -13,6 +15,7 @@ class ConsoleListener {
this.events.on('outputDone', () => {
this.outputDone = true;
});
this._registerAPI();
this.events.on("contractsDeployed", () => {
this.contractsDeployed = true;
@ -91,12 +94,25 @@ class ConsoleListener {
gasUsed = utils.hexToNumber(gasUsed);
blockNumber = utils.hexToNumber(blockNumber);
this.logs.push(Object.assign({}, request, {name, functionName, paramString}));
this.logger.info(`Blockchain>`.underline + ` ${name}.${functionName}(${paramString})`.bold + ` | ${transactionHash} | gas:${gasUsed} | blk:${blockNumber} | status:${status}`);
} else {
this.logger.info(JSON.stringify(request));
}
});
}
_registerAPI() {
const apiRoute = '/embark-api/contracts/logs';
this.embark.registerAPICall(
'get',
apiRoute,
(req, res) => {
res.send(JSON.stringify(this.logs));
}
);
}
}
module.exports = ConsoleListener;