refactor(@embark/console-listener): strip tx logger out of listener

Adds package embark-transaction-logger and put the tx listener there
This commit is contained in:
Jonathan Rainville 2019-08-28 09:46:08 -04:00
parent 1bd32bbbbb
commit 42673002cc
12 changed files with 355 additions and 222 deletions

View File

@ -1,6 +1,6 @@
# `embark-console-listener`
> Console listener component for Embark
> Listens to Embark logs and stores them so that they can be printed in Cockpit
Visit [embark.status.im](https://embark.status.im/) to get started with
[Embark](https://github.com/embark-framework/embark).

View File

@ -47,12 +47,7 @@
},
"dependencies": {
"@babel/runtime-corejs2": "7.3.1",
"async": "2.6.1",
"embark-core": "^4.1.0-beta.5",
"embark-i18n": "^4.1.0-beta.3",
"embark-utils": "^4.1.0-beta.5",
"ethereumjs-tx": "1.3.7",
"ethereumjs-util": "6.0.0"
"embark-process-logs-api": "^4.1.0-beta.5"
},
"devDependencies": {
"@babel/cli": "7.2.3",

View File

@ -1,229 +1,15 @@
const async = require('async');
import {__} from 'embark-i18n';
const {blockchain: blockchainConstants} = require('embark-core/constants');
import {dappPath, getAddressToContract, getTransactionParams, hexToNumber} from 'embark-utils';
const ProcessLogsApi = require('embark-process-logs-api');
const EMBARK_PROCESS_NAME = 'embark';
const Transaction = require('ethereumjs-tx');
const ethUtil = require('ethereumjs-util');
const LISTENED_METHODS = [
blockchainConstants.transactionMethods.eth_call,
blockchainConstants.transactionMethods.eth_getTransactionReceipt,
blockchainConstants.transactionMethods.eth_sendTransaction,
blockchainConstants.transactionMethods.eth_sendRawTransaction
];
class ConsoleListener {
constructor(embark, _options) {
this.embark = embark;
this.logger = embark.logger;
this.events = embark.events;
this.fs = embark.fs;
this.addressToContract = [];
this.contractsConfig = embark.config.contractsConfig;
this.contractsDeployed = false;
this.outputDone = false;
this.logFile = dappPath(".embark", "contractLogs.json");
this.processLogsApi = new ProcessLogsApi({embark: this.embark, processName: EMBARK_PROCESS_NAME, silent: false});
this.transactions = {};
this.processLogsApi = new ProcessLogsApi({embark: embark, processName: EMBARK_PROCESS_NAME, silent: false});
this._listenForLogRequests();
this._registerAPI();
this.events.on("contracts:log", this._saveLog.bind(this));
this.events.on("log", (logLevel, message) => {
this.processLogsApi.logHandler.handleLog({logLevel, message}, true);
});
this.events.on('outputDone', () => {
this.outputDone = true;
});
this.events.on("contractsDeployed", () => {
this.contractsDeployed = true;
this._getContractsList((contractsList) => {
this.addressToContract = getAddressToContract(contractsList, this.addressToContract);
});
});
this.writeLogFile = async.cargo((tasks, callback) => {
const data = this._readLogs();
tasks.forEach(task => {
data[new Date().getTime()] = task;
});
this.fs.writeJson(this.logFile, data, err => {
if (err) {
console.error(err);
}
callback();
});
});
}
_getContractsList(callback) {
this.events.request("contracts:list", (err, contractsList) => {
if (err) {
this.logger.error(__("no contracts found"));
return callback();
}
callback(contractsList);
});
}
_listenForLogRequests() {
this.events.on('deploy:contract:receipt', receipt => {
this.events.emit('contracts:log', {
name: receipt.className,
functionName: 'constructor',
paramString: '',
address: receipt.contractAddress,
status: receipt.status ? '0x1' : '0x0',
gasUsed: receipt.gasUsed,
blockNumber: receipt.blockNumber,
transactionHash: receipt.transactionHash
});
});
this.events.on('blockchain:proxy:response', this._onLogRequest.bind(this));
}
_onLogRequest(args) {
const method = args.reqData.method;
if (!this.contractsDeployed || !LISTENED_METHODS.includes(method)) {
return;
}
if (method === blockchainConstants.transactionMethods.eth_sendTransaction) {
// We just gather data and wait for the receipt
this.transactions[args.respData.result] = {
address: args.reqData.params[0].to,
data: args.reqData.params[0].data,
txHash: args.respData.result
};
return;
} else if (method === blockchainConstants.transactionMethods.eth_sendRawTransaction) {
const rawData = Buffer.from(ethUtil.stripHexPrefix(args.reqData.params[0]), 'hex');
const tx = new Transaction(rawData, 'hex');
this.transactions[args.respData.result] = {
address: '0x' + tx.to.toString('hex'),
data: '0x' + tx.data.toString('hex')
};
return;
}
let dataObject;
if (method === blockchainConstants.transactionMethods.eth_getTransactionReceipt) {
dataObject = args.respData.result;
if (!dataObject) {
return;
}
if (this.transactions[args.respData.result.transactionHash]) {
// This is the normal case. If we don't get here, it's because we missed a TX
dataObject = Object.assign(dataObject, this.transactions[args.respData.result.transactionHash]);
delete this.transactions[args.respData.result.transactionHash]; // No longer needed
}
} else {
dataObject = args.reqData.params[0];
}
const {to: address, data} = dataObject;
if (!address) {
// It's a deployment
return;
}
const contract = this.addressToContract[address];
if (!contract) {
this.logger.info(`Contract log for unknown contract: ${JSON.stringify(args)}`);
return this._getContractsList((contractsList) => {
this.addressToContract = getAddressToContract(contractsList, this.addressToContract);
});
}
const {name, silent} = contract;
if (silent && !this.outputDone) {
return;
}
let functionName, paramString;
if (!data) {
// We missed the TX
functionName = 'unknown';
paramString = 'unknown';
} else {
const txParams = getTransactionParams(contract, data);
functionName = txParams.functionName;
paramString = txParams.paramString;
}
if (method === blockchainConstants.transactionMethods.eth_call) {
const log = Object.assign({}, args, {name, functionName, paramString});
log.status = '0x1';
return this.events.emit('contracts:log', log);
}
let {transactionHash, blockNumber, gasUsed, status} = args.respData.result;
gasUsed = hexToNumber(gasUsed);
blockNumber = hexToNumber(blockNumber);
const log = Object.assign({}, args, {name, functionName, paramString, gasUsed, blockNumber});
this.events.emit('contracts:log', log);
this.logger.info(`Blockchain>`.underline + ` ${name}.${functionName}(${paramString})`.bold + ` | ${transactionHash} | gas:${gasUsed} | blk:${blockNumber} | status:${status}`);
this.events.emit('blockchain:tx', {
name: name,
functionName: functionName,
paramString: paramString,
transactionHash: transactionHash,
gasUsed: gasUsed,
blockNumber: blockNumber,
status: status
});
}
_registerAPI() {
const apiRoute = '/embark-api/contracts/logs';
this.embark.registerAPICall(
'ws',
apiRoute,
(ws, _req) => {
// FIXME this will be broken probably in the cokcpit because we don't send the same data as before
this.events.on('contracts:log', function(log) {
ws.send(JSON.stringify(log), () => {
});
});
}
);
this.embark.registerAPICall(
'get',
apiRoute,
(req, res) => {
res.send(JSON.stringify(this._getLogs()));
}
);
}
_getLogs() {
const data = this._readLogs();
return Object.values(data).reverse();
}
_saveLog(log) {
this.writeLogFile.push(log);
}
_readLogs() {
this.fs.ensureFileSync(this.logFile);
try {
return JSON.parse(this.fs.readFileSync(this.logFile));
} catch (_error) {
return {};
}
}
}

View File

@ -0,0 +1,4 @@
engine-strict = true
package-lock = false
save-exact = true
scripts-prepend-node-path = true

View File

@ -0,0 +1,40 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [4.1.0-beta.5](https://github.com/embark-framework/embark/compare/v4.1.0-beta.4...v4.1.0-beta.5) (2019-07-10)
**Note:** Version bump only for package embark-console-listener
# [4.1.0-beta.4](https://github.com/embark-framework/embark/compare/v4.1.0-beta.3...v4.1.0-beta.4) (2019-06-27)
**Note:** Version bump only for package embark-console-listener
# [4.1.0-beta.3](https://github.com/embark-framework/embark/compare/v4.1.0-beta.2...v4.1.0-beta.3) (2019-06-07)
**Note:** Version bump only for package embark-console-listener
# [4.1.0-beta.2](https://github.com/embark-framework/embark/compare/v4.1.0-beta.1...v4.1.0-beta.2) (2019-05-22)
**Note:** Version bump only for package embark-console-listener
# [4.1.0-beta.1](https://github.com/embark-framework/embark/compare/v4.1.0-beta.0...v4.1.0-beta.1) (2019-05-15)
**Note:** Version bump only for package embark-console-listener

View File

@ -0,0 +1,6 @@
# `embark-transaction-logger`
> Listens and logs transactions in a human readble way
Visit [embark.status.im](https://embark.status.im/) to get started with
[Embark](https://github.com/embark-framework/embark).

View File

@ -0,0 +1,73 @@
{
"name": "embark-transaction-logger",
"version": "4.1.0-beta.5",
"author": "Iuri Matias <iuri.matias@gmail.com>",
"contributors": [],
"description": "Transaction listener and logger for Embark",
"homepage": "https://github.com/embark-framework/embark/tree/master/packages/embark-transaction-logger#readme",
"bugs": "https://github.com/embark-framework/embark/issues",
"keywords": [
"blockchain",
"dapps",
"ethereum",
"ipfs",
"serverless",
"solc",
"solidity"
],
"files": [
"dist"
],
"license": "MIT",
"repository": {
"directory": "packages/embark-transaction-logger",
"type": "git",
"url": "https://github.com/embark-framework/embark.git"
},
"main": "./dist/index.js",
"scripts": {
"build": "cross-env BABEL_ENV=node babel src --extensions \".js\" --out-dir dist --root-mode upward --source-maps",
"ci": "npm run qa",
"clean": "npm run reset",
"lint": "npm-run-all lint:*",
"lint:js": "eslint src/",
"// lint:ts": "tslint -c tslint.json \"src/**/*.ts\"",
"package": "npm pack",
"// qa": "npm-run-all lint typecheck build package",
"qa": "npm-run-all lint build package",
"reset": "npx rimraf dist embark-*.tgz package",
"start": "npm run watch",
"// typecheck": "tsc",
"watch": "run-p watch:*",
"watch:build": "npm run build -- --verbose --watch",
"// watch:typecheck": "npm run typecheck -- --preserveWatchOutput --watch"
},
"eslintConfig": {
"extends": "../../.eslintrc.json"
},
"dependencies": {
"@babel/runtime-corejs2": "7.3.1",
"async": "2.6.1",
"embark-core": "^4.1.0-beta.5",
"embark-i18n": "^4.1.0-beta.3",
"embark-utils": "^4.1.0-beta.5",
"ethereumjs-tx": "1.3.7",
"ethereumjs-util": "6.0.0"
},
"devDependencies": {
"@babel/cli": "7.2.3",
"@babel/core": "7.2.2",
"cross-env": "5.2.0",
"eslint": "5.7.0",
"npm-run-all": "4.1.5",
"rimraf": "2.6.3",
"source-map-support": "0.5.9",
"tslint": "5.16.0",
"typescript": "3.4.5"
},
"engines": {
"node": ">=8.12.0 <12.0.0",
"npm": ">=6.4.1",
"yarn": ">=1.12.3"
}
}

View File

@ -0,0 +1,220 @@
const async = require('async');
import {__} from 'embark-i18n';
const {blockchain: blockchainConstants} = require('embark-core/constants');
import {dappPath, getAddressToContract, getTransactionParams, hexToNumber} from 'embark-utils';
const Transaction = require('ethereumjs-tx');
const ethUtil = require('ethereumjs-util');
const LISTENED_METHODS = [
blockchainConstants.transactionMethods.eth_call,
blockchainConstants.transactionMethods.eth_getTransactionReceipt,
blockchainConstants.transactionMethods.eth_sendTransaction,
blockchainConstants.transactionMethods.eth_sendRawTransaction
];
class TransactionLogger {
constructor(embark, _options) {
this.embark = embark;
this.logger = embark.logger;
this.events = embark.events;
this.fs = embark.fs;
this.addressToContract = [];
this.contractsConfig = embark.config.contractsConfig;
this.contractsDeployed = false;
this.outputDone = false;
this.logFile = dappPath(".embark", "contractLogs.json");
this.transactions = {};
this._listenForLogRequests();
this._registerAPI();
this.events.on("contracts:log", this._saveLog.bind(this));
this.events.on('outputDone', () => {
this.outputDone = true;
});
this.events.on("contractsDeployed", () => {
this.contractsDeployed = true;
this._getContractsList((contractsList) => {
this.addressToContract = getAddressToContract(contractsList, this.addressToContract);
});
});
this.writeLogFile = async.cargo((tasks, callback) => {
const data = this._readLogs();
tasks.forEach(task => {
data[new Date().getTime()] = task;
});
this.fs.writeJson(this.logFile, data, err => {
if (err) {
console.error(err);
}
callback();
});
});
}
_getContractsList(callback) {
this.events.request("contracts:list", (err, contractsList) => {
if (err) {
this.logger.error(__("no contracts found"));
return callback();
}
callback(contractsList);
});
}
_listenForLogRequests() {
this.events.on('deploy:contract:receipt', receipt => {
this.events.emit('contracts:log', {
name: receipt.className,
functionName: 'constructor',
paramString: '',
address: receipt.contractAddress,
status: receipt.status ? '0x1' : '0x0',
gasUsed: receipt.gasUsed,
blockNumber: receipt.blockNumber,
transactionHash: receipt.transactionHash
});
});
this.events.on('blockchain:proxy:response', this._onLogRequest.bind(this));
}
_onLogRequest(args) {
const method = args.reqData.method;
if (!this.contractsDeployed || !LISTENED_METHODS.includes(method)) {
return;
}
if (method === blockchainConstants.transactionMethods.eth_sendTransaction) {
// We just gather data and wait for the receipt
this.transactions[args.respData.result] = {
address: args.reqData.params[0].to,
data: args.reqData.params[0].data,
txHash: args.respData.result
};
return;
} else if (method === blockchainConstants.transactionMethods.eth_sendRawTransaction) {
const rawData = Buffer.from(ethUtil.stripHexPrefix(args.reqData.params[0]), 'hex');
const tx = new Transaction(rawData, 'hex');
this.transactions[args.respData.result] = {
address: '0x' + tx.to.toString('hex'),
data: '0x' + tx.data.toString('hex')
};
return;
}
let dataObject;
if (method === blockchainConstants.transactionMethods.eth_getTransactionReceipt) {
dataObject = args.respData.result;
if (this.transactions[args.respData.result.transactionHash]) {
// This is the normal case. If we don't get here, it's because we missed a TX
dataObject = Object.assign(dataObject, this.transactions[args.respData.result.transactionHash]);
delete this.transactions[args.respData.result.transactionHash]; // No longer needed
}
} else {
dataObject = args.reqData.params[0];
}
const {to: address, data} = dataObject;
if (!address) {
// It's a deployment
return;
}
const contract = this.addressToContract[address];
if (!contract) {
this.logger.info(`Contract log for unknown contract: ${JSON.stringify(args)}`);
return this._getContractsList((contractsList) => {
this.addressToContract = getAddressToContract(contractsList, this.addressToContract);
});
}
const {name, silent} = contract;
if (silent && !this.outputDone) {
return;
}
let functionName, paramString;
if (!data) {
// We missed the TX
functionName = 'unknown';
paramString = 'unknown';
} else {
const txParams = getTransactionParams(contract, data);
functionName = txParams.functionName;
paramString = txParams.paramString;
}
if (method === blockchainConstants.transactionMethods.eth_call) {
const log = Object.assign({}, args, {name, functionName, paramString});
log.status = '0x1';
return this.events.emit('contracts:log', log);
}
let {transactionHash, blockNumber, gasUsed, status} = args.respData.result;
gasUsed = hexToNumber(gasUsed);
blockNumber = hexToNumber(blockNumber);
const log = Object.assign({}, args, {name, functionName, paramString, gasUsed, blockNumber});
this.events.emit('contracts:log', log);
this.logger.info(`Blockchain>`.underline + ` ${name}.${functionName}(${paramString})`.bold + ` | ${transactionHash} | gas:${gasUsed} | blk:${blockNumber} | status:${status}`);
this.events.emit('blockchain:tx', {
name: name,
functionName: functionName,
paramString: paramString,
transactionHash: transactionHash,
gasUsed: gasUsed,
blockNumber: blockNumber,
status: status
});
}
_registerAPI() {
const apiRoute = '/embark-api/contracts/logs';
this.embark.registerAPICall(
'ws',
apiRoute,
(ws, _req) => {
// FIXME this will be broken probably in the cokcpit because we don't send the same data as before
this.events.on('contracts:log', function(log) {
ws.send(JSON.stringify(log), () => {
});
});
}
);
this.embark.registerAPICall(
'get',
apiRoute,
(req, res) => {
res.send(JSON.stringify(this._getLogs()));
}
);
}
_getLogs() {
const data = this._readLogs();
return Object.values(data).reverse();
}
_saveLog(log) {
this.writeLogFile.push(log);
}
_readLogs() {
this.fs.ensureFileSync(this.logFile);
try {
return JSON.parse(this.fs.readFileSync(this.logFile));
} catch (_error) {
return {};
}
}
}
module.exports = TransactionLogger;

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*"]
}

View File

@ -0,0 +1,3 @@
{
"extends": "../../tslint.json"
}

View File

@ -127,6 +127,7 @@
"embark-storage": "^4.1.0-beta.5",
"embark-swarm": "^4.1.0-beta.5",
"embark-test-runner": "^4.1.0-beta.5",
"embark-transaction-logger": "^4.1.0-beta.5",
"embark-transaction-tracker": "^4.1.0-beta.3",
"embark-ui": "^4.1.0-beta.5",
"embark-utils": "^4.1.0-beta.5",

View File

@ -213,6 +213,7 @@ class Engine {
this.registerModulePackage('embark-accounts-manager');
this.registerModulePackage('embark-specialconfigs', {plugins: this.plugins});
this.registerModulePackage('embark-console-listener');
this.registerModulePackage('embark-transaction-logger');
}
storageComponent() {