Skip to content

Commit

Permalink
fix() enhance ws connection
Browse files Browse the repository at this point in the history
  • Loading branch information
immortal-tofu committed Dec 6, 2023
1 parent 0926283 commit 53ae153
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 28 deletions.
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"copy-to-clipboard": "^3.3.3",
"ethers": "^6.9.0",
"fhevmjs": "^0.2.0",
"isomorphic-ws": "^5.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.0"
Expand Down
2 changes: 0 additions & 2 deletions src/fhevmjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ export const createFhevmInstance = async (account: string) => {
to: '0x000000000000000000000000000000000000005d',
data: '0xd9d47bb001',
});

const strKP = getStorage(account);
const keypairs: ExportedContractKeypairs | undefined = strKP ? JSON.parse(strKP) : undefined;
instances[account] = await createInstance({ chainId, publicKey, keypairs });
console.log(publicKey);
};

const getStorageKey = (account: string) => {
Expand Down
33 changes: 18 additions & 15 deletions src/modules/game/components/Game/Game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,23 +117,26 @@ export const Game = ({ account, provider }: GameProps) => {
console.log('on');
void refreshInformations();
void refreshPlayers();
const gameContract = getEventContract(contract);
void gameContract.on(gameContract.filters.GameOpen, gameHasBeenOpen);
void gameContract.on(gameContract.filters.GameStart, gameHasStarted);
void gameContract.on(gameContract.filters.PlayerNameChanged, onPlayerNameChanged);
void gameContract.on(gameContract.filters.PlayerJoined, onPlayerJoined);
void gameContract.on(gameContract.filters.PlayerKicked, onPlayerLeave);
void gameContract.on(gameContract.filters.GoodGuysWin, onGoodGuysWin);
void gameContract.on(gameContract.filters.BadGuysWin, onBadGuysWin);
void getEventContract(contract).then((gameContract) => {
void gameContract.on(gameContract.filters.GameOpen, gameHasBeenOpen);
void gameContract.on(gameContract.filters.GameStart, gameHasStarted);
void gameContract.on(gameContract.filters.PlayerNameChanged, onPlayerNameChanged);
void gameContract.on(gameContract.filters.PlayerJoined, onPlayerJoined);
void gameContract.on(gameContract.filters.PlayerKicked, onPlayerLeave);
void gameContract.on(gameContract.filters.GoodGuysWin, onGoodGuysWin);
void gameContract.on(gameContract.filters.BadGuysWin, onBadGuysWin);
});
return () => {
console.log('off');
void gameContract.off(gameContract.filters.GameOpen, gameHasBeenOpen);
void gameContract.off(gameContract.filters.GameStart, gameHasStarted);
void gameContract.off(gameContract.filters.PlayerNameChanged, onPlayerNameChanged);
void gameContract.off(gameContract.filters.PlayerJoined, onPlayerJoined);
void gameContract.off(gameContract.filters.PlayerKicked, onPlayerLeave);
void gameContract.off(gameContract.filters.GoodGuysWin, onGoodGuysWin);
void gameContract.off(gameContract.filters.BadGuysWin, onBadGuysWin);
void getEventContract(contract).then((gameContract) => {
void gameContract.off(gameContract.filters.GameOpen, gameHasBeenOpen);
void gameContract.off(gameContract.filters.GameStart, gameHasStarted);
void gameContract.off(gameContract.filters.PlayerNameChanged, onPlayerNameChanged);
void gameContract.off(gameContract.filters.PlayerJoined, onPlayerJoined);
void gameContract.off(gameContract.filters.PlayerKicked, onPlayerLeave);
void gameContract.off(gameContract.filters.GoodGuysWin, onGoodGuysWin);
void gameContract.off(gameContract.filters.BadGuysWin, onBadGuysWin);
});
};
}
}, [contract]);
Expand Down
13 changes: 8 additions & 5 deletions src/modules/game/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,15 @@ export const Table = ({ contract, account, players }: TableProps) => {
};

if (contract) {
const gameContract = getEventContract(contract);
void gameContract.on(gameContract.filters.CardPicked, onCardPicked);
void gameContract.on(gameContract.filters.GoodDeal, onGoodDeal);
return () => {
void gameContract.off(gameContract.filters.CardPicked, onCardPicked);
void getEventContract(contract).then((gameContract) => {
void gameContract.on(gameContract.filters.CardPicked, onCardPicked);
void gameContract.on(gameContract.filters.GoodDeal, onGoodDeal);
});
return () => {
void getEventContract(contract).then((gameContract) => {
void gameContract.off(gameContract.filters.CardPicked, onCardPicked);
void gameContract.on(gameContract.filters.GoodDeal, onGoodDeal);
});
};
}
}, [contract, refresh]);
Expand Down
99 changes: 93 additions & 6 deletions src/utils/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,114 @@
import { Contract, JsonRpcProvider, WebSocketProvider } from 'ethers';
import { Contract, JsonRpcProvider, SocketBlockSubscriber, WebSocketProvider } from 'ethers';
import WebSocket from 'isomorphic-ws';

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

// Testing WebSocket on connection / reconnection before initiating new provider to prevent deadlock
const testWS = async (url: string, reconnectDelay = 100): Promise<{ chainId: number; block: { number: number } }> => {
const MAX_RETRY = 5;
let retry = 0;
let errorObject;

while (retry < MAX_RETRY + 1) {
try {
return await new Promise((resolve, reject) => {
const socket = new WebSocket(url);

socket.onopen = () => {
socket.send(
JSON.stringify([
{
jsonrpc: '2.0',
method: 'eth_chainId',
params: [],
id: 1,
},
{
jsonrpc: '2.0',
method: 'eth_getBlockByNumber',
params: ['latest', false],
id: 2,
},
]),
);
};

socket.onmessage = (event: any) => {
const data = JSON.parse(event.data);

resolve({
chainId: Number(data[0]?.result),
block: data[1]?.result,
});
};

socket.onerror = (e: Error) => {
reject(e);
};
});
} catch (e) {
console.log(`Connection to ${url} failed, attempt: (${retry} of ${MAX_RETRY})`);
await sleep(reconnectDelay);
errorObject = e;
retry++;
}
}

throw errorObject;
};

const connectWS = async (url: string, reconnectDelay = 100) => {
// Test websocket connection to prevent WebSocketProvider deadlock caused by await this._start();
const { chainId, block } = await testWS(url, reconnectDelay);
console.log(`WebSocket ${url} connected: Chain ${chainId} Block ${Number(block?.number)}`);

const provider = new WebSocketProvider(url);
const blockSub = new SocketBlockSubscriber(provider);

(provider.websocket as any).onclose = () => {
console.log(`Socket ${url} is closed, reconnecting in ${reconnectDelay} ms`);
setTimeout(() => connectWS(url, reconnectDelay), reconnectDelay);
};

provider.websocket.onerror = (e: Error) => {
console.error(`Socket ${url} encountered error, reconnecting it:\n${e.stack || e.message}`);
blockSub.stop();
void provider.destroy();
};

blockSub._handleMessage = (result) => {
console.log((provider as any)._wrapBlock({ ...result, transactions: [] }));
};
blockSub.start();

void provider.on('pending', (tx: string) => {
console.log(`New pending tx: ${tx}`);
});
return provider;
};

// const JSONRPC_URL = 'http://localhost:8545/';
// const WEBSOCKET_URL = 'ws://localhost:8546';
const JSONRPC_URL = 'https://devnet.zama.ai/';
const WEBSOCKET_URL = 'wss://devnet.ws.zama.ai/';

const jsonProvider = new JsonRpcProvider(JSONRPC_URL);
const wsProvider = new WebSocketProvider(WEBSOCKET_URL);
const wsProvider = connectWS(WEBSOCKET_URL);

const getJsonProvider = () => {
return jsonProvider;
};

const getWsProvider = () => {
return wsProvider;
const getWsProvider = async () => {
return await wsProvider;
};

export const getReadContract = (contract: Contract): Contract => {
return contract.connect(getJsonProvider()) as Contract;
};

export const getEventContract = (contract: Contract): Contract => {
return contract.connect(getWsProvider()) as Contract;
export const getEventContract = async (contract: Contract): Promise<Contract> => {
return contract.connect(await getWsProvider()) as Contract;
};

export const onNextBlock = (fn: () => void | Promise<void>) => {
Expand Down

0 comments on commit 53ae153

Please sign in to comment.