From 005987921377cd9e7562b756694f1d025f310546 Mon Sep 17 00:00:00 2001 From: Avi Cohen Date: Sun, 7 Jun 2026 18:20:51 +0300 Subject: [PATCH] starknet_transaction_prover: relay opaque additional_data on proveTransaction response 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. --- .../blocking_check_integration_test.rs | 70 ++++++++++++++++++- .../src/proving/virtual_snos_prover.rs | 31 +++++--- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/crates/starknet_transaction_prover/src/proving/blocking_check_integration_test.rs b/crates/starknet_transaction_prover/src/proving/blocking_check_integration_test.rs index 0a91e1fdef3..4113652fa7b 100644 --- a/crates/starknet_transaction_prover/src/proving/blocking_check_integration_test.rs +++ b/crates/starknet_transaction_prover/src/proving/blocking_check_integration_test.rs @@ -11,6 +11,7 @@ use async_trait::async_trait; use blockifier_reexecution::state_reader::rpc_objects::BlockId; use mockito::Server; +use serde_json::{json, Map, Value}; use starknet_api::invoke_tx_args; use starknet_api::rpc_transaction::RpcTransaction; use starknet_api::test_utils::invoke::rpc_invoke_tx; @@ -18,9 +19,9 @@ use starknet_api::transaction::fields::ValidResourceBounds; use starknet_api::transaction::InvokeTransaction; use url::Url; -use super::virtual_snos_prover::VirtualSnosProver; use crate::blocking_check::BlockingCheckClient; use crate::errors::{RunnerError, VirtualSnosProverError}; +use crate::proving::virtual_snos_prover::{ProveTransactionResult, VirtualSnosProver}; use crate::running::runner::{RunnerOutput, VirtualSnosRunner}; use crate::test_utils::resource_bounds_for_client_side_tx; @@ -124,6 +125,73 @@ async fn test_check_allowed_proceeds_to_proving() { assert_runner_error(&result); } +#[tokio::test] +async fn test_check_allowed_with_additional_data_proceeds_to_proving() { + let mut server = Server::new_async().await; + let _mock = server + .mock("POST", "/") + .with_status(200) + .with_body( + r#"{"jsonrpc":"2.0","result":{"allowed":true,"additional_data":{"signature":{"issued_at":1716579600,"sig_r":"0x1","sig_s":"0x2"}}},"id":1}"#, + ) + .create_async() + .await; + + let url = Url::parse(&server.url()).unwrap(); + let client = BlockingCheckClient::new(url, TEST_TIMEOUT_MILLIS, true); + let prover = build_prover(MockRunner, Some(client)); + + // The MockRunner errors before producing a result, so the verbatim relay is + // covered by the serde tests below; this confirms an allow carrying + // additional_data routes to proving rather than blocking. + let result = prove(&prover).await; + assert_runner_error(&result); +} + +fn sample_additional_data() -> Map { + serde_json::from_value(json!({ + "signature": { "issued_at": 1716579600, "sig_r": "0x6e6f63c8", "sig_s": "0x58a68a71" } + })) + .unwrap() +} + +#[test] +fn test_additional_data_relays_object_verbatim() { + // The prover does not interpret additional_data; an arbitrary object, + // including keys it has never heard of, round-trips unchanged. + let additional_data: Map = + serde_json::from_value(json!({ "signature": { "sig_r": "0x1" }, "future_key": [1, 2, 3] })) + .unwrap(); + let fixture = include_str!("../../resources/mock_proving_rpc/prove_transaction_result.json"); + let mut result: ProveTransactionResult = serde_json::from_str(fixture).unwrap(); + result.additional_data = Some(additional_data.clone()); + + let json = serde_json::to_value(&result).unwrap(); + assert_eq!(json["additional_data"], Value::Object(additional_data)); +} + +#[test] +fn test_empty_additional_data_is_omitted_from_prove_result_json() { + // The committed mock fixture carries no additional_data; the field must + // deserialize to `None` and stay absent on re-serialization. + let fixture = include_str!("../../resources/mock_proving_rpc/prove_transaction_result.json"); + let result: ProveTransactionResult = serde_json::from_str(fixture).unwrap(); + assert!(result.additional_data.is_none()); + + let json = serde_json::to_value(&result).unwrap(); + assert!(json.get("additional_data").is_none()); +} + +#[test] +fn test_populated_additional_data_is_present_in_prove_result_json() { + let fixture = include_str!("../../resources/mock_proving_rpc/prove_transaction_result.json"); + let mut result: ProveTransactionResult = serde_json::from_str(fixture).unwrap(); + result.additional_data = Some(sample_additional_data()); + + let json = serde_json::to_value(&result).unwrap(); + assert_eq!(json["additional_data"]["signature"]["sig_r"], "0x6e6f63c8"); +} + #[tokio::test] async fn test_inconclusive_fail_open_proceeds_to_proving() { let mut server = Server::new_async().await; diff --git a/crates/starknet_transaction_prover/src/proving/virtual_snos_prover.rs b/crates/starknet_transaction_prover/src/proving/virtual_snos_prover.rs index 0401304e801..cab35ca317b 100644 --- a/crates/starknet_transaction_prover/src/proving/virtual_snos_prover.rs +++ b/crates/starknet_transaction_prover/src/proving/virtual_snos_prover.rs @@ -13,6 +13,7 @@ use blockifier_reexecution::utils::get_chain_info; #[cfg(feature = "stwo_proving")] use privacy_prove::{prepare_recursive_prover_precomputes, RecursiveProverPrecomputes}; use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; use starknet_api::block::GasPrice; use starknet_api::execution_resources::GasAmount; use starknet_api::rpc_transaction::{RpcInvokeTransaction, RpcInvokeTransactionV3, RpcTransaction}; @@ -37,6 +38,13 @@ pub struct ProveTransactionResult { pub proof_facts: ProofFacts, /// Messages sent from L2 to L1 during execution. pub l2_to_l1_messages: Vec, + /// Opaque side-channel relayed verbatim from the blocking check's allow + /// response; omitted from the JSON response when absent. The prover does not + /// interpret its contents (a screened deposit carries a screening signature + /// under `signature`, but that is the screening domain's concern, not the + /// prover's). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub additional_data: Option>, } /// Virtual SNOS prover for Starknet transactions. @@ -234,22 +242,24 @@ impl VirtualSnosProver { tokio::time::timeout(timeout_duration, client.check_transaction(block_id, transaction)) .await; - let allow = match check_outcome { + // A transaction allowed via the fail-open policy (inconclusive check or + // timeout) carries no additional_data. + let (allow, additional_data) = match check_outcome { Ok(BlockingCheckResult::Blocked) => { info!("Transaction blocked by external check"); - false + (false, None) } - Ok(BlockingCheckResult::Allowed(_)) => { + Ok(BlockingCheckResult::Allowed(additional_data)) => { info!("Transaction allowed by external check"); - true + (true, additional_data) } Ok(BlockingCheckResult::Inconclusive) => { info!(fail_open = client.fail_open, "Blocking check inconclusive"); - client.fail_open + (client.fail_open, None) } Err(_) => { info!(fail_open = client.fail_open, "Blocking check timed out"); - client.fail_open + (client.fail_open, None) } }; @@ -258,11 +268,13 @@ impl VirtualSnosProver { return Err(VirtualSnosProverError::TransactionBlocked); } - match prove_handle.await { - Ok(result) => result, + let mut result = match prove_handle.await { + Ok(prove_result) => prove_result?, Err(err) if err.is_panic() => std::panic::resume_unwind(err.into_panic()), Err(err) => unreachable!("prove task cancelled unexpectedly: {err}"), - } + }; + result.additional_data = additional_data; + Ok(result) } /// Proves a Virtual Starknet OS run from its output. @@ -329,6 +341,7 @@ async fn prove_virtual_snos_run_with_precomputes( proof: prover_output.proof, proof_facts, l2_to_l1_messages: runner_output.l2_to_l1_messages, + additional_data: None, }) }