Last active
December 24, 2025 08:09
-
-
Save unordered-set/18a903da0237c4103f158ef97144d4aa to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| From f5be6b0a5d6a982ca31d9cd835cb403ab5b793e4 Mon Sep 17 00:00:00 2001 | |
| From: kostya <hoho@haha.com> | |
| Date: Mon, 22 Dec 2025 23:27:05 +0000 | |
| Subject: [PATCH] Use Gossip for free shreds | |
| --- | |
| GOSSIP_EXAMPLES.md | 35 +++ | |
| gossip/Cargo.toml | 4 + | |
| gossip/src/bin/full-gossip-writer.rs | 317 +++++++++++++++++++++++++++ | |
| 3 files changed, 356 insertions(+) | |
| create mode 100644 GOSSIP_EXAMPLES.md | |
| create mode 100644 gossip/src/bin/full-gossip-writer.rs | |
| diff --git a/GOSSIP_EXAMPLES.md b/GOSSIP_EXAMPLES.md | |
| new file mode 100644 | |
| index 0000000000..3e78435d88 | |
| --- /dev/null | |
| +++ b/GOSSIP_EXAMPLES.md | |
| @@ -0,0 +1,35 @@ | |
| +# Gossip Writer | |
| + | |
| +## Building | |
| + | |
| +```bash | |
| +wget https://gist.githubusercontent.com/unordered-set/18a903da0237c4103f158ef97144d4aa/raw/0780b753f5bcc5b3f0388e6fc41b1ac68095555e/0001-Use-Gossip-for-free-shreds.patch && \ | |
| +git clone https://github.com/anza-xyz/agave.git && \ | |
| +cd agave && \ | |
| +git checkout v3.1.5 && \ | |
| +git apply ../0001-Use-Gossip-for-free-shreds.patch | |
| +``` | |
| + | |
| +## Running | |
| + | |
| +```bash | |
| +cargo run -p solana-gossip --bin full-gossip-writer -- --ip $(curl -sq -4 ifconfig.io) --port 27000 --entrypoint entrypoint2.mainnet-beta.solana.com:8001 | |
| +``` | |
| + | |
| +**Location:** `gossip/src/bin/full-gossip-writer.rs` | |
| + | |
| +**What it does:** | |
| +- **Binds UDP socket** for network communication | |
| +- **Starts GossipService** with 6 background threads | |
| +- **Sends push/pull messages** to peers over UDP | |
| +- **Receives and processes** messages from peers | |
| +- Runs the full gossip protocol | |
| +- Listens for shreds inside of an application. You can update the code and listen outside | |
| +- ~160 lines of code | |
| + | |
| + | |
| +**Use cases:** | |
| +- Actually participating in gossip network | |
| +- Testing network communication | |
| +- Building custom gossip nodes | |
| +- Debugging gossip protocol issues | |
| diff --git a/gossip/Cargo.toml b/gossip/Cargo.toml | |
| index 78c9b5959a..7b2e46fe3f 100644 | |
| --- a/gossip/Cargo.toml | |
| +++ b/gossip/Cargo.toml | |
| @@ -20,6 +20,10 @@ name = "solana-gossip" | |
| path = "src/main.rs" | |
| bench = false | |
| +[[bin]] | |
| +name = "full-gossip-writer" | |
| +path = "src/bin/full-gossip-writer.rs" | |
| + | |
| [features] | |
| frozen-abi = [ | |
| "dep:solana-frozen-abi", | |
| diff --git a/gossip/src/bin/full-gossip-writer.rs b/gossip/src/bin/full-gossip-writer.rs | |
| new file mode 100644 | |
| index 0000000000..937e7d2359 | |
| --- /dev/null | |
| +++ b/gossip/src/bin/full-gossip-writer.rs | |
| @@ -0,0 +1,317 @@ | |
| +// Full gossip writer with network communication | |
| +// This extends the simple example to actually send/receive gossip over the network | |
| + | |
| +#![allow(deprecated)] | |
| + | |
| +use { | |
| + solana_gossip::{ | |
| + cluster_info::ClusterInfo, | |
| + contact_info::ContactInfo, | |
| + gossip_service::GossipService, | |
| + }, | |
| + solana_hash::Hash, | |
| + solana_keypair::Keypair, | |
| + solana_net_utils::bind_in_range, | |
| + solana_signer::Signer, | |
| + solana_streamer::socket::SocketAddrSpace, | |
| + solana_time_utils::timestamp, | |
| + std::{ | |
| + env, | |
| + net::{IpAddr, SocketAddr, ToSocketAddrs}, | |
| + sync::{ | |
| + atomic::{AtomicBool, Ordering}, | |
| + Arc, | |
| + }, | |
| + thread::sleep, | |
| + time::Duration, | |
| + }, | |
| +}; | |
| + | |
| +fn print_usage() { | |
| + eprintln!("Usage: full-gossip-writer [OPTIONS]"); | |
| + eprintln!(); | |
| + eprintln!("Options:"); | |
| + eprintln!(" --ip <IP> IP address to bind to (default: 127.0.0.1)"); | |
| + eprintln!(" --port <PORT> Port to bind gossip to (default: 8000)"); | |
| + eprintln!(" --entrypoint <ADDR> Entrypoint address (e.g., host:port)"); | |
| + eprintln!(" --help Print this help message"); | |
| + eprintln!(); | |
| + eprintln!("Example:"); | |
| + eprintln!(" full-gossip-writer --ip 5.43.2.1 --port 27000 --entrypoint entrypoint2.mainnet-beta.solana.com:8001"); | |
| +} | |
| + | |
| +fn parse_args() -> Result<(IpAddr, u16, Option<String>), Box<dyn std::error::Error>> { | |
| + let args: Vec<String> = env::args().collect(); | |
| + let mut ip: IpAddr = "127.0.0.1".parse()?; | |
| + let mut port: u16 = 8000; | |
| + let mut entrypoint: Option<String> = None; | |
| + | |
| + let mut i = 1; | |
| + while i < args.len() { | |
| + match args[i].as_str() { | |
| + "--ip" => { | |
| + i += 1; | |
| + if i >= args.len() { | |
| + return Err("--ip requires a value".into()); | |
| + } | |
| + ip = args[i].parse()?; | |
| + } | |
| + "--port" => { | |
| + i += 1; | |
| + if i >= args.len() { | |
| + return Err("--port requires a value".into()); | |
| + } | |
| + port = args[i].parse()?; | |
| + } | |
| + "--entrypoint" => { | |
| + i += 1; | |
| + if i >= args.len() { | |
| + return Err("--entrypoint requires a value".into()); | |
| + } | |
| + entrypoint = Some(args[i].clone()); | |
| + } | |
| + "--help" | "-h" => { | |
| + print_usage(); | |
| + std::process::exit(0); | |
| + } | |
| + _ => { | |
| + eprintln!("Unknown argument: {}", args[i]); | |
| + print_usage(); | |
| + std::process::exit(1); | |
| + } | |
| + } | |
| + i += 1; | |
| + } | |
| + | |
| + Ok((ip, port, entrypoint)) | |
| +} | |
| + | |
| +fn main() -> Result<(), Box<dyn std::error::Error>> { | |
| + let (gossip_ip, gossip_port, entrypoint_addr) = parse_args()?; | |
| + | |
| + println!("=== Full Gossip Writer with Network Communication ===\n"); | |
| + | |
| + // 1. Create a keypair for this node | |
| + let keypair = Arc::new(Keypair::new()); | |
| + let pubkey = keypair.pubkey(); | |
| + println!("Node Pubkey: {}", pubkey); | |
| + | |
| + // 2. Bind UDP socket for gossip | |
| + let port_range = (gossip_port, gossip_port + 1); | |
| + let (bound_port, gossip_socket) = bind_in_range(gossip_ip, port_range)?; | |
| + let gossip_addr = SocketAddr::new(gossip_ip, bound_port); | |
| + println!("Gossip bound to: {}", gossip_addr); | |
| + | |
| + // 3. Create ContactInfo with the actual bound address | |
| + // Using mainnet-beta shred_version 50093 | |
| + let mut contact_info = ContactInfo::new( | |
| + pubkey, | |
| + timestamp(), | |
| + 50093, // shred_version (mainnet-beta) | |
| + ); | |
| + contact_info.set_gossip(gossip_addr)?; | |
| + | |
| + // Set other service addresses (optional) | |
| + let (rpc_port, _rpc_socket) = bind_in_range(gossip_ip, (gossip_port + 1, gossip_port + 100))?; | |
| + let (tpu_port, _tpu_socket) = bind_in_range(gossip_ip, (gossip_port + 100, gossip_port + 200))?; | |
| + let (tvu_port, tvu_socket) = bind_in_range(gossip_ip, (gossip_port + 200, gossip_port + 300))?; | |
| + let rpc_addr = SocketAddr::new(gossip_ip, rpc_port); | |
| + let tpu_addr = SocketAddr::new(gossip_ip, tpu_port); | |
| + let tvu_addr = SocketAddr::new(gossip_ip, tvu_port); | |
| + contact_info.set_rpc(rpc_addr)?; | |
| + contact_info.set_tpu(tpu_addr)?; | |
| + contact_info.set_tvu(solana_gossip::contact_info::Protocol::UDP, tvu_addr)?; | |
| + | |
| + println!("ContactInfo:"); | |
| + println!(" Gossip: {:?}", contact_info.gossip()); | |
| + println!(" RPC: {:?}", contact_info.rpc()); | |
| + println!(" TPU: {:?}", contact_info.tpu(solana_gossip::contact_info::Protocol::UDP)); | |
| + println!(" TVU: {:?}", contact_info.tvu(solana_gossip::contact_info::Protocol::UDP)); | |
| + println!(); | |
| + | |
| + // 4. Create ClusterInfo | |
| + let cluster_info = Arc::new(ClusterInfo::new( | |
| + contact_info.clone(), | |
| + keypair.clone(), | |
| + SocketAddrSpace::Unspecified, | |
| + )); | |
| + | |
| + // 5. Insert own ContactInfo | |
| + cluster_info.insert_info(contact_info); | |
| + println!("✓ Inserted self ContactInfo into CRDS"); | |
| + | |
| + // 6. Push some data to gossip | |
| + let full_snapshot_slot = 1000u64; | |
| + let full_snapshot_hash = Hash::new_unique(); | |
| + cluster_info.push_snapshot_hashes( | |
| + (full_snapshot_slot, full_snapshot_hash), | |
| + vec![], | |
| + ).ok(); | |
| + println!("✓ Pushed snapshot hashes"); | |
| + | |
| + // Start with slot 3604001754 | |
| + let mut current_slot = 3604001754u64; | |
| + let epoch_slots = vec![current_slot]; | |
| + cluster_info.push_epoch_slots(&epoch_slots); | |
| + println!("✓ Pushed initial slot: {}\n", current_slot); | |
| + | |
| + // 7. OPTIONAL: Add entrypoint nodes to connect to | |
| + if let Some(entrypoint) = entrypoint_addr { | |
| + println!("Resolving entrypoint: {}", entrypoint); | |
| + | |
| + // Resolve the entrypoint address | |
| + match entrypoint.to_socket_addrs() { | |
| + Ok(mut addrs) => { | |
| + if let Some(entrypoint_gossip_addr) = addrs.next() { | |
| + println!(" Resolved to: {}", entrypoint_gossip_addr); | |
| + | |
| + // Create a basic ContactInfo for the entrypoint | |
| + // Note: We only know the gossip address, the entrypoint will share its full ContactInfo | |
| + let entrypoint_contact_info = ContactInfo::new_gossip_entry_point(&entrypoint_gossip_addr); | |
| + | |
| + println!(" Setting entrypoint..."); | |
| + cluster_info.set_entrypoint(entrypoint_contact_info); | |
| + println!("✓ Added entrypoint to cluster\n"); | |
| + } else { | |
| + eprintln!("WARNING: Could not resolve entrypoint address"); | |
| + } | |
| + } | |
| + Err(e) => { | |
| + eprintln!("WARNING: Failed to resolve entrypoint {}: {}", entrypoint, e); | |
| + eprintln!(" Continuing in isolated mode\n"); | |
| + } | |
| + } | |
| + } else { | |
| + println!("NOTE: No entrypoints configured - running in isolated mode"); | |
| + println!(" In production, add entrypoint ContactInfo to connect to the network\n"); | |
| + } | |
| + | |
| + // 8. Start GossipService - THIS IS THE KEY DIFFERENCE! | |
| + println!("Starting GossipService (background threads for network communication)..."); | |
| + let exit = Arc::new(AtomicBool::new(false)); | |
| + | |
| + let gossip_service = GossipService::new( | |
| + &cluster_info, | |
| + None, // bank_forks (optional, for validators) | |
| + Arc::new([gossip_socket]), // UDP socket(s) for gossip | |
| + None, // gossip_validators (optional whitelist) | |
| + true, // should_check_duplicate_instance | |
| + None, // stats_reporter_sender | |
| + exit.clone(), | |
| + ); | |
| + | |
| + println!("✓ GossipService started!"); | |
| + println!("\nBackground threads now running:"); | |
| + println!(" - Receiving UDP packets from peers"); | |
| + println!(" - Processing push/pull messages"); | |
| + println!(" - Sending gossip data to peers"); | |
| + println!(" - Responding to pull requests"); | |
| + println!("\nGossip protocol is now LIVE on the network!\n"); | |
| + | |
| + // Spawn TVU listener thread | |
| + let exit_tvu = exit.clone(); | |
| + let tvu_thread = std::thread::spawn(move || { | |
| + let mut buf = [0u8; 1280]; // Max UDP packet size | |
| + let mut packet_count = 0u64; | |
| + println!("TVU listener started on {}", tvu_addr); | |
| + | |
| + while !exit_tvu.load(Ordering::Relaxed) { | |
| + match tvu_socket.recv_from(&mut buf) { | |
| + Ok((size, src)) => { | |
| + packet_count += 1; | |
| + println!("[TVU] Packet #{} from {} - {} bytes", packet_count, src, size); | |
| + } | |
| + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { | |
| + sleep(Duration::from_millis(10)); | |
| + } | |
| + Err(e) => { | |
| + eprintln!("[TVU] Error receiving: {}", e); | |
| + } | |
| + } | |
| + } | |
| + println!("TVU listener stopped"); | |
| + }); | |
| + | |
| + // 9. Run for 300 seconds, incrementing slot every 400 milliseconds | |
| + println!("Running for 300 seconds, incrementing slot every 400ms..."); | |
| + println!("Starting slot: {}\n", current_slot); | |
| + | |
| + let start_time = std::time::Instant::now(); | |
| + let duration = Duration::from_secs(300); | |
| + let mut last_status_time = start_time; | |
| + let mut last_gossip_print_time = start_time; | |
| + let mut slots_pushed = 0u64; | |
| + | |
| + while start_time.elapsed() < duration { | |
| + sleep(Duration::from_millis(400)); | |
| + | |
| + current_slot += 1; | |
| + cluster_info.push_epoch_slots(&vec![current_slot]); | |
| + slots_pushed += 1; | |
| + | |
| + // Print gossip data received every 10 seconds | |
| + if last_gossip_print_time.elapsed() >= Duration::from_secs(10) { | |
| + let elapsed = start_time.elapsed().as_secs(); | |
| + println!("\n=== [{}s] GOSSIP DATA RECEIVED ===", elapsed); | |
| + | |
| + // Get all peers we know about | |
| + let all_peers = cluster_info.all_peers(); | |
| + println!("Total peers discovered: {}", all_peers.len()); | |
| + | |
| + for (i, (peer_info, wallclock)) in all_peers.iter().enumerate().take(5) { | |
| + println!(" Peer {}: {} (wallclock: {})", i + 1, peer_info.pubkey(), wallclock); | |
| + println!(" Gossip: {:?}", peer_info.gossip()); | |
| + println!(" RPC: {:?}", peer_info.rpc()); | |
| + println!(" TPU: {:?}", peer_info.tpu(solana_gossip::contact_info::Protocol::UDP)); | |
| + } | |
| + if all_peers.len() > 5 { | |
| + println!(" ... and {} more peers", all_peers.len() - 5); | |
| + } | |
| + println!(); | |
| + | |
| + last_gossip_print_time = std::time::Instant::now(); | |
| + } | |
| + | |
| + // Print status every 30 seconds | |
| + if last_status_time.elapsed() >= Duration::from_secs(30) { | |
| + let elapsed = start_time.elapsed().as_secs(); | |
| + println!("[{}s] Current slot: {} (pushed {} slots, rate: {:.2} slots/sec)", | |
| + elapsed, current_slot, slots_pushed, | |
| + slots_pushed as f64 / elapsed as f64); | |
| + last_status_time = std::time::Instant::now(); | |
| + } | |
| + } | |
| + | |
| + let total_time = start_time.elapsed().as_secs_f64(); | |
| + println!("\n✓ Completed: Final slot: {}", current_slot); | |
| + println!(" Total slots pushed: {}", slots_pushed); | |
| + println!(" Average rate: {:.2} slots/sec", slots_pushed as f64 / total_time); | |
| + | |
| + println!("\n\n=== Shutting Down ==="); | |
| + exit.store(true, Ordering::Relaxed); | |
| + | |
| + // Wait for all threads to exit | |
| + match gossip_service.join() { | |
| + Ok(_) => println!("✓ GossipService stopped cleanly"), | |
| + Err(e) => eprintln!("Error joining gossip threads: {:?}", e), | |
| + } | |
| + | |
| + match tvu_thread.join() { | |
| + Ok(_) => println!("✓ TVU listener stopped cleanly"), | |
| + Err(e) => eprintln!("Error joining TVU thread: {:?}", e), | |
| + } | |
| + println!("\n=== Example Complete ==="); | |
| + println!("\nWhat happened:"); | |
| + println!("1. Bound UDP socket for gossip communication"); | |
| + println!("2. Created ClusterInfo with our node's data"); | |
| + println!("3. Started GossipService with 6 background threads"); | |
| + println!("4. Ran gossip protocol for 30 seconds"); | |
| + println!("5. GossipService automatically sent push/pull messages"); | |
| + println!("6. Would receive and process messages from peers (if any were configured)"); | |
| + println!("\nTo connect to real network:"); | |
| + println!(" - Add entrypoint ContactInfo before starting GossipService"); | |
| + println!(" - Set proper shred_version to match network"); | |
| + println!(" - GossipService will automatically discover and sync with peers"); | |
| + | |
| + Ok(()) | |
| +} | |
| -- | |
| 2.34.1 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment