Validators & RPC Nodes
Understanding the different types of nodes in the Solana network is crucial for both developers and operators. This chapter explores the roles, requirements, and interactions of validators and RPC nodes.
Node Types Overview
Solana has two primary node types:
Text
┌─────────────────────────────────────────────────────────────────┐
│ SOLANA NETWORK │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Validator │ │ Validator │ │ Validator │ │
│ │ │ │ │ │ │ │
│ │ • Stakes │ │ • Stakes │ │ • Stakes │ │
│ │ • Votes │ │ • Votes │ │ • Votes │ │
│ │ • Produces │ │ • Produces │ │ • Produces │ │
│ │ blocks │ │ blocks │ │ blocks │ │
│ │ (leader) │ │ (leader) │ │ (leader) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ Gossip Net │ │
│ └────────┬────────┘ │
│ │ │
│ ┌─────────────┐ ┌─────┴───────┐ ┌─────────────┐ │
│ │ RPC Node │ │ RPC Node │ │ RPC Node │ │
│ │ │ │ │ │ │ │
│ │ • No stake │ │ • No stake │ │ • No stake │ │
│ │ • No votes │ │ • No votes │ │ • No votes │ │
│ │ • Serves │ │ • Serves │ │ • Serves │ │
│ │ queries │ │ queries │ │ queries │ │
│ └──────┬──────┘ └─────────────┘ └──────┬──────┘ │
│ │ │ │
└──────────┼──────────────────────────────────────┼───────────────┘
│ │
┌─────┴─────┐ ┌─────┴─────┐
│ dApps │ │ Users │
└───────────┘ └───────────┘
Validators
Validators are the backbone of the network, responsible for consensus and block production.
Validator Responsibilities
Text
1. TRANSACTION PROCESSING
└── Receive transactions from TPU
└── Execute programs
└── Update account states
2. CONSENSUS PARTICIPATION
└── Vote on block validity
└── Build on longest valid chain
└── Participate in Tower BFT
3. BLOCK PRODUCTION (when leader)
└── Order transactions
└── Create entries
└── Broadcast blocks
4. REPLICATION
└── Store blockchain history
└── Serve data to peers
└── Participate in gossip
Validator Architecture
TypeScript
interface ValidatorComponents {
// Network components
tpu: TransactionProcessingUnit; // Receives transactions
tvu: TransactionValidationUnit; // Validates blocks
gossip: GossipService; // P2P communication
// Processing components
banking: BankingStage; // Transaction execution
poh: PoHService; // Proof of History
// Storage components
blockstore: Blockstore; // Block storage
ledger: Ledger; // Account states
snapshot: SnapshotService; // State snapshots
// Consensus components
towerBft: TowerBFT; // Voting logic
voteAccount: VoteAccount; // On-chain votes
}
Validator Economics
Validators earn rewards through:
Text
VALIDATOR INCOME
================
1. Inflation Rewards
└── ~6.9% annual inflation (at launch, decreasing)
└── Distributed to staked validators
└── Proportional to stake
2. Transaction Fees
└── 50% of base fee burned
└── 50% goes to leader
└── Priority fees go to leader
3. MEV (Maximal Extractable Value)
└── Block ordering opportunities
└── Arbitrage inclusion
└── Jito-style rewards
Example calculation for 10,000 SOL stake:
├── Inflation rewards: ~690 SOL/year (6.9%)
├── Commission (10%): 69 SOL to validator
├── To stakers: 621 SOL
└── Plus transaction fees as leader
Hardware Requirements
Text
MINIMUM VALIDATOR SPECS (as of 2024)
====================================
CPU:
├── 24+ cores @ 3.0+ GHz
├── AMD EPYC or Intel Xeon
└── AVX2 support required
RAM:
├── 512 GB (mainnet)
├── 256 GB (minimum)
└── ECC recommended
Storage:
├── NVMe SSD
├── 1 TB for ledger
├── 2+ TB for accounts
└── 500K+ IOPS
Network:
├── 1 Gbps (minimum)
├── 10 Gbps (recommended)
├── Low latency to other validators
└── Unmetered bandwidth
GPU (optional but helps):
├── NVIDIA GPU
├── CUDA support
└── For PoH verification
Running a Validator
Bash
# Download Solana CLI
sh -c "$(curl -sSfL https://release.solana.com/v1.18.0/install)"
# Create keys
solana-keygen new -o ~/validator-keypair.json
solana-keygen new -o ~/vote-account-keypair.json
solana-keygen new -o ~/withdrawer-keypair.json
# Create vote account
solana create-vote-account \
~/vote-account-keypair.json \
~/validator-keypair.json \
~/withdrawer-keypair.json \
--commission 10
# Start validator (simplified)
solana-validator \
--identity ~/validator-keypair.json \
--vote-account ~/vote-account-keypair.json \
--ledger ~/validator-ledger \
--rpc-port 8899 \
--dynamic-port-range 8000-8020 \
--entrypoint mainnet-beta.solana.com:8001 \
--expected-genesis-hash 5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d \
--limit-ledger-size
RPC Nodes
RPC nodes provide the API interface for applications to interact with Solana.
RPC Node Role
Text
RPC nodes:
✓ Full copy of blockchain
✓ Execute read queries
✓ Submit transactions
✓ Serve historical data
✓ Provide WebSocket subscriptions
RPC nodes DON'T:
✗ Vote on consensus
✗ Produce blocks
✗ Earn staking rewards
✗ Participate in Tower BFT
RPC Methods
TypeScript
// Account queries
const account = await connection.getAccountInfo(publicKey);
const balance = await connection.getBalance(publicKey);
const tokenAccounts = await connection.getTokenAccountsByOwner(owner, {
programId: TOKEN_PROGRAM_ID,
});
// Transaction queries
const transaction = await connection.getTransaction(signature);
const signatures = await connection.getSignaturesForAddress(address);
const status = await connection.getSignatureStatuses([signature]);
// Block queries
const block = await connection.getBlock(slot);
const blockHeight = await connection.getBlockHeight();
const slot = await connection.getSlot();
// Cluster info
const clusterNodes = await connection.getClusterNodes();
const epochInfo = await connection.getEpochInfo();
const version = await connection.getVersion();
// Sending transactions
const signature = await connection.sendTransaction(transaction, [signer]);
const result = await connection.confirmTransaction(signature);
WebSocket Subscriptions
TypeScript
import { Connection } from "@solana/web3.js";
const connection = new Connection("wss://api.mainnet-beta.solana.com");
// Account change subscription
const accountSubId = connection.onAccountChange(
publicKey,
(accountInfo, context) => {
console.log("Account changed:", accountInfo);
console.log("Slot:", context.slot);
},
"confirmed",
);
// Log subscription
const logSubId = connection.onLogs(
programId,
(logs, context) => {
console.log("Program logs:", logs);
},
"confirmed",
);
// Slot subscription
const slotSubId = connection.onSlotChange((slotInfo) => {
console.log("New slot:", slotInfo.slot);
});
// Cleanup
connection.removeAccountChangeListener(accountSubId);
connection.removeOnLogsListener(logSubId);
connection.removeSlotChangeListener(slotSubId);
RPC Node Economics
Running an RPC node:
Text
COSTS:
├── Hardware: $1,000 - $3,000/month
├── Bandwidth: $500 - $2,000/month
├── Operations: Variable
└── Total: $2,000 - $5,000/month
REVENUE OPTIONS:
├── Sell RPC access (metered)
├── Support your own dApps
├── White-label for others
└── Specialized data services
PROVIDERS (Alternative to self-hosting):
├── Helius
├── QuickNode
├── Alchemy
├── Triton
├── GetBlock
└── Many others
RPC Provider Comparison
When choosing an RPC provider:
TypeScript
// Rate limiting varies by provider
// Example: Checking provider capabilities
async function checkRpcProvider(endpoint: string) {
const connection = new Connection(endpoint);
const tests = {
basic: async () => {
return await connection.getSlot();
},
accountHistory: async () => {
// Some providers limit historical queries
return await connection.getSignaturesForAddress(publicKey, {
limit: 1000,
});
},
throughput: async () => {
// Test request rate
const start = Date.now();
const promises = Array(100)
.fill(null)
.map(() => connection.getSlot());
await Promise.all(promises);
return Date.now() - start;
},
websockets: async () => {
return new Promise((resolve) => {
const subId = connection.onSlotChange((info) => {
connection.removeSlotChangeListener(subId);
resolve(true);
});
setTimeout(() => resolve(false), 5000);
});
},
};
return tests;
}
Commitment Levels
RPC queries accept commitment levels:
TypeScript
type Commitment =
| "processed" // Node's most recent block
| "confirmed" // Voted on by supermajority
| "finalized"; // Confirmed + maximum lockout
// Usage
const balance = await connection.getBalance(
publicKey,
"confirmed", // Default for most queries
);
// Commitment comparison
/*
processed:
├── Speed: Fastest (~400ms)
├── Reliability: Can be rolled back
├── Use case: UI responsiveness
confirmed:
├── Speed: ~400ms + voting time
├── Reliability: Very unlikely to roll back
├── Use case: Most applications
finalized:
├── Speed: ~400ms + ~32 slots
├── Reliability: Cannot roll back
├── Use case: Exchanges, high-value transfers
*/
Validator vs RPC Decision Matrix
When should you run each type?
Text
RUN A VALIDATOR IF:
├── You have significant SOL to stake
├── You want to earn rewards
├── You want to participate in governance
├── You have hardware budget ($3,000+/month)
└── You have operational expertise
RUN AN RPC NODE IF:
├── Your dApp needs dedicated RPC
├── You need historical data access
├── You want to resell RPC access
├── Public RPCs are rate-limiting you
└── You need custom data indexing
USE A PROVIDER IF:
├── You're starting out
├── Cost is a concern
├── You don't want operational overhead
├── Your needs are standard
└── You prioritize development time
Gossip Network
Both validators and RPC nodes participate in gossip:
Text
GOSSIP PROTOCOL
═══════════════
Purpose:
├── Cluster discovery
├── Network health
├── Block propagation
└── Vote dissemination
Message types:
├── ContactInfo (node addresses)
├── Vote (consensus votes)
├── LowestSlot (pruning info)
├── Shred (block data)
└── CrdsValue (various metadata)
┌────────────────────────────────────────────────────┐
│ │
│ Node A ──────┬──────────────┬────────── Node D │
│ │ │ │ │ │
│ │ │ Gossip │ │ │
│ │ │ Messages │ │ │
│ │ │ │ │ │
│ Node B ──────┴──────────────┴────────── Node C │
│ │
│ Each node shares with random peers │
│ Information propagates exponentially │
│ Full network coverage in O(log n) rounds │
└────────────────────────────────────────────────────┘
Connecting to RPC Nodes
TypeScript
import { Connection, clusterApiUrl } from "@solana/web3.js";
// Public endpoints (rate-limited)
const mainnet = new Connection(clusterApiUrl("mainnet-beta"));
const devnet = new Connection(clusterApiUrl("devnet"));
const testnet = new Connection(clusterApiUrl("testnet"));
// Custom endpoint
const custom = new Connection("https://your-rpc-node.com");
// With configuration
const configured = new Connection("https://api.mainnet-beta.solana.com", {
commitment: "confirmed",
confirmTransactionInitialTimeout: 60000,
wsEndpoint: "wss://api.mainnet-beta.solana.com",
});
// Health check
async function checkHealth(connection: Connection) {
try {
const slot = await connection.getSlot();
const health = await connection.getHealth();
const version = await connection.getVersion();
return {
healthy: health === "ok",
slot,
version: version["solana-core"],
};
} catch (error) {
return { healthy: false, error };
}
}
Best Practices
For Application Developers
TypeScript
// 1. Use multiple RPC providers for reliability
const providers = [
"https://primary-rpc.com",
"https://backup-rpc-1.com",
"https://backup-rpc-2.com",
];
async function resilientRequest<T>(
fn: (connection: Connection) => Promise<T>,
): Promise<T> {
for (const provider of providers) {
try {
const connection = new Connection(provider);
return await fn(connection);
} catch (error) {
console.warn(`Provider ${provider} failed:`, error);
}
}
throw new Error("All providers failed");
}
// 2. Implement proper retry logic
async function sendWithRetry(
connection: Connection,
transaction: Transaction,
signers: Keypair[],
maxRetries = 3,
): Promise<string> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const signature = await connection.sendTransaction(transaction, signers);
await connection.confirmTransaction(signature, "confirmed");
return signature;
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)));
}
}
throw new Error("Should not reach here");
}
// 3. Use appropriate commitment levels
const userBalance = await connection.getBalance(wallet, "confirmed");
const forExchange = await connection.getBalance(wallet, "finalized");
For Node Operators
Bash
# Monitor validator health
solana-validator-status
# Check vote account
solana vote-account ~/vote-account-keypair.json
# Monitor catchup status
solana catchup ~/validator-keypair.json
# Important metrics to monitor:
# - Slot height vs cluster
# - Vote credits
# - Skip rate
# - RPC latency
# - Memory/CPU usage
Key Takeaways
- Validators secure the network through staking and voting
- RPC nodes provide API access without participating in consensus
- Hardware requirements are significant for both types
- Multiple RPC providers improve application reliability
- Commitment levels trade off speed for finality guarantees
Next: Slots & Epochs - Understanding Solana's time organization.