Skip to content

Commit 2253a85

Browse files
authored
catch sign_and_submit_then_watch_default panic (#18)
1 parent 8eaa82d commit 2253a85

File tree

4 files changed

+60
-52
lines changed

4 files changed

+60
-52
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pub mod resources;
44
pub mod server;
55
pub mod substrate;
66
pub mod tools;
7+
pub mod utils;

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod resources;
88
mod server;
99
mod substrate;
1010
mod tools;
11+
mod utils;
1112

1213
use server::SubstrateService;
1314

src/tools/mod.rs

Lines changed: 41 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use futures::FutureExt;
12
use rmcp::{
23
model::{CallToolResult, Content, RawContent, RawTextContent},
34
schemars, ErrorData as McpError,
@@ -6,6 +7,8 @@ use serde::Deserialize;
67
use subxt::{OnlineClient, PolkadotConfig};
78
use subxt_signer::sr25519::dev;
89

10+
use crate::utils::{mcp_error_internal, mcp_error_invalid_params};
11+
912
#[derive(Debug, Deserialize, Clone, schemars::JsonSchema)]
1013
pub struct SubmitExtrinsicProperties {
1114
/// The RPC URL to connect to
@@ -25,25 +28,18 @@ pub async fn handle_submit_dev_extrinsic(
2528
) -> Result<CallToolResult, McpError> {
2629
let client = OnlineClient::<PolkadotConfig>::from_url(&properties.rpc_url)
2730
.await
28-
.map_err(|e| McpError {
29-
code: rmcp::model::ErrorCode(-32603),
30-
message: format!("Failed to connect to chain: {e}").into(),
31-
data: None,
32-
})?;
31+
.map_err(|e| mcp_error_internal(format!("Failed to connect to chain: {e}")))?;
3332

3433
let (scale_args_result, remainder) = scale_value::stringify::from_str(&properties.args);
35-
let scale_args = scale_args_result.map_err(|e| McpError {
36-
code: rmcp::model::ErrorCode(-32602),
37-
message: format!("Failed to parse arguments: {e}. See 'substrate:scale-value-format' resource for syntax guide").into(),
38-
data: None,
39-
})?;
34+
35+
let scale_args = scale_args_result.map_err(|e|
36+
mcp_error_invalid_params(format!("Failed to parse arguments: {e}. See 'substrate:scale-value-format' resource for syntax guide"))
37+
)?;
4038

4139
if !remainder.trim().is_empty() {
42-
return Err(McpError {
43-
code: rmcp::model::ErrorCode(-32602),
44-
message: format!("Unexpected content after arguments: '{remainder}'").into(),
45-
data: None,
46-
});
40+
return Err(mcp_error_invalid_params(format!(
41+
"Unexpected content after parsing arguments: '{remainder}'"
42+
)));
4743
}
4844

4945
// Create composite from the scale value
@@ -52,6 +48,7 @@ pub async fn handle_submit_dev_extrinsic(
5248
scale_value::ValueDef::Composite(composite) => composite,
5349
_ => scale_value::Composite::Unnamed(vec![scale_args]),
5450
};
51+
let call_data = subxt::dynamic::tx(&properties.pallet, &properties.call, composite_args);
5552

5653
// Get the appropriate signer based on the requested dev account
5754
let signer = match properties.signer.to_lowercase().as_str() {
@@ -62,54 +59,46 @@ pub async fn handle_submit_dev_extrinsic(
6259
"eve" => dev::eve(),
6360
"ferdie" => dev::ferdie(),
6461
_ => {
65-
return Err(McpError {
66-
code: rmcp::model::ErrorCode(-32602),
67-
message: format!(
68-
"Invalid signer '{}'. Supported signers: alice, bob, charlie, dave, eve, ferdie",
69-
properties.signer
70-
).into(),
71-
data: None,
72-
});
62+
return Err(mcp_error_invalid_params(format!(
63+
"Invalid signer '{}'. Supported signers: alice, bob, charlie, dave, eve, ferdie",
64+
properties.signer
65+
)));
7366
}
7467
};
7568

76-
// Create the dynamic call payload
77-
let call_data = subxt::dynamic::tx(&properties.pallet, &properties.call, composite_args);
78-
79-
// Submit and wait for finalization
80-
let tx_progress = client
81-
.tx()
82-
.sign_and_submit_then_watch_default(&call_data, &signer)
83-
.await
84-
.map_err(|e| McpError {
85-
code: rmcp::model::ErrorCode(-32603),
86-
message: format!("Failed to submit transaction: {e}").into(),
87-
data: None,
88-
})?;
69+
// NOTE: `sign_and_submit_then_watch_default` panics when call exists and arguments
70+
// are valid SCALE but don't fit the call type. We get around this by catching the
71+
// panic
72+
let tx_progress = std::panic::AssertUnwindSafe(
73+
client
74+
.tx()
75+
.sign_and_submit_then_watch_default(&call_data, &signer),
76+
)
77+
.catch_unwind()
78+
.await
79+
.map_err(|_| {
80+
mcp_error_internal(format!(
81+
"Transaction submission panicked - likely due to invalid call data. \
82+
Please verify the call data matches the expectd format for pallet '{}' and call '{}'",
83+
properties.pallet, properties.call
84+
))
85+
})?
86+
.map_err(|e| mcp_error_internal(format!("Failed to submit transaction: {e}")))?;
8987

9088
// Wait for the transaction to be finalized and check for success
9189
let tx_events = tx_progress
9290
.wait_for_finalized_success()
9391
.await
94-
.map_err(|e| McpError {
95-
code: rmcp::model::ErrorCode(-32603),
96-
message: format!("Transaction failed: {e}").into(),
97-
data: None,
98-
})?;
92+
.map_err(|e| mcp_error_internal(format!("Transaction failed: {e}")))?;
9993

10094
let mut events_info = Vec::new();
10195
for event in tx_events.iter() {
102-
let event = event.map_err(|e| McpError {
103-
code: rmcp::model::ErrorCode(-32603),
104-
message: format!("Failed to decode event: {e}").into(),
105-
data: None,
106-
})?;
107-
108-
let fields = event.field_values().map_err(|e| McpError {
109-
code: rmcp::model::ErrorCode(-32603),
110-
message: format!("Failed to decode event fields: {e}").into(),
111-
data: None,
112-
})?;
96+
let event =
97+
event.map_err(|e| mcp_error_internal(format!("Failed to decode event: {e}")))?;
98+
99+
let fields = event
100+
.field_values()
101+
.map_err(|e| mcp_error_internal(format!("Failed to decode event fields: {e}")))?;
113102

114103
let value = scale_value::Value {
115104
value: scale_value::ValueDef::Composite(fields),

src/utils.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use rmcp::ErrorData as McpError;
2+
3+
pub fn mcp_error_internal(message: String) -> McpError {
4+
McpError {
5+
code: rmcp::model::ErrorCode::INTERNAL_ERROR,
6+
message: message.into(),
7+
data: None,
8+
}
9+
}
10+
11+
pub fn mcp_error_invalid_params(message: String) -> McpError {
12+
McpError {
13+
code: rmcp::model::ErrorCode::INVALID_PARAMS,
14+
message: message.into(),
15+
data: None,
16+
}
17+
}

0 commit comments

Comments
 (0)