11use crate :: p2p_handler:: auth:: AuthenticationManager ;
22use crate :: p2p_handler:: message_processor:: { MessageProcessor , MessageProcessorConfig } ;
33use crate :: p2p_handler:: { Message , MessageType , Service as P2PService } ;
4+ use alloy:: primitives:: Address ;
45use p2p:: { Keypair , PeerId } ;
56use pyo3:: prelude:: * ;
67use pythonize:: pythonize;
78use shared:: models:: node:: DiscoveryNode ;
89use 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 } ;
1012use std:: sync:: Arc ;
1113use std:: time:: Duration ;
1214use 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