Skip to content

Instantly share code, notes, and snippets.

@unordered-set
Last active December 24, 2025 08:09
Show Gist options
  • Select an option

  • Save unordered-set/18a903da0237c4103f158ef97144d4aa to your computer and use it in GitHub Desktop.

Select an option

Save unordered-set/18a903da0237c4103f158ef97144d4aa to your computer and use it in GitHub Desktop.
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