Skip to content
This repository was archived by the owner on Jan 27, 2026. It is now read-only.

Commit 31870ad

Browse files
committed
allow validator to validate nodes using sdk
1 parent 86bdde1 commit 31870ad

File tree

2 files changed

+175
-37
lines changed

2 files changed

+175
-37
lines changed

crates/prime-protocol-py/examples/validator_list_nodes.py

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,6 @@
1111
logging.basicConfig(format=FORMAT)
1212
logging.getLogger().setLevel(logging.INFO)
1313

14-
15-
def print_node_summary(nodes: List) -> None:
16-
"""Print a summary of nodes"""
17-
print(f"\nTotal nodes found: {len(nodes)}")
18-
19-
if not nodes:
20-
print("No non-validated nodes found.")
21-
return
22-
23-
print("\nNon-validated nodes:")
24-
print("-" * 80)
25-
26-
for idx, node in enumerate(nodes, 1):
27-
print(f"\n{idx}. Node ID: {node.id}")
28-
print(f" Provider Address: {node.provider_address}")
29-
print(f" IP: {node.ip_address}:{node.port}")
30-
print(f" Compute Pool ID: {node.compute_pool_id}")
31-
print(f" Active: {node.is_active}")
32-
print(f" Whitelisted: {node.is_provider_whitelisted}")
33-
print(f" Blacklisted: {node.is_blacklisted}")
34-
35-
if node.worker_p2p_id:
36-
print(f" P2P ID: {node.worker_p2p_id}")
37-
38-
if node.created_at:
39-
print(f" Created At: {node.created_at}")
40-
41-
if node.last_updated:
42-
print(f" Last Updated: {node.last_updated}")
43-
44-
4514
def main():
4615
# Get configuration from environment variables
4716
rpc_url = os.getenv("RPC_URL", "http://localhost:8545")
@@ -62,19 +31,28 @@ def main():
6231
validator = ValidatorClient(
6332
rpc_url=rpc_url,
6433
private_key=private_key,
65-
discovery_urls=discovery_urls
34+
discovery_urls=discovery_urls,
6635
)
36+
print("Starting validator client...")
37+
validator.start()
38+
print("Validator client started")
6739

6840
# List all non-validated nodes
6941
print("\nFetching non-validated nodes from discovery service...")
7042
non_validated_nodes = validator.list_non_validated_nodes()
71-
72-
# Print summary
73-
print_node_summary(non_validated_nodes)
43+
for node in non_validated_nodes:
44+
print(node.id)
45+
if node.is_validated is False:
46+
print(f"Validating node {node.id}...")
47+
validator.validate_node(node.id, node.provider_address)
48+
print(f"Node {node.id} validated")
49+
else:
50+
print(f"Node {node.id} is already validated")
7451

7552
# You can also get all nodes as dictionaries for more flexibility
7653
print("\n\nFetching all nodes as dictionaries...")
7754
all_nodes = validator.list_all_nodes_dict()
55+
print(all_nodes)
7856

7957
# Count validated vs non-validated
8058
validated_count = sum(1 for node in all_nodes if node['is_validated'])

crates/prime-protocol-py/src/validator/mod.rs

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use crate::p2p_handler::auth::AuthenticationManager;
22
use crate::p2p_handler::message_processor::{MessageProcessor, MessageProcessorConfig};
33
use crate::p2p_handler::{Message, MessageType, Service as P2PService};
4+
use alloy::primitives::Address;
45
use p2p::{Keypair, PeerId};
56
use pyo3::prelude::*;
67
use pythonize::pythonize;
78
use shared::models::node::DiscoveryNode;
89
use shared::security::request_signer::sign_request_with_nonce;
9-
use shared::web3::wallet::Wallet;
10+
use shared::web3::contracts::core::builder::{ContractBuilder, Contracts};
11+
use shared::web3::wallet::{Wallet, WalletProvider};
1012
use std::sync::Arc;
1113
use std::time::Duration;
1214
use tokio::sync::mpsc::{Receiver, Sender};
@@ -94,6 +96,7 @@ pub(crate) struct ValidatorClient {
9496
user_message_rx: Option<Arc<Mutex<Receiver<Message>>>>,
9597
message_processor_handle: Option<JoinHandle<()>>,
9698
peer_id: Option<PeerId>,
99+
contracts: Option<Arc<Contracts<WalletProvider>>>,
97100
}
98101

99102
#[pymethods]
@@ -128,6 +131,7 @@ impl ValidatorClient {
128131
user_message_rx: None,
129132
message_processor_handle: None,
130133
peer_id: None,
134+
contracts: None,
131135
})
132136
}
133137

@@ -188,7 +192,40 @@ impl ValidatorClient {
188192
/// Initialize the validator client with optional P2P support
189193
#[pyo3(signature = (p2p_port=None))]
190194
pub fn start(&mut self, py: Python, p2p_port: Option<u16>) -> PyResult<()> {
191-
let rt = self.get_or_create_runtime()?;
195+
// Initialize contracts if not already done
196+
if self.contracts.is_none() {
197+
let wallet = self.wallet.as_ref().ok_or_else(|| {
198+
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("Wallet not initialized")
199+
})?;
200+
201+
let wallet_provider = wallet.provider();
202+
203+
// Get runtime reference and use it before mutating self
204+
let rt = self.runtime.as_ref().ok_or_else(|| {
205+
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("Runtime not initialized")
206+
})?;
207+
208+
let contracts = py.allow_threads(|| {
209+
rt.block_on(async {
210+
// Build all contracts (required due to known bug)
211+
ContractBuilder::new(wallet_provider)
212+
.with_compute_pool()
213+
.with_compute_registry()
214+
.with_ai_token()
215+
.with_prime_network()
216+
.with_stake_manager()
217+
.build()
218+
.map_err(|e| {
219+
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
220+
"Failed to build contracts: {}",
221+
e
222+
))
223+
})
224+
})
225+
})?;
226+
227+
self.contracts = Some(Arc::new(contracts));
228+
}
192229

193230
if let Some(port) = p2p_port {
194231
// Initialize P2P if port is provided
@@ -202,6 +239,11 @@ impl ValidatorClient {
202239

203240
let cancellation_token = self.cancellation_token.clone();
204241

242+
// Get runtime reference for P2P initialization
243+
let rt = self.runtime.as_ref().ok_or_else(|| {
244+
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("Runtime not initialized")
245+
})?;
246+
205247
// Create the P2P components
206248
let (auth_manager, peer_id, outbound_tx, user_message_rx, message_processor_handle) =
207249
py.allow_threads(|| {
@@ -296,6 +338,124 @@ impl ValidatorClient {
296338
}
297339
}
298340

341+
/// Validate a node on the Prime Network contract
342+
///
343+
/// Args:
344+
/// node_address: The node address to validate
345+
/// provider_address: The provider's address
346+
///
347+
/// Returns:
348+
/// Transaction hash as a string if successful
349+
pub fn validate_node(
350+
&self,
351+
py: Python,
352+
node_address: String,
353+
provider_address: String,
354+
) -> PyResult<String> {
355+
let rt = self.get_or_create_runtime()?;
356+
357+
let contracts = self.contracts.as_ref().ok_or_else(|| {
358+
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
359+
"Contracts not initialized. Call start() first.",
360+
)
361+
})?;
362+
363+
let contracts_clone = contracts.clone();
364+
365+
// Release the GIL while performing async operations
366+
py.allow_threads(|| {
367+
rt.block_on(async {
368+
// Parse addresses
369+
let provider_addr =
370+
Address::parse_checksummed(&provider_address, None).map_err(|e| {
371+
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
372+
"Invalid provider address: {}",
373+
e
374+
))
375+
})?;
376+
377+
let node_addr = Address::parse_checksummed(&node_address, None).map_err(|e| {
378+
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
379+
"Invalid node address: {}",
380+
e
381+
))
382+
})?;
383+
384+
let tx_hash = contracts_clone
385+
.prime_network
386+
.validate_node(provider_addr, node_addr)
387+
.await
388+
.map_err(|e| {
389+
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
390+
"Failed to validate node: {}",
391+
e
392+
))
393+
})?;
394+
395+
Ok(format!("0x{}", hex::encode(tx_hash)))
396+
})
397+
})
398+
}
399+
400+
/// Validate a node on the Prime Network contract with explicit addresses
401+
///
402+
/// Args:
403+
/// provider_address: The provider's address
404+
/// node_address: The node's address
405+
///
406+
/// Returns:
407+
/// Transaction hash as a string if successful
408+
pub fn validate_node_with_addresses(
409+
&self,
410+
py: Python,
411+
provider_address: String,
412+
node_address: String,
413+
) -> PyResult<String> {
414+
let rt = self.get_or_create_runtime()?;
415+
416+
let contracts = self.contracts.as_ref().ok_or_else(|| {
417+
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
418+
"Contracts not initialized. Call start() first.",
419+
)
420+
})?;
421+
422+
let contracts_clone = contracts.clone();
423+
424+
// Release the GIL while performing async operations
425+
py.allow_threads(|| {
426+
rt.block_on(async {
427+
// Parse addresses
428+
let provider_addr =
429+
Address::parse_checksummed(&provider_address, None).map_err(|e| {
430+
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
431+
"Invalid provider address: {}",
432+
e
433+
))
434+
})?;
435+
436+
let node_addr = Address::parse_checksummed(&node_address, None).map_err(|e| {
437+
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
438+
"Invalid node address: {}",
439+
e
440+
))
441+
})?;
442+
443+
let tx_hash = contracts_clone
444+
.prime_network
445+
.validate_node(provider_addr, node_addr)
446+
.await
447+
.map_err(|e| {
448+
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
449+
"Failed to validate node: {}",
450+
e
451+
))
452+
})?;
453+
454+
Ok(format!("0x{}", hex::encode(tx_hash)))
455+
})
456+
})
457+
}
458+
299459
/// Get the validator's peer ID
300460
pub fn get_peer_id(&self) -> PyResult<Option<String>> {
301461
Ok(self.peer_id.map(|id| id.to_string()))

0 commit comments

Comments
 (0)