Skip to content

Commit fb579dc

Browse files
authored
improved rpc method checker logging and UI (#2514)
1 parent 8407d63 commit fb579dc

File tree

2 files changed

+195
-54
lines changed

2 files changed

+195
-54
lines changed

toolbox/src/components/RPCURLInput.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ interface RPCURLInputProps {
1010
label?: string
1111
placeholder?: string
1212
disabled?: boolean
13+
helperText?: string
1314
}
1415

15-
export function RPCURLInput({ value, onChange, label = "RPC URL", placeholder, disabled }: RPCURLInputProps) {
16+
export function RPCURLInput({ value, onChange, label = "RPC URL", placeholder, disabled, helperText }: RPCURLInputProps) {
1617
const [error, setError] = useState<string | null>(null)
1718

1819
useEffect(() => {
@@ -32,6 +33,9 @@ export function RPCURLInput({ value, onChange, label = "RPC URL", placeholder, d
3233
return (
3334
<div className="space-y-2">
3435
<Input label={label} value={value} onChange={onChange} placeholder={placeholder} disabled={disabled} />
36+
{helperText && !error && (
37+
<p className="text-sm text-zinc-600 dark:text-zinc-400">{helperText}</p>
38+
)}
3539
{error && (
3640
<div className="p-3 bg-red-50/80 border border-red-200 text-red-900 rounded">
3741
<div className="flex items-start gap-2">

toolbox/src/toolbox/Nodes/RPCMethodsCheck.tsx

Lines changed: 190 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,33 @@
22

33
import { useEffect, useState } from "react";
44
import { Button } from "../../components/Button";
5-
import { createPublicClient, http } from 'viem';
5+
import { createPublicClient, http, formatUnits } from 'viem';
66
import { useErrorBoundary } from "react-error-boundary";
77
import { pvm } from '@avalabs/avalanchejs';
88
import { RPCURLInput } from "../../components/RPCURLInput";
99
import { useWalletStore } from "../../stores/walletStore";
10+
import { ChevronDown, ChevronUp } from "lucide-react";
1011

1112
type TestResult = Record<string, { passed: boolean, message: string }>;
13+
14+
// Add utility functions at the top level
15+
const formatPChainBalance = (balance: bigint): string => {
16+
return `${(Number(balance) / 1e9).toLocaleString(undefined, {
17+
minimumFractionDigits: 0,
18+
maximumFractionDigits: 9
19+
})} AVAX`;
20+
};
21+
22+
const formatEVMBalance = (balance: bigint): string => {
23+
const formattedBalance = formatUnits(balance, 18);
24+
return Number(formattedBalance).toLocaleString(undefined, {
25+
minimumFractionDigits: 0,
26+
maximumFractionDigits: 18
27+
});
28+
};
29+
1230
async function runPChainTests(payload: { evmChainRpcUrl: string, baseURL: string, pChainAddress: string, ethAddress: string }): Promise<TestResult> {
1331
const pvmApi = new pvm.PVMApi(payload.baseURL);
14-
1532
const result: TestResult = {};
1633

1734
try {
@@ -21,7 +38,7 @@ async function runPChainTests(payload: { evmChainRpcUrl: string, baseURL: string
2138
}
2239
result["Get Balance"] = {
2340
passed: true,
24-
message: `Balance: ${balanceResponse.balance}`
41+
message: formatPChainBalance(balanceResponse.balance)
2542
};
2643
} catch (error) {
2744
console.log('error', error);
@@ -42,9 +59,14 @@ async function runEVMTests(payload: { evmChainRpcUrl: string, baseURL: string, p
4259
});
4360

4461
try {
45-
const balance = await publicClient.getBalance({ address: payload.ethAddress as `0x${string}` });
62+
const balance = await publicClient.getBalance({
63+
address: payload.ethAddress as `0x${string}`
64+
});
4665

47-
result["Get Balance"] = { passed: true, message: `Balance: ${balance}` };
66+
result["Get Balance"] = {
67+
passed: true,
68+
message: formatEVMBalance(balance)
69+
};
4870
} catch (error) {
4971
console.log('error', error);
5072
result["Get Balance"] = {
@@ -239,6 +261,7 @@ const isInExtBcFormat = (rpcUrl: string) => {
239261
export default function RPCMethodsCheck() {
240262
const [evmChainRpcUrl, setEvmChainRpcUrl] = useState<string>("");
241263
const { pChainAddress, walletEVMAddress } = useWalletStore();
264+
const [baseURL, setBaseURL] = useState<string>("https://api.avax-test.network");
242265

243266
const { showBoundary } = useErrorBoundary();
244267
const [isChecking, setIsChecking] = useState(false);
@@ -248,8 +271,6 @@ export default function RPCMethodsCheck() {
248271
admin: TestResult | null,
249272
metrics: TestResult | null
250273
}>({ pChain: null, evm: null, admin: null, metrics: null });
251-
const [baseURL, setBaseURL] = useState<string>("");
252-
253274

254275
useEffect(() => {
255276
if (!baseURL && isInExtBcFormat(evmChainRpcUrl)) {
@@ -287,60 +308,176 @@ export default function RPCMethodsCheck() {
287308
}
288309
}
289310

290-
const TestGroup = ({ title, results }: { title: string, results: TestResult | null }) => (
291-
<div className="border rounded-lg p-4">
292-
<h3 className="font-semibold mb-3">{title}</h3>
293-
{results ? (
294-
<div className="space-y-2">
295-
{Object.entries(results).map(([testName, result]) => (
296-
<div key={testName} className="flex items-center space-x-2">
297-
<div className={`w-2 h-2 rounded-full ${result.passed ? 'bg-green-500' : 'bg-red-500'}`} />
298-
<div>
299-
<span>{testName}</span>
300-
{result.message && (
301-
<span className="text-sm "> | {result.message.substring(0, 100)}{result.message.length > 100 && '...'}</span>
302-
)}
303-
</div>
304-
</div>
305-
))}
311+
const TestGroup = ({ title, results, description }: {
312+
title: string,
313+
results: TestResult | null,
314+
description: string
315+
}) => {
316+
const [expandedTests, setExpandedTests] = useState<Record<string, boolean>>({});
317+
318+
const toggleExpand = (testName: string) => {
319+
setExpandedTests(prev => ({
320+
...prev,
321+
[testName]: !prev[testName]
322+
}));
323+
};
324+
325+
const shouldShowDropdown = (testName: string) => {
326+
// Only show direct values for Balance and "Get latest block"
327+
return !testName.includes('Balance') && testName !== 'Get latest block';
328+
};
329+
330+
return (
331+
<div className="border rounded-lg p-4">
332+
<div className="mb-2">
333+
<h3 className="font-semibold">{title}</h3>
334+
<p className="text-sm text-zinc-600 dark:text-zinc-400">{description}</p>
306335
</div>
307-
) : (
308-
<p className="">No results yet</p>
309-
)}
310-
</div>
311-
);
336+
{results ? (
337+
<div className="space-y-1">
338+
{Object.entries(results).map(([testName, result], index, array) => {
339+
const isDirectDisplay = !shouldShowDropdown(testName);
340+
const nextIsDirectDisplay = index < array.length - 1 && !shouldShowDropdown(array[index + 1][0]);
341+
342+
return (
343+
<div key={testName} className={`flex flex-col ${(isDirectDisplay || nextIsDirectDisplay) ? 'mb-4' : ''}`}>
344+
{shouldShowDropdown(testName) ? (
345+
// Dropdown layout (original horizontal layout)
346+
<div className="flex items-center justify-between py-1">
347+
<div className="flex items-center space-x-2 min-w-[120px]">
348+
{result.passed ? (
349+
<div className="flex items-center text-green-600 dark:text-green-500">
350+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
351+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
352+
</svg>
353+
</div>
354+
) : (
355+
<div className="flex items-center text-red-600 dark:text-red-500">
356+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
357+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
358+
</svg>
359+
</div>
360+
)}
361+
<span className="font-medium whitespace-nowrap">{testName}</span>
362+
</div>
363+
{result.message && (
364+
<button
365+
onClick={() => toggleExpand(testName)}
366+
className="flex items-center text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-200"
367+
>
368+
{expandedTests[testName] ? (
369+
<ChevronUp className="w-4 h-4" />
370+
) : (
371+
<ChevronDown className="w-4 h-4" />
372+
)}
373+
</button>
374+
)}
375+
</div>
376+
) : (
377+
// Direct display layout (new vertical layout)
378+
<div className="flex flex-col space-y-2">
379+
<div className="flex items-center space-x-2">
380+
{result.passed ? (
381+
<div className="flex items-center text-green-600 dark:text-green-500">
382+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
383+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
384+
</svg>
385+
</div>
386+
) : (
387+
<div className="flex items-center text-red-600 dark:text-red-500">
388+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
389+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
390+
</svg>
391+
</div>
392+
)}
393+
<span className="font-medium whitespace-nowrap">{testName}</span>
394+
</div>
395+
{result.message && (
396+
<div className="text-sm text-zinc-600 dark:text-zinc-400 break-all pl-6">
397+
{result.message}
398+
</div>
399+
)}
400+
</div>
401+
)}
402+
{shouldShowDropdown(testName) && expandedTests[testName] && result.message && (
403+
<div className="text-sm text-zinc-600 dark:text-zinc-400 mt-2 pl-6 break-all">
404+
{result.message}
405+
</div>
406+
)}
407+
</div>
408+
);
409+
})}
410+
</div>
411+
) : (
412+
<p className="text-zinc-500 dark:text-zinc-400">No results yet</p>
413+
)}
414+
</div>
415+
);
416+
};
312417

313418
return (
314419
<div className="space-y-4">
315-
<h2 className="text-lg font-semibold">RPC Methods Check</h2>
316-
<div className="space-y-4">
317-
<RPCURLInput
318-
label="RPC URL"
319-
value={evmChainRpcUrl}
320-
onChange={setEvmChainRpcUrl}
321-
placeholder="Enter RPC URL"
322-
/>
323-
<RPCURLInput
324-
label="Base URL to query P Chain, optional"
325-
value={baseURL}
326-
onChange={setBaseURL}
327-
/>
328-
<Button
329-
variant="primary"
330-
onClick={checkRpc}
331-
loading={isChecking}
332-
disabled={!evmChainRpcUrl}
333-
>
334-
Run Tests
335-
</Button>
420+
<div className="border rounded-lg p-4 bg-white dark:bg-zinc-900">
421+
<h2 className="text-lg font-semibold mb-4">RPC Methods Security Check</h2>
422+
<p className="text-zinc-600 dark:text-zinc-400 mb-4">
423+
This tool helps verify the security configuration of your node's RPC endpoints.
424+
</p>
336425

337426
<div className="space-y-4">
338-
<TestGroup title="P-Chain" results={testResults.pChain} />
339-
<TestGroup title="EVM" results={testResults.evm} />
340-
<TestGroup title="Admin API" results={testResults.admin} />
341-
<TestGroup title="Metrics API" results={testResults.metrics} />
427+
<RPCURLInput
428+
label="EVM Chain RPC URL"
429+
value={evmChainRpcUrl}
430+
onChange={setEvmChainRpcUrl}
431+
placeholder="e.g., http://localhost:9650/ext/bc/C/rpc"
432+
/>
433+
<RPCURLInput
434+
label="P-Chain URL"
435+
value={baseURL}
436+
onChange={setBaseURL}
437+
placeholder="e.g., http://localhost:9650"
438+
/>
439+
<Button
440+
variant="primary"
441+
onClick={checkRpc}
442+
loading={isChecking}
443+
disabled={!evmChainRpcUrl}
444+
>
445+
{isChecking ? 'Running Security Checks...' : 'Run Security Check'}
446+
</Button>
342447
</div>
343448
</div>
449+
450+
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-3 text-sm">
451+
<h3 className="font-semibold text-blue-800 dark:text-blue-400">Understanding Results</h3>
452+
<ul className="mt-1 space-y-1 text-blue-700 dark:text-blue-300 ml-4 list-disc">
453+
<li>Green checkmark means the endpoint is properly configured</li>
454+
<li>Red X indicates a potential security concern</li>
455+
<li>For Admin and Debug endpoints, errors indicate proper security, the appropriate errors will return green checkmarks</li>
456+
</ul>
457+
</div>
458+
459+
<div className="space-y-2">
460+
<TestGroup
461+
title="P-Chain API Tests"
462+
results={testResults.pChain}
463+
description="Verifies basic P-Chain operations like balance queries."
464+
/>
465+
<TestGroup
466+
title="EVM API Tests"
467+
results={testResults.evm}
468+
description="Checks EVM endpoints and debug/trace method restrictions."
469+
/>
470+
<TestGroup
471+
title="Admin API Security"
472+
results={testResults.admin}
473+
description="Verifies administrative endpoints are properly secured."
474+
/>
475+
<TestGroup
476+
title="Metrics API Security"
477+
results={testResults.metrics}
478+
description="Ensures metrics endpoint is properly restricted."
479+
/>
480+
</div>
344481
</div>
345482
);
346483
};

0 commit comments

Comments
 (0)