From 8e9cafa1e75c621298fea7af1b5e067e5c5115f2 Mon Sep 17 00:00:00 2001 From: Sam Vitello Date: Tue, 27 Feb 2018 16:52:03 -0500 Subject: [PATCH] feat(disputes): createdAt and ruledAt timestamps added in events --- src/abstractWrappers/Disputes.js | 193 ++++++++++++++++---------- src/abstractWrappers/Notifications.js | 65 +++++---- src/constants/eth.js | 2 +- src/kleros.js | 5 + src/utils/StoreProviderWrapper.js | 5 +- src/utils/Web3Wrapper.js | 2 +- tests/kleros.test.js | 35 +++-- 7 files changed, 185 insertions(+), 122 deletions(-) diff --git a/src/abstractWrappers/Disputes.js b/src/abstractWrappers/Disputes.js index 04a274e..1dc421f 100644 --- a/src/abstractWrappers/Disputes.js +++ b/src/abstractWrappers/Disputes.js @@ -2,7 +2,7 @@ import _ from 'lodash' import * as ethConstants from '../constants/eth' import * as arbitratorConstants from '../constants/arbitrator' -import * as contractConstants from '../constants/contract' +import * as notificationConstants from '../constants/notification' import AbstractWrapper from './AbstractWrapper' @@ -28,7 +28,6 @@ class Disputes extends AbstractWrapper { contractAddress = arbitratorAddress, address = account ) => { - console.log("adding dispute for " + address) const disputeId = event.args._disputeID.toNumber() const disputeData = await this.getDataForDispute( contractAddress, @@ -37,12 +36,16 @@ class Disputes extends AbstractWrapper { ) // if listener is a party in dispute add to store if (disputeData.partyA === address || disputeData.partyB === address) { - console.log("should be for partyA") const blockNumber = event.blockNumber const block = this._Arbitrator._Web3Wrapper.getBlock(blockNumber) // add new dispute with timestamp - const dispute = await this._updateStoreForDispute(contractAddress, disputeId, address, block.timestamp) + await this._updateStoreForDispute( + contractAddress, + disputeId, + address, + block.timestamp * 1000 + ) } } @@ -103,11 +106,13 @@ class Disputes extends AbstractWrapper { ) } - addDisputeRulingHandler = async ( - arbitratorAddress, - account, - callback - ) => { + /** + * Event listener that sends notification when a dispute has been ruled on. + * @param {string} arbitratorAddress - The arbitrator contract's address. + * @param {string} account - The users eth account. + * @param {function} callback - function to be called when event is triggered. + */ + addDisputeRulingHandler = async (arbitratorAddress, account, callback) => { if (!this._eventListener) return const _disputeRulingHandler = async ( @@ -116,17 +121,17 @@ class Disputes extends AbstractWrapper { address = account, notificationCallback = callback ) => { - console.log("in handler") const newPeriod = event.args._period.toNumber() const txHash = event.transactionHash // send appeal possible notifications - if (newPeriod === PERIODS.APPEAL) { - console.log("doing the do") + if (newPeriod === arbitratorConstants.PERIOD.APPEAL) { this._checkArbitratorWrappersSet() const userProfile = await this._StoreProvider.getUserProfile(address) // contract data - const arbitratorData = await this._Arbitrator.getData(contractAddress, address) - const currentDisputes = [] + const arbitratorData = await this._Arbitrator.getData( + contractAddress, + address + ) let disputeId = 0 const currentSession = arbitratorData.session @@ -134,54 +139,81 @@ class Disputes extends AbstractWrapper { while (1) { // iterate over all disputes (FIXME inefficient) try { - dispute = await this._Arbitrator.getDispute(contractAddress, disputeId) - if (dispute.arbitratedContract === NULL_ADDRESS) break - // session + number of appeals - const disputeSession = dispute.firstSession + dispute.numberOfAppeals - // if dispute not in current session skip - if (disputeSession !== currentSession) { - disputeId++ - dispute = await this._Arbitrator.getDispute(contractAddress, disputeId) - continue - } - - const ruling = await this._Arbitrator.currentRulingForDispute(contractAddress, disputeId) - - if (_.findIndex(userProfile.disputes, dispute => { - return (dispute.disputeId === disputeId && dispute.arbitratorAddress === contractAddress) - }) >= 0) { - await this._StoreProvider.newNotification( - address, - txHash, - disputeId, // use disputeId instead of logIndex since it doens't have its own event - NOTIFICATION_TYPES.APPEAL_POSSIBLE, - 'A ruling has been made. Appeal is possible', - { - disputeId, - contractAddress, - ruling - } - ) - // get ruledAt from block timestamp - const blockNumber = event.blockNumber - const block = this._Arbitrator._Web3Wrapper.getBlock(blockNumber) - // add ruledAt to store - await this._updateStoreForDispute(contractAddress, disputeId, address, null, block.timestamp) - - if (callback) { - const userProfile = await this._StoreProvider.getUserProfile(address) - const notification = _.filter(userProfile.notifications, notification => { - return (notification.txHash === txHash && notification.logIndex === disputeId) - }) - - if (notification) { - callback(notification[0]) - } - } - } - // check next dispute - disputeId += 1 - } catch (e) { + dispute = await this._Arbitrator.getDispute( + contractAddress, + disputeId + ) + if (dispute.arbitratedContract === ethConstants.NULL_ADDRESS) break + // session + number of appeals + const disputeSession = + dispute.firstSession + dispute.numberOfAppeals + // if dispute not in current session skip + if (disputeSession !== currentSession) { + disputeId++ + dispute = await this._Arbitrator.getDispute( + contractAddress, + disputeId + ) + continue + } + + const ruling = await this._Arbitrator.currentRulingForDispute( + contractAddress, + disputeId + ) + + if ( + _.findIndex( + userProfile.disputes, + dispute => + dispute.disputeId === disputeId && + dispute.arbitratorAddress === contractAddress + ) >= 0 + ) { + await this._StoreProvider.newNotification( + address, + txHash, + disputeId, // use disputeId instead of logIndex since it doens't have its own event + notificationConstants.TYPE.APPEAL_POSSIBLE, + 'A ruling has been made. Appeal is possible', + { + disputeId, + contractAddress, + ruling + } + ) + // get ruledAt from block timestamp + const blockNumber = event.blockNumber + const block = this._Arbitrator._Web3Wrapper.getBlock(blockNumber) + // add ruledAt to store + await this._updateStoreForDispute( + contractAddress, + disputeId, + address, + null, + block.timestamp * 1000 + ) + + if (notificationCallback) { + const userProfile = await this._StoreProvider.getUserProfile( + address + ) + const notification = _.filter( + userProfile.notifications, + notification => + notification.txHash === txHash && + notification.logIndex === disputeId + ) + + if (notification) { + notificationCallback(notification[0]) + } + } + } + // check next dispute + disputeId += 1 + // eslint-disable-next-line no-unused-vars + } catch (err) { // getDispute(n) throws an error if index out of range break } @@ -189,7 +221,10 @@ class Disputes extends AbstractWrapper { } } - await this._eventListener.registerArbitratorEvent('NewPeriod', _disputeRulingHandler) + await this._eventListener.registerArbitratorEvent( + 'NewPeriod', + _disputeRulingHandler + ) } // **************************** // @@ -252,7 +287,7 @@ class Disputes extends AbstractWrapper { * Get disputes for user with extra data from arbitrated transaction and store * @param {string} arbitratorAddress address of Kleros contract * @param {string} account address of user - * @return {object[]} dispute data objects for user + * @returns {object[]} dispute data objects for user */ getDisputesForUser = async (arbitratorAddress, account) => { // FIXME don't like having to call this every fnc @@ -450,8 +485,8 @@ class Disputes extends AbstractWrapper { /** * Gets the deadline for an arbitrator's period, which is also the deadline for all its disputes. * @param {string} arbitratorAddress - The address of the arbitrator contract. - * @param {number} [period=arbitratorConstants.PERIOD.VOTE] - The period to get the deadline for. - * @returns {Date} - A date object. + * @param {number} [period=PERIODS.VOTE] - The period to get the deadline for. + * @returns {number} - epoch timestamp */ getDeadlineForDispute = async ( arbitratorAddress, @@ -469,11 +504,14 @@ class Disputes extends AbstractWrapper { } /** - * update store with new dispute data - * @param {string} arbitratorAddress Address address of arbitrator contract - * @param {int} disputeId index of dispute - * @param {string} account address of party to update dispute or - */ + * update store with new dispute data + * @param {string} arbitratorAddress Address address of arbitrator contract + * @param {int} disputeId index of dispute + * @param {string} account address of party to update dispute or + * @param {number} createdAt epoch timestamp of when dispute was created + * @param {number} ruledAt epoch timestamp of when dispute was ruled on + * @returns {object} updated dispute object + */ _updateStoreForDispute = async ( arbitratorAddress, disputeId, @@ -488,10 +526,11 @@ class Disputes extends AbstractWrapper { ) // update dispute - await this._StoreProvider.updateDispute( + const dispute = await this._StoreProvider.updateDispute( disputeData.disputeId, disputeData.arbitratorAddress, disputeData.hash, + disputeData.arbitrableContractAddress, disputeData.partyA, disputeData.partyB, disputeData.title, @@ -501,9 +540,9 @@ class Disputes extends AbstractWrapper { disputeData.information, disputeData.justification, disputeData.resolutionOptions, - createdAt ? createdAt : disputeData.createdAt, - ruledAt ? ruledAt : disputeData.ruledAt - ).body + createdAt || disputeData.createdAt, + ruledAt || disputeData.ruledAt + ) // update profile for account await this._StoreProvider.updateDisputeProfile( @@ -645,7 +684,7 @@ class Disputes extends AbstractWrapper { if (account) { votes = await this.getVotesForJuror(arbitratorAddress, disputeId, account) try { - const userData = await this.getUserDisputeFromStore( + const userData = await this._StoreProvider.getDisputeData( arbitratorAddress, disputeId, account @@ -708,7 +747,7 @@ class Disputes extends AbstractWrapper { netPNK, ruledAt, createdAt - }) + } } } diff --git a/src/abstractWrappers/Notifications.js b/src/abstractWrappers/Notifications.js index 8402979..4e5b6c0 100644 --- a/src/abstractWrappers/Notifications.js +++ b/src/abstractWrappers/Notifications.js @@ -160,34 +160,45 @@ class Notifications extends AbstractWrapper { } // Repartition and execute - if (currentPeriod === PERIODS.EXECUTE) { - console.log("Starting....") - await Promise.all(userProfile.disputes.map(async dispute => { - const disputeData = await this._Arbitrator.getDispute(dispute.arbitratorAddress, dispute.disputeId) - if (disputeData.firstSession + disputeData.numberOfAppeals === currentSession) { - if (disputeData.state <= DISPUTE_STATES.RESOLVING) { - notifications.push(this._createNotification( - NOTIFICATION_TYPES.CAN_REPARTITION, - "Ready to repartition dispute", - { - disputeId: dispute.disputeId, - arbitratorAddress: dispute.arbitratorAddress - } - )) - console.log("did stuff") - } else if (disputeData.state === DISPUTE_STATES.EXECUTABLE) { - notifications.push(this._createNotification( - NOTIFICATION_TYPES.CAN_EXECUTE, - "Ready to execute dispute", - { - disputeId: dispute.disputeId, - arbitratorAddress: dispute.arbitratorAddress - } - )) + if (currentPeriod === arbitratorConstants.PERIOD.EXECUTE) { + await Promise.all( + userProfile.disputes.map(async dispute => { + const disputeData = await this._Arbitrator.getDispute( + dispute.arbitratorAddress, + dispute.disputeId + ) + if ( + disputeData.firstSession + disputeData.numberOfAppeals === + currentSession + ) { + if (disputeData.state <= disputeConstants.STATE.RESOLVING) { + notifications.push( + this._createNotification( + notificationConstants.TYPE.CAN_REPARTITION, + 'Ready to repartition dispute', + { + disputeId: dispute.disputeId, + arbitratorAddress: dispute.arbitratorAddress + } + ) + ) + } else if ( + disputeData.state === disputeConstants.STATE.EXECUTABLE + ) { + notifications.push( + this._createNotification( + notificationConstants.TYPE.CAN_EXECUTE, + 'Ready to execute dispute', + { + disputeId: dispute.disputeId, + arbitratorAddress: dispute.arbitratorAddress + } + ) + ) + } } - } - })) - console.log("Done....") + }) + ) } return notifications diff --git a/src/constants/eth.js b/src/constants/eth.js index 857efe7..4724b63 100644 --- a/src/constants/eth.js +++ b/src/constants/eth.js @@ -1,5 +1,5 @@ export const LOCALHOST_ETH_PROVIDER = 'http://localhost:8545' -export const LOCALHOST_STORE_PROVIDER = 'https://kleros.in' +export const LOCALHOST_STORE_PROVIDER = 'http://localhost:3001' export const NULL_ADDRESS = '0x' diff --git a/src/kleros.js b/src/kleros.js index e159866..4cbc6b5 100644 --- a/src/kleros.js +++ b/src/kleros.js @@ -88,6 +88,11 @@ class Kleros { account, callback ) + await this.disputes.addDisputeRulingHandler( + arbitratorAddress, + account, + callback + ) await this.notifications.registerNotificationListeners( arbitratorAddress, account, diff --git a/src/utils/StoreProviderWrapper.js b/src/utils/StoreProviderWrapper.js index e03bf43..c318396 100644 --- a/src/utils/StoreProviderWrapper.js +++ b/src/utils/StoreProviderWrapper.js @@ -80,7 +80,7 @@ class StoreProviderWrapper { return httpResponse } - getDisputeData = async (userAddress, arbitratorAddress, disputeId) => { + getDisputeData = async (arbitratorAddress, disputeId, userAddress) => { const userProfile = await this.getUserProfile(userAddress) if (!userProfile) throw new Error(`No profile found for address: ${userAddress}`) @@ -91,7 +91,6 @@ class StoreProviderWrapper { o.arbitratorAddress === arbitratorAddress && o.disputeId === disputeId ) - if (_.isEmpty(disputeData)) return null const httpResponse = await this._makeRequest( 'GET', `${this._storeUri}/arbitrators/${arbitratorAddress}/disputes/${disputeId}` @@ -245,7 +244,6 @@ class StoreProviderWrapper { ruledAt }) ) - return httpResponse } @@ -306,7 +304,6 @@ class StoreProviderWrapper { data }) ) - return httpResponse } } diff --git a/src/utils/Web3Wrapper.js b/src/utils/Web3Wrapper.js index bbe065c..a2025a1 100644 --- a/src/utils/Web3Wrapper.js +++ b/src/utils/Web3Wrapper.js @@ -32,7 +32,7 @@ class Web3Wrapper { blockNumber = () => this._web3.eth.blockNumber - getBlock = () => this._web3.eth.getBlock + getBlock = blockNumber => this._web3.eth.getBlock(blockNumber) doesContractExistAtAddressAsync = async address => { const code = await this._web3.eth.getCode(address) diff --git a/tests/kleros.test.js b/tests/kleros.test.js index 8c51f51..83468d1 100644 --- a/tests/kleros.test.js +++ b/tests/kleros.test.js @@ -580,7 +580,7 @@ describe('Kleros', () => { }) let newState - // pass state so juror1s are selected + // pass state so jurors are selected for (let i = 1; i < 3; i++) { // NOTE we need to make another block before we can generate the random number. Should not be an issue on main nets where avg block time < period length if (i === 2) @@ -687,6 +687,7 @@ describe('Kleros', () => { // delay 1 second await delaySecond() + // move to execute period await KlerosInstance.arbitrator.passPeriod(klerosCourt.address, other) // stateful notifications @@ -700,13 +701,13 @@ describe('Kleros', () => { notificationConstants.TYPE.CAN_REPARTITION ) - partyBStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications( + let partyAStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications( klerosCourt.address, - partyB, + partyA, false ) - expect(partyBStatefullNotifications.length).toEqual(1) - expect(partyBStatefullNotifications[0].notificationType).toEqual( + expect(partyAStatefullNotifications.length).toEqual(1) + expect(partyAStatefullNotifications[0].notificationType).toEqual( notificationConstants.TYPE.CAN_REPARTITION ) @@ -731,13 +732,13 @@ describe('Kleros', () => { notificationConstants.TYPE.CAN_EXECUTE ) - partyBStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications( + partyAStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications( klerosCourt.address, - partyB, + partyA, false ) - expect(partyBStatefullNotifications.length).toEqual(1) - expect(partyBStatefullNotifications[0].notificationType).toEqual( + expect(partyAStatefullNotifications.length).toEqual(1) + expect(partyAStatefullNotifications[0].notificationType).toEqual( notificationConstants.TYPE.CAN_EXECUTE ) @@ -779,12 +780,12 @@ describe('Kleros', () => { ) expect(juror1StatefullNotifications.length).toEqual(0) - partyBStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications( + partyAStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications( klerosCourt.address, - partyB, + partyA, false ) - expect(partyBStatefullNotifications.length).toEqual(0) + expect(partyAStatefullNotifications.length).toEqual(0) expect(notifications.length).toBeTruthy() // partyA got notifications @@ -854,6 +855,16 @@ describe('Kleros', () => { KlerosInstance.eventListener.stopWatchingArbitratorEvents( klerosCourt.address ) + + // make sure createdAt set + const disputeData = await KlerosInstance.disputes.getDataForDispute( + klerosCourt.address, + 0, + partyA + ) + + expect(disputeData.createdAt).toBeTruthy() + expect(disputeData.ruledAt).toBeTruthy() }, 80000 )