diff --git a/.gitignore b/.gitignore index 27075be5cd..0b8833bfd5 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,9 @@ metrics/prometheus/prometheus.yml sn_node_manager/.vagrant .venv/ uv.lock +*.so +*.pyc + +*.pyc +*.swp + diff --git a/autonomi/examples/autonomi_advanced.py b/autonomi/examples/autonomi_advanced.py new file mode 100644 index 0000000000..310766192e --- /dev/null +++ b/autonomi/examples/autonomi_advanced.py @@ -0,0 +1,79 @@ +from autonomi_client import Client, Wallet, PaymentOption +import sys + +def init_wallet(private_key: str) -> Wallet: + try: + wallet = Wallet(private_key) + print(f"Initialized wallet with address: {wallet.address()}") + + balance = wallet.balance() + print(f"Wallet balance: {balance}") + + return wallet + except Exception as e: + print(f"Failed to initialize wallet: {e}") + sys.exit(1) + +def connect_to_network(peers: list[str]) -> Client: + try: + client = Client.connect(peers) + print("Successfully connected to network") + return client + except Exception as e: + print(f"Failed to connect to network: {e}") + sys.exit(1) + +def upload_data(client: Client, data: bytes, payment: PaymentOption) -> str: + try: + addr = client.data_put(data, payment) + print(f"Successfully uploaded data to: {addr}") + return addr + except Exception as e: + print(f"Failed to upload data: {e}") + sys.exit(1) + +def download_data(client: Client, addr: str) -> bytes: + try: + data = client.data_get(addr) + print(f"Successfully downloaded {len(data)} bytes") + return data + except Exception as e: + print(f"Failed to download data: {e}") + sys.exit(1) + +def main(): + # Configuration + private_key = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + peers = ["/ip4/127.0.0.1/tcp/12000"] + + # Initialize + wallet = init_wallet(private_key) + client = connect_to_network(peers) + payment = PaymentOption.wallet(wallet) + + # Upload test data + test_data = b"Hello, Safe Network!" + addr = upload_data(client, test_data, payment) + + # Download and verify + downloaded = download_data(client, addr) + assert downloaded == test_data, "Data verification failed!" + print("Data verification successful!") + + # Example file handling + try: + with open("example.txt", "rb") as f: + file_data = f.read() + file_addr = upload_data(client, file_data, payment) + + # Download and save to new file + downloaded = download_data(client, file_addr) + with open("example_downloaded.txt", "wb") as f_out: + f_out.write(downloaded) + print("File operations completed successfully!") + except IOError as e: + print(f"File operation failed: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/autonomi/examples/autonomi_data_registers.py b/autonomi/examples/autonomi_data_registers.py new file mode 100644 index 0000000000..a7b8ba42ff --- /dev/null +++ b/autonomi/examples/autonomi_data_registers.py @@ -0,0 +1,89 @@ +from autonomi_client import Client, Wallet, PaymentOption, RegisterSecretKey +import hashlib + +def handle_data_operations(client: Client, payment: PaymentOption): + """Example of various data operations""" + print("\n=== Data Operations ===") + + # Upload some text data + text_data = b"Hello, Safe Network!" + text_addr = client.data_put(text_data, payment) + print(f"Text data uploaded to: {text_addr}") + + # Upload binary data (like an image) + with open("example.jpg", "rb") as f: + image_data = f.read() + image_addr = client.data_put(image_data, payment) + print(f"Image uploaded to: {image_addr}") + + # Download and verify data + downloaded_text = client.data_get(text_addr) + assert downloaded_text == text_data, "Text data verification failed!" + print("Text data verified successfully") + + # Download and save image + downloaded_image = client.data_get(image_addr) + with open("downloaded_example.jpg", "wb") as f: + f.write(downloaded_image) + print("Image downloaded successfully") + +def handle_register_operations(client: Client, wallet: Wallet): + """Example of register operations""" + print("\n=== Register Operations ===") + + # Create a register key + register_key = client.register_generate_key() + print(f"Generated register key") + + # Create a register with initial value + register_name = "my_first_register" + initial_value = b"Initial register value" + register = client.register_create( + initial_value, + register_name, + register_key, + wallet + ) + print(f"Created register at: {register.address()}") + + # Read current value + values = register.values() + print(f"Current register values: {[v.decode() for v in values]}") + + # Update register value + new_value = b"Updated register value" + client.register_update(register, new_value, register_key) + print("Register updated") + + # Read updated value + updated_register = client.register_get(register.address()) + updated_values = updated_register.values() + print(f"Updated register values: {[v.decode() for v in updated_values]}") + +def main(): + # Initialize wallet and client + private_key = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + peers = ["/ip4/127.0.0.1/tcp/12000"] + + try: + # Setup + wallet = Wallet(private_key) + print(f"Wallet address: {wallet.address()}") + print(f"Wallet balance: {wallet.balance()}") + + client = Client.connect(peers) + payment = PaymentOption.wallet(wallet) + + # Run examples + handle_data_operations(client, payment) + handle_register_operations(client, wallet) + + except Exception as e: + print(f"Error: {e}") + return 1 + + print("\nAll operations completed successfully!") + return 0 + +if __name__ == "__main__": + exit(main()) \ No newline at end of file diff --git a/autonomi/examples/autonomi_example.py b/autonomi/examples/autonomi_example.py new file mode 100644 index 0000000000..496446173c --- /dev/null +++ b/autonomi/examples/autonomi_example.py @@ -0,0 +1,38 @@ +from autonomi_client import Client, Wallet, PaymentOption + +def main(): + # Initialize a wallet with a private key + # This should be a valid Ethereum private key (64 hex chars without '0x' prefix) + private_key = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + wallet = Wallet(private_key) + print(f"Wallet address: {wallet.address()}") + print(f"Wallet balance: {wallet.balance()}") + + # Connect to the network + # These should be valid multiaddresses of network nodes + peers = [ + "/ip4/127.0.0.1/tcp/12000", + "/ip4/127.0.0.1/tcp/12001" + ] + client = Client.connect(peers) + + # Create payment option using the wallet + payment = PaymentOption.wallet(wallet) + + # Upload some data + data = b"Hello, Safe Network!" + addr = client.data_put(data, payment) + print(f"Data uploaded to address: {addr}") + + # Download the data back + downloaded = client.data_get(addr) + print(f"Downloaded data: {downloaded.decode()}") + + # You can also upload files + with open("example.txt", "rb") as f: + file_data = f.read() + file_addr = client.data_put(file_data, payment) + print(f"File uploaded to address: {file_addr}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/autonomi/examples/autonomi_private_data.py b/autonomi/examples/autonomi_private_data.py new file mode 100644 index 0000000000..3b0d9327e4 --- /dev/null +++ b/autonomi/examples/autonomi_private_data.py @@ -0,0 +1,90 @@ +from autonomi_client import Client, Wallet, PaymentOption, RegisterSecretKey, RegisterPermissions +from typing import List, Optional +import json + +class DataManager: + def __init__(self, client: Client, wallet: Wallet): + self.client = client + self.wallet = wallet + self.payment = PaymentOption.wallet(wallet) + + def store_private_data(self, data: bytes) -> str: + """Store data privately and return its address""" + addr = self.client.private_data_put(data, self.payment) + return addr + + def retrieve_private_data(self, addr: str) -> bytes: + """Retrieve privately stored data""" + return self.client.private_data_get(addr) + + def create_shared_register(self, name: str, initial_value: bytes, + allowed_writers: List[str]) -> str: + """Create a register that multiple users can write to""" + register_key = self.client.register_generate_key() + + # Create permissions for all writers + permissions = RegisterPermissions.new_with(allowed_writers) + + register = self.client.register_create_with_permissions( + initial_value, + name, + register_key, + permissions, + self.wallet + ) + + return register.address() + +def main(): + # Initialize + private_key = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + peers = ["/ip4/127.0.0.1/tcp/12000"] + + try: + wallet = Wallet(private_key) + client = Client.connect(peers) + manager = DataManager(client, wallet) + + # Store private data + user_data = { + "username": "alice", + "preferences": { + "theme": "dark", + "notifications": True + } + } + private_data = json.dumps(user_data).encode() + private_addr = manager.store_private_data(private_data) + print(f"Stored private data at: {private_addr}") + + # Retrieve and verify private data + retrieved_data = manager.retrieve_private_data(private_addr) + retrieved_json = json.loads(retrieved_data.decode()) + print(f"Retrieved data: {retrieved_json}") + + # Create shared register + allowed_writers = [ + wallet.address(), # self + "0x1234567890abcdef1234567890abcdef12345678" # another user + ] + register_addr = manager.create_shared_register( + "shared_config", + b"initial shared data", + allowed_writers + ) + print(f"Created shared register at: {register_addr}") + + # Verify register + register = client.register_get(register_addr) + values = register.values() + print(f"Register values: {[v.decode() for v in values]}") + + except Exception as e: + print(f"Error: {e}") + return 1 + + print("All operations completed successfully!") + return 0 + +if __name__ == "__main__": + exit(main()) \ No newline at end of file diff --git a/autonomi/examples/autonomi_private_encryption.py b/autonomi/examples/autonomi_private_encryption.py new file mode 100644 index 0000000000..7f71a6b8d6 --- /dev/null +++ b/autonomi/examples/autonomi_private_encryption.py @@ -0,0 +1,75 @@ +from autonomi_client import ( + Client, Wallet, PaymentOption, PrivateDataAccess, + encrypt, hash_to_short_string +) +import json + +def demonstrate_private_data(client: Client, payment: PaymentOption): + """Show private data handling""" + print("\n=== Private Data Operations ===") + + # Create some private data + secret_data = { + "password": "very_secret", + "api_key": "super_secret_key" + } + data_bytes = json.dumps(secret_data).encode() + + # Store it privately + access = client.private_data_put(data_bytes, payment) + print(f"Stored private data, access token: {access.to_hex()}") + print(f"Short reference: {access.address()}") + + # Retrieve it + retrieved_bytes = client.private_data_get(access) + retrieved_data = json.loads(retrieved_bytes.decode()) + print(f"Retrieved private data: {retrieved_data}") + + return access.to_hex() + +def demonstrate_encryption(): + """Show self-encryption functionality""" + print("\n=== Self-Encryption Operations ===") + + # Create test data + test_data = b"This is some test data for encryption" + + # Encrypt it + data_map, chunks = encrypt(test_data) + print(f"Original data size: {len(test_data)} bytes") + print(f"Data map size: {len(data_map)} bytes") + print(f"Number of chunks: {len(chunks)}") + print(f"Total chunks size: {sum(len(c) for c in chunks)} bytes") + +def main(): + # Initialize + private_key = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + peers = ["/ip4/127.0.0.1/tcp/12000"] + + try: + # Setup + wallet = Wallet(private_key) + print(f"Wallet address: {wallet.address()}") + print(f"Wallet balance: {wallet.balance()}") + + client = Client.connect(peers) + payment = PaymentOption.wallet(wallet) + + # Run demonstrations + access_token = demonstrate_private_data(client, payment) + demonstrate_encryption() + + # Show utility function + print("\n=== Utility Functions ===") + short_hash = hash_to_short_string(access_token) + print(f"Short hash of access token: {short_hash}") + + except Exception as e: + print(f"Error: {e}") + return 1 + + print("\nAll operations completed successfully!") + return 0 + +if __name__ == "__main__": + exit(main()) \ No newline at end of file diff --git a/autonomi/examples/autonomi_vault.py b/autonomi/examples/autonomi_vault.py new file mode 100644 index 0000000000..6a26d3707a --- /dev/null +++ b/autonomi/examples/autonomi_vault.py @@ -0,0 +1,53 @@ +from autonomi_client import Client, Wallet, PaymentOption, VaultSecretKey, UserData + +def main(): + # Initialize + private_key = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + peers = ["/ip4/127.0.0.1/tcp/12000"] + + try: + # Setup + wallet = Wallet(private_key) + client = Client.connect(peers) + payment = PaymentOption.wallet(wallet) + + # Create vault key + vault_key = VaultSecretKey.new() + print(f"Created vault key: {vault_key.to_hex()}") + + # Get vault cost + cost = client.vault_cost(vault_key) + print(f"Vault cost: {cost}") + + # Create user data + user_data = UserData() + + # Store some data in vault + data = b"Hello from vault!" + content_type = 1 # Custom content type + cost = client.write_bytes_to_vault(data, payment, vault_key, content_type) + print(f"Wrote data to vault, cost: {cost}") + + # Read data back + retrieved_data, retrieved_type = client.fetch_and_decrypt_vault(vault_key) + print(f"Retrieved data: {retrieved_data.decode()}") + print(f"Content type: {retrieved_type}") + + # Store user data + cost = client.put_user_data_to_vault(vault_key, payment, user_data) + print(f"Stored user data, cost: {cost}") + + # Get user data + retrieved_user_data = client.get_user_data_from_vault(vault_key) + print("File archives:", retrieved_user_data.file_archives()) + print("Private file archives:", retrieved_user_data.private_file_archives()) + + except Exception as e: + print(f"Error: {e}") + return 1 + + print("All vault operations completed successfully!") + return 0 + +if __name__ == "__main__": + exit(main()) \ No newline at end of file diff --git a/sn_node/src/bin/safenode/main.rs b/sn_node/src/bin/safenode/main.rs index 1b18429e89..b094b3f538 100644 --- a/sn_node/src/bin/safenode/main.rs +++ b/sn_node/src/bin/safenode/main.rs @@ -9,7 +9,6 @@ #[macro_use] extern crate tracing; -mod rpc_service; mod subcommands; use crate::subcommands::EvmNetworkCommand; @@ -318,7 +317,7 @@ fn main() -> Result<()> { #[cfg(feature = "open-metrics")] node_builder.metrics_server_port(metrics_server_port); let restart_options = - run_node(node_builder, opt.rpc, &log_output_dest, log_reload_handle).await?; + run_node(node_builder,&log_output_dest, log_reload_handle).await?; Ok::<_, eyre::Report>(restart_options) })?; @@ -340,11 +339,9 @@ fn main() -> Result<()> { /// and it's listening port if we want to retain_peer_id on restart. async fn run_node( node_builder: NodeBuilder, - rpc: Option, log_output_dest: &str, log_reload_handle: ReloadHandle, ) -> Result> { - let started_instant = std::time::Instant::now(); info!("Starting node ..."); let running_node = node_builder.build_and_run()?; @@ -443,19 +440,7 @@ You can check your reward balance by running: } }); - // Start up gRPC interface if enabled by user - if let Some(addr) = rpc { - rpc_service::start_rpc_service( - addr, - log_output_dest, - running_node.clone(), - ctrl_tx, - started_instant, - log_reload_handle, - ); - } - - // Keep the node and gRPC service (if enabled) running. + // Keep the node running. // We'll monitor any NodeCtrl cmd to restart/stop/update, loop { match ctrl_rx.recv().await { diff --git a/sn_node/src/bin/safenode/rpc_service.rs b/sn_node/src/bin/safenode/rpc_service.rs deleted file mode 100644 index 8d16ba8f3d..0000000000 --- a/sn_node/src/bin/safenode/rpc_service.rs +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright 2024 MaidSafe.net limited. -// -// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. -// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed -// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. Please review the Licences for the specific language governing -// permissions and limitations relating to use of the SAFE Network Software. - -use eyre::{ErrReport, Result}; -use sn_logging::ReloadHandle; -use sn_node::RunningNode; -use sn_protocol::node_rpc::{NodeCtrl, StopResult}; -use sn_protocol::safenode_proto::{ - k_buckets_response, - safe_node_server::{SafeNode, SafeNodeServer}, - KBucketsRequest, KBucketsResponse, NetworkInfoRequest, NetworkInfoResponse, NodeEvent, - NodeEventsRequest, NodeInfoRequest, NodeInfoResponse, RecordAddressesRequest, - RecordAddressesResponse, RestartRequest, RestartResponse, StopRequest, StopResponse, - UpdateLogLevelRequest, UpdateLogLevelResponse, UpdateRequest, UpdateResponse, -}; -use std::{ - collections::HashMap, - env, - net::SocketAddr, - process, - time::{Duration, Instant}, -}; -use tokio::sync::mpsc::{self, Sender}; -use tokio_stream::wrappers::ReceiverStream; -use tonic::{transport::Server, Code, Request, Response, Status}; -use tracing::{debug, info}; - -// Defining a struct to hold information used by our gRPC service backend -struct SafeNodeRpcService { - addr: SocketAddr, - log_dir: String, - running_node: RunningNode, - ctrl_tx: Sender, - started_instant: Instant, - log_reload_handle: ReloadHandle, -} - -// Implementing RPC interface for service defined in .proto -#[tonic::async_trait] -impl SafeNode for SafeNodeRpcService { - type NodeEventsStream = ReceiverStream>; - - async fn node_info( - &self, - request: Request, - ) -> Result, Status> { - debug!( - "RPC request received at {}: {:?}", - self.addr, - request.get_ref() - ); - - let resp = Response::new(NodeInfoResponse { - peer_id: self.running_node.peer_id().to_bytes(), - log_dir: self.log_dir.clone(), - data_dir: self - .running_node - .root_dir_path() - .to_string_lossy() - .to_string(), - pid: process::id(), - bin_version: env!("CARGO_PKG_VERSION").to_string(), - uptime_secs: self.started_instant.elapsed().as_secs(), - wallet_balance: 0, // NB TODO: Implement this using metrics data? - }); - - Ok(resp) - } - - async fn network_info( - &self, - request: Request, - ) -> Result, Status> { - debug!( - "RPC request received at {}: {:?}", - self.addr, - request.get_ref() - ); - - let state = self - .running_node - .get_swarm_local_state() - .await - .expect("failed to get local swarm state"); - let connected_peers = state.connected_peers.iter().map(|p| p.to_bytes()).collect(); - let listeners = state.listeners.iter().map(|m| m.to_string()).collect(); - - let resp = Response::new(NetworkInfoResponse { - connected_peers, - listeners, - }); - - Ok(resp) - } - - async fn node_events( - &self, - request: Request, - ) -> Result, Status> { - debug!( - "RPC request received at {}: {:?}", - self.addr, - request.get_ref() - ); - - let (client_tx, client_rx) = mpsc::channel(4); - - let mut events_rx = self.running_node.node_events_channel().subscribe(); - let _handle = tokio::spawn(async move { - while let Ok(event) = events_rx.recv().await { - let event_bytes = match event.to_bytes() { - Ok(bytes) => bytes, - Err(err) => { - debug!( - "Error {err:?} while converting NodeEvent to bytes, ignoring the error" - ); - continue; - } - }; - - let event = NodeEvent { event: event_bytes }; - - if let Err(err) = client_tx.send(Ok(event)).await { - debug!( - "Dropping stream sender to RPC client due to failure in \ - last attempt to notify an event: {err}" - ); - break; - } - } - }); - - Ok(Response::new(ReceiverStream::new(client_rx))) - } - - async fn record_addresses( - &self, - request: Request, - ) -> Result, Status> { - debug!( - "RPC request received at {}: {:?}", - self.addr, - request.get_ref() - ); - - let addresses = self - .running_node - .get_all_record_addresses() - .await - .expect("failed to get record addresses") - .into_iter() - .map(|addr| addr.as_bytes()) - .collect(); - - Ok(Response::new(RecordAddressesResponse { addresses })) - } - - async fn k_buckets( - &self, - request: Request, - ) -> Result, Status> { - debug!( - "RPC request received at {}: {:?}", - self.addr, - request.get_ref() - ); - - let kbuckets: HashMap = self - .running_node - .get_kbuckets() - .await - .expect("failed to get k-buckets") - .into_iter() - .map(|(ilog2_distance, peers)| { - let peers = peers.into_iter().map(|peer| peer.to_bytes()).collect(); - let peers = k_buckets_response::Peers { peers }; - (ilog2_distance, peers) - }) - .collect(); - - Ok(Response::new(KBucketsResponse { kbuckets })) - } - - async fn stop(&self, request: Request) -> Result, Status> { - debug!( - "RPC request received at {}: {:?}", - self.addr, - request.get_ref() - ); - - let cause = if let Some(addr) = request.remote_addr() { - ErrReport::msg(format!( - "Node has been stopped by an RPC request from {addr}." - )) - } else { - ErrReport::msg("Node has been stopped by an RPC request from an unknown address.") - }; - - let delay = Duration::from_millis(request.get_ref().delay_millis); - match self - .ctrl_tx - .send(NodeCtrl::Stop { - delay, - result: StopResult::Success(cause.to_string()), - }) - .await - { - Ok(()) => Ok(Response::new(StopResponse {})), - Err(err) => Err(Status::new( - Code::Internal, - format!("Failed to stop the node: {err}"), - )), - } - } - - async fn restart( - &self, - request: Request, - ) -> Result, Status> { - debug!( - "RPC request received at {}: {:?}", - self.addr, - request.get_ref() - ); - - let delay = Duration::from_millis(request.get_ref().delay_millis); - match self - .ctrl_tx - .send(NodeCtrl::Restart { - delay, - retain_peer_id: request.get_ref().retain_peer_id, - }) - .await - { - Ok(()) => Ok(Response::new(RestartResponse {})), - Err(err) => Err(Status::new( - Code::Internal, - format!("Failed to restart the node: {err}"), - )), - } - } - - async fn update( - &self, - request: Request, - ) -> Result, Status> { - debug!( - "RPC request received at {}: {:?}", - self.addr, - request.get_ref() - ); - - let delay = Duration::from_millis(request.get_ref().delay_millis); - match self.ctrl_tx.send(NodeCtrl::Update(delay)).await { - Ok(()) => Ok(Response::new(UpdateResponse {})), - Err(err) => Err(Status::new( - Code::Internal, - format!("Failed to update the node: {err}"), - )), - } - } - - async fn update_log_level( - &self, - request: Request, - ) -> Result, Status> { - debug!( - "RPC request received at {}: {:?}", - self.addr, - request.get_ref() - ); - - match self - .log_reload_handle - .modify_log_level(&request.get_ref().log_level) - { - Ok(()) => Ok(Response::new(UpdateLogLevelResponse {})), - Err(err) => Err(Status::new( - Code::Internal, - format!("Failed to update node's log level: {err:?}"), - )), - } - } -} - -pub(crate) fn start_rpc_service( - addr: SocketAddr, - log_dir_path: &str, - running_node: RunningNode, - ctrl_tx: Sender, - started_instant: Instant, - log_reload_handle: ReloadHandle, -) { - // creating a service - let service = SafeNodeRpcService { - addr, - log_dir: log_dir_path.to_string(), - running_node, - ctrl_tx, - started_instant, - log_reload_handle, - }; - info!("RPC Server listening on {addr}"); - println!("RPC Server listening on {addr}"); - - let _handle = tokio::spawn(async move { - // adding our service to our server. - if let Err(e) = Server::builder() - .add_service(SafeNodeServer::new(service)) - .serve(addr) - .await - { - error!("RPC Server failed to start: {e:?}"); - } - }); -}