mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-01-21 10:39:06 +00:00
Send mobymask p2p messages to laconicd (#339)
* Use laconic ETH RPC endpoint for querying * Run peer with message handler to send tx to laconic * Handle revoke messages in mobymask p2p * Set tx gasLimit explicitly for slow eth_estimateGas call * Convert delegationHash to hex string before broadcasting json
This commit is contained in:
parent
787991c432
commit
c44eff36b4
@ -15,3 +15,4 @@ export * from './job-runner';
|
||||
export * from './index-block';
|
||||
export * from './fill';
|
||||
export * from './peer';
|
||||
export * from './utils';
|
||||
|
@ -158,7 +158,7 @@ export class Indexer implements IndexerInterface {
|
||||
if (this._serverMode === ETH_CALL_MODE) {
|
||||
const contract = new ethers.Contract(token, this._abi, this._ethProvider);
|
||||
|
||||
// eth_call doesnt support calling method by blockHash https://eth.wiki/json-rpc/API#the-default-block-parameter
|
||||
// eth_call doesn't support calling method by blockHash https://eth.wiki/json-rpc/API#the-default-block-parameter
|
||||
const value = await contract.balanceOf(owner, { blockTag: blockHash });
|
||||
|
||||
result = {
|
||||
|
@ -24,7 +24,8 @@
|
||||
"import-state:dev": "DEBUG=vulcanize:* ts-node src/cli/import-state.ts",
|
||||
"inspect-cid": "DEBUG=vulcanize:* ts-node src/cli/inspect-cid.ts",
|
||||
"index-block": "DEBUG=vulcanize:* ts-node src/cli/index-block.ts",
|
||||
"peer": "DEBUG='vulcanize:*, laconic:*' node --enable-source-maps dist/cli/peer.js"
|
||||
"peer": "DEBUG='vulcanize:*, laconic:*' node --enable-source-maps dist/cli/peer.js",
|
||||
"peer-listener": "DEBUG='vulcanize:*, laconic:*' node --enable-source-maps dist/peer-listener.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -42,6 +43,7 @@
|
||||
"@cerc-io/ipld-eth-client": "^0.2.30",
|
||||
"@cerc-io/solidity-mapper": "^0.2.30",
|
||||
"@cerc-io/util": "^0.2.30",
|
||||
"@cerc-io/peer": "^0.2.30",
|
||||
"@ethersproject/providers": "^5.4.4",
|
||||
"apollo-type-bigint": "^0.1.3",
|
||||
"debug": "^4.3.1",
|
||||
|
@ -11,7 +11,7 @@ import { ethers } from 'ethers';
|
||||
import { JsonFragment } from '@ethersproject/abi';
|
||||
import { JsonRpcProvider } from '@ethersproject/providers';
|
||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
||||
import { MappingKey, StorageLayout, getStorageValue } from '@cerc-io/solidity-mapper';
|
||||
import {
|
||||
Indexer as BaseIndexer,
|
||||
IndexerInterface,
|
||||
@ -270,11 +270,14 @@ export class Indexer implements IndexerInterface {
|
||||
defaultValue: any
|
||||
): Promise<Entity> {
|
||||
const [{ number }, syncStatus] = await Promise.all([
|
||||
this._ethProvider.send('eth_getHeaderByHash', [blockHash]),
|
||||
// Laconicd doesn't support eth_getHeaderByHash
|
||||
// this._ethProvider.send('eth_getHeaderByHash', [blockHash]),
|
||||
this._ethProvider.getBlock(blockHash),
|
||||
this.getSyncStatus()
|
||||
]);
|
||||
|
||||
const blockNumber = ethers.BigNumber.from(number).toNumber();
|
||||
// const blockNumber = ethers.BigNumber.from(number).toNumber();
|
||||
const blockNumber = number;
|
||||
|
||||
let result: ValueResult = {
|
||||
value: defaultValue
|
||||
@ -294,7 +297,17 @@ export class Indexer implements IndexerInterface {
|
||||
const storageLayout = this._storageLayoutMap.get(KIND_PHISHERREGISTRY);
|
||||
assert(storageLayout);
|
||||
|
||||
result = await this._baseIndexer.getStorageValue(
|
||||
// Get storage value using ipld-eth-server
|
||||
// result = await this._baseIndexer.getStorageValue(
|
||||
// storageLayout,
|
||||
// blockHash,
|
||||
// contractAddress,
|
||||
// storageVariableName,
|
||||
// ...Object.values(mappingKeys)
|
||||
// );
|
||||
|
||||
// Get storage value using ETH RPC endpoint
|
||||
result = await this._getStorageValueRPC(
|
||||
storageLayout,
|
||||
blockHash,
|
||||
contractAddress,
|
||||
@ -313,6 +326,30 @@ export class Indexer implements IndexerInterface {
|
||||
} as any;
|
||||
}
|
||||
|
||||
async _getStorageValueRPC (storageLayout: StorageLayout, blockHash: string, contractAddress: string, variable: string, ...mappingKeys: MappingKey[]): Promise<ValueResult> {
|
||||
const getStorageAt = async (params: { blockHash: string, contract: string, slot: string }) => {
|
||||
const { blockHash, contract, slot } = params;
|
||||
const value = await this._ethProvider.getStorageAt(contract, slot, blockHash);
|
||||
|
||||
return {
|
||||
value,
|
||||
proof: {
|
||||
// Returning null value as proof, since ethers library getStorageAt method doesn't return proof.
|
||||
data: JSON.stringify(null)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return getStorageValue(
|
||||
storageLayout,
|
||||
getStorageAt,
|
||||
blockHash,
|
||||
contractAddress,
|
||||
variable,
|
||||
...mappingKeys
|
||||
);
|
||||
}
|
||||
|
||||
async getStorageValue (storageLayout: StorageLayout, blockHash: string, contractAddress: string, variable: string, ...mappingKeys: MappingKey[]): Promise<ValueResult> {
|
||||
return this._baseIndexer.getStorageValue(
|
||||
storageLayout,
|
||||
@ -608,11 +645,19 @@ export class Indexer implements IndexerInterface {
|
||||
return this._baseIndexer.getAncestorAtDepth(blockHash, depth);
|
||||
}
|
||||
|
||||
// Get latest block using eth client.
|
||||
// Get latest block using eth provider.
|
||||
async getLatestBlock (): Promise<BlockHeight> {
|
||||
const { block } = await this._ethClient.getBlockByHash();
|
||||
// Use ipld-eth-server
|
||||
// const { block } = await this._ethClient.getBlockByHash();
|
||||
|
||||
return block;
|
||||
// Use ETH RPC endpoint
|
||||
const number = await this._ethProvider.getBlockNumber();
|
||||
const { hash } = await this._ethProvider.getBlock(number);
|
||||
|
||||
return {
|
||||
number,
|
||||
hash
|
||||
};
|
||||
}
|
||||
|
||||
// Get full transaction data.
|
||||
|
@ -3,10 +3,13 @@
|
||||
//
|
||||
|
||||
import debug from 'debug';
|
||||
import { ethers } from 'ethers';
|
||||
import { ethers, Signer } from 'ethers';
|
||||
import { TransactionReceipt, TransactionResponse } from '@ethersproject/providers';
|
||||
|
||||
import { abi as PhisherRegistryABI } from './artifacts/PhisherRegistry.json';
|
||||
|
||||
const log = debug('laconic:libp2p-utils');
|
||||
|
||||
const contractInterface = new ethers.utils.Interface(PhisherRegistryABI);
|
||||
|
||||
const MESSAGE_KINDS = {
|
||||
@ -14,6 +17,71 @@ const MESSAGE_KINDS = {
|
||||
REVOKE: 'revoke'
|
||||
};
|
||||
|
||||
export async function sendMessageToL2 (
|
||||
signer: Signer,
|
||||
{ contractAddress, gasLimit }: {
|
||||
contractAddress: string,
|
||||
gasLimit: number
|
||||
},
|
||||
data: any
|
||||
): Promise<void> {
|
||||
const { kind, message } = data;
|
||||
const contract = new ethers.Contract(contractAddress, PhisherRegistryABI, signer);
|
||||
let receipt: TransactionReceipt | undefined;
|
||||
|
||||
try {
|
||||
switch (kind) {
|
||||
case MESSAGE_KINDS.INVOKE: {
|
||||
const signedInvocations = message;
|
||||
|
||||
const transaction: TransactionResponse = await contract.invoke(
|
||||
signedInvocations,
|
||||
// Setting gasLimit as eth_estimateGas call takes too long in L2 chain
|
||||
{ gasLimit }
|
||||
);
|
||||
|
||||
receipt = await transaction.wait();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MESSAGE_KINDS.REVOKE: {
|
||||
const { signedDelegation, signedIntendedRevocation } = message;
|
||||
|
||||
const transaction: TransactionResponse = await contract.revokeDelegation(
|
||||
signedDelegation,
|
||||
signedIntendedRevocation,
|
||||
// Setting gasLimit as eth_estimateGas call takes too long in L2 chain
|
||||
{ gasLimit }
|
||||
);
|
||||
|
||||
receipt = await transaction.wait();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
log(`Handler for libp2p message kind ${kind} not implemented`);
|
||||
log(JSON.stringify(message, null, 2));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (receipt) {
|
||||
log(`Transaction receipt for ${kind} message`, {
|
||||
to: receipt.to,
|
||||
blockNumber: receipt.blockNumber,
|
||||
blockHash: receipt.blockHash,
|
||||
transactionHash: receipt.transactionHash,
|
||||
effectiveGasPrice: receipt.effectiveGasPrice.toString(),
|
||||
gasUsed: receipt.gasUsed.toString()
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
log(error);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseLibp2pMessage (log: debug.Debugger, peerId: string, data: any): void {
|
||||
log('Received a message on mobymask P2P network from peer:', peerId);
|
||||
const { kind, message } = data;
|
||||
@ -57,17 +125,5 @@ function _parseRevocation (log: debug.Debugger, msg: any): void {
|
||||
log('Signed delegation:');
|
||||
log(JSON.stringify(signedDelegation, null, 2));
|
||||
log('Signed intention to revoke:');
|
||||
const stringifiedSignedIntendedRevocation = JSON.stringify(
|
||||
signedIntendedRevocation,
|
||||
(key, value) => {
|
||||
if (key === 'delegationHash' && value.type === 'Buffer') {
|
||||
// Show hex value for delegationHash instead of Buffer
|
||||
return ethers.utils.hexlify(Buffer.from(value));
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
2
|
||||
);
|
||||
log(stringifiedSignedIntendedRevocation);
|
||||
log(JSON.stringify(signedIntendedRevocation, null, 2));
|
||||
}
|
||||
|
105
packages/mobymask-v2-watcher/src/peer-listener.ts
Normal file
105
packages/mobymask-v2-watcher/src/peer-listener.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import debug from 'debug';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import yargs from 'yargs';
|
||||
import assert from 'assert';
|
||||
|
||||
import { Config, DEFAULT_CONFIG_PATH, getConfig, initClients } from '@cerc-io/util';
|
||||
import {
|
||||
PeerInitConfig,
|
||||
PeerIdObj
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1319854183
|
||||
} from '@cerc-io/peer';
|
||||
|
||||
import { sendMessageToL2 } from './libp2p-utils';
|
||||
import { readPeerId } from '@cerc-io/cli';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
const log = debug('vulcanize:peer-listener');
|
||||
|
||||
const DEFAULT_GAS_LIMIT = 500000;
|
||||
|
||||
interface Arguments {
|
||||
configFile: string;
|
||||
privateKey: string;
|
||||
contractAddress: string;
|
||||
gasLimit: number;
|
||||
}
|
||||
|
||||
export const main = async (): Promise<any> => {
|
||||
const argv = _getArgv();
|
||||
const config: Config = await getConfig(argv.configFile);
|
||||
const { ethProvider } = await initClients(config);
|
||||
|
||||
const p2pConfig = config.server.p2p;
|
||||
const peerConfig = p2pConfig.peer;
|
||||
assert(peerConfig, 'Peer config not set');
|
||||
|
||||
const { Peer } = await import('@cerc-io/peer');
|
||||
|
||||
let peerIdObj: PeerIdObj | undefined;
|
||||
if (peerConfig.peerIdFile) {
|
||||
peerIdObj = readPeerId(peerConfig.peerIdFile);
|
||||
}
|
||||
|
||||
const peer = new Peer(peerConfig.relayMultiaddr, true);
|
||||
|
||||
const peerNodeInit: PeerInitConfig = {
|
||||
pingInterval: peerConfig.pingInterval,
|
||||
pingTimeout: peerConfig.pingTimeout,
|
||||
maxRelayConnections: peerConfig.maxRelayConnections,
|
||||
relayRedialInterval: peerConfig.relayRedialInterval,
|
||||
maxConnections: peerConfig.maxConnections,
|
||||
dialTimeout: peerConfig.dialTimeout,
|
||||
enableDebugInfo: peerConfig.enableDebugInfo
|
||||
};
|
||||
|
||||
await peer.init(peerNodeInit, peerIdObj);
|
||||
const wallet = new ethers.Wallet(argv.privateKey, ethProvider);
|
||||
|
||||
peer.subscribeTopic(peerConfig.pubSubTopic, (peerId, data) => {
|
||||
log('Received a message on mobymask P2P network from peer:', peerId);
|
||||
|
||||
// TODO: throttle message handler
|
||||
sendMessageToL2(wallet, argv, data);
|
||||
});
|
||||
|
||||
log(`Peer ID: ${peer.peerId?.toString()}`);
|
||||
};
|
||||
|
||||
const _getArgv = (): Arguments => {
|
||||
return yargs(hideBin(process.argv)).parserConfiguration({
|
||||
'parse-numbers': false
|
||||
}).options({
|
||||
configFile: {
|
||||
alias: 'config-file',
|
||||
describe: 'configuration file path (toml)',
|
||||
type: 'string',
|
||||
default: DEFAULT_CONFIG_PATH
|
||||
},
|
||||
privateKey: {
|
||||
alias: 'private-key',
|
||||
demandOption: true,
|
||||
describe: 'Private key of the account to use for eth_call',
|
||||
type: 'string'
|
||||
},
|
||||
contractAddress: {
|
||||
alias: 'contract',
|
||||
demandOption: true,
|
||||
describe: 'Address of MobyMask contract',
|
||||
type: 'string'
|
||||
},
|
||||
gasLimit: {
|
||||
alias: 'gas-limit',
|
||||
describe: 'Gas limit for eth txs',
|
||||
type: 'number',
|
||||
default: DEFAULT_GAS_LIMIT
|
||||
}
|
||||
// https://github.com/yargs/yargs/blob/main/docs/typescript.md?plain=1#L83
|
||||
}).parseSync();
|
||||
};
|
||||
|
||||
main().then(() => {
|
||||
log('Starting peer...');
|
||||
}).catch(err => {
|
||||
log(err);
|
||||
});
|
@ -5,8 +5,8 @@
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
"lib": ["es2019"], /* Specify library files to be included in the compilation. */
|
||||
"module": "CommonJS", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
"lib": [ "ES5", "ES6", "ES2020" ], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
@ -44,7 +44,7 @@
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
"moduleResolution": "Node16", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
|
@ -13,7 +13,7 @@ import isArray from 'lodash/isArray';
|
||||
|
||||
import { StorageLayout, GetStorageAt } from '../src';
|
||||
|
||||
// storageLayout doesnt exist in type CompilerOutput doesnt.
|
||||
// storageLayout doesn't exist in type CompilerOutput doesn't.
|
||||
// Extending CompilerOutput type to include storageLayout property.
|
||||
interface StorageCompilerOutput extends CompilerOutput {
|
||||
contracts: {
|
||||
@ -82,7 +82,7 @@ export const getStorageAt: GetStorageAt = async ({ blockHash, contract, slot })
|
||||
return {
|
||||
value,
|
||||
proof: {
|
||||
// Returning null value as proof, since ethers library getStorageAt method doesnt return proof.
|
||||
// Returning null value as proof, since ethers library getStorageAt method doesn't return proof.
|
||||
// This function is used in tests to mock the getStorageAt method of ipld-eth-client which returns proof along with value.
|
||||
data: JSON.stringify(null)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user