Saturday, April 11, 2026

Building a P2P Blockchain Crawler & Multi-Sig Treasury in Rust (UTXO Networks)

[SYSTEM START: INFRASTRUCTURE PROTOCOL INITIATED]

Navigating a decentralized UTXO blockchain (like Bitcoin, Smoke, or Cannacoin) often feels like exploring a dark forest. There is no central server to ask for a map, and securing network funds requires moving beyond basic single-key wallets.

If you are a blockchain engineer or a node operator, relying on pre-packaged GUI tools won't cut it. You need to touch the metal.

In this guide, we are going to build two essential infrastructure utilities in Rust: a highly concurrent P2P Node Spider to map out the network, and a Multi-Sig Treasury Forge to secure decentralized funds. We will leverage tokio, reqwest, and your local daemon’s JSON-RPC interface to do the heavy lifting.


Part 1: The P2P Network Spider (节点发现)

In a P2P network, nodes discover each other via a gossip protocol. To crawl this network without writing thousands of lines of raw TCP handshake logic, the smartest approach is to use your local daemon as the crawling engine.

Our Rust spider will ping our local node, inject a known "seed node" to force a connection, and then recursively harvest and connect to newly discovered IP addresses to map the grid.

The Stack

  • Language: Rust (Edition 2021)

  • Libraries: reqwest (HTTP client), tokio (Async runtime), serde_json (JSON parsing), dotenv (Secrets management).

The Rust Implementation

Here is the core logic for the smoke-crawler. It authenticates via .env credentials, commands the local node to connect to the seed, and loops to discover new peers.

Rust
use reqwest::Client;
use serde_json::{json, Value};
use std::collections::HashSet;
use std::env;
use std::time::Duration;
use tokio::time::sleep;
use dotenv::dotenv;

// [ RPC EXECUTOR ]
async fn rpc_call(client: &Client, url: &str, user: &str, pass: &str, method: &str, params: Value) -> Result<Value, Box<dyn std::error::Error>> {
    let request_body = json!({ "jsonrpc": "1.0", "id": "crawler", "method": method, "params": params });
    let response = client.post(url).basic_auth(user, Some(pass)).json(&request_body).send().await?;
    Ok(response.json().await?)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenv().ok(); 
    println!(">>> SMOKE_NODE_SPIDER_V2.0 // ACTIVE CRAWLER <<<");

    let rpc_user = env::var("RPC_USER").expect("Missing RPC_USER");
    let rpc_pass = env::var("RPC_PASS").expect("Missing RPC_PASS");
    let rpc_port = env::var("RPC_PORT").expect("Missing RPC_PORT");
    let seed_node = env::var("SEED_NODE").unwrap_or_else(|_| "cannacoin.duckdns.org".to_string());
    
    let rpc_url = format!("http://127.0.0.1:{}", rpc_port);
    let client = Client::builder().timeout(Duration::from_secs(10)).build()?;
    let mut known_nodes: HashSet<String> = HashSet::new();

    // Command local daemon to handshake with the seed node
    let _ = rpc_call(&client, &rpc_url, &rpc_user, &rpc_pass, "addnode", json!([seed_node, "onetry"])).await;
    println!("[*] INIT COMPLETE. BEGINNING RECURSIVE CRAWL...\n");

    loop {
        sleep(Duration::from_secs(5)).await; // Wait for P2P gossip
        
        if let Ok(res) = rpc_call(&client, &rpc_url, &rpc_user, &rpc_pass, "getpeerinfo", json!([])).await {
            if let Some(peers) = res["result"].as_array() {
                for peer in peers {
                    if let Some(addr) = peer["addr"].as_str() {
                        let clean_addr = addr.to_string();
                        
                        if !known_nodes.contains(&clean_addr) {
                            known_nodes.insert(clean_addr.clone());
                            let subver = peer["subver"].as_str().unwrap_or("UNKNOWN_AGENT");
                            println!("[+] NODE DETECTED: {} | {}", clean_addr, subver);
                            
                            // FORCE CRAWL: Command daemon to actively probe this new node
                            let _ = rpc_call(&client, &rpc_url, &rpc_user, &rpc_pass, "addnode", json!([clean_addr, "onetry"])).await;
                        }
                    }
                }
            }
        }
    }
}

Why this works: By constantly forcing addnode commands on newly discovered IPs, your local daemon is forced to extract peer lists from deeper and deeper within the network.


Part 2: The Multi-Sig Treasury Forge (多重签名金库)

Mapping the network is great, but securing the capital operating on it is critical. If you are launching a project, keeping treasury funds in a single-signature wallet is a massive security risk.

You need an M-of-N Multi-Signature Wallet (e.g., a 2-of-3 setup where 2 out of 3 founders must sign to execute a transaction).

The Rust Treasury CLI

Instead of manually typing easily-fumbled commands into the terminal, this Rust script prompts the user for the required public keys and directly interfaces with the daemon to generate the cryptographic lock.

Rust
use reqwest::Client;
use serde_json::{json, Value};
use std::env;
use std::io::{self, Write};
use std::time::Duration;
use dotenv::dotenv;

fn prompt_input(prompt_text: &str) -> String {
    print!("{}", prompt_text);
    io::stdout().flush().unwrap();
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();
    input.trim().to_string()
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenv().ok();
    println!(">>> SMOKE_TREASURY_FORGE_RS (多重签名金库) <<<");

    let rpc_user = env::var("RPC_USER").unwrap();
    let rpc_pass = env::var("RPC_PASS").unwrap();
    let rpc_url = format!("http://127.0.0.1:{}", env::var("RPC_PORT").unwrap());

    let m: u32 = prompt_input("[?] Required signatures (M) [e.g., 2]: ").parse().unwrap();
    let n: u32 = prompt_input("[?] Total number of keys (N) [e.g., 3]: ").parse().unwrap();

    let mut keys: Vec<String> = Vec::new();
    for i in 1..=n {
        keys.push(prompt_input(&format!("[?] Enter Public Key #{}: ", i)));
    }

    let client = Client::builder().timeout(Duration::from_secs(10)).build()?;
    let request_body = json!({
        "jsonrpc": "1.0", "id": "treasury_forge",
        "method": "createmultisig", "params": [m, keys]
    });

    let response = client.post(&rpc_url).basic_auth(rpc_user, Some(rpc_pass)).json(&request_body).send().await?;

    if let Ok(res_json) = response.json::<Value>().await {
        println!(">>> TREASURY ESTABLISHED <<<");
        println!("{}", serde_json::to_string_pretty(&res_json["result"]).unwrap());
        println!("[!] 警告: SECURE THE REDEEM SCRIPT OFFLINE.");
    }
    Ok(())
}

The Golden Rule: Protect the Redeem Script (焚毁风险)

When you run this script, it will output an address (where people send the funds) and a redeemScript.

Do not lose the redeemScript. The blockchain does not natively know your specific 2-of-3 rules until you try to spend the funds. You must provide that exact redeemScript alongside your signatures to authorize a withdrawal. If you lose it, the treasury is permanently locked.


Building a P2P Blockchain Crawler & Multi-Sig Treasury in Rust (UTXO Networks)

[SYSTEM START: INFRASTRUCTURE PROTOCOL INITIATED] Navigating a decentralized UTXO blockchain (like Bitcoin, Smoke, or Cannacoin) often feels...