diff --git a/Cargo.toml b/Cargo.toml index eed6798..05bea6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ async-stream = "0.3.5" bincode = "1.3" cairo-proof-parser = { git = "https://github.com/Okm165/cairo-proof-parser", rev = "97a04bbee07330311b38d6f4cecfed3acb237626" } cairo-vm = { git = "https://github.com/lambdaclass/cairo-vm.git" } +crypto-bigint = { version = "0.5.3", features = ["serde", "alloc"] } futures = "0.3.30" futures-core = "0.3.30" futures-util = "0.3.30" diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 2b4426e..e5560f9 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -9,6 +9,7 @@ license-file.workspace = true [dependencies] cairo-vm.workspace = true +crypto-bigint.workspace = true futures.workspace = true hex.workspace = true libp2p.workspace = true @@ -22,4 +23,4 @@ starknet.workspace = true strum.workspace = true tempfile.workspace = true thiserror.workspace = true -tokio.workspace = true \ No newline at end of file +tokio.workspace = true diff --git a/crates/common/src/job.rs b/crates/common/src/job.rs index 16755e1..8087532 100644 --- a/crates/common/src/job.rs +++ b/crates/common/src/job.rs @@ -1,5 +1,6 @@ use crate::hash; use cairo_vm::vm::runners::cairo_pie::CairoPie; +use crypto_bigint::U256; use serde::{Deserialize, Serialize}; use starknet::signers::{SigningKey, VerifyingKey}; use starknet_crypto::{poseidon_hash_many, FieldElement, Signature}; @@ -40,11 +41,15 @@ impl Job { .verify(&message_hash, &Signature { r: self.signature_r, s: self.signature_s }) .unwrap() } + + pub fn reward(&self) -> U256 { + self.job_data.reward + } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct JobData { - pub reward: u64, + pub reward: U256, pub num_of_steps: u64, #[serde(with = "chunk_felt_array")] pub cairo_pie_compressed: Vec, @@ -52,14 +57,12 @@ pub struct JobData { } impl JobData { - pub fn new(reward: u64, cairo_pie_compressed: Vec, registry_address: FieldElement) -> Self { + pub fn new(tip: u64, cairo_pie_compressed: Vec, registry_address: FieldElement) -> Self { let pie = Self::decompress_cairo_pie(&cairo_pie_compressed); - Self { - reward, - num_of_steps: pie.execution_resources.n_steps as u64, - cairo_pie_compressed, - registry_address, - } + let num_of_steps = pie.execution_resources.n_steps as u64; + // TODO - calculate reward based on the number of steps and the tip + let reward = U256::from(num_of_steps / 10000 + tip); + Self { reward, num_of_steps, cairo_pie_compressed, registry_address } } fn decompress_cairo_pie(cairo_pie_compressed: &[u8]) -> CairoPie { diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 55948a6..0fba13d 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -6,7 +6,6 @@ pub mod job_witness; pub mod layout; pub mod macros; pub mod network; -pub mod node_account; pub mod process; pub mod topic; diff --git a/crates/common/src/node_account.rs b/crates/common/src/node_account.rs deleted file mode 100644 index 2fe873f..0000000 --- a/crates/common/src/node_account.rs +++ /dev/null @@ -1,65 +0,0 @@ -use starknet::{ - accounts::{ConnectedAccount, ExecutionEncoding, SingleOwnerAccount}, - core::types::FieldElement, - providers::Provider, - signers::{LocalWallet, SigningKey, VerifyingKey}, -}; - -use crate::network::Network; - -pub struct NodeAccount

-where - P: Provider + Sync + Send + 'static, -{ - /// Key pair for the p2p network. - /// This represents the identity of the node in the network. - p2p_keypair: libp2p::identity::Keypair, - /// The account for the StarkNet network. - /// This account is used to interact with the Registry contract. - account: SingleOwnerAccount, - /// - /// - signing_key: SigningKey, -} - -impl

NodeAccount

-where - P: Provider + Sync + Send + 'static, -{ - pub fn new(private_key: Vec, address: Vec, network: Network, provider: P) -> Self { - let secret_key = libp2p::identity::ecdsa::SecretKey::try_from_bytes(private_key.as_slice()) - .expect("Failed to create secret key from private key."); - let p2p_keypair = - libp2p::identity::Keypair::from(libp2p::identity::ecdsa::Keypair::from(secret_key)); - let signing_key = SigningKey::from_secret_scalar( - FieldElement::from_byte_slice_be(private_key.as_slice()).unwrap(), - ); - let signer = LocalWallet::from(signing_key.clone()); - let address = FieldElement::from_byte_slice_be(address.as_slice()).unwrap(); - let network = network.to_field_element(); - let account = - SingleOwnerAccount::new(provider, signer, address, network, ExecutionEncoding::New); - - Self { p2p_keypair, account, signing_key } - } - - pub fn get_keypair(&self) -> &libp2p::identity::Keypair { - &self.p2p_keypair - } - - pub fn get_account(&self) -> &SingleOwnerAccount { - &self.account - } - - pub fn get_provider(&self) -> &P { - self.account.provider() - } - - pub fn get_signing_key(&self) -> &SigningKey { - &self.signing_key - } - - pub fn get_verifying_key(&self) -> VerifyingKey { - self.signing_key.verifying_key() - } -} diff --git a/crates/compiler/src/cairo_compiler/mod.rs b/crates/compiler/src/cairo_compiler/mod.rs index db67146..c01de69 100644 --- a/crates/compiler/src/cairo_compiler/mod.rs +++ b/crates/compiler/src/cairo_compiler/mod.rs @@ -3,11 +3,8 @@ use async_process::Stdio; use futures::Future; use rand::{thread_rng, Rng}; use serde_json::json; -use sharp_p2p_common::job::JobData; use sharp_p2p_common::layout::Layout; -use sharp_p2p_common::{job::Job, process::Process}; -use starknet::signers::SigningKey; -use starknet_crypto::FieldElement; +use sharp_p2p_common::process::Process; use std::io::Write; use std::path::PathBuf; use std::{io::Read, pin::Pin}; @@ -17,25 +14,33 @@ use tracing::debug; pub mod tests; -pub struct CairoCompiler<'identity> { - signing_key: &'identity SigningKey, - registry_contract: FieldElement, +/* + Cairo Compiler + This object is responsible for compiling the cairo program and generating the PIE. + It compiles the cairo program and generates the PIE. +*/ +pub struct CairoCompiler {} + +impl CairoCompiler { + pub fn new() -> Self { + Self {} + } } -impl<'identity> CairoCompiler<'identity> { - pub fn new(signing_key: &'identity SigningKey, registry_contract: FieldElement) -> Self { - Self { signing_key, registry_contract } +impl Default for CairoCompiler { + fn default() -> Self { + Self::new() } } -impl<'identity> CompilerController for CairoCompiler<'identity> { +impl CompilerController for CairoCompiler { fn run( &self, program_path: PathBuf, _program_input_path: PathBuf, - ) -> Result>, CompilerControllerError> { + ) -> Result, CompilerControllerError>>, CompilerControllerError> { let (terminate_tx, mut terminate_rx) = mpsc::channel::<()>(10); - let future: Pin> + '_>> = + let future: Pin, CompilerControllerError>> + '_>> = Box::pin(async move { let layout: &str = Layout::RecursiveWithPoseidon.into(); @@ -118,10 +123,7 @@ impl<'identity> CompilerController for CairoCompiler<'identity> { let mut cairo_pie_compressed = Vec::new(); cairo_pie.read_to_end(&mut cairo_pie_compressed)?; - Ok(Job::try_from_job_data( - JobData::new(0, cairo_pie_compressed, self.registry_contract), - self.signing_key, - )) + Ok(cairo_pie_compressed) }); Ok(Process::new(future, terminate_tx)) diff --git a/crates/compiler/src/cairo_compiler/tests/single_job.rs b/crates/compiler/src/cairo_compiler/tests/single_job.rs index 7fb8f73..730d0b6 100644 --- a/crates/compiler/src/cairo_compiler/tests/single_job.rs +++ b/crates/compiler/src/cairo_compiler/tests/single_job.rs @@ -3,21 +3,20 @@ use crate::{ traits::CompilerController, }; use starknet::signers::SigningKey; -use starknet_crypto::FieldElement; #[tokio::test] async fn run_single_job() { let fixture = fixture(); - let identity = SigningKey::from_random(); - let compiler = CairoCompiler::new(&identity, FieldElement::ZERO); + let _identity = SigningKey::from_random(); + let compiler = CairoCompiler::new(); compiler.run(fixture.program_path, fixture.program_input_path).unwrap().await.unwrap(); } #[tokio::test] async fn abort_single_jobs() { let fixture = fixture(); - let identity = SigningKey::from_random(); - let compiler = CairoCompiler::new(&identity, FieldElement::ZERO); + let _identity = SigningKey::from_random(); + let compiler = CairoCompiler::new(); let job = compiler.run(fixture.program_path, fixture.program_input_path).unwrap(); job.abort().await.unwrap(); job.await.unwrap_err(); diff --git a/crates/compiler/src/traits.rs b/crates/compiler/src/traits.rs index 30aeb81..c6a20e1 100644 --- a/crates/compiler/src/traits.rs +++ b/crates/compiler/src/traits.rs @@ -1,13 +1,12 @@ use crate::errors::CompilerControllerError; -use sharp_p2p_common::{job::Job, process::Process}; +use sharp_p2p_common::process::Process; use std::path::PathBuf; /* The `CompilerController` trait is responsible for taking a user's program and preparing a `Job` object. This process involves compiling the user's code and creating a Cairo PIE (Proof-of-Inclusion-Execution) object from it. - The resulting `Job` object encapsulates the necessary information for later execution by a `RunnerController`. - The `run` method accepts the paths to the program and its input, returning a `Result` containing a `Process` object. - Upon successful completion, it yields a `Job` object, ready to be utilized by a `RunnerController` to execute the program. + The resulting `Vec` object that represents the Cairo PIE is then compressed and stored in the `JobData` object. + Later, the `Job` object is then signed by the delegator's private key and sent to the network for execution. */ pub trait CompilerController { @@ -15,5 +14,5 @@ pub trait CompilerController { &self, program_path: PathBuf, program_input_path: PathBuf, - ) -> Result>, CompilerControllerError>; + ) -> Result, CompilerControllerError>>, CompilerControllerError>; } diff --git a/crates/delegator/src/main.rs b/crates/delegator/src/main.rs index d450828..6aa91b1 100644 --- a/crates/delegator/src/main.rs +++ b/crates/delegator/src/main.rs @@ -4,9 +4,8 @@ use futures::{stream::FuturesUnordered, StreamExt}; use libp2p::gossipsub::Event; use sharp_p2p_common::{ hash, - job::Job, + job::{Job, JobData}, network::Network, - node_account::NodeAccount, process::Process, topic::{gossipsub_ident_topic, Topic}, }; @@ -15,14 +14,14 @@ use sharp_p2p_compiler::{ errors::CompilerControllerError, traits::CompilerController, }; -use sharp_p2p_peer::{registry::RegistryHandler, swarm::SwarmRunner}; +use sharp_p2p_peer::{node_account::NodeAccount, registry::RegistryHandler, swarm::SwarmRunner}; use starknet::providers::{jsonrpc::HttpTransport, JsonRpcClient, Url}; use std::hash::{DefaultHasher, Hash, Hasher}; use tokio::{ io::{stdin, AsyncBufReadExt, BufReader}, sync::mpsc, }; -use tracing::{debug, info}; +use tracing::{debug, error, info}; use tracing_subscriber::EnvFilter; #[tokio::main] @@ -58,15 +57,18 @@ async fn main() -> Result<(), Box> { let (send_topic_tx, send_topic_rx) = mpsc::channel::>(1000); let mut message_stream = swarm_runner.run(new_job_topic, send_topic_rx); + // TODO: Subscribe to `WitnessMetadata` event let mut event_stream = registry_handler.subscribe_events(vec!["0x0".to_string()]); - let compiler = CairoCompiler::new(node_account.get_signing_key(), registry_address); + let compiler = CairoCompiler::new(); let mut compiler_scheduler = - FuturesUnordered::>>::new(); + FuturesUnordered::, CompilerControllerError>>>::new(); // Read cairo program path from stdin let mut stdin = BufReader::new(stdin()).lines(); + // TODO: Accept dynamic tip + let tip = 0; loop { tokio::select! { @@ -104,7 +106,16 @@ async fn main() -> Result<(), Box> { Some(Ok(event_vec)) = event_stream.next() => { debug!("{:?}", event_vec); }, - Some(Ok(job)) = compiler_scheduler.next() => { + Some(Ok(cairo_pie_compressed)) = compiler_scheduler.next() => { + let job_data = JobData::new(tip, cairo_pie_compressed,registry_address); + let expected_reward = job_data.reward; + let deposit_amount = node_account.balance(registry_address).await?; + if deposit_amount < expected_reward{ + error!("Staked amount is less than expected reward"); + return Ok(()); + } + let job = Job::try_from_job_data(job_data, node_account.get_signing_key()); + let serialized_job = serde_json::to_string(&job).unwrap(); send_topic_tx.send(serialized_job.into()).await?; info!("Sent a new job: {}", hash!(&job)); diff --git a/crates/executor/src/main.rs b/crates/executor/src/main.rs index df5dea0..cb66272 100644 --- a/crates/executor/src/main.rs +++ b/crates/executor/src/main.rs @@ -9,11 +9,10 @@ use sharp_p2p_common::{ job_trace::JobTrace, job_witness::JobWitness, network::Network, - node_account::NodeAccount, process::Process, topic::{gossipsub_ident_topic, Topic}, }; -use sharp_p2p_peer::{registry::RegistryHandler, swarm::SwarmRunner}; +use sharp_p2p_peer::{node_account::NodeAccount, registry::RegistryHandler, swarm::SwarmRunner}; use sharp_p2p_prover::{ errors::ProverControllerError, stone_prover::StoneProver, traits::ProverController, }; @@ -51,6 +50,7 @@ async fn main() -> Result<(), Box> { let mut registry_handler = RegistryHandler::new(JsonRpcClient::new(HttpTransport::new(Url::parse(url)?))); + let registry_address = registry_handler.get_registry_address(); let node_account = NodeAccount::new( private_key, account_address, @@ -69,6 +69,7 @@ async fn main() -> Result<(), Box> { let (send_topic_tx, send_topic_rx) = mpsc::channel::>(1000); let mut message_stream = swarm_runner.run(picked_job_topic, send_topic_rx); + // TODO: Subscribe to `WitnessMetadata` event let mut event_stream = registry_handler.subscribe_events(vec!["0x0".to_string()]); let verifying_key = node_account.get_verifying_key(); @@ -124,6 +125,7 @@ async fn main() -> Result<(), Box> { }, Some(Ok(job_witness)) = prover_scheduler.next() => { info!("Calculated job_witness: {}", hash!(&job_witness)); + node_account.verify_job_witness(registry_address, job_witness).await?; }, else => break }; diff --git a/crates/peer/Cargo.toml b/crates/peer/Cargo.toml index 19f53d6..84fed67 100644 --- a/crates/peer/Cargo.toml +++ b/crates/peer/Cargo.toml @@ -9,6 +9,7 @@ license-file.workspace = true [dependencies] async-stream.workspace = true +crypto-bigint.workspace = true futures.workspace = true libp2p.workspace = true starknet.workspace = true diff --git a/crates/peer/src/lib.rs b/crates/peer/src/lib.rs index eb9839a..1bfd2e3 100644 --- a/crates/peer/src/lib.rs +++ b/crates/peer/src/lib.rs @@ -1,2 +1,3 @@ +pub mod node_account; pub mod registry; pub mod swarm; diff --git a/crates/peer/src/node_account.rs b/crates/peer/src/node_account.rs new file mode 100644 index 0000000..67f740a --- /dev/null +++ b/crates/peer/src/node_account.rs @@ -0,0 +1,157 @@ +use std::error::Error; + +use crypto_bigint::U256; +use sharp_p2p_common::{job_witness::JobWitness, network::Network}; +use starknet::{ + accounts::{Account, Call, ConnectedAccount, ExecutionEncoding, SingleOwnerAccount}, + core::types::{BlockId, BlockTag, FieldElement, FunctionCall}, + macros::selector, + providers::Provider, + signers::{LocalWallet, SigningKey, VerifyingKey}, +}; +use tracing::{info, trace}; + +/* + Node Account + This object represents the account of a node in the network. + It holds the key pair for the p2p network, the account for the StarkNet network, and the signing key. + The account is used to interact with the Registry contract. +*/ +pub struct NodeAccount

+where + P: Provider + Sync + Send + 'static, +{ + /// Key pair for the p2p network. + /// This represents the identity of the node in the network. + p2p_keypair: libp2p::identity::Keypair, + /// The account for the StarkNet network. + /// This account is used to interact with the Registry contract. + account: SingleOwnerAccount, + /// The signing key for the account. + signing_key: SigningKey, +} + +impl

NodeAccount

+where + P: Provider + Sync + Send + 'static, +{ + pub fn new(private_key: Vec, address: Vec, network: Network, provider: P) -> Self { + let secret_key = libp2p::identity::ecdsa::SecretKey::try_from_bytes(private_key.as_slice()) + .expect("Failed to create secret key from private key."); + let p2p_keypair = + libp2p::identity::Keypair::from(libp2p::identity::ecdsa::Keypair::from(secret_key)); + let signing_key = SigningKey::from_secret_scalar( + FieldElement::from_byte_slice_be(private_key.as_slice()).unwrap(), + ); + let signer = LocalWallet::from(signing_key.clone()); + let address = FieldElement::from_byte_slice_be(address.as_slice()).unwrap(); + let network = network.to_field_element(); + let account = + SingleOwnerAccount::new(provider, signer, address, network, ExecutionEncoding::New); + + Self { p2p_keypair, account, signing_key } + } + + pub fn get_keypair(&self) -> &libp2p::identity::Keypair { + &self.p2p_keypair + } + + pub fn get_account(&self) -> &SingleOwnerAccount { + &self.account + } + + pub fn get_provider(&self) -> &P { + self.account.provider() + } + + pub fn get_signing_key(&self) -> &SigningKey { + &self.signing_key + } + + pub fn get_verifying_key(&self) -> VerifyingKey { + self.signing_key.verifying_key() + } + + pub async fn deposit( + &self, + amount: FieldElement, + registry_address: FieldElement, + ) -> Result<(), Box> { + let result = self + .account + .execute(vec![Call { + to: registry_address, + selector: selector!("deposit"), + calldata: vec![amount], + }]) + .send() + .await + .unwrap(); + + trace!("Deposit result: {:?}", result); + Ok(()) + } + + pub async fn balance(&self, registry_address: FieldElement) -> Result> { + let account_address = self.account.address(); + let call_result = self + .get_provider() + .call( + FunctionCall { + contract_address: registry_address, + entry_point_selector: selector!("balance"), + calldata: vec![account_address], + }, + BlockId::Tag(BlockTag::Latest), + ) + .await + .expect("failed to call contract"); + + let low: u128 = call_result[0].try_into().unwrap(); + let high: u128 = call_result[1].try_into().unwrap(); + let call_result = U256::from(high << 64 | low); + info!("Balance result: {:?}", call_result); + + Ok(call_result) + } + + pub async fn withdraw( + &self, + amount: FieldElement, + registry_address: FieldElement, + ) -> Result<(), Box> { + let result = self + .account + .execute(vec![Call { + to: registry_address, + selector: selector!("withdraw"), + calldata: vec![amount], + }]) + .send() + .await + .unwrap(); + + trace!("Withdraw result: {:?}", result); + Ok(()) + } + + pub async fn verify_job_witness( + &self, + registry_address: FieldElement, + job_withness: JobWitness, + ) -> Result<(), Box> { + let result = self + .account + .execute(vec![Call { + to: registry_address, + selector: selector!("verify_job_witness"), + calldata: job_withness.proof, + }]) + .send() + .await + .unwrap(); + + trace!("Verify job witness result: {:?}", result); + Ok(()) + } +} diff --git a/crates/peer/src/registry.rs b/crates/peer/src/registry.rs index de07a93..4100db1 100644 --- a/crates/peer/src/registry.rs +++ b/crates/peer/src/registry.rs @@ -1,19 +1,21 @@ use async_stream::try_stream; use futures::stream::Stream; use starknet::{ - accounts::{Account, Call, SingleOwnerAccount}, - core::{ - types::{BlockId, EmittedEvent, EventFilter, FieldElement}, - utils::get_selector_from_name, - }, + core::types::{BlockId, EmittedEvent, EventFilter, FieldElement}, providers::Provider, - signers::Signer, }; use std::{error::Error, pin::Pin}; use tracing::trace; const EVENT_SCRAPE_INTERVAL: u64 = 2; -const REGISTRY_CONTRACT: &str = "0xcdd51fbc4e008f4ef807eaf26f5043521ef5931bbb1e04032a25bd845d286b"; +const REGISTRY_CONTRACT: &str = + "0x030938966f24f5084d9570ac52aeff76fe30559f4f3fe086a2b0cb4017ce4384"; + +/* + Registry Handler + This object is responsible for handle continuous scraping of events from the Registry contract. + It scrapes the events from the Registry contract and provides a stream of events. +*/ pub struct RegistryHandler

{ provider: P, @@ -78,48 +80,4 @@ where }; Box::pin(stream) } - - pub async fn deposit( - &self, - amount: FieldElement, - account: SingleOwnerAccount, - ) -> Result<(), Box> - where - S: Signer + Sync + Send + 'static, - { - let result = account - .execute(vec![Call { - to: self.registry_address, - selector: get_selector_from_name("deposit").unwrap(), - calldata: vec![amount], - }]) - .send() - .await - .unwrap(); - - trace!("Deposit result: {:?}", result); - Ok(()) - } - - pub async fn balance( - &self, - target: FieldElement, - account: SingleOwnerAccount, - ) -> Result<(), Box> - where - S: Signer + Sync + Send + 'static, - { - let result = account - .execute(vec![Call { - to: self.registry_address, - selector: get_selector_from_name("balance").unwrap(), - calldata: vec![target], - }]) - .send() - .await - .unwrap(); - - trace!("Balance result: {:?}", result); - Ok(()) - } }