Skip to content

Commit 87edb2d

Browse files
authored
refactor(rpc): Cartridge Controller deployment layer (#431)
1 parent e329a21 commit 87edb2d

File tree

52 files changed

+2405
-749
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2405
-749
lines changed

Cargo.lock

Lines changed: 268 additions & 124 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/katana/src/cli/rpc/starknet.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use clap::{Args, Subcommand};
55
use katana_primitives::block::{BlockHash, BlockIdOrTag, BlockNumber, ConfirmedBlockIdOrTag};
66
use katana_primitives::class::ClassHash;
77
use katana_primitives::contract::StorageKey;
8-
use katana_primitives::execution::{EntryPointSelector, FunctionCall};
8+
use katana_primitives::execution::{Call, EntryPointSelector};
99
use katana_primitives::transaction::TxHash;
1010
use katana_primitives::{ContractAddress, Felt};
1111
use katana_rpc_types::event::{EventFilter, EventFilterWithPage, ResultPageRequest};
@@ -398,8 +398,7 @@ impl StarknetCommands {
398398
let entry_point_selector = args.selector;
399399
let calldata = args.calldata;
400400

401-
let function_call =
402-
FunctionCall { contract_address, entry_point_selector, calldata };
401+
let function_call = Call { contract_address, entry_point_selector, calldata };
403402

404403
let block_id = args.block.0;
405404
let result = client.call(function_call, block_id).await?;

crates/cartridge/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ build = "build.rs"
1010
katana-contracts.workspace = true
1111
katana-genesis.workspace = true
1212
katana-primitives.workspace = true
13-
katana-rpc-types = { path = "../rpc/rpc-types" }
13+
katana-rpc-types = { workspace = true }
1414

1515
anyhow.workspace = true
1616
ark-ff = "0.4.2"
17+
cainome-cairo-serde.workspace = true
1718
lazy_static.workspace = true
1819
reqwest.workspace = true
1920
serde.workspace = true
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ pub enum Error {
1717

1818
/// Client for interacting with the Cartridge service.
1919
#[derive(Debug, Clone)]
20-
pub struct Client {
20+
pub struct CartridgeApiClient {
2121
url: Url,
2222
client: reqwest::Client,
2323
}
2424

25-
impl Client {
25+
impl CartridgeApiClient {
2626
/// Creates a new [`CartridgeApiClient`] with the given URL.
2727
pub fn new(url: Url) -> Self {
2828
Self { url, client: reqwest::Client::new() }

crates/cartridge/src/lib.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
22

3-
pub mod client;
3+
pub mod api;
44
pub mod vrf;
55

6-
pub use client::Client;
7-
pub use vrf::{
8-
bootstrap_vrf, get_vrf_account, resolve_executable, wait_for_http_ok, InfoResponse,
9-
RequestContext, SignedOutsideExecution, VrfAccountCredentials, VrfBootstrap,
10-
VrfBootstrapConfig, VrfBootstrapResult, VrfClient, VrfClientError, VrfOutsideExecution,
11-
VrfService, VrfServiceConfig, VrfServiceProcess, VRF_ACCOUNT_SALT, VRF_CONSUMER_SALT,
12-
VRF_HARDCODED_SECRET_KEY, VRF_SERVER_PORT,
6+
pub use api::CartridgeApiClient;
7+
pub use vrf::server::{
8+
bootstrap_vrf, get_vrf_account, resolve_executable, wait_for_http_ok, VrfAccountCredentials,
9+
VrfBootstrap, VrfBootstrapConfig, VrfBootstrapResult, VrfServer, VrfServerConfig,
10+
VrfServiceProcess, VRF_ACCOUNT_SALT, VRF_CONSUMER_SALT, VRF_HARDCODED_SECRET_KEY,
11+
VRF_SERVER_PORT,
1312
};
1413

1514
#[rustfmt::skip]

crates/cartridge/src/vrf/client.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
use katana_primitives::Felt;
1+
use cainome_cairo_serde::CairoSerde;
2+
use katana_primitives::execution::Call;
3+
use katana_primitives::{ContractAddress, Felt};
24
use katana_rpc_types::outside_execution::{OutsideExecutionV2, OutsideExecutionV3};
35
use serde::{Deserialize, Serialize};
6+
use starknet::macros::selector;
47
use url::Url;
58

69
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
@@ -25,11 +28,30 @@ pub enum VrfOutsideExecution {
2528
/// A signed outside execution request.
2629
#[derive(Debug, Clone, Serialize, Deserialize)]
2730
pub struct SignedOutsideExecution {
28-
pub address: Felt,
31+
pub address: ContractAddress,
2932
pub outside_execution: VrfOutsideExecution,
3033
pub signature: Vec<Felt>,
3134
}
3235

36+
impl From<SignedOutsideExecution> for Call {
37+
fn from(value: SignedOutsideExecution) -> Self {
38+
let (entry_point_selector, mut calldata) = match &value.outside_execution {
39+
VrfOutsideExecution::V2(v) => {
40+
let calldata = OutsideExecutionV2::cairo_serialize(v);
41+
(selector!("execute_from_outside_v2"), calldata)
42+
}
43+
VrfOutsideExecution::V3(v) => {
44+
let calldata = OutsideExecutionV3::cairo_serialize(v);
45+
(selector!("execute_from_outside_v3"), calldata)
46+
}
47+
};
48+
49+
calldata.extend(value.signature);
50+
51+
Call { contract_address: value.address, entry_point_selector, calldata }
52+
}
53+
}
54+
3355
/// Response from GET /info endpoint.
3456
#[derive(Debug, Clone, Serialize, Deserialize)]
3557
pub struct InfoResponse {

crates/cartridge/src/vrf/mod.rs

Lines changed: 2 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -1,181 +1,6 @@
1-
//! VRF (Verifiable Random Function) support for Cartridge.
2-
//!
3-
//! This module provides:
4-
//! - VRF client for communicating with the VRF server
5-
//! - Bootstrap logic for deploying VRF contracts
6-
//! - Sidecar process management
1+
//! Cartridge VRF (Verifiable Random Function) service.
72
8-
mod bootstrap;
93
mod client;
4+
pub mod server;
105

11-
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
12-
use std::path::{Path, PathBuf};
13-
use std::process::Stdio;
14-
use std::time::{Duration, Instant};
15-
use std::{env, io};
16-
17-
pub use bootstrap::{
18-
bootstrap_vrf, get_vrf_account, VrfAccountCredentials, VrfBootstrap, VrfBootstrapConfig,
19-
VrfBootstrapResult, BOOTSTRAP_TIMEOUT, VRF_ACCOUNT_SALT, VRF_CONSUMER_SALT,
20-
VRF_HARDCODED_SECRET_KEY,
21-
};
226
pub use client::*;
23-
use katana_primitives::{ContractAddress, Felt};
24-
use tokio::process::{Child, Command};
25-
use tokio::time::sleep;
26-
use tracing::{debug, info, warn};
27-
use url::Url;
28-
29-
const LOG_TARGET: &str = "katana::cartridge::vrf::sidecar";
30-
31-
pub const VRF_SERVER_PORT: u16 = 3000;
32-
const DEFAULT_VRF_SERVICE_PATH: &str = "vrf-server";
33-
pub const SIDECAR_TIMEOUT: Duration = Duration::from_secs(10);
34-
35-
#[derive(Debug, thiserror::Error)]
36-
pub enum Error {
37-
#[error("bootstrap_result not set - call bootstrap() or bootstrap_result()")]
38-
BootstrapResultNotSet,
39-
#[error("sidecar binary not found at {0}")]
40-
BinaryNotFound(PathBuf),
41-
#[error("sidecar binary '{0}' not found in PATH")]
42-
BinaryNotInPath(PathBuf),
43-
#[error("PATH environment variable is not set")]
44-
PathNotSet,
45-
#[error("failed to spawn VRF sidecar")]
46-
Spawn(#[source] io::Error),
47-
#[error("VRF sidecar did not become ready before timeout")]
48-
SidecarTimeout,
49-
#[error("bootstrap failed")]
50-
Bootstrap(#[source] anyhow::Error),
51-
}
52-
53-
pub type Result<T, E = Error> = std::result::Result<T, E>;
54-
55-
#[derive(Debug, Clone)]
56-
pub struct VrfServiceConfig {
57-
pub vrf_account_address: ContractAddress,
58-
pub vrf_private_key: Felt,
59-
pub secret_key: u64,
60-
}
61-
62-
#[derive(Debug, Clone)]
63-
pub struct VrfService {
64-
config: VrfServiceConfig,
65-
path: PathBuf,
66-
}
67-
68-
impl VrfService {
69-
pub fn new(config: VrfServiceConfig) -> Self {
70-
Self { config, path: PathBuf::from(DEFAULT_VRF_SERVICE_PATH) }
71-
}
72-
73-
/// Sets the path to the vrf service program.
74-
///
75-
/// If no path is set, the default executable name [`DEFAULT_VRF_SERVICE_PATH`] will be used.
76-
pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
77-
self.path = path.into();
78-
self
79-
}
80-
81-
pub async fn start(self) -> Result<VrfServiceProcess> {
82-
let bin = resolve_executable(&self.path)?;
83-
84-
let mut command = Command::new(bin);
85-
command
86-
.arg("--port")
87-
.arg(VRF_SERVER_PORT.to_string())
88-
.arg("--account-address")
89-
.arg(self.config.vrf_account_address.to_hex_string())
90-
.arg("--account-private-key")
91-
.arg(self.config.vrf_private_key.to_hex_string())
92-
.arg("--secret-key")
93-
.arg(self.config.secret_key.to_string())
94-
.stdout(Stdio::inherit())
95-
.stderr(Stdio::inherit())
96-
.kill_on_drop(true);
97-
98-
let process = command.spawn().map_err(Error::Spawn)?;
99-
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), VRF_SERVER_PORT);
100-
101-
let url = Url::parse(&format!("http://{addr}")).expect("valid url");
102-
let client = VrfClient::new(url);
103-
wait_for_http_ok(&client, "vrf info", SIDECAR_TIMEOUT).await?;
104-
105-
info!(%addr, "VRF service started.");
106-
107-
Ok(VrfServiceProcess { process, addr, inner: self })
108-
}
109-
}
110-
111-
/// A running VRF sidecar process.
112-
#[derive(Debug)]
113-
pub struct VrfServiceProcess {
114-
process: Child,
115-
inner: VrfService,
116-
addr: SocketAddr,
117-
}
118-
119-
impl VrfServiceProcess {
120-
/// Get the address of the VRF service.
121-
pub fn addr(&self) -> &SocketAddr {
122-
&self.addr
123-
}
124-
125-
pub fn process(&mut self) -> &mut Child {
126-
&mut self.process
127-
}
128-
129-
pub fn config(&self) -> &VrfServiceConfig {
130-
&self.inner.config
131-
}
132-
133-
pub async fn shutdown(&mut self) -> io::Result<()> {
134-
self.process.kill().await
135-
}
136-
}
137-
138-
/// Resolve an executable path, searching in PATH if necessary.
139-
pub fn resolve_executable(path: &Path) -> Result<PathBuf> {
140-
if path.components().count() > 1 {
141-
return if path.is_file() {
142-
Ok(path.to_path_buf())
143-
} else {
144-
Err(Error::BinaryNotFound(path.to_path_buf()))
145-
};
146-
}
147-
148-
let path_var = env::var_os("PATH").ok_or(Error::PathNotSet)?;
149-
for dir in env::split_paths(&path_var) {
150-
let candidate = dir.join(path);
151-
if candidate.is_file() {
152-
return Ok(candidate);
153-
}
154-
}
155-
156-
Err(Error::BinaryNotInPath(path.to_path_buf()))
157-
}
158-
159-
/// Wait for the VRF sidecar to become ready by polling its `/info` endpoint.
160-
pub async fn wait_for_http_ok(client: &VrfClient, name: &str, timeout: Duration) -> Result<()> {
161-
let start = Instant::now();
162-
163-
loop {
164-
match client.info().await {
165-
Ok(_) => {
166-
info!(target: LOG_TARGET, %name, "sidecar ready");
167-
return Ok(());
168-
}
169-
Err(err) => {
170-
debug!(target: LOG_TARGET, %name, error = %err, "waiting for sidecar");
171-
}
172-
}
173-
174-
if start.elapsed() > timeout {
175-
warn!(target: LOG_TARGET, %name, "sidecar did not become ready in time");
176-
return Err(Error::SidecarTimeout);
177-
}
178-
179-
sleep(Duration::from_millis(200)).await;
180-
}
181-
}
File renamed without changes.

0 commit comments

Comments
 (0)