diff --git a/components/toolbox/components/ReverseProxySetup.tsx b/components/toolbox/components/ReverseProxySetup.tsx index 74eec54b48d..b15b572aca0 100644 --- a/components/toolbox/components/ReverseProxySetup.tsx +++ b/components/toolbox/components/ReverseProxySetup.tsx @@ -50,7 +50,7 @@ const generateHealthCheckCommand = (domain: string, chainId: string) => { const processedDomain = nipify(domain); return `curl -X POST --data '{ - "jsonrpc":"2.0", "method":"eth_chainId", "params":[], "id":1 + "jsonrpc":"2.0", "method":"eth_blockNumber", "params":[], "id":1 }' -H 'content-type:application/json;' \\ https://${processedDomain}/ext/bc/${chainId}/rpc`; }; diff --git a/components/toolbox/console/layer-1/AvalancheGoDockerL1.tsx b/components/toolbox/console/layer-1/AvalancheGoDockerL1.tsx index 4f3a63f29ea..b7fa57934f6 100644 --- a/components/toolbox/console/layer-1/AvalancheGoDockerL1.tsx +++ b/components/toolbox/console/layer-1/AvalancheGoDockerL1.tsx @@ -7,65 +7,152 @@ import { Container } from "../../components/Container"; import { getBlockchainInfo, getSubnetInfo } from "../../coreViem/utils/glacier"; import InputSubnetId from "../../components/InputSubnetId"; import BlockchainDetailsDisplay from "../../components/BlockchainDetailsDisplay"; -import { Steps, Step } from "fumadocs-ui/components/steps"; import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'; import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; import { Button } from "../../components/Button"; -import { Success } from "../../components/Success"; -import { DockerInstallation } from "../../components/DockerInstallation"; -import { NodeBootstrapCheck } from "../../components/NodeBootstrapCheck"; +import { Steps, Step } from "fumadocs-ui/components/steps"; +import { SyntaxHighlightedJSON } from "../../components/genesis/SyntaxHighlightedJSON"; import { ReverseProxySetup } from "../../components/ReverseProxySetup"; -import { AddToWalletStep } from "../../components/AddToWalletStep"; -import { ConfigureNodeType } from "../../components/ConfigureNodeType"; -import { generateDockerCommand } from "./create/config"; +import { GenesisHighlightProvider, useGenesisHighlight } from "../../components/genesis/GenesisHighlightContext"; import { SUBNET_EVM_VM_ID } from "@/constants/console"; +import { generateChainConfig, generateDockerCommand, generateConfigFileCommand } from "./node-config"; +import { useNodeConfigHighlighting } from "./useNodeConfigHighlighting"; -export default function AvalanchegoDocker() { +function AvalanchegoDockerInner() { + const { setHighlightPath, clearHighlight, highlightPath } = useGenesisHighlight(); const [chainId, setChainId] = useState(""); const [subnetId, setSubnetId] = useState(""); const [subnet, setSubnet] = useState(null); const [blockchainInfo, setBlockchainInfo] = useState(null); const [isLoading, setIsLoading] = useState(false); const [nodeType, setNodeType] = useState<"validator" | "public-rpc">("validator"); - const [rpcCommand, setRpcCommand] = useState(""); const [domain, setDomain] = useState(""); const [enableDebugTrace, setEnableDebugTrace] = useState(false); + const [adminApiEnabled, setAdminApiEnabled] = useState(false); const [pruningEnabled, setPruningEnabled] = useState(true); + const [logLevel, setLogLevel] = useState("info"); const [subnetIdError, setSubnetIdError] = useState(null); - const [chainAddedToWallet, setChainAddedToWallet] = useState(null); - const [nodeIsReady, setNodeIsReady] = useState(false); const [selectedRPCBlockchainId, setSelectedRPCBlockchainId] = useState(""); const [minDelayTarget, setMinDelayTarget] = useState(250); + const [configJson, setConfigJson] = useState(""); + + // Advanced cache settings + const [trieCleanCache, setTrieCleanCache] = useState(512); + const [trieDirtyCache, setTrieDirtyCache] = useState(512); + const [trieDirtyCommitTarget, setTrieDirtyCommitTarget] = useState(20); + const [triePrefetcherParallelism, setTriePrefetcherParallelism] = useState(16); + const [snapshotCache, setSnapshotCache] = useState(256); + const [commitInterval, setCommitInterval] = useState(4096); + const [stateSyncServerTrieCache, setStateSyncServerTrieCache] = useState(64); + + // API settings + const [rpcGasCap, setRpcGasCap] = useState(50000000); + const [rpcTxFeeCap, setRpcTxFeeCap] = useState(100); + const [apiMaxBlocksPerRequest, setApiMaxBlocksPerRequest] = useState(0); + const [allowUnfinalizedQueries, setAllowUnfinalizedQueries] = useState(false); + const [batchRequestLimit, setBatchRequestLimit] = useState(0); // 0 = no limit (default) + const [batchResponseMaxSize, setBatchResponseMaxSize] = useState(25000000); + + // State and history + const [acceptedCacheSize, setAcceptedCacheSize] = useState(32); + const [transactionHistory, setTransactionHistory] = useState(0); + const [stateSyncEnabled, setStateSyncEnabled] = useState(false); + const [skipTxIndexing, setSkipTxIndexing] = useState(false); + + // Transaction settings + const [preimagesEnabled, setPreimagesEnabled] = useState(false); + const [localTxsEnabled, setLocalTxsEnabled] = useState(false); + + // Gossip settings (validator specific) + const [pushGossipNumValidators, setPushGossipNumValidators] = useState(100); + const [pushGossipPercentStake, setPushGossipPercentStake] = useState(0.9); + + // Profiling + const [continuousProfilerDir, setContinuousProfilerDir] = useState(""); + const [continuousProfilerFrequency, setContinuousProfilerFrequency] = useState("15m"); + + // Show advanced settings + const [showAdvancedSettings, setShowAdvancedSettings] = useState(false); const { avalancheNetworkID } = useWalletStore(); const isRPC = nodeType === "public-rpc"; + // Get highlighted lines for JSON preview + const highlightedLines = useNodeConfigHighlighting(highlightPath, configJson); + + // Generate Subnet-EVM chain configuration JSON when parameters change useEffect(() => { + if (!subnetId || !chainId || !blockchainInfo) { + setConfigJson(""); + return; + } + try { - const vmId = blockchainInfo?.vmId || SUBNET_EVM_VM_ID; - setRpcCommand(generateDockerCommand( - [subnetId], - isRPC, - avalancheNetworkID, - chainId, - vmId, + const config = generateChainConfig( + nodeType, enableDebugTrace, + adminApiEnabled, pruningEnabled, - false, // isPrimaryNetwork = false - isRPC ? null : minDelayTarget // Pass minDelayTarget only for validators - )); + logLevel, + minDelayTarget, + trieCleanCache, + trieDirtyCache, + trieDirtyCommitTarget, + triePrefetcherParallelism, + snapshotCache, + commitInterval, + stateSyncServerTrieCache, + rpcGasCap, + rpcTxFeeCap, + apiMaxBlocksPerRequest, + allowUnfinalizedQueries, + batchRequestLimit, + batchResponseMaxSize, + acceptedCacheSize, + transactionHistory, + stateSyncEnabled, + skipTxIndexing, + preimagesEnabled, + localTxsEnabled, + pushGossipNumValidators, + pushGossipPercentStake, + continuousProfilerDir, + continuousProfilerFrequency + ); + setConfigJson(JSON.stringify(config, null, 2)); } catch (error) { - setRpcCommand((error as Error).message); + setConfigJson(`Error: ${(error as Error).message}`); } - }, [subnetId, isRPC, avalancheNetworkID, enableDebugTrace, chainId, pruningEnabled, blockchainInfo, minDelayTarget]); + }, [subnetId, chainId, nodeType, enableDebugTrace, adminApiEnabled, pruningEnabled, logLevel, blockchainInfo, minDelayTarget, trieCleanCache, trieDirtyCache, trieDirtyCommitTarget, triePrefetcherParallelism, snapshotCache, commitInterval, stateSyncServerTrieCache, rpcGasCap, rpcTxFeeCap, apiMaxBlocksPerRequest, allowUnfinalizedQueries, batchRequestLimit, batchResponseMaxSize, acceptedCacheSize, transactionHistory, stateSyncEnabled, skipTxIndexing, preimagesEnabled, localTxsEnabled, pushGossipNumValidators, pushGossipPercentStake, continuousProfilerDir, continuousProfilerFrequency]); useEffect(() => { if (nodeType === "validator") { + // Validator node defaults - optimized for block production setDomain(""); setEnableDebugTrace(false); + setAdminApiEnabled(false); setPruningEnabled(true); - setMinDelayTarget(250); // Reset to default for L1 + setLogLevel("info"); + setMinDelayTarget(250); // Fast block times for L1 + setAllowUnfinalizedQueries(false); + // Standard cache sizes for validators + setTrieCleanCache(512); + setTrieDirtyCache(512); + setSnapshotCache(256); + setAcceptedCacheSize(32); + setTransactionHistory(0); // Keep all tx history by default + } else if (nodeType === "public-rpc") { + // RPC node defaults - optimized for query performance + setPruningEnabled(false); // RPC nodes typically need full history + setLogLevel("info"); + setAllowUnfinalizedQueries(true); // Enable real-time queries + // Larger caches for better RPC performance + setTrieCleanCache(1024); // 2x for better read performance + setTrieDirtyCache(1024); + setSnapshotCache(512); // 2x for snapshot queries + setAcceptedCacheSize(64); // Larger for more recent history + setTransactionHistory(0); // Keep all tx history by default for getLogs } }, [nodeType]); @@ -140,120 +227,127 @@ export default function AvalanchegoDocker() { setSubnet(null); setBlockchainInfo(null); setNodeType("validator"); - setChainAddedToWallet(null); - setRpcCommand(""); setDomain(""); setEnableDebugTrace(false); + setAdminApiEnabled(false); setPruningEnabled(true); + setLogLevel("info"); setSubnetIdError(null); - setNodeIsReady(false); setSelectedRPCBlockchainId(""); setMinDelayTarget(250); + setConfigJson(""); + setTrieCleanCache(512); + setTrieDirtyCache(512); + setTrieDirtyCommitTarget(20); + setTriePrefetcherParallelism(16); + setSnapshotCache(256); + setCommitInterval(4096); + setStateSyncServerTrieCache(64); + setRpcGasCap(50000000); + setRpcTxFeeCap(100); + setApiMaxBlocksPerRequest(0); + setAllowUnfinalizedQueries(false); + setBatchRequestLimit(0); + setBatchResponseMaxSize(25000000); + setAcceptedCacheSize(32); + setTransactionHistory(0); + setStateSyncEnabled(false); + setSkipTxIndexing(false); + setPreimagesEnabled(false); + setLocalTxsEnabled(false); + setPushGossipNumValidators(100); + setPushGossipPercentStake(0.9); + setContinuousProfilerDir(""); + setContinuousProfilerFrequency("15m"); + setShowAdvancedSettings(false); }; // Check if this blockchain uses a custom VM const isCustomVM = blockchainInfo && blockchainInfo.vmId !== SUBNET_EVM_VM_ID; return ( - <> - - - -

Set up Instance

-

Set up a linux server with any cloud provider, like AWS, GCP, Azure, or Digital Ocean. Low specs (e.g. 2 vCPUs, 4GB RAM, 20GB storage) are sufficient for basic tests. For more extensive test and production L1s use a larger instance with appropriate resources (e.g. 8 vCPUs, 16GB RAM, 1 TB storage).

- -

If you do not have access to a server, you can also run a node for educational purposes locally. Simply select the "RPC Node (Local)" option in the next step.

- -
- - - -

- If you do not want to use Docker, you can follow the instructions{" "} - - here - - . -

-
- - -

Select L1

-

Enter the Avalanche Subnet ID of the L1 you want to run a node for.

- - - - {/* Show subnet details if available */} - {subnet && subnet.blockchains && subnet.blockchains.length > 0 && ( -
- {subnet.blockchains.map((blockchain: { blockchainId: string; blockchainName: string; createBlockTimestamp: number; createBlockNumber: string; vmId: string; subnetId: string; evmChainId: number }) => ( - - ))} -
- )} -
+ + + +

Select L1

+

+ Enter the Avalanche Subnet ID of the L1 you want to run a node for +

- {subnetId && blockchainInfo && ( - <> - - - {/* Blockchain selection for multiple blockchains */} - {nodeType === "public-rpc" && subnet && subnet.blockchains && subnet.blockchains.length > 1 && ( -
- - -

- This blockchain will be used for the RPC endpoint URL generation. -

-
- )} + + + {subnet && subnet.blockchains && subnet.blockchains.length > 0 && ( +
+ {subnet.blockchains.map((blockchain: { blockchainId: string; blockchainName: string; createBlockTimestamp: number; createBlockNumber: string; vmId: string; subnetId: string; evmChainId: number }) => ( + + ))} +
+ )} +
+ + {subnetId && blockchainInfo && ( + <> + +

Configure Node Settings

+ +
+
+
+ + +
- {/* Min delay target for validator nodes */} +
setHighlightPath('logLevel')} onMouseLeave={clearHighlight}> + + +

+ Controls the verbosity of node logs +

+
{nodeType === "validator" && ( -
+
setHighlightPath('minDelayTarget')} onMouseLeave={clearHighlight}> @@ -264,160 +358,735 @@ export default function AvalanchegoDocker() { const value = Math.min(2000, Math.max(0, parseInt(e.target.value) || 0)); setMinDelayTarget(value); }} + onFocus={() => setHighlightPath('minDelayTarget')} + onBlur={clearHighlight} min="0" max="2000" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white" /> -

- The minimum delay between blocks (in milliseconds) that this node will attempt to use when creating blocks. Maximum: 2000ms. Default for L1: 250ms. +

+ The minimum delay between blocks (in milliseconds). Maximum: 2000ms. Default: 250ms.

)} - - +
setHighlightPath('pruning')} onMouseLeave={clearHighlight}> + +

+ {nodeType === "validator" + ? "Recommended for validators. Reduces disk usage by removing old state data." + : "Not recommended for RPC nodes that need full historical data."} +

+
- -

Start AvalancheGo Node

-

Run the following Docker command to start your node:

- - - -

- For advanced node configuration options, see the{" "} - - AvalancheGo configuration flags documentation - . -

+
setHighlightPath('adminApi')} onMouseLeave={clearHighlight}> + +

+ Enables administrative APIs. Only enable if needed and secured. +

+
- {isCustomVM && ( - - -

- This blockchain uses a non-standard Virtual Machine ID. The Docker command automatically includes the AVAGO_VM_ALIASES_FILE_CONTENT environment variable with base64 encoded VM aliases configuration. -

-

- VM ID: {blockchainInfo.vmId}
- Aliases to: {SUBNET_EVM_VM_ID} -

-
-
- )} + {nodeType === "public-rpc" && ( + <> +
setHighlightPath('ethApis')} onMouseLeave={clearHighlight}> + +

+ Enables debug APIs and detailed tracing capabilities +

+
- - -

To run multiple validator nodes on the same machine, ensure each node has:

-
    -
  • Unique container name (change --name parameter)
  • -
  • Different ports (modify AVAGO_HTTP_PORT and AVAGO_STAKING_PORT)
  • -
  • Separate data directories (change the local volume path ~/.avalanchego to a unique directory)
  • -
-

Example for second node: Use ports 9652/9653 (HTTP/staking), container name "avago2", and data directory "~/.avalanchego2"

-
-
-
+ {subnet && subnet.blockchains && subnet.blockchains.length > 1 && ( +
+ + +

+ This blockchain will be used for the RPC endpoint URL generation. +

+
+ )} + + )} + {/* Advanced Settings */} +
+ - {/* Conditional steps based on nodeType */} - {nodeType === "public-rpc" && ( - - - - )} - {((nodeType === "public-rpc" && !!domain)) && ( - - - - )} - - {nodeType === "validator" && ( - -

Wait for the Node to Bootstrap

-

Your node will now bootstrap and sync the P-Chain and your L1. This process should take a few minutes. You can follow the process by checking the logs with the following command:

+ {showAdvancedSettings && ( +
+ + For advanced configuration options, see the{" "} + + AvalancheGo configuration + {" "} + and{" "} + + Subnet-EVM configuration + documentation. + +
+

Cache Settings

- +
+
setHighlightPath('trieCleanCache')} onMouseLeave={clearHighlight}> + + setTrieCleanCache(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('trieCleanCache')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +
- - -

The bootstrapping has three phases:

- -
    -
  • - Fetching the blocks of the P-Chain: - The node fetches all the P-Chain blocks. The eta field is giving the estimated remaining time for the fetching process. - -
  • -
  • - Executing the blocks of the P-Chain: - The node will sync the P-Chain and your L1. - -
  • -
-

After the P-Chain is fetched and executed the process is repeated for the tracked Subnet.

-
-
- - setNodeIsReady(checked)} - /> - - )} - - {/* Show success message when node is ready for validator mode */} - {nodeIsReady && nodeType === "validator" && ( - -

Node Setup Complete

-

Your AvalancheGo node is now fully bootstrapped and ready to be used as a validator node.

- -
-
-
- - - -
-
-

- Node is ready for validation -

-

- Your node has successfully synced with the network and is ready to be added as a validator. -

+
setHighlightPath('trieDirtyCache')} onMouseLeave={clearHighlight}> + + setTrieDirtyCache(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('trieDirtyCache')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +
+ +
setHighlightPath('snapshotCache')} onMouseLeave={clearHighlight}> + + setSnapshotCache(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('snapshotCache')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +
+ +
setHighlightPath('acceptedCacheSize')} onMouseLeave={clearHighlight}> + + setAcceptedCacheSize(Math.max(1, parseInt(e.target.value) || 1))} + onFocus={() => setHighlightPath('acceptedCacheSize')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Depth of accepted headers and logs cache +

+
+ +
setHighlightPath('trieDirtyCommitTarget')} onMouseLeave={clearHighlight}> + + setTrieDirtyCommitTarget(Math.max(1, parseInt(e.target.value) || 1))} + onFocus={() => setHighlightPath('trieDirtyCommitTarget')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Memory limit before commit +

+
+ +
setHighlightPath('triePrefetcherParallelism')} onMouseLeave={clearHighlight}> + + setTriePrefetcherParallelism(Math.max(1, parseInt(e.target.value) || 1))} + onFocus={() => setHighlightPath('triePrefetcherParallelism')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Max concurrent disk reads +

+
+ +
setHighlightPath('stateSyncServerTrieCache')} onMouseLeave={clearHighlight}> + + setStateSyncServerTrieCache(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('stateSyncServerTrieCache')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Trie cache for state sync server +

+
+
+
+ +
+

Performance Settings

+ +
+
setHighlightPath('commitInterval')} onMouseLeave={clearHighlight}> + + setCommitInterval(Math.max(1, parseInt(e.target.value) || 1))} + onFocus={() => setHighlightPath('commitInterval')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Interval to persist EVM and atomic tries +

+
+ +
setHighlightPath('rpcGasCap')} onMouseLeave={clearHighlight}> + + setRpcGasCap(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('rpcGasCap')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Maximum gas limit for RPC calls +

+
+ +
setHighlightPath('rpcTxFeeCap')} onMouseLeave={clearHighlight}> + + setRpcTxFeeCap(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('rpcTxFeeCap')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Maximum transaction fee cap +

+
+
+
+ +
+

API Limits

+ +
+
setHighlightPath('batchRequestLimit')} onMouseLeave={clearHighlight}> + + setBatchRequestLimit(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('batchRequestLimit')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Max batched requests (0 = no limit) +

+
+ +
setHighlightPath('batchResponseMaxSize')} onMouseLeave={clearHighlight}> + + setBatchResponseMaxSize(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('batchResponseMaxSize')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Max batch response size (default: 25MB) +

+
+
+
+ +
+

Transaction & State

+ +
+
setHighlightPath('transactionHistory')} onMouseLeave={clearHighlight}> + + setTransactionHistory(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('transactionHistory')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Max blocks to keep tx indices. 0 = archive mode (all history) +

+
+ +
setHighlightPath('skipTxIndexing')} onMouseLeave={clearHighlight}> + +

+ Disable tx indexing entirely (saves disk space) +

+
+ +
setHighlightPath('stateSyncEnabled')} onMouseLeave={clearHighlight}> + +

+ Fast sync from state summary +

+
+ +
setHighlightPath('preimagesEnabled')} onMouseLeave={clearHighlight}> + +

+ Record preimages (uses more disk) +

+
+ +
setHighlightPath('localTxsEnabled')} onMouseLeave={clearHighlight}> + +

+ Treat local account txs as local +

+
+
+
+ + {nodeType === "validator" && ( +
+

Gossip Settings (Validator)

+ +
+
setHighlightPath('pushGossipNumValidators')} onMouseLeave={clearHighlight}> + + setPushGossipNumValidators(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('pushGossipNumValidators')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Number of validators to push gossip to +

+
+ +
setHighlightPath('pushGossipPercentStake')} onMouseLeave={clearHighlight}> + + setPushGossipPercentStake(Math.min(1, Math.max(0, parseFloat(e.target.value) || 0)))} + onFocus={() => setHighlightPath('pushGossipPercentStake')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Percentage of total stake to gossip to (0-1) +

+
+
+
+ )} + + {isRPC && ( +
+

RPC-Specific Settings

+ +
+
setHighlightPath('apiMaxBlocksPerRequest')} onMouseLeave={clearHighlight}> + + setApiMaxBlocksPerRequest(Math.max(0, parseInt(e.target.value) || 0))} + onFocus={() => setHighlightPath('apiMaxBlocksPerRequest')} + onBlur={clearHighlight} + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ 0 = no limit. Limits blocks per getLogs request +

+
+ +
setHighlightPath('allowUnfinalizedQueries')} onMouseLeave={clearHighlight}> + +

+ Allows queries for unfinalized/pending blocks +

+
+
+
+ )} + +
+

Profiling (Optional)

+ +
+
setHighlightPath('continuousProfilerDir')} onMouseLeave={clearHighlight}> + + setContinuousProfilerDir(e.target.value)} + onFocus={() => setHighlightPath('continuousProfilerDir')} + onBlur={clearHighlight} + placeholder="./profiles (leave empty to disable)" + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ Directory for continuous profiler output +

+
+ + {continuousProfilerDir && ( +
setHighlightPath('continuousProfilerFrequency')} onMouseLeave={clearHighlight}> + + setContinuousProfilerFrequency(e.target.value)} + onFocus={() => setHighlightPath('continuousProfilerFrequency')} + onBlur={clearHighlight} + placeholder="15m" + className="w-full px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md dark:bg-gray-700 dark:text-white" + /> +

+ How often to create profiles (e.g., 15m, 1h) +

+
+ )} +
+
+ )} +
+
+ + {/* Configuration Preview */} +
+
+
+

Configuration Preview

+
+
+ {configJson && !configJson.startsWith("Error:") ? ( + + ) : ( +
+ {configJson.startsWith("Error:") ? configJson : "Configure your node to see the Subnet-EVM chain config"} +
+ )}
- - )} - )} +
+
+
+ +

Create Configuration File

+

+ Run this command on your server to create the Subnet-EVM chain configuration file: +

- + { + try { + const config = JSON.parse(configJson); + return generateConfigFileCommand(chainId, config); + } catch { + return "# Error generating config file command"; + } + })()} + /> - {chainAddedToWallet && ( - <> - - +

+ This creates the configuration file at ~/.avalanchego/configs/chains/{chainId}/config.json +

+

+ Read the documentation for more information on the configuration options. {" "} + + AvalancheGo configuration + + {" "}and{" "} + + Subnet-EVM configuration + +

+
+ + +

Run Docker Command

+

+ Start the node using Docker: +

+ + { + try { + const config = JSON.parse(configJson); + const vmId = blockchainInfo?.vmId || SUBNET_EVM_VM_ID; + return generateDockerCommand( + subnetId, + chainId, + config, + nodeType, + avalancheNetworkID, + vmId + ); + } catch { + return "# Error generating Docker command"; + } + })()} + /> + +

+ The container will read the config from ~/.avalanchego/configs/chains/{chainId}/config.json via the mounted volume. +

+ + + + {isCustomVM && ( + +

+ This blockchain uses a non-standard Virtual Machine ID. The Docker command includes VM aliases mapping. +

+

+ VM ID: {blockchainInfo.vmId}
+ Aliases to: {SUBNET_EVM_VM_ID} +

+
+ )} + +

To run multiple nodes on the same machine, ensure each node has:

+
    +
  • Unique container name (change --name parameter)
  • +
  • Different ports (modify port mappings)
  • +
  • Separate data directories (change ~/.avalanchego path)
  • +
+
+ + +

Monitor your node with:

+ +
+
+
+ + {nodeType === "public-rpc" && ( + + + + )} )} + + + {configJson && !configJson.startsWith("Error:") && ( +
+ +
+ )} + + ); +} - - +export default function AvalanchegoDocker() { + return ( + + + ); -}; +} diff --git a/components/toolbox/console/layer-1/node-config.ts b/components/toolbox/console/layer-1/node-config.ts new file mode 100644 index 00000000000..3edb708bbb1 --- /dev/null +++ b/components/toolbox/console/layer-1/node-config.ts @@ -0,0 +1,331 @@ +// Node configuration generation for L1 Docker setup +import { SUBNET_EVM_VM_ID } from '@/constants/console'; +import { getContainerVersions } from '@/components/toolbox/utils/containerVersions'; + +// Subnet-EVM default configuration values +// Reference: https://build.avax.network/docs/nodes/chain-configs/subnet-evm +const SUBNET_EVM_DEFAULTS = { + "pruning-enabled": true, + "commit-interval": 4096, + "trie-clean-cache": 512, + "trie-dirty-cache": 512, + "trie-dirty-commit-target": 20, + "trie-prefetcher-parallelism": 16, + "snapshot-cache": 256, + "state-sync-server-trie-cache": 64, + "rpc-gas-cap": 50000000, + "rpc-tx-fee-cap": 100, + "log-level": "info", + "metrics-expensive-enabled": false, + "accepted-cache-size": 32, + "batch-request-limit": 0, + "batch-response-max-size": 25000000, + "state-sync-enabled": false, + "allow-unfinalized-queries": false, + "api-max-duration": 0, + "api-max-blocks-per-request": 0, + // Default eth-apis + "eth-apis": ["eth", "eth-filter", "net", "web3", "internal-eth", "internal-blockchain", "internal-transaction"], +}; + +/** + * Generates the Subnet-EVM chain configuration + * Only includes values that differ from defaults + * This configuration is saved to ~/.avalanchego/configs/chains//config.json + */ +export const generateChainConfig = ( + nodeType: 'validator' | 'public-rpc', + enableDebugTrace: boolean = false, + adminApiEnabled: boolean = false, + pruningEnabled: boolean = true, + logLevel: string = "info", + minDelayTarget: number = 250, + trieCleanCache: number = 512, + trieDirtyCache: number = 512, + trieDirtyCommitTarget: number = 20, + triePrefetcherParallelism: number = 16, + snapshotCache: number = 256, + commitInterval: number = 4096, + stateSyncServerTrieCache: number = 64, + rpcGasCap: number = 50000000, + rpcTxFeeCap: number = 100, + apiMaxBlocksPerRequest: number = 0, + allowUnfinalizedQueries: boolean = false, + batchRequestLimit: number = 0, + batchResponseMaxSize: number = 25000000, + acceptedCacheSize: number = 32, + transactionHistory: number = 0, + stateSyncEnabled: boolean = false, + skipTxIndexing: boolean = false, + preimagesEnabled: boolean = false, + localTxsEnabled: boolean = false, + pushGossipNumValidators: number = 100, + pushGossipPercentStake: number = 0.9, + continuousProfilerDir: string = "", + continuousProfilerFrequency: string = "15m" +) => { + const isRPC = nodeType === 'public-rpc'; + const config: any = {}; + + // Helper function to add config only if it differs from default + const addIfNotDefault = (key: string, value: any, defaultValue?: any) => { + const defaultVal = defaultValue !== undefined ? defaultValue : SUBNET_EVM_DEFAULTS[key as keyof typeof SUBNET_EVM_DEFAULTS]; + + // For arrays, do a deep comparison + if (Array.isArray(value) && Array.isArray(defaultVal)) { + if (JSON.stringify(value) !== JSON.stringify(defaultVal)) { + config[key] = value; + } + } else if (value !== defaultVal) { + config[key] = value; + } + }; + + // Always include pruning-enabled for explicitness in L1 node setup + config["pruning-enabled"] = pruningEnabled; + + // Cache settings - only add if different from defaults + addIfNotDefault("trie-clean-cache", trieCleanCache); + addIfNotDefault("trie-dirty-cache", trieDirtyCache); + addIfNotDefault("trie-dirty-commit-target", trieDirtyCommitTarget); + addIfNotDefault("trie-prefetcher-parallelism", triePrefetcherParallelism); + addIfNotDefault("snapshot-cache", snapshotCache); + addIfNotDefault("state-sync-server-trie-cache", stateSyncServerTrieCache); + addIfNotDefault("accepted-cache-size", acceptedCacheSize); + addIfNotDefault("commit-interval", commitInterval); + + // Performance settings + addIfNotDefault("rpc-gas-cap", rpcGasCap); + addIfNotDefault("rpc-tx-fee-cap", rpcTxFeeCap); + + // Logging - only add if different from default (info) + if (logLevel !== "info") { + config["log-level"] = logLevel; + } + + // Metrics - only add if enabled (default is false) + if (true) { // We always want expensive metrics enabled + config["metrics-expensive-enabled"] = true; + } + + // Min delay target - only add if non-zero (default is 0, meaning no minimum delay) + if (minDelayTarget > 0) { + config["min-delay-target"] = minDelayTarget; + } + + // Batch limits - only add if different from defaults + addIfNotDefault("batch-request-limit", batchRequestLimit); + addIfNotDefault("batch-response-max-size", batchResponseMaxSize); + + // Warp API - typically enabled for L1s, but not a default for all Subnet-EVM chains + config["warp-api-enabled"] = true; + + // State sync - only add if enabled + if (stateSyncEnabled) { + config["state-sync-enabled"] = true; + } + + // Transaction indexing - only add if non-default + if (skipTxIndexing) { + config["skip-tx-indexing"] = true; + } else if (transactionHistory > 0) { + config["transaction-history"] = transactionHistory; + } + + // Transaction settings - only add if enabled + if (preimagesEnabled) { + config["preimages-enabled"] = true; + } + if (localTxsEnabled) { + config["local-txs-enabled"] = true; + } + + // Configure APIs based on node type + // Always include eth-apis for explicitness in L1 node setup + if (enableDebugTrace) { + config["eth-apis"] = [ + "eth", + "eth-filter", + "net", + "admin", + "web3", + "internal-eth", + "internal-blockchain", + "internal-transaction", + "internal-debug", + "internal-account", + "internal-personal", + "debug", + "debug-tracer", + "debug-file-tracer", + "debug-handler" + ]; + } else { + // Include standard APIs explicitly for L1 nodes (even though these are defaults) + // This makes the configuration more explicit and easier to understand + config["eth-apis"] = [ + "eth", + "eth-filter", + "net", + "web3", + "internal-eth", + "internal-blockchain", + "internal-transaction" + ]; + } + + // Admin API - only enable if explicitly requested + if (adminApiEnabled) { + config["admin-api-enabled"] = true; + // Add admin to eth-apis if not already present (when debug is disabled) + if (!enableDebugTrace && !config["eth-apis"].includes("admin")) { + config["eth-apis"].push("admin"); + } + } + + // RPC-specific settings + if (isRPC) { + // api-max-duration: 0 is default (no time limit), already default so we don't need to add + // api-max-blocks-per-request: 0 is default (no limit) + addIfNotDefault("api-max-blocks-per-request", apiMaxBlocksPerRequest); + + // Only add if enabled (default is false) + if (allowUnfinalizedQueries) { + config["allow-unfinalized-queries"] = true; + } + } + + // Gossip settings (primarily for validators) - only add if non-default + if (nodeType === 'validator') { + // These don't have documented defaults, so we always add them for validators + config["push-gossip-num-validators"] = pushGossipNumValidators; + config["push-gossip-percent-stake"] = pushGossipPercentStake; + } + + // Continuous profiling - only add if enabled + if (continuousProfilerDir) { + config["continuous-profiler-dir"] = continuousProfilerDir; + config["continuous-profiler-frequency"] = continuousProfilerFrequency; + } + + return config; +}; + +/** + * Metadata object to store deployment information (separate from chain config) + */ +export const generateDeploymentMetadata = ( + subnetId: string, + blockchainId: string, + nodeType: 'validator' | 'public-rpc', + networkID: number, + vmId: string = SUBNET_EVM_VM_ID, + domain?: string +) => { + const isTestnet = networkID === 5; + const isCustomVM = vmId !== SUBNET_EVM_VM_ID; + + return { + "deployment": { + "network": isTestnet ? "fuji" : "mainnet", + "networkID": networkID, + "subnetId": subnetId, + "blockchainId": blockchainId, + "vmId": vmId, + "isCustomVM": isCustomVM, + "nodeType": nodeType, + "domain": domain || null, + "timestamp": new Date().toISOString() + } + }; +}; + +/** + * Generates base64-encoded chain config for environment variable + */ +export const encodeChainConfig = ( + blockchainId: string, + chainConfig: any +) => { + const chainConfigMap: Record = {}; + chainConfigMap[blockchainId] = { + "Config": btoa(JSON.stringify(chainConfig)), + "Upgrade": null + }; + return btoa(JSON.stringify(chainConfigMap)); +}; + +/** + * Generates a command to create the chain config file in the correct location + */ +export const generateConfigFileCommand = ( + blockchainId: string, + chainConfig: any +) => { + const configJson = JSON.stringify(chainConfig, null, 2); + const configPath = `~/.avalanchego/configs/chains/${blockchainId}`; + + // Escape single quotes in the JSON for the shell command + const escapedJson = configJson.replace(/'/g, "'\\''"); + + return `# Create the chain config directory and file +mkdir -p ${configPath} && cat > ${configPath}/config.json << 'EOF' +${configJson} +EOF`; +}; + +/** + * Generates the complete Docker command + * The chain config is read from the mounted volume at ~/.avalanchego/configs/chains//config.json + */ +export const generateDockerCommand = ( + subnetId: string, + blockchainId: string, + chainConfig: any, + nodeType: 'validator' | 'public-rpc', + networkID: number, + vmId: string = SUBNET_EVM_VM_ID +) => { + const isRPC = nodeType === 'public-rpc'; + const isTestnet = networkID === 5; // Fuji + const isCustomVM = vmId !== SUBNET_EVM_VM_ID; + const versions = getContainerVersions(isTestnet); + + const env: Record = { + AVAGO_PUBLIC_IP_RESOLUTION_SERVICE: "opendns", + AVAGO_HTTP_HOST: "0.0.0.0", + AVAGO_PARTIAL_SYNC_PRIMARY_NETWORK: "true", + AVAGO_TRACK_SUBNETS: subnetId, + AVAGO_CHAIN_CONFIG_DIR: "/root/.avalanchego/configs/chains" + }; + + // Set network ID + if (networkID === 5) { + env.AVAGO_NETWORK_ID = "fuji"; + } + + // Configure RPC settings + if (isRPC) { + env.AVAGO_HTTP_ALLOWED_HOSTS = '"*"'; + } + + // Add VM aliases if custom VM + if (isCustomVM) { + const vmAliases = { + [vmId]: [SUBNET_EVM_VM_ID] + }; + env.AVAGO_VM_ALIASES_FILE_CONTENT = btoa(JSON.stringify(vmAliases, null, 2)); + } + + const chunks = [ + "docker run -it -d", + `--name avago`, + `-p ${isRPC ? "" : "127.0.0.1:"}9650:9650 -p 9651:9651`, + `-v ~/.avalanchego:/root/.avalanchego`, + ...Object.entries(env).map(([key, value]) => `-e ${key}=${value}`), + `avaplatform/subnet-evm_avalanchego:${versions['avaplatform/subnet-evm_avalanchego']}` + ]; + + return chunks.map(chunk => ` ${chunk}`).join(" \\\n").trim(); +}; + diff --git a/components/toolbox/console/layer-1/useNodeConfigHighlighting.ts b/components/toolbox/console/layer-1/useNodeConfigHighlighting.ts new file mode 100644 index 00000000000..3cdba795c99 --- /dev/null +++ b/components/toolbox/console/layer-1/useNodeConfigHighlighting.ts @@ -0,0 +1,78 @@ +import { useMemo } from 'react'; + +export function useNodeConfigHighlighting(highlightPath: string | null, configJson: string) { + return useMemo(() => { + if (!highlightPath || !configJson) return []; + + const lines = configJson.split('\n'); + const highlighted: number[] = []; + + // Map of highlight paths to their JSON keys + const fieldMap: Record = { + 'pruning': 'pruning-enabled', + 'minDelayTarget': 'min-delay-target', + 'trieCleanCache': 'trie-clean-cache', + 'trieDirtyCache': 'trie-dirty-cache', + 'trieDirtyCommitTarget': 'trie-dirty-commit-target', + 'triePrefetcherParallelism': 'trie-prefetcher-parallelism', + 'snapshotCache': 'snapshot-cache', + 'acceptedCacheSize': 'accepted-cache-size', + 'stateSyncServerTrieCache': 'state-sync-server-trie-cache', + 'commitInterval': 'commit-interval', + 'rpcGasCap': 'rpc-gas-cap', + 'rpcTxFeeCap': 'rpc-tx-fee-cap', + 'transactionHistory': 'transaction-history', + 'apiMaxBlocksPerRequest': 'api-max-blocks-per-request', + 'allowUnfinalizedQueries': 'allow-unfinalized-queries', + 'batchRequestLimit': 'batch-request-limit', + 'batchResponseMaxSize': 'batch-response-max-size', + 'skipTxIndexing': 'skip-tx-indexing', + 'stateSyncEnabled': 'state-sync-enabled', + 'preimagesEnabled': 'preimages-enabled', + 'localTxsEnabled': 'local-txs-enabled', + 'pushGossipNumValidators': 'push-gossip-num-validators', + 'pushGossipPercentStake': 'push-gossip-percent-stake', + 'continuousProfilerDir': 'continuous-profiler-dir', + 'continuousProfilerFrequency': 'continuous-profiler-frequency', + 'logLevel': 'log-level', + 'warpApi': 'warp-api-enabled', + 'ethApis': 'eth-apis', + 'adminApi': 'admin-api-enabled', + 'metricsExpensive': 'metrics-expensive-enabled', + // Additional fields that might be in config + 'apiMaxDuration': 'api-max-duration' + }; + + const fieldName = fieldMap[highlightPath]; + if (fieldName) { + const searchKey = typeof fieldName === 'string' ? fieldName : fieldName[0]; + const idx = lines.findIndex(line => line.includes(`"${searchKey}"`)); + if (idx >= 0) { + highlighted.push(idx + 1); + + // For arrays, highlight multiple lines + if (searchKey === 'eth-apis') { + let bracketCount = 0; + let inArray = false; + for (let i = idx; i < lines.length && i < idx + 30; i++) { + const line = lines[i]; + if (line.includes('[')) { + inArray = true; + bracketCount++; + } + if (inArray) { + highlighted.push(i + 1); + if (line.includes(']')) { + bracketCount--; + if (bracketCount === 0) break; + } + } + } + } + } + } + + return [...new Set(highlighted)]; + }, [highlightPath, configJson]); +} +