diff --git a/dapps/templates/demo/contracts/simple_storage.sol b/dapps/templates/demo/contracts/simple_storage.sol index c8f998912..c372da942 100644 --- a/dapps/templates/demo/contracts/simple_storage.sol +++ b/dapps/templates/demo/contracts/simple_storage.sol @@ -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) { diff --git a/packages/cockpit/ui/src/actions/index.js b/packages/cockpit/ui/src/actions/index.js index 6471d5745..0d31fe1fb 100644 --- a/packages/cockpit/ui/src/actions/index.js +++ b/packages/cockpit/ui/src/actions/index.js @@ -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'}) }; diff --git a/packages/core/utils/src/file.ts b/packages/core/utils/src/file.ts index 6e7f38819..a3e96db31 100644 --- a/packages/core/utils/src/file.ts +++ b/packages/core/utils/src/file.ts @@ -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); +} diff --git a/packages/core/utils/src/index.ts b/packages/core/utils/src/index.ts index bb181ab0b..8575f4c9b 100644 --- a/packages/core/utils/src/index.ts +++ b/packages/core/utils/src/index.ts @@ -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, diff --git a/packages/plugins/ethereum-blockchain-client/src/api.js b/packages/plugins/ethereum-blockchain-client/src/api.js index 86e419434..7ee8f1490 100644 --- a/packages/plugins/ethereum-blockchain-client/src/api.js +++ b/packages/plugins/ethereum-blockchain-client/src/api.js @@ -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 ? '[]' : []; } } diff --git a/packages/plugins/ethereum-blockchain-client/src/index.js b/packages/plugins/ethereum-blockchain-client/src/index.js index 8ab730a37..a7424611f 100644 --- a/packages/plugins/ethereum-blockchain-client/src/index.js +++ b/packages/plugins/ethereum-blockchain-client/src/index.js @@ -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"); diff --git a/packages/plugins/transaction-logger/src/index.js b/packages/plugins/transaction-logger/src/index.js index d457471e9..2cc13f87d 100644 --- a/packages/plugins/transaction-logger/src/index.js +++ b/packages/plugins/transaction-logger/src/index.js @@ -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 ? '[]' : []; } } diff --git a/packages/stack/blockchain/src/api.ts b/packages/stack/blockchain/src/api.ts index 0fb072306..716546d1c 100644 --- a/packages/stack/blockchain/src/api.ts +++ b/packages/stack/blockchain/src/api.ts @@ -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 }); }