Skip to content

Commit

Permalink
Merge pull request #300 from JoinColony/feat/wormhole-tx-matching
Browse files Browse the repository at this point in the history
[Proxy colonies] Feat: Get the main-chain transaction hash from a proxy chain
  • Loading branch information
bassgeta authored Jan 13, 2025
2 parents ec576a4 + 6b1d83e commit d843a62
Show file tree
Hide file tree
Showing 17 changed files with 606 additions and 59 deletions.
72 changes: 70 additions & 2 deletions apps/main-chain/src/handlers/proxyColonies/proxyColonyRequested.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,75 @@
import { ContractEvent } from '@joincolony/blocks';
import {
ContractEvent,
ContractEventsSignatures,
ProxyColonyEvents,
} from '@joincolony/blocks';
import { output } from '@joincolony/utils';
import { constants, utils } from 'ethers';
import blockManager from '~blockManager';
import rpcProvider from '~provider';
import { writeActionFromEvent } from '~utils/actions/writeAction';
import { ColonyActionType } from '@joincolony/graphql';

export const handleProxyColonyRequested = async (
event: ContractEvent,
): Promise<void> => {
console.log('proxy colony requested event', event);
const { blockNumber, contractAddress: colonyAddress } = event;

const { destinationChainId } = event.args;

const logs = await rpcProvider.getProviderInstance().getLogs({
fromBlock: blockNumber,
toBlock: blockNumber,
topics: [
[
utils.id(ContractEventsSignatures.ProxyColonyRequested),
utils.id(ContractEventsSignatures.LogMessagePublished),
],
],
});

const events = await Promise.all(
logs.map((log) =>
blockManager.mapLogToContractEvent(log, ProxyColonyEvents),
),
);

const wormholeEvent = events.find(
(event) =>
ContractEventsSignatures.LogMessagePublished === event?.signature,
);
const proxyRequestedEvent = events.find(
(event) =>
ContractEventsSignatures.ProxyColonyRequested === event?.signature,
);

if (!wormholeEvent || !proxyRequestedEvent) {
output(
`ProxyColonyRequested or LogMessagePublished are not present in the same block`,
);

return;
}

const { sender, sequence } = wormholeEvent.args;
const emitterAddress = sender.toString();
const emitterSequence = sequence.toString();

if (!emitterAddress || !sequence) {
output('Missing arguments on the LogMessagePublished events');
return;
}

await writeActionFromEvent(event, colonyAddress, {
type: ColonyActionType.AddProxyColony,
initiatorAddress: constants.AddressZero,
multiChainInfo: {
completed: false,
targetChainId: destinationChainId.toNumber(),
wormholeInfo: {
sequence: emitterSequence,
emitterAddress,
},
},
});
};
7 changes: 7 additions & 0 deletions apps/main-chain/src/multiChainBridgeClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { WormholeClient } from "@joincolony/clients";

const bridgeEndpoint = process.env.MULTI_CHAIN_BRIDGE_ENDPOINT ?? '';

const client = new WormholeClient(bridgeEndpoint);

export default client;
4 changes: 1 addition & 3 deletions apps/proxy-chain/src/eventListeners/colony.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import { addProxyColoniesEventListener } from './proxyColonies';
import { handleProxyColonyDeployed } from '~handlers/proxyColonies';

export const setupListenersForColonies = async (): Promise<void> => {

addProxyColoniesEventListener(
ContractEventsSignatures.ProxyColonyDeployed,
handleProxyColonyDeployed,
);

};
};
143 changes: 133 additions & 10 deletions apps/proxy-chain/src/handlers/proxyColonies/proxyColonyDeployed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,157 @@ import {
CreateProxyColonyDocument,
CreateProxyColonyMutation,
CreateProxyColonyMutationVariables,
GetActionInfoDocument,
GetActionInfoQuery,
GetActionInfoQueryVariables,
UpdateColonyActionDocument,
UpdateColonyActionMutation,
UpdateColonyActionMutationVariables,
} from '@joincolony/graphql';
import { ContractEvent } from '@joincolony/blocks';
import {
ContractEvent,
ContractEventsSignatures,
ProxyColonyEvents,
} from '@joincolony/blocks';
import amplifyClient from '~amplifyClient';
import rpcProvider from '~provider';
import { output } from '@joincolony/utils';
import { utils } from 'ethers';
import blockManager from '~blockManager';
import multiChainBridgeClient from '~multiChainBridgeClient';

export const handleProxyColonyDeployed = async (
event: ContractEvent,
): Promise<void> => {
console.log('proxy colony deployed event', event);
const { proxyColony: proxyColonyAddress } = event.args;
const {
blockNumber,
args: { proxyColony: proxyColonyAddress },
} = event;

if (!proxyColonyAddress) {
output('No proxyColony emitted!');
return;
}

const logs = await rpcProvider.getProviderInstance().getLogs({
fromBlock: blockNumber,
toBlock: blockNumber,
topics: [
[
utils.id(ContractEventsSignatures.ProxyColonyDeployed),
utils.id(ContractEventsSignatures.WormholeMessageReceived),
],
],
});

const events = await Promise.all(
logs.map((log) =>
blockManager.mapLogToContractEvent(log, ProxyColonyEvents),
),
);

const wormholeEvent = events.find(
(event) =>
ContractEventsSignatures.WormholeMessageReceived === event?.signature,
);
const proxyDeployedEvent = events.find(
(event) =>
ContractEventsSignatures.ProxyColonyDeployed === event?.signature,
);

if (!wormholeEvent || !proxyDeployedEvent) {
output(
`ProxyColonyDeployed or WormholeMessageReceived are not present in the same block`,
);

return;
}

console.log(`RPC provider chain id`, rpcProvider.getChainId());
console.log(
`Mapped wormhole chain id`,
multiChainBridgeClient.getWormholeChainId(rpcProvider.getChainId()),
);

const { emitterChainId, emitterAddress, sequence } = wormholeEvent.args;
const chainId = rpcProvider.getChainId();
let sourceChainTxHash;
let isDeploymentCompleted = false;

try {
const multiChainBridgeOperationsData =
await multiChainBridgeClient.fetchOperationDetails({
emitterAddress,
emitterChainId,
sequence,
});

sourceChainTxHash =
multiChainBridgeOperationsData?.sourceChain?.transaction?.txHash;
const sourceChainOperationStatus =
multiChainBridgeOperationsData?.sourceChain?.status;
isDeploymentCompleted =
sourceChainOperationStatus ===
multiChainBridgeClient.REQ_STATUS.CONFIRMED;
} catch (error) {
output(
`Error while fetching multi-chain bridge operations details: ${
(error as Error).message
}.`,
);
}

if (!sourceChainTxHash) {
output(`Missing source chain txHash`);
return;
}

// if this event was fired we NEED to save it in the database, even if the action doesn't exist
await amplifyClient.mutate<
CreateProxyColonyMutation,
CreateProxyColonyMutationVariables
>(CreateProxyColonyDocument, {
input: {
id: `${proxyColonyAddress}_${chainId}`,
colonyAddress: proxyColonyAddress,
chainId,
isActive: true,
>(CreateProxyColonyDocument, {
input: {
id: `${proxyColonyAddress}_${chainId}`,
colonyAddress: proxyColonyAddress,
chainId,
isActive: true,
},
});

const actionResponse = await amplifyClient.query<
GetActionInfoQuery,
GetActionInfoQueryVariables
>(GetActionInfoDocument, { transactionHash: sourceChainTxHash });
const actionData = actionResponse?.data?.getColonyAction;

if (!actionData) {
output(
`The txHash: ${sourceChainTxHash} is not an action on the main chain.`,
);
return;
}

if (!actionData.multiChainInfo) {
output(`The action: ${sourceChainTxHash} doesn't have multi chain data.`);
return;
}

await amplifyClient.mutate<
UpdateColonyActionMutation,
UpdateColonyActionMutationVariables
>(UpdateColonyActionDocument, {
input: {
id: sourceChainTxHash,
multiChainInfo: {
...actionData.multiChainInfo,
completed: isDeploymentCompleted,
wormholeInfo: {
emitterAddress: emitterAddress.toString(),
emitterChainId,
sequence: sequence.toString(),
},
});
},
},
});
};
7 changes: 7 additions & 0 deletions apps/proxy-chain/src/multiChainBridgeClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { WormholeClient } from "@joincolony/clients";

const bridgeEndpoint = process.env.MULTI_CHAIN_BRIDGE_ENDPOINT ?? '';

const client = new WormholeClient(bridgeEndpoint);

export default client;
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
},
"homepage": "https://github.com/JoinColony/tx-ingestor",
"dependencies": {
"@colony/colony-js": "^8.0.0-next.0",
"@colony/events": "^4.0.0-next.0",
"@colony/colony-js": "^8.0.0-next.1",
"@colony/events": "^4.0.0-next.1",
"@magicbell/core": "^5.0.16",
"aws-amplify": "^4.3.43",
"cross-fetch": "^4.0.0",
Expand Down
5 changes: 1 addition & 4 deletions packages/blocks/src/blocks/blockManager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { Log } from '@ethersproject/abstract-provider';
import { output, verbose } from '@joincolony/utils';
import { EventManager, ContractEvent, EthersObserverEvents } from '../events';
import {
Block,
BlockWithTransactions,
} from './types';
import { Block, BlockWithTransactions } from './types';
import { RpcProvider } from '@joincolony/clients';
import { utils } from 'ethers';
import { StatsManager } from '../stats/statsManager';
Expand Down
5 changes: 4 additions & 1 deletion packages/blocks/src/events/eventManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ import {
import { Extension, getExtensionHash } from '@colony/colony-js';

// @TODO @chmanie is gonna make this better, for now let's just hardcode the proxy colony events
const ProxyColonyEvents = new utils.Interface([
export const ProxyColonyEvents = new utils.Interface([
'event ProxyColonyRequested(uint256 destinationChainId, bytes32 salt)',
'event ProxyColonyDeployed(address proxyColony)',
// @TODO decouple these into MultiChainBridgeEvents
'event WormholeMessageReceived(uint16 emitterChainId, bytes32 emitterAddress, uint64 sequence)',
'event LogMessagePublished(address indexed sender,uint64 sequence,uint32 nonce,bytes payload,uint8 consistencyLevel)',
]);

export class EventManager {
Expand Down
2 changes: 2 additions & 0 deletions packages/blocks/src/events/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export enum ContractEventsSignatures {
// Proxy colonies
ProxyColonyRequested = 'ProxyColonyRequested(uint256,bytes32)',
ProxyColonyDeployed = 'ProxyColonyDeployed(address)',
LogMessagePublished = 'LogMessagePublished(address,uint64,uint32,bytes,uint8)',
WormholeMessageReceived = 'WormholeMessageReceived(uint16,bytes32,uint64)',
}

/*
Expand Down
1 change: 1 addition & 0 deletions packages/clients/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { RpcProvider } from './rpcProvider';
export * from './amplifyClient';
export * from './networkClient';
export * from './wormholeClient';
// @TODO rename clients into providers
48 changes: 31 additions & 17 deletions packages/clients/src/networkClient.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import { ColonyNetworkClient, Network, getColonyNetworkClient } from '@colony/colony-js';
import {
ColonyNetworkClient,
Network,
NetworkClientOptions,
getColonyNetworkClient,
} from '@colony/colony-js';

import { RpcProvider } from './rpcProvider';

export class NetworkClient {
private readonly rpcProvider: RpcProvider;
private readonly network: Network;
private readonly networkAddress?: string;
private readonly rpcProvider: RpcProvider;
private readonly network: Network;
private readonly networkAddress?: string;

constructor(rpcProvider: RpcProvider, network: Network, networkAddress?: string) {
this.rpcProvider = rpcProvider;
this.network = network;
this.networkAddress = networkAddress;
}
constructor(
rpcProvider: RpcProvider,
network: Network,
networkAddress?: string,
) {
this.rpcProvider = rpcProvider;
this.network = network;
this.networkAddress = networkAddress;
}

// @TODO maybe add here an options object
public getInstance(): ColonyNetworkClient {
return getColonyNetworkClient(this.network, this.rpcProvider.getProviderInstance(), {
networkAddress: this.networkAddress,
disableVersionCheck: true,
});
}
}
public getInstance(
options: NetworkClientOptions = { disableVersionCheck: true },
): ColonyNetworkClient {
return getColonyNetworkClient(
this.network,
this.rpcProvider.getProviderInstance(),
{
...options,
networkAddress: this.networkAddress,
},
);
}
}
Loading

0 comments on commit d843a62

Please sign in to comment.