Nectarflower-rs, using nectar without hive-nectar part 3

in Synergy Builders7 days ago

Following up on my previous libraries like nectarflower-js (which I wrote about here) and the more recent nectarflower-go (written about here), I wanted to tackle this useful concept from yet another language perspective, probably the most difficult, imho.

It's Rust's turn!

The main point I keep wanting to demonstrate is just how incredibly useful it is that @nectarflower keeps their Hive node benchmark data updated hourly and stores it right in their account's JSON metadata. Seriously, having this reliable list of passing nodes available on-chain is an amazing thing for developers. It makes getting good API endpoints so much easier, no matter what language you're building with.

So, here's my attempt at making this accessible for Rust developers: nectarflower-rs. The goal is the same: provide a simple way to fetch and use that valuable node data stored in the metadata.

Getting the list of passing nodes within your Rust code is pretty straightforward:

use nectarflower_rs::Client;

fn main() {
    // Create a client just to fetch nodes
    let client = Client::new();

    // Get nodes from account
    match client.get_nodes_from_account("nectarflower") {
        Ok(node_data) => {
            // Display passing nodes
            println!("Passing nodes:");
            for node in node_data.nodes {
                println!("{}", node);
            }
            
            // Display failing nodes
            println!("\nFailing nodes:");
            if node_data.failing_nodes.is_empty() {
                println!("None");
            } else {
                for (node, reason) in node_data.failing_nodes {
                    println!("{} - Reason: {}", node, reason);
                }
            }
        },
        Err(e) => eprintln!("Error fetching nodes: {}", e),
    }
}

Rust makes fetching node data simple too!

Now, just like the Go version, to actually pull the account info containing the metadata, the library needs some basic Hive client functionality baked in. It's fairly minimal right now, but it gets the job done. Here’s a quick example of the client part fetching something basic, like a block near the head block:

//! Example usage for nectarflower-rs
use nectarflower_rs::Client;
use serde_json::Value;

fn main() {
    // Create a new Hive client with default node
    let mut client = Client::new();
    println!("Default client initialized with: {:?}", client.nodes);

    // Account to fetch nodes from
    let account_name = "nectarflower";

    // Get nodes from account
    println!("\nFetching nodes from account {}...", account_name);
    match client.get_nodes_from_account(account_name) {
        Ok(node_data) => {
            println!("Found {} nodes in account metadata", node_data.nodes.len());
            println!("Nodes: {:?}", node_data.nodes);
            if !node_data.failing_nodes.is_empty() {
                println!(
                    "Found {} failing nodes in account metadata\nFailing nodes: {:?}",
                    node_data.failing_nodes.len(),
                    node_data.failing_nodes
                );
            }

            // Update client with new nodes
            println!("\nUpdating client with new nodes...");
            client.set_nodes(node_data.nodes.clone(), node_data.failing_nodes.clone());
            println!("Updated client initialized with: {:?}", client.nodes);
        }
        Err(e) => {
            eprintln!("Error fetching nodes: {e}");
            return;
        }
    }

    // Test the updated client with a simple query
    println!("\nTesting updated client with a query...");
    let props: Result<Value, _> =
        client.call::<(), Value>("database_api.get_dynamic_global_properties", ());
    match props {
        Ok(props) => {
            let block_num = props.get("head_block_number").and_then(|v| v.as_i64());
            if let Some(num) = block_num {
                println!("Query successful! Current block number: {}", num);
            } else {
                println!(
                    "Query successful! Current block number: {:?}",
                    props.get("head_block_number")
                );
            }
        }
        Err(e) => {
            eprintln!("Error fetching global properties: {e}");
            return;
        }
    }

    // Demonstrate the all-in-one function
    println!("\nDemonstrating the all-in-one UpdateNodesFromAccount function...");
    let mut new_client = Client::new();
    match new_client.update_nodes_from_account(account_name) {
        Ok(()) => println!(
            "One-step update complete. Client initialized with: {:?}",
            new_client.nodes
        ),
        Err(e) => {
            eprintln!("Error updating nodes: {e}");
            return;
        }
    }

    // Example: Fetch a recent block
    println!("\nFetching a recent block...");
    // First get the current block number
    let block_props: Result<Value, _> =
        client.call::<(), Value>("database_api.get_dynamic_global_properties", ());
    let current_block_num = match block_props {
        Ok(props) => props.get("head_block_number").and_then(|v| v.as_i64()),
        Err(_) => None,
    };

    if let Some(block_num) = current_block_num {
        // Fetch a block that's a few blocks behind the head to ensure it's available
        let target_block_num = block_num - 10;
        println!("Fetching block #{}", target_block_num);

        // Create parameters for get_block method
        let block_params = serde_json::json!({
            "block_num": target_block_num
        });

        // Fetch the block
        let block: Result<Value, _> = client.call("block_api.get_block", block_params);
        match block {
            Ok(block) => {
                // Extract block data
                if let Some(block_data) = block.get("block") {
                    // Print block details
                    println!("Block details:");
                    println!(
                        "  Block ID: {}",
                        block_data
                            .get("block_id")
                            .and_then(|v| v.as_str())
                            .unwrap_or("N/A")
                    );
                    println!(
                        "  Previous: {}",
                        block_data
                            .get("previous")
                            .and_then(|v| v.as_str())
                            .unwrap_or("N/A")
                    );
                    println!(
                        "  Timestamp: {}",
                        block_data
                            .get("timestamp")
                            .and_then(|v| v.as_str())
                            .unwrap_or("N/A")
                    );

                    // Print transaction count
                    if let Some(transactions) =
                        block_data.get("transactions").and_then(|v| v.as_array())
                    {
                        println!("  Transaction count: {}", transactions.len());

                        // If there are transactions, print details of the first one
                        if !transactions.is_empty() {
                            let tx_ids =
                                block_data.get("transaction_ids").and_then(|v| v.as_array());
                            let tx_id = tx_ids
                                .and_then(|ids| ids.get(0))
                                .and_then(|id| id.as_str())
                                .unwrap_or("unknown");

                            println!("\nFirst transaction details:");
                            println!("  Transaction ID: {}", tx_id);

                            // Pretty print the first transaction
                            if let Some(tx) = transactions.get(0) {
                                let tx_json = serde_json::to_string_pretty(tx)
                                    .unwrap_or_else(|_| "Error formatting transaction".to_string());
                                println!("  Transaction data:\n{}", tx_json);
                            }
                        }
                    }
                } else {
                    eprintln!("Error extracting block data");
                }
            }
            Err(e) => eprintln!("Error fetching block: {e}"),
        }
    } else {
        eprintln!("Could not determine current block number");
    }
}

Rust handling the raw Hive data retrieval.


You can check out the full example code and the library itself over in the GitHub repository.

It's really cool how this approach of storing benchmark data directly in account metadata lowers the barrier for developers across different ecosystems. Hopefully, this Rust version proves useful to someone out there exploring Hive development with Rust. Plus, any excuse to dive deeper into Rust is always fun, right?

As usual,
Michael Garcia a.k.a. TheCrazyGM

Sort:  

Rusty Flowers - I LIKE!