Skip to content

Commit

Permalink
fix: 🐛 geth node introspection if nodeInfo method is disabled
Browse files Browse the repository at this point in the history
Fixed error when talking to Geth nodes with admin_nodeInfo method
disabled by falling back to the generic node platform adapter. Also
improved logging of enode field value in node info messages.
  • Loading branch information
ziegfried committed Jan 30, 2020
1 parent 3af2a9c commit e3f04a0
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 16 deletions.
1 change: 1 addition & 0 deletions src/eth/client.ts
Expand Up @@ -115,6 +115,7 @@ export class BatchedEthereumClient extends EthereumClient {
callback: (e, result) => {
if (e) {
reject(e);
return;
}
try {
checkError(result);
Expand Down
12 changes: 11 additions & 1 deletion src/eth/http.ts
Expand Up @@ -2,7 +2,7 @@ import { default as HttpAgent, HttpOptions, HttpsAgent } from 'agentkeepalive';
import fetch from 'node-fetch';
import { createModuleDebug } from '../utils/debug';
import { isHttps } from '../utils/httputils';
import { isValidJsonRpcResponse, JsonRpcRequest, JsonRpcResponse } from './jsonrpc';
import { isValidJsonRpcResponse, JsonRpcRequest, JsonRpcResponse, checkError } from './jsonrpc';
import { EthereumTransport } from './transport';
import { httpClientStats, AggregateMetric } from '../utils/stats';
import { HttpTransportConfig } from '../config';
Expand Down Expand Up @@ -99,6 +99,16 @@ export class HttpTransport implements EthereumTransport {
}
this.aggregates.requestDuration.push(Date.now() - startTime);
debug('Completed JSON RPC request in %d ms', Date.now() - startTime);

if (Array.isArray(request) !== Array.isArray(data)) {
checkError(Array.isArray(data) ? data[0] : data);
throw new Error(
Array.isArray(request)
? 'JSON RPC returned single message, was expecting batch'
: 'JSON RPC returned batch, was expecting single message'
);
}

return data as JsonRpcResponse | JsonRpcResponse[];
} catch (e) {
this.counters.errors++;
Expand Down
4 changes: 2 additions & 2 deletions src/eth/jsonrpc.ts
Expand Up @@ -41,8 +41,8 @@ export class JsonRpcError extends Error {
}
}

export function checkError(msg: JsonRpcResponse) {
if (msg.error) {
export function checkError(msg?: JsonRpcResponse) {
if (msg?.error) {
throw new JsonRpcError(msg.error.message, msg.error.code, msg.error.data);
}
}
Expand Down
13 changes: 12 additions & 1 deletion src/introspect.ts
Expand Up @@ -46,13 +46,24 @@ export async function introspectTargetNodePlatform(
});
info('Retrieved ethereum node version: %s', version);

const adapter = createNodeAdapter(version, network);
let adapter = createNodeAdapter(version, network);
if (typeof adapter.initialize === 'function') {
debug('Initializing node platform adatper: %s', adapter.name);
try {
await adapter.initialize(eth);
} catch (e) {
error('Failed to initialize node platform adapter:', e);

try {
adapter = new GenericNodeAdapter(version, network);
info('Attempting to use generic node platform adapter: %s', adapter.name);
if (typeof adapter.initialize === 'function') {
await adapter.initialize(eth);
}
return adapter;
} catch (e) {
error('Failed to initialize generic node platform adapter:', e);
}
throw new Error(`Failed initialize node platform adapter ${adapter.name}`);
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/meta.ts
@@ -1,10 +1,9 @@
import { hostname } from 'os';
import { EthloggerConfig } from './config';
import { KNOWN_NETOWORK_NAMES } from './eth/networks';
import { NodePlatformAdapter } from './platforms';
import { createModuleDebug } from './utils/debug';
import { removeEmtpyValues } from './utils/obj';
import { subsituteVariablesInValues, subsituteVariables } from './utils/vars';
import { subsituteVariables, subsituteVariablesInValues } from './utils/vars';

const { debug } = createModuleDebug('meta');

Expand Down Expand Up @@ -63,7 +62,7 @@ export function substituteVariablesInHecConfig(
ENODE: platformAdapter.enode ?? undefined,
PLATFORM: platformAdapter.name,
NETWORK_ID: networkId != null ? String(networkId) : undefined,
NETWORK: config.eth.network ?? (networkId != null ? KNOWN_NETOWORK_NAMES[networkId] : undefined),
NETWORK: platformAdapter.networkName ?? undefined,
PID: String(pid),
VERSION: ethloggerVersion,
NODE_VERSION: nodeVersion,
Expand Down
22 changes: 14 additions & 8 deletions src/platforms/generic.ts
@@ -1,23 +1,24 @@
import { NodePlatformAdapter } from '.';
import { EthereumClient } from '../eth/client';
import { JsonRpcError } from '../eth/jsonrpc';
import { KNOWN_NETOWORK_NAMES } from '../eth/networks';
import {
blockNumber,
clientVersion,
gasPrice,
hashRate,
netVersion,
peerCount,
pendingTransactions,
protocolVersion,
syncing,
clientVersion,
} from '../eth/requests';
import { formatPendingTransaction } from '../format';
import { PendingTransactionMessage, NodeInfo } from '../msgs';
import { NodeInfo, PendingTransactionMessage } from '../msgs';
import { OutputMessage } from '../output';
import { bigIntToNumber } from '../utils/bn';
import { createModuleDebug } from '../utils/debug';
import { prefixKeys } from '../utils/obj';
import { JsonRpcError } from '../eth/jsonrpc';

const { debug, warn, error } = createModuleDebug('platforms:generic');

Expand Down Expand Up @@ -87,16 +88,17 @@ export async function checkPendingTransactionsMethodSupport(ethClient: EthereumC
try {
debug('Checking if generic node supports pendingTranscations RPC method');
await ethClient.request(pendingTransactions());
debug('Pending transactions');
debug('Pending transactions seem to be supported');
return true;
} catch (e) {
if (e instanceof JsonRpcError && e.message === 'Method not found') {
warn('Generic node does not seem to support the eth_pendingTransactions RPC method');
if (e instanceof JsonRpcError) {
warn('Generic node does not seem to support the eth_pendingTransactions RPC method', e);
return false;
} else {
error('Encountered unexpected error while checking for pendingTransaction method support', e);
return false;
}
}
return true;
}

export class GenericNodeAdapter implements NodePlatformAdapter {
Expand Down Expand Up @@ -126,7 +128,7 @@ export class GenericNodeAdapter implements NodePlatformAdapter {
protocolVersion: defaultInfo.protocolVersion ?? null,
clientVersion: version,
platform: `generic:${name}`,
network: this.network ?? null,
network: this.networkName,
};
return this.nodeInfo;
}
Expand All @@ -147,6 +149,10 @@ export class GenericNodeAdapter implements NodePlatformAdapter {
return this.nodeInfo?.networkId ?? null;
}

public get networkName(): string | null {
return this.network ?? (this.networkId != null ? KNOWN_NETOWORK_NAMES[this.networkId] : null) ?? null;
}

public get protocolVersion(): number | null {
return this.nodeInfo?.protocolVersion ?? null;
}
Expand Down
7 changes: 6 additions & 1 deletion src/platforms/geth.ts
@@ -1,13 +1,14 @@
import { NodePlatformAdapter } from '.';
import { EthereumClient } from '../eth/client';
import { KNOWN_NETOWORK_NAMES } from '../eth/networks';
import { clientVersion, gethMemStats, gethMetrics, gethNodeInfo, gethPeers, gethTxpool } from '../eth/requests';
import { GethMemStats, GethMetrics, GethNodeInfoResponse, GethPeer } from '../eth/responses';
import { formatPendingTransaction } from '../format';
import { NodeInfo, PendingTransactionMessage } from '../msgs';
import { OutputMessage } from '../output';
import { createModuleDebug } from '../utils/debug';
import { durationStringToMs, parseAbbreviatedNumber } from '../utils/parse';
import { captureDefaultMetrics, fetchDefaultNodeInfo } from './generic';
import { parseAbbreviatedNumber, durationStringToMs } from '../utils/parse';

const { debug, error } = createModuleDebug('platforms:geth');

Expand Down Expand Up @@ -186,6 +187,10 @@ export class GethAdapter implements NodePlatformAdapter {
return this.nodeInfo?.protocolVersion ?? null;
}

public get networkName(): string | null {
return this.network ?? (this.networkId != null ? KNOWN_NETOWORK_NAMES[this.networkId] : null) ?? null;
}

public async captureNodeStats(ethClient: EthereumClient, captureTime: number): Promise<OutputMessage[]> {
const [defaultMetrics, getMetrics, txpoolData] = await Promise.all([
captureDefaultMetrics(ethClient, captureTime),
Expand Down
1 change: 1 addition & 0 deletions src/platforms/index.ts
Expand Up @@ -9,6 +9,7 @@ export interface NodePlatformAdapter {
readonly enode: string | null;
readonly networkId: number | null;
readonly protocolVersion: number | null;
readonly networkName: string | null;
initialize?(ethClient: EthereumClient): Promise<void>;
captureNodeStats(ethClient: EthereumClient, captureTime: number): Promise<OutputMessage[]>;
captureNodeInfo(ethClient: EthereumClient): Promise<NodeInfo>;
Expand Down
5 changes: 5 additions & 0 deletions src/platforms/parity.ts
Expand Up @@ -5,6 +5,7 @@ import { createModuleDebug } from '../utils/debug';
import { NodeInfo } from '../msgs';
import { fetchDefaultNodeInfo, captureDefaultMetrics } from './generic';
import { ParityMode, ParityNodeKind } from '../eth/responses';
import { KNOWN_NETOWORK_NAMES } from '../eth/networks';

const { debug } = createModuleDebug('platforms:parity');

Expand Down Expand Up @@ -67,6 +68,10 @@ export class ParityAdapter implements NodePlatformAdapter {
return this.nodeInfo?.protocolVersion ?? null;
}

public get networkName(): string | null {
return this.network ?? (this.networkId != null ? KNOWN_NETOWORK_NAMES[this.networkId] : null) ?? null;
}

public async captureNodeStats(eth: EthereumClient, captureTime: number) {
// TODO
return [await captureDefaultMetrics(eth, captureTime)];
Expand Down

0 comments on commit e3f04a0

Please sign in to comment.