Skip to content

Commit 084d115

Browse files
avi-starkwareclaude
andcommitted
starknet_transaction_prover: add exhaustive error enum and error mapping tests
Add SpecErrorKind enum to make spec error coverage compiler-enforced: adding a new error to the spec requires adding a variant, or the match is non-exhaustive. Refactor rpc_spec_test to use SpecErrorKind::ALL. Add 7 error code verification tests covering all RPC error code paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9b31a07 commit 084d115

File tree

3 files changed

+133
-14
lines changed

3 files changed

+133
-14
lines changed

crates/starknet_transaction_prover/src/server/errors.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,58 @@ use jsonrpsee::types::ErrorObjectOwned;
1616

1717
use crate::errors::{RunnerError, VirtualBlockExecutorError, VirtualSnosProverError};
1818

19+
// --- Test-only: exhaustive spec error enum ---
20+
21+
/// Every error defined in the OpenRPC spec (`components/errors`).
22+
///
23+
/// Adding a new error to the spec requires adding a variant here; the compiler enforces
24+
/// exhaustive coverage via `SpecErrorKind::ALL` in the spec test.
25+
#[cfg(test)]
26+
#[derive(Clone, Copy)]
27+
pub(crate) enum SpecErrorKind {
28+
BlockNotFound,
29+
AccountValidationFailed,
30+
UnsupportedTxVersion,
31+
ServiceBusy,
32+
InvalidTransactionInput,
33+
}
34+
35+
#[cfg(test)]
36+
impl SpecErrorKind {
37+
/// All variants — used by the spec completeness test.
38+
pub(crate) const ALL: &[Self] = &[
39+
Self::BlockNotFound,
40+
Self::AccountValidationFailed,
41+
Self::UnsupportedTxVersion,
42+
Self::ServiceBusy,
43+
Self::InvalidTransactionInput,
44+
];
45+
46+
/// The key in `components/errors` of the OpenRPC spec.
47+
pub(crate) fn spec_key(self) -> &'static str {
48+
match self {
49+
Self::BlockNotFound => "BLOCK_NOT_FOUND",
50+
Self::AccountValidationFailed => "ACCOUNT_VALIDATION_FAILED",
51+
Self::UnsupportedTxVersion => "UNSUPPORTED_TX_VERSION",
52+
Self::ServiceBusy => "SERVICE_BUSY",
53+
Self::InvalidTransactionInput => "INVALID_TRANSACTION_INPUT",
54+
}
55+
}
56+
57+
/// A representative `ErrorObjectOwned` for this error kind.
58+
pub(crate) fn example_error(self) -> jsonrpsee::types::ErrorObjectOwned {
59+
match self {
60+
Self::BlockNotFound => block_not_found(),
61+
Self::AccountValidationFailed => validation_failure("test".to_string()),
62+
Self::UnsupportedTxVersion => unsupported_tx_version("v99".to_string()),
63+
Self::ServiceBusy => service_busy(2),
64+
Self::InvalidTransactionInput => {
65+
invalid_transaction_input("test field invalid".to_string())
66+
}
67+
}
68+
}
69+
}
70+
1971
// Starknet RPC v0.10 error codes.
2072

2173
/// Block not found (code 24).

crates/starknet_transaction_prover/src/server/errors_test.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@ use crate::errors::{
99
VirtualBlockExecutorError,
1010
VirtualSnosProverError,
1111
};
12-
use crate::server::errors::internal_server_error;
12+
use crate::server::errors::{internal_server_error, service_busy};
1313

1414
fn error_data(err: VirtualSnosProverError) -> String {
1515
let rpc_err: jsonrpsee::types::ErrorObjectOwned = err.into();
1616
let raw_value = rpc_err.data().expect("expected error data to be present");
1717
serde_json::from_str(raw_value.get()).expect("error data should be valid JSON string")
1818
}
1919

20+
fn error_code(err: VirtualSnosProverError) -> i32 {
21+
let rpc_err: jsonrpsee::types::ErrorObjectOwned = err.into();
22+
rpc_err.code()
23+
}
24+
2025
#[test]
2126
fn test_transaction_reverted_produces_user_friendly_message() {
2227
let tx_hash = TransactionHash(felt!("0x123"));
@@ -122,3 +127,73 @@ fn test_internal_server_error_has_data_field() {
122127
let rpc_err = internal_server_error("some detail");
123128
assert!(rpc_err.data().is_some(), "internal_server_error should always include a data field");
124129
}
130+
131+
#[test]
132+
fn test_invalid_transaction_type_produces_unsupported_tx_version() {
133+
let err = VirtualSnosProverError::InvalidTransactionType("unsupported version".to_string());
134+
let rpc_err: jsonrpsee::types::ErrorObjectOwned = err.into();
135+
136+
assert_eq!(rpc_err.code(), 61);
137+
assert_eq!(rpc_err.message(), "The transaction version is not supported");
138+
}
139+
140+
#[test]
141+
fn test_invalid_transaction_input_produces_code_1000() {
142+
let err = VirtualSnosProverError::InvalidTransactionInput("bad input".to_string());
143+
let rpc_err: jsonrpsee::types::ErrorObjectOwned = err.into();
144+
145+
assert_eq!(rpc_err.code(), 1000);
146+
assert_eq!(rpc_err.message(), "Invalid transaction input");
147+
}
148+
149+
#[test]
150+
fn test_pending_block_validation_error_produces_block_not_found() {
151+
let err = VirtualSnosProverError::ValidationError(
152+
"Pending blocks are not supported in this context".to_string(),
153+
);
154+
let rpc_err: jsonrpsee::types::ErrorObjectOwned = err.into();
155+
156+
assert_eq!(rpc_err.code(), 24);
157+
assert_eq!(rpc_err.message(), "Block not found");
158+
}
159+
160+
#[test]
161+
fn test_non_pending_validation_error_produces_validation_failure() {
162+
let err = VirtualSnosProverError::ValidationError("some other validation error".to_string());
163+
let rpc_err: jsonrpsee::types::ErrorObjectOwned = err.into();
164+
165+
assert_eq!(rpc_err.code(), 55);
166+
assert_eq!(rpc_err.message(), "Account validation failed");
167+
}
168+
169+
#[test]
170+
fn test_bouncer_lock_error_produces_internal_error() {
171+
let inner = VirtualBlockExecutorError::BouncerLockError("lock poisoned".to_string());
172+
let err = VirtualSnosProverError::from(Box::new(RunnerError::from(inner)));
173+
174+
// Internal error code is -32603.
175+
assert_eq!(error_code(err), -32603);
176+
}
177+
178+
#[test]
179+
fn test_reexecution_error_produces_internal_error() {
180+
use blockifier_reexecution::errors::ReexecutionError;
181+
let inner = VirtualBlockExecutorError::ReexecutionError(Box::new(
182+
ReexecutionError::AmbiguousChainIdFromUrl("http://example.com".to_string()),
183+
));
184+
let err = VirtualSnosProverError::from(Box::new(RunnerError::from(inner)));
185+
186+
assert_eq!(error_code(err), -32603);
187+
}
188+
189+
#[test]
190+
fn test_service_busy_has_correct_code_and_message() {
191+
let rpc_err = service_busy(2);
192+
193+
assert_eq!(rpc_err.code(), -32005);
194+
assert_eq!(rpc_err.message(), "Service is busy");
195+
let raw_value = rpc_err.data().expect("service_busy should include a data field");
196+
let data: String =
197+
serde_json::from_str(raw_value.get()).expect("data should be a valid JSON string");
198+
assert!(data.contains("2 concurrent"), "Expected '2 concurrent' in: {data}");
199+
}

crates/starknet_transaction_prover/src/server/rpc_spec_test.rs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use starknet_types_core::felt::Felt;
2424

2525
use crate::config::ProverConfig;
2626
use crate::proving::virtual_snos_prover::RpcVirtualSnosProver;
27-
use crate::server::errors;
2827
use crate::server::mock_rpc::MockProvingRpc;
2928
use crate::server::rpc_api::ProvingRpcServer;
3029
use crate::server::rpc_impl::ProvingRpcServerImpl;
@@ -288,26 +287,19 @@ async fn test_prove_transaction_rejects_pending_block_id(
288287
}
289288

290289
#[test]
291-
// TODO(Avi): Add an error enum to make this test exhastive.
292290
fn test_error_responses_match_spec() {
293-
let test_cases: Vec<(&str, ErrorObjectOwned)> = vec![
294-
("BLOCK_NOT_FOUND", errors::block_not_found()),
295-
("ACCOUNT_VALIDATION_FAILED", errors::validation_failure("test".to_string())),
296-
("UNSUPPORTED_TX_VERSION", errors::unsupported_tx_version("v99".to_string())),
297-
("SERVICE_BUSY", errors::service_busy(2)),
298-
(
299-
"INVALID_TRANSACTION_INPUT",
300-
errors::invalid_transaction_input("test field invalid".to_string()),
301-
),
302-
];
291+
use crate::server::errors::SpecErrorKind;
292+
293+
let test_cases: Vec<(&str, ErrorObjectOwned)> =
294+
SpecErrorKind::ALL.iter().map(|kind| (kind.spec_key(), kind.example_error())).collect();
303295

304296
// Completeness guard: ensure all spec errors have a test case.
305297
let spec_error_keys: HashSet<&str> =
306298
SPEC["components"]["errors"].as_object().unwrap().keys().map(|k| k.as_str()).collect();
307299
let tested_error_keys: HashSet<&str> = test_cases.iter().map(|(key, _)| *key).collect();
308300
assert_eq!(
309301
tested_error_keys, spec_error_keys,
310-
"Test cases don't cover all spec errors. Update the test_cases list above."
302+
"Test cases don't cover all spec errors. Add a variant to SpecErrorKind."
311303
);
312304

313305
for (spec_key, actual) in &test_cases {

0 commit comments

Comments
 (0)