Skip to content

Commit d0e173b

Browse files
committed
starknet_transaction_prover: relay opaque additional_data on proveTransaction response (#14411)
ProveTransactionResult gains an optional additional_data field carrying the opaque object the blocking check relayed; it is passed through verbatim (omitted from the JSON response when absent) and the prover never interprets its contents. A transaction allowed via the fail-open policy carries none.
1 parent 05a0b75 commit d0e173b

2 files changed

Lines changed: 92 additions & 10 deletions

File tree

crates/starknet_transaction_prover/src/proving/blocking_check_integration_test.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@
1111
use async_trait::async_trait;
1212
use blockifier_reexecution::state_reader::rpc_objects::BlockId;
1313
use mockito::Server;
14+
use serde_json::{json, Map, Value};
1415
use starknet_api::invoke_tx_args;
1516
use starknet_api::rpc_transaction::RpcTransaction;
1617
use starknet_api::test_utils::invoke::rpc_invoke_tx;
1718
use starknet_api::transaction::fields::ValidResourceBounds;
1819
use starknet_api::transaction::InvokeTransaction;
1920
use url::Url;
2021

21-
use super::virtual_snos_prover::VirtualSnosProver;
2222
use crate::blocking_check::BlockingCheckClient;
2323
use crate::errors::{RunnerError, VirtualSnosProverError};
24+
use crate::proving::virtual_snos_prover::{ProveTransactionResult, VirtualSnosProver};
2425
use crate::running::runner::{RunnerOutput, VirtualSnosRunner};
2526
use crate::test_utils::resource_bounds_for_client_side_tx;
2627

@@ -124,6 +125,74 @@ async fn test_check_allowed_proceeds_to_proving() {
124125
assert_runner_error(&result);
125126
}
126127

128+
#[tokio::test]
129+
async fn test_check_allowed_with_additional_data_proceeds_to_proving() {
130+
let mut server = Server::new_async().await;
131+
let _mock = server
132+
.mock("POST", "/")
133+
.with_status(200)
134+
.with_body(
135+
r#"{"jsonrpc":"2.0","result":{"allowed":true,"additional_data":{"signature":{"issued_at":1716579600,"sig_r":"0x1","sig_s":"0x2"}}},"id":1}"#,
136+
)
137+
.create_async()
138+
.await;
139+
140+
let url = Url::parse(&server.url()).unwrap();
141+
let client = BlockingCheckClient::new(url, TEST_TIMEOUT_MILLIS, true);
142+
let prover = build_prover(MockRunner, Some(client));
143+
144+
// The MockRunner errors before producing a result, so the verbatim relay is
145+
// covered by the serde tests below; this confirms an allow carrying
146+
// additional_data routes to proving rather than blocking.
147+
let result = prove(&prover).await;
148+
assert_runner_error(&result);
149+
}
150+
151+
fn sample_additional_data() -> Map<String, Value> {
152+
serde_json::from_value(json!({
153+
"signature": { "issued_at": 1716579600, "sig_r": "0x6e6f63c8", "sig_s": "0x58a68a71" }
154+
}))
155+
.unwrap()
156+
}
157+
158+
#[test]
159+
fn test_additional_data_relays_object_verbatim() {
160+
// The prover does not interpret additional_data; an arbitrary object,
161+
// including keys it has never heard of, round-trips unchanged.
162+
let additional_data: Map<String, Value> =
163+
serde_json::from_value(json!({ "signature": { "sig_r": "0x1" }, "future_key": [1, 2, 3] }))
164+
.unwrap();
165+
let fixture = include_str!("../../resources/mock_proving_rpc/prove_transaction_result.json");
166+
let mut result: ProveTransactionResult = serde_json::from_str(fixture).unwrap();
167+
result.additional_data = Some(additional_data.clone());
168+
169+
let json = serde_json::to_value(&result).unwrap();
170+
assert_eq!(json["additional_data"], Value::Object(additional_data));
171+
}
172+
173+
#[test]
174+
fn test_empty_additional_data_is_omitted_from_prove_result_json() {
175+
// The committed mock fixture carries no additional_data; the field must
176+
// deserialize to `None` and stay absent on re-serialization.
177+
let fixture = include_str!("../../resources/mock_proving_rpc/prove_transaction_result.json");
178+
let result: ProveTransactionResult = serde_json::from_str(fixture).unwrap();
179+
assert!(result.additional_data.is_none());
180+
181+
let json = serde_json::to_value(&result).unwrap();
182+
// Must be absent, not serialized as `null`: `contains_key` is true for an explicit null.
183+
assert!(!json.as_object().unwrap().contains_key("additional_data"));
184+
}
185+
186+
#[test]
187+
fn test_populated_additional_data_is_present_in_prove_result_json() {
188+
let fixture = include_str!("../../resources/mock_proving_rpc/prove_transaction_result.json");
189+
let mut result: ProveTransactionResult = serde_json::from_str(fixture).unwrap();
190+
result.additional_data = Some(sample_additional_data());
191+
192+
let json = serde_json::to_value(&result).unwrap();
193+
assert_eq!(json["additional_data"]["signature"]["sig_r"], "0x6e6f63c8");
194+
}
195+
127196
#[tokio::test]
128197
async fn test_inconclusive_fail_open_proceeds_to_proving() {
129198
let mut server = Server::new_async().await;

crates/starknet_transaction_prover/src/proving/virtual_snos_prover.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use blockifier_reexecution::utils::get_chain_info;
1313
#[cfg(feature = "stwo_proving")]
1414
use privacy_prove::{prepare_recursive_prover_precomputes, RecursiveProverPrecomputes};
1515
use serde::{Deserialize, Serialize};
16+
use serde_json::{Map, Value};
1617
use starknet_api::block::GasPrice;
1718
use starknet_api::execution_resources::GasAmount;
1819
use starknet_api::rpc_transaction::{RpcInvokeTransaction, RpcInvokeTransactionV3, RpcTransaction};
@@ -37,6 +38,13 @@ pub struct ProveTransactionResult {
3738
pub proof_facts: ProofFacts,
3839
/// Messages sent from L2 to L1 during execution.
3940
pub l2_to_l1_messages: Vec<MessageToL1>,
41+
/// Opaque side-channel relayed verbatim from the blocking check's allow
42+
/// response; omitted from the JSON response when absent. The prover does not
43+
/// interpret its contents (a screened deposit carries a screening signature
44+
/// under `signature`, but that is the screening domain's concern, not the
45+
/// prover's).
46+
#[serde(default, skip_serializing_if = "Option::is_none")]
47+
pub additional_data: Option<Map<String, Value>>,
4048
}
4149

4250
/// Virtual SNOS prover for Starknet transactions.
@@ -241,22 +249,24 @@ impl<R: VirtualSnosRunner + 'static> VirtualSnosProver<R> {
241249
tokio::time::timeout(timeout_duration, client.check_transaction(block_id, transaction))
242250
.await;
243251

244-
let allow = match check_outcome {
252+
// A transaction allowed via the fail-open policy (inconclusive check or
253+
// timeout) carries no additional_data.
254+
let (allow, additional_data) = match check_outcome {
245255
Ok(BlockingCheckResult::Blocked) => {
246256
info!("Transaction blocked by external check");
247-
false
257+
(false, None)
248258
}
249-
Ok(BlockingCheckResult::Allowed(_)) => {
259+
Ok(BlockingCheckResult::Allowed(additional_data)) => {
250260
info!("Transaction allowed by external check");
251-
true
261+
(true, additional_data)
252262
}
253263
Ok(BlockingCheckResult::Inconclusive) => {
254264
info!(fail_open = client.fail_open, "Blocking check inconclusive");
255-
client.fail_open
265+
(client.fail_open, None)
256266
}
257267
Err(_) => {
258268
info!(fail_open = client.fail_open, "Blocking check timed out");
259-
client.fail_open
269+
(client.fail_open, None)
260270
}
261271
};
262272

@@ -265,11 +275,13 @@ impl<R: VirtualSnosRunner + 'static> VirtualSnosProver<R> {
265275
return Err(VirtualSnosProverError::TransactionBlocked);
266276
}
267277

268-
match prove_handle.await {
269-
Ok(result) => result,
278+
let mut result = match prove_handle.await {
279+
Ok(prove_result) => prove_result?,
270280
Err(err) if err.is_panic() => std::panic::resume_unwind(err.into_panic()),
271281
Err(err) => unreachable!("prove task cancelled unexpectedly: {err}"),
272-
}
282+
};
283+
result.additional_data = additional_data;
284+
Ok(result)
273285
}
274286

275287
/// Proves a Virtual Starknet OS run from its output.
@@ -336,6 +348,7 @@ async fn prove_virtual_snos_run_with_precomputes(
336348
proof: prover_output.proof,
337349
proof_facts,
338350
l2_to_l1_messages: runner_output.l2_to_l1_messages,
351+
additional_data: None,
339352
})
340353
}
341354

0 commit comments

Comments
 (0)