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:
Nabarun Gogoi 2023-03-17 10:26:47 +05:30 committed by GitHub
parent 787991c432
commit c44eff36b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 237 additions and 28 deletions

View File

@ -15,3 +15,4 @@ export * from './job-runner';
export * from './index-block';
export * from './fill';
export * from './peer';
export * from './utils';

View File

@ -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 = {

View File

@ -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",

View File

@ -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.

View File

@ -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));
}

View 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);
});

View File

@ -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. */

View File

@ -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)
}