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:
2023-03-17 10:26:47 +05:30
committed by GitHub
parent 787991c432
commit c44eff36b4
8 changed files with 237 additions and 28 deletions
+52 -7
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.
@@ -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));
}
@@ -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);
});