fix(@embark/blockchain-api): add back contract event listen and log

Adds back the watch on contract events and writes them to a file
with the same method as contract logs from transaction-logger, so
I extracted those methods to utils/file so that both could use the
same functions.
This commit is contained in:
Jonathan Rainville 2020-01-31 14:54:40 -05:00
parent 87a04cd5db
commit 5592753116
8 changed files with 85 additions and 70 deletions

View File

@ -3,12 +3,15 @@ pragma solidity ^0.6.0;
contract SimpleStorage {
uint public storedData;
event Set(address caller, uint _value);
constructor(uint initialValue) public {
storedData = initialValue;
}
function set(uint x) public {
storedData = x;
emit Set(msg.sender, x);
}
function get() public view returns (uint retVal) {

View File

@ -180,7 +180,7 @@ export const contractLogs = {
export const CONTRACT_EVENTS = createRequestTypes('CONTRACT_EVENTS');
export const contractEvents = {
request: () => action(CONTRACT_EVENTS[REQUEST]),
success: (contractEvents) => action(CONTRACT_EVENTS[SUCCESS], {contractEvents}),
success: (contractEvents) => action(CONTRACT_EVENTS[SUCCESS], {contractEvents: contractEvents ? contractEvents.reverse() : []}),
failure: (error) => action(CONTRACT_EVENTS[FAILURE], {error, name: 'contractEvents'})
};

View File

@ -1,9 +1,11 @@
import { Logger } from 'embark-logger';
import { __ } from 'embark-i18n';
import * as fs from 'fs-extra';
import * as path from 'path';
import { downloadFile } from './network';
import { dappPath, embarkPath } from './pathUtils';
import { ImportRemapping, prepareForCompilation } from './solidity/remapImports';
import { cargo } from 'async';
const HTTP_CONTRACTS_DIRECTORY = '.embark/contracts/';
@ -179,3 +181,53 @@ export function getExternalContractUrl(file: string, providerUrl: string) {
url,
};
}
export function getCircularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
}
export function getAppendLogFileCargo(logFilePath: string, logger: Logger) {
return cargo((tasks, callback) => {
let appendThis = '';
tasks.forEach(task => {
// Write each line to a JSON string. The replacer is to avoid circular dependencies
// Add a comma at the end to be able to make an array off of it when reading
appendThis += `${JSON.stringify(task, getCircularReplacer())},\n`;
});
fs.appendFile(logFilePath, appendThis, (err) => {
if (err) {
logger.error('Error writing to the log file', err.message);
logger.trace(err);
}
callback();
});
});
}
export async function readAppendedLogs(logFile: string, asString: boolean = false) {
await fs.ensureFile(logFile);
const data = await fs.readFile(logFile);
let stringData = data.toString();
if (!stringData) {
return asString ? '[]' : [];
}
// remove last comma and add brackets around to make it an array of object logs
stringData = `[${stringData.substring(0, stringData.length - 2)}]`;
if (asString) {
return stringData;
}
return JSON.parse(stringData);
}

View File

@ -59,7 +59,7 @@ const { extendZeroAddressShorthand, replaceZeroAddressShorthand } = AddressUtils
export { compact, last, recursiveMerge, groupBy } from './collections';
export { prepareForCompilation } from './solidity/remapImports';
export { File, getExternalContractUrl, Types } from './file';
export { File, getExternalContractUrl, Types, getAppendLogFileCargo, getCircularReplacer, readAppendedLogs } from './file';
export {
findMonorepoPackageFromRoot,

View File

@ -1,5 +1,5 @@
import async from "async";
import {dappPath} from 'embark-utils';
import {dappPath, getAppendLogFileCargo, readAppendedLogs} from 'embark-utils';
const embarkJsUtils = require('embarkjs').Utils;
const {bigNumberify} = require('ethers/utils/bignumber');
const RLP = require('ethers/utils/rlp');
@ -11,11 +11,20 @@ const BLOCK_LIMIT = 100;
export default class EthereumAPI {
constructor(embark, web3, blockchainName) {
this.embark = embark;
this.events = embark.events;
this.logger = embark.logger;
this.blockchainName = blockchainName;
this.web3 = web3;
this.requestManager = new Manager(web3.currentProvider);
this.fs = embark.fs;
this.logFile = dappPath(".embark", "contractEvents.json");
this.logFile = dappPath(".embark", "contractEvents.json.txt");
this.contractsSubscriptions = [];
this.contractsEvents = [];
this.writeLogFile = getAppendLogFileCargo(this.logFile, this.logger);
this.events.on('contractsDeployed', () => {
this.subscribeToContractEvents();
});
}
registerAPIs() {
@ -26,7 +35,7 @@ export default class EthereumAPI {
this.embark.events.request("blockchain:api:register", this.blockchainName, "getTransactions", this.getTransactions.bind(this));
this.embark.events.request("blockchain:api:register", this.blockchainName, "getTransactionByHash", this.getTransactionByHash.bind(this));
this.embark.events.request("blockchain:api:register", this.blockchainName, "getTransactionByRawTransactionHash", this.getTransactionByRawTransactionHash.bind(this));
this.embark.events.request("blockchain:api:register", this.blockchainName, "getEvents", this.getEvents.bind(this));
this.embark.events.request("blockchain:api:register", this.blockchainName, "getEvents", this.readEvents.bind(this));
this.embark.events.request("blockchain:api:register", this.blockchainName, "signMessage", this.signMessage.bind(this));
this.embark.events.request("blockchain:api:register", this.blockchainName, "verifyMessage", this.verifyMessage.bind(this));
}
@ -356,7 +365,7 @@ export default class EthereumAPI {
});
}
subscribeToContractEvents(callback) {
subscribeToContractEvents() {
this.contractsSubscriptions.forEach((eventEmitter) => {
const reqMgr = eventEmitter.options.requestManager;
// attempting an eth_unsubscribe when not connected throws an
@ -378,28 +387,24 @@ export default class EthereumAPI {
eventEmitter.on('data', (data) => {
const dataWithName = Object.assign(data, {name: contractObject.className});
this.contractsEvents.push(dataWithName);
this.saveEvent(dataWithName);
this.events.emit('blockchain:contracts:event', dataWithName);
});
});
callback();
});
}
getEvents() {
const data = this.readEvents();
return Object.values(data).reverse();
}
saveEvent(event) {
this.writeLogFile.push(event);
}
readEvents() {
this.fs.ensureFileSync(this.logFile);
async readEvents(asString) {
try {
return JSON.parse(this.fs.readFileSync(this.logFile));
} catch (_error) {
return {};
return readAppendedLogs(this.logFile, asString);
} catch (e) {
this.logger.error('Error reading contract log file', e.message);
this.logger.trace(e.trace);
return asString ? '[]' : [];
}
}

View File

@ -15,8 +15,6 @@ class EthereumBlockchainClient {
this.events = embark.events;
this.logger = embark.logger;
this.fs = embark.fs;
this.contractsSubscriptions = [];
this.contractsEvents = [];
this.logFile = dappPath(".embark", "contractEvents.json");

View File

@ -1,10 +1,9 @@
const async = require('async');
import { __ } from 'embark-i18n';
const Web3 = require('web3');
const util = require('util');
const { blockchain: blockchainConstants } = require('embark-core/constants');
import { dappPath, hexToNumber } from 'embark-utils';
import { dappPath, hexToNumber, getAppendLogFileCargo, readAppendedLogs } from 'embark-utils';
import { getAddressToContract, getTransactionParams } from './transactionUtils';
export { getAddressToContract, getTransactionParams };
@ -18,19 +17,6 @@ const LISTENED_METHODS = [
blockchainConstants.transactionMethods.eth_sendRawTransaction
];
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
export default class TransactionLogger {
constructor(embark, _options) {
this.embark = embark;
@ -60,21 +46,7 @@ export default class TransactionLogger {
});
});
this.writeLogFile = async.cargo((tasks, callback) => {
let appendThis = '';
tasks.forEach(task => {
// Write each line to a JSON string. The replacer is to avoid circular dependencies
// Add a comma at the end to be able to make an array off of it when reading
appendThis += `${JSON.stringify(task, getCircularReplacer())},\n`;
});
this.fs.appendFile(this.logFile, appendThis, (err) => {
if (err) {
this.logger.error('Error writing to the log file', err.message);
this.logger.trace(err);
}
callback();
});
});
this.writeLogFile = getAppendLogFileCargo(this.logFile, this.logger);
}
get web3() {
@ -272,25 +244,10 @@ export default class TransactionLogger {
async _readLogs(asString = false) {
try {
await this.fs.ensureFile(this.logFile);
let data = await this.fs.readFile(this.logFile);
data = data.toString();
if (!data) {
return asString ? '[]' : [];
}
// remove last comma and add brackets around to make it an array of object logs
data = `[${data.substring(0, data.length - 2)}]`;
if (asString) {
return data;
}
return JSON.parse(data);
} catch (error) {
this.logger.error('Error reading contract log file', error.message);
this.logger.trace(error.trace);
return readAppendedLogs(this.logFile, asString);
} catch (e) {
this.logger.error('Error reading contract log file', e.message);
this.logger.trace(e.trace);
return asString ? '[]' : [];
}
}

View File

@ -248,10 +248,10 @@ export default class BlockchainAPI {
this.embark.registerAPICall(
"get",
"/embark-api/blockchain/contracts/events",
(_req, res) => {
async (_req, res) => {
try {
const getEvents = this.getCallForBlockchain(blockchainName, "getEvents");
res.send(JSON.stringify(getEvents()));
res.send(await getEvents(true));
} catch (error) {
res.status(500).send({ error });
}