diff --git a/apps/main-chain/package.json b/apps/main-chain/package.json index 62d5e406..219d91a2 100644 --- a/apps/main-chain/package.json +++ b/apps/main-chain/package.json @@ -12,6 +12,7 @@ "dependencies": { "@joincolony/graphql": "workspace:*", "@joincolony/clients": "workspace:*", + "@joincolony/blocks": "workspace:*", "@joincolony/utils": "workspace:*" } } diff --git a/apps/main-chain/src/blockProcessor.ts b/apps/main-chain/src/blockProcessor.ts index 5ffe5983..dd663f0b 100644 --- a/apps/main-chain/src/blockProcessor.ts +++ b/apps/main-chain/src/blockProcessor.ts @@ -1,6 +1,6 @@ import { Log } from '@ethersproject/abstract-provider'; import { blocksMap, getLatestSeenBlockNumber } from '~blockListener'; -import { getMatchingListeners } from '~eventListeners'; +import eventManager from '~eventManager'; import { getInterfaceByListener } from '~interfaces'; import rpcProvider from '~provider'; @@ -182,7 +182,10 @@ export const processNextBlock = async (): Promise => { for (const log of logs) { // Find listeners that match the log - const listeners = getMatchingListeners(log.topics, log.address); + const listeners = eventManager.getMatchingListeners( + log.topics, + log.address, + ); if (!listeners.length) { continue; } diff --git a/apps/main-chain/src/eventListeners/colony.ts b/apps/main-chain/src/eventListeners/colony.ts index 6015fcf0..b12cffc1 100644 --- a/apps/main-chain/src/eventListeners/colony.ts +++ b/apps/main-chain/src/eventListeners/colony.ts @@ -9,7 +9,6 @@ import { import { ContractEventsSignatures, EventHandler } from '~types'; import { notNull } from '~utils'; import { - addEventListener, addNetworkEventListener, addTokenEventListener, } from '~eventListeners'; @@ -51,13 +50,14 @@ import { handleProxyColonyRequested } from '~handlers/proxyColonies'; import setTokenAuthority from '~handlers/tokens/setTokenAuthority'; import { addProxyColoniesEventListener } from './proxyColonies'; import { output } from '@joincolony/utils'; +import eventManager from '~eventManager'; const addColonyEventListener = ( eventSignature: ContractEventsSignatures, address: string, handler: EventHandler, ): void => { - addEventListener({ + eventManager.addEventListener({ type: EventListenerType.Colony, address, eventSignature, diff --git a/apps/main-chain/src/eventListeners/eventListeners.ts b/apps/main-chain/src/eventListeners/eventListeners.ts deleted file mode 100644 index ef9fcfe0..00000000 --- a/apps/main-chain/src/eventListeners/eventListeners.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { verbose } from '@joincolony/utils'; - -import { EventListener } from './types'; - -let listeners: EventListener[] = []; - -export const getEventListeners = (): EventListener[] => listeners; - -export const setEventListeners = (newListeners: EventListener[]): void => { - listeners = newListeners; -}; - -export const addEventListener = (listener: EventListener): void => { - verbose( - `Added listener for event ${listener.eventSignature}`, - listener.address ? `filtering address ${listener.address}` : '', - ); - listeners.push(listener); -}; - -export const getMatchingListeners = ( - logTopics: string[], - logAddress: string, -): EventListener[] => { - return listeners.filter((listener) => { - if (listener.address && logAddress !== listener.address) { - return false; - } - - if (listener.topics.length > logTopics.length) { - return false; - } - - return listener.topics.every((topic, index) => { - if (topic === null) { - // if listener topic is null, skip the check - return true; - } - - return topic.toLowerCase() === logTopics[index].toLowerCase(); - }); - }); -}; - -export const getListenersStats = (): string => JSON.stringify(listeners); diff --git a/apps/main-chain/src/eventListeners/extension/index.ts b/apps/main-chain/src/eventListeners/extension/index.ts index 866e92ec..e7cddfd0 100644 --- a/apps/main-chain/src/eventListeners/extension/index.ts +++ b/apps/main-chain/src/eventListeners/extension/index.ts @@ -4,12 +4,10 @@ import { Extension, getExtensionHash } from '@colony/colony-js'; import { ContractEventsSignatures, EventHandler } from '~types'; import { EventListenerType, - addEventListener, - getEventListeners, - setEventListeners, setupListenerForOneTxPaymentExtensions, setupListenersForStagedExpenditureExtensions, } from '~eventListeners'; +import eventManager from '~eventManager'; import { ExtensionFragment, ListExtensionsDocument, @@ -44,7 +42,7 @@ export const addExtensionEventListener = ( colonyAddress: string, handler: EventHandler, ): void => { - addEventListener({ + eventManager.addEventListener({ type: EventListenerType.Extension, eventSignature, address: extensionAddress, @@ -58,8 +56,8 @@ export const addExtensionEventListener = ( export const removeExtensionEventListeners = ( extensionAddress: string, ): void => { - const existingListeners = getEventListeners(); - setEventListeners( + const existingListeners = eventManager.getEventListeners(); + eventManager.setEventListeners( existingListeners.filter((listener) => { if (listener.type !== EventListenerType.Extension) { return true; diff --git a/apps/main-chain/src/eventListeners/index.ts b/apps/main-chain/src/eventListeners/index.ts index a4d169de..414298d1 100644 --- a/apps/main-chain/src/eventListeners/index.ts +++ b/apps/main-chain/src/eventListeners/index.ts @@ -1,4 +1,3 @@ -export * from './eventListeners'; export * from './types'; export * from './colony'; export * from './network'; diff --git a/apps/main-chain/src/eventListeners/network.ts b/apps/main-chain/src/eventListeners/network.ts index a515f04f..1f873773 100644 --- a/apps/main-chain/src/eventListeners/network.ts +++ b/apps/main-chain/src/eventListeners/network.ts @@ -1,13 +1,14 @@ +import { EventListenerType } from '@joincolony/blocks'; import { utils } from 'ethers'; -import { EventListenerType, addEventListener } from '~eventListeners'; +import eventManager from '~eventManager'; import { ContractEventsSignatures, EventHandler } from '~types'; export const addNetworkEventListener = ( eventSignature: ContractEventsSignatures, handler: EventHandler, ): void => - addEventListener({ + eventManager.addEventListener({ type: EventListenerType.Network, eventSignature, topics: [utils.id(eventSignature)], diff --git a/apps/main-chain/src/eventListeners/proxyColonies.ts b/apps/main-chain/src/eventListeners/proxyColonies.ts index 29158c66..964b3421 100644 --- a/apps/main-chain/src/eventListeners/proxyColonies.ts +++ b/apps/main-chain/src/eventListeners/proxyColonies.ts @@ -1,13 +1,14 @@ import { utils } from 'ethers'; -import { EventListenerType, addEventListener } from '~eventListeners'; import { ContractEventsSignatures, EventHandler } from '~types'; +import eventManager from '~eventManager'; +import { EventListenerType } from '@joincolony/blocks'; export const addProxyColoniesEventListener = ( eventSignature: ContractEventsSignatures, handler: EventHandler, ): void => - addEventListener({ + eventManager.addEventListener({ type: EventListenerType.ProxyColonies, eventSignature, topics: [utils.id(eventSignature)], diff --git a/apps/main-chain/src/eventListeners/token.ts b/apps/main-chain/src/eventListeners/token.ts index 119ad536..3e9a7fe3 100644 --- a/apps/main-chain/src/eventListeners/token.ts +++ b/apps/main-chain/src/eventListeners/token.ts @@ -1,6 +1,6 @@ import { ContractEventsSignatures, EventHandler } from '~types'; -import { addEventListener, getEventListeners } from '~eventListeners'; -import { EventListenerType, EventListener } from './types'; +import eventManager from '~eventManager'; +import { EventListenerType, EventListener } from '@joincolony/blocks'; import { utils } from 'ethers'; import isEqual from 'lodash/isEqual'; @@ -32,12 +32,12 @@ export const addTokenEventListener = ( * As a general rule, this will only *NOT* apply to the token Transfer event * as that's the only one treated differently. */ - const listenerExists = getEventListeners().some((existingListener) => - isEqual(existingListener, tokenListener), - ); + const listenerExists = eventManager + .getEventListeners() + .some((existingListener) => isEqual(existingListener, tokenListener)); if (listenerExists) { return; } - return addEventListener(tokenListener); + return eventManager.addEventListener(tokenListener); }; diff --git a/apps/main-chain/src/eventManager.ts b/apps/main-chain/src/eventManager.ts new file mode 100644 index 00000000..7b258a2f --- /dev/null +++ b/apps/main-chain/src/eventManager.ts @@ -0,0 +1,5 @@ +import { EventManager } from '@joincolony/blocks'; + +const eventManager = new EventManager(); + +export default eventManager; diff --git a/apps/main-chain/src/index.ts b/apps/main-chain/src/index.ts index 5816b3b6..011a0ebe 100644 --- a/apps/main-chain/src/index.ts +++ b/apps/main-chain/src/index.ts @@ -3,6 +3,7 @@ import { utils } from 'ethers'; import { startBlockListener } from '~blockListener'; import '~amplifyClient'; +import '~eventManager'; import { startStatsServer } from '~stats'; import { setupListenersForColonies, diff --git a/apps/main-chain/src/stats.ts b/apps/main-chain/src/stats.ts index 7d05447b..ce40ae79 100644 --- a/apps/main-chain/src/stats.ts +++ b/apps/main-chain/src/stats.ts @@ -1,7 +1,7 @@ import express from 'express'; import { getLastBlockNumber, getStats, initStats } from '~utils'; -import { getListenersStats } from '~eventListeners'; +import eventManager from '~eventManager'; import rpcProvider from '~provider'; import { output } from '@joincolony/utils'; @@ -33,7 +33,7 @@ app.get('/stats', async (_, res) => { * Use to check currently active listeners */ app.get('/listeners', async (_, res) => { - res.type('json').send(getListenersStats()); + res.type('json').send(eventManager.getListenersStats()); }); export const startStatsServer = async (): Promise => { diff --git a/packages/blocks/package.json b/packages/blocks/package.json new file mode 100644 index 00000000..d2cb61c9 --- /dev/null +++ b/packages/blocks/package.json @@ -0,0 +1,8 @@ +{ + "name": "@joincolony/blocks", + "main": "src/index.ts", + "version": "1.0.0", + "dependencies": { + "@joincolony/utils": "workspace:*" + } +} diff --git a/packages/blocks/src/eventManager.ts b/packages/blocks/src/eventManager.ts new file mode 100644 index 00000000..28ae3672 --- /dev/null +++ b/packages/blocks/src/eventManager.ts @@ -0,0 +1,48 @@ +import { verbose } from '@joincolony/utils'; +import { EventListener } from './types'; + +export class EventManager { + private listeners: EventListener[] = []; + + public getEventListeners(): EventListener[] { + return this.listeners; + } + + public setEventListeners(newListeners: EventListener[]): void { + this.listeners = newListeners; + } + + public addEventListener(listener: EventListener): void { + verbose( + `Added listener for event ${listener.eventSignature}`, + listener.address ? `filtering address ${listener.address}` : '', + ); + this.listeners.push(listener); + } + + public getMatchingListeners( + logTopics: string[], + logAddress: string, + ): EventListener[] { + return this.listeners.filter((listener) => { + if (listener.address && logAddress !== listener.address) { + return false; + } + + if (listener.topics.length > logTopics.length) { + return false; + } + + return listener.topics.every((topic, index) => { + return ( + topic === null || + topic.toLowerCase() === logTopics[index].toLowerCase() + ); + }); + }); + } + + public getListenersStats(): string { + return JSON.stringify(this.listeners); + } +} diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts new file mode 100644 index 00000000..9473fe15 --- /dev/null +++ b/packages/blocks/src/index.ts @@ -0,0 +1,2 @@ +export * from './eventManager'; +export * from './types'; diff --git a/packages/blocks/src/types.ts b/packages/blocks/src/types.ts new file mode 100644 index 00000000..f2c11b0b --- /dev/null +++ b/packages/blocks/src/types.ts @@ -0,0 +1,202 @@ +import { + AnyColonyClient, + AnyVotingReputationClient, + ColonyNetworkClient, + TokenClient, +} from '@colony/colony-js'; +import { LogDescription } from '@ethersproject/abi'; +/* + * Custom contract event, since we need some log values as well + */ +export interface ContractEvent extends LogDescription { + transactionHash: string; + logIndex: number; + contractAddress: string; + blockNumber: number; + blockHash: string; + timestamp: number; +} + +/* + * All contract events signatures we deal with + */ +export enum ContractEventsSignatures { + UnknownEvent = 'UnknownEvent()', + ColonyAdded = 'ColonyAdded(uint256,address,address)', + ColonyFundsClaimed = 'ColonyFundsClaimed(address,address,uint256,uint256)', + ColonyVersionAdded = 'ColonyVersionAdded(uint256,address)', + ColonyUpgraded = 'ColonyUpgraded(address,uint256,uint256)', + NetworkFeeInverseSet = 'NetworkFeeInverseSet(uint256)', + FundingPotAdded = 'FundingPotAdded(uint256)', + + // Tokens + Transfer = 'Transfer(address,address,uint256)', + LogSetAuthority = 'LogSetAuthority(address)', + LogSetOwner = 'LogSetOwner(address)', + + // Extensions + ExtensionAddedToNetwork = 'ExtensionAddedToNetwork(bytes32,uint256)', + ExtensionInstalled = 'ExtensionInstalled(bytes32,address,uint256)', + ExtensionUninstalled = 'ExtensionUninstalled(bytes32,address)', + ExtensionDeprecated = 'ExtensionDeprecated(bytes32,address,bool)', + ExtensionUpgraded = 'ExtensionUpgraded(bytes32,address,uint256)', + ExtensionInitialised = 'ExtensionInitialised()', + + // Actions + TokensMinted = 'TokensMinted(address,address,uint256)', + PayoutClaimed = 'PayoutClaimed(address,uint256,address,uint256)', + OneTxPaymentMade = 'OneTxPaymentMade(address,uint256,uint256)', + DomainAdded = 'DomainAdded(address,uint256)', + DomainMetadata = 'DomainMetadata(address,uint256,string)', + TokenUnlocked = 'TokenUnlocked(address)', + ColonyFundsMovedBetweenFundingPots = 'ColonyFundsMovedBetweenFundingPots(address,uint256,uint256,uint256,address)', + ColonyMetadata = 'ColonyMetadata(address,string)', + ArbitraryReputationUpdate = 'ArbitraryReputationUpdate(address,address,uint256,int256)', + ColonyRoleSet = 'ColonyRoleSet(address,address,uint256,uint8,bool)', + ColonyRoleSet_OLD = 'ColonyRoleSet(address,uint256,uint8,bool)', + RecoveryRoleSet = 'RecoveryRoleSet(address,bool)', + ArbitraryTransaction = 'ArbitraryTransaction(address,bytes,bool)', + + // Motions + MotionCreated = 'MotionCreated(uint256,address,uint256)', + MotionStaked = 'MotionStaked(uint256,address,uint256,uint256)', + MotionFinalized = 'MotionFinalized(uint256,bytes,bool)', + MotionRewardClaimed = 'MotionRewardClaimed(uint256,address,uint256,uint256)', + MotionVoteSubmitted = 'MotionVoteSubmitted(uint256,address)', + MotionVoteRevealed = 'MotionVoteRevealed(uint256,address,uint256)', + MotionEventSet = 'MotionEventSet(uint256,uint256)', + + // Multisig + MultisigRoleSet = 'MultisigRoleSet(address,address,uint256,uint256,bool)', + MultisigMotionExecuted = 'MotionExecuted(address,uint256,bool)', + MultisigMotionCancelled = 'MotionCancelled(address,uint256)', + MultisigMotionCreated = 'MotionCreated(address,uint256)', + MultisigApprovalChanged = 'ApprovalChanged(address,uint256,uint8,bool)', + MultisigRejectionChanged = 'RejectionChanged(address,uint256,uint8,bool)', + MultisigGlobalThresholdSet = 'GlobalThresholdSet(uint256)', + MultisigDomainSkillThresholdSet = 'DomainSkillThresholdSet(uint256,uint256)', + + // Expenditures + ExpenditureGlobalClaimDelaySet = 'ExpenditureGlobalClaimDelaySet(address,uint256)', + ExpenditureClaimDelaySet = 'ExpenditureClaimDelaySet(address,uint256,uint256,uint256)', + ExpenditureAdded = 'ExpenditureAdded(address,uint256)', + ExpenditureTransferred = 'ExpenditureTransferred(address,uint256,address)', + ExpenditureCancelled = 'ExpenditureCancelled(address,uint256)', + ExpenditureLocked = 'ExpenditureLocked(address,uint256)', + ExpenditureFinalized = 'ExpenditureFinalized(address,uint256)', + ExpenditureRecipientSet = 'ExpenditureRecipientSet(address,uint256,uint256,address)', + ExpenditurePayoutSet = 'ExpenditurePayoutSet(address,uint256,uint256,address,uint256)', + ExpenditurePayoutModifierSet = 'ExpenditurePayoutModifierSet(address,uint256,uint256,int256)', + ExpenditurePayoutClaimed = 'PayoutClaimed(address,uint256,uint256,address,uint256)', + ExpenditureStateChanged = 'ExpenditureStateChanged(address,uint256,uint256,bool[],bytes32[],bytes32)', + + // Staked Expenditures + StakeReclaimed = 'StakeReclaimed(uint256)', + ExpenditureStakerPunished = 'ExpenditureStakerPunished(address,uint256,bool)', + ExpenditureMadeViaStake = 'ExpenditureMadeViaStake(address,uint256,uint256)', + StakeFractionSet = 'StakeFractionSet(address,uint256)', + + // Staged Expenditures + ExpenditureMadeStaged = 'ExpenditureMadeStaged(address,uint256,bool)', + StagedPaymentReleased = 'StagedPaymentReleased(address,uint256,uint256)', + + // Streaming Payments + StreamingPaymentCreated = 'StreamingPaymentCreated(address,uint256)', + PaymentTokenUpdated = 'PaymentTokenUpdated(address,uint256,address,uint256)', + + // Annotations + AnnotateTransaction = 'Annotation(address,bytes32,string)', + + // Reputation + ReputationMiningCycleComplete = 'ReputationMiningCycleComplete(bytes32,uint256)', + // Metadata delta + ColonyMetadataDelta = 'ColonyMetadataDelta(address,string)', + // Proxy colonies + ProxyColonyRequested = 'ProxyColonyRequested(uint256,bytes32)', +} + +/* + * The internal Ethers event names for which we can set listeners + * (Which for some reason Ethers doesn't export the types for) + */ +export enum EthersObserverEvents { + Block = 'block', +} + +export type ChainID = string; + +export type Block = Awaited; +// @TODO ReturnType +export type BlockWithTransactions = Awaited; +// @TODO ReturnType + +export type NetworkClients = + | ColonyNetworkClient + | TokenClient + | AnyColonyClient + | AnyVotingReputationClient; + +export type EventHandler = ( + event: ContractEvent, + listener: EventListener, +) => Promise; + +export interface BaseEventListener { + type: EventListenerType; + eventSignature: ContractEventsSignatures; + topics: Array; + handler: EventHandler; + address?: string; +} + +export enum EventListenerType { + Colony = 'Colony', + Network = 'Network', + Extension = 'Extension', + Token = 'Token', + MultisigPermissions = 'MultisigPermissions', + ProxyColonies = 'ProxyColonies', +} + +export interface ColonyEventListener extends BaseEventListener { + type: EventListenerType.Colony; + address: string; +} + +export interface NetworkEventListener extends BaseEventListener { + type: EventListenerType.Network; + address: string; +} + +// Special listener just for Token transfers +// Due to the volume of these events, we can't process them in the same way +// as the normal events +export interface TokenTransferEventListener extends BaseEventListener { + type: EventListenerType.Token; +} + +export interface TokenEventListener extends BaseEventListener { + type: EventListenerType.Token; + address: string; +} + +export interface ProxyColoniesListener extends BaseEventListener { + type: EventListenerType.ProxyColonies; + address: string; +} + +export interface ExtensionEventListener extends BaseEventListener { + type: EventListenerType.Extension; + address: string; + colonyAddress: string; + extensionHash: string; + handler: EventHandler; +} + +export type EventListener = + | ColonyEventListener + | NetworkEventListener + | TokenEventListener + | TokenTransferEventListener + | ProxyColoniesListener + | ExtensionEventListener; diff --git a/packages/blocks/tsconfig.json b/packages/blocks/tsconfig.json new file mode 100644 index 00000000..ea468dfe --- /dev/null +++ b/packages/blocks/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": {}, + "include": ["src", "./*.ts"] +} diff --git a/packages/clients/src/amplifyClient.ts b/packages/clients/src/amplifyClient.ts index 878c1d0b..3b89cded 100644 --- a/packages/clients/src/amplifyClient.ts +++ b/packages/clients/src/amplifyClient.ts @@ -9,7 +9,6 @@ export type GraphQLFnReturn = Promise< export class AmplifyClient { constructor(appSyncEndpoint: string, appSyncApiKey: string) { - console.log('getting created'); Amplify.configure({ aws_appsync_graphqlEndpoint: appSyncEndpoint, aws_appsync_authenticationType: 'API_KEY', @@ -40,7 +39,6 @@ export class AmplifyClient { mutationDocument: DocumentNode, variables?: TVariables, ): GraphQLFnReturn { - console.log('this111', this); try { const result = await API.graphql>( graphqlOperation(mutationDocument, variables), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 036175e6..5151725d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,6 +111,9 @@ importers: apps/main-chain: dependencies: + '@joincolony/blocks': + specifier: workspace:* + version: link:../../packages/blocks '@joincolony/clients': specifier: workspace:* version: link:../../packages/clients @@ -127,6 +130,12 @@ importers: specifier: workspace:* version: link:../../packages/graphql + packages/blocks: + dependencies: + '@joincolony/utils': + specifier: workspace:* + version: link:../utils + packages/clients: dependencies: '@joincolony/utils':