diff --git a/backend-rust/CHANGELOG.md b/backend-rust/CHANGELOG.md index 0b0b5a3c..ad4b0e42 100644 --- a/backend-rust/CHANGELOG.md +++ b/backend-rust/CHANGELOG.md @@ -22,6 +22,7 @@ Database schema version: 2 ### Fixed +- Fix button to display the schema decoding error at front-end by returning the error as an object. - Fix typo in `versions` endpoint. - Fix unit conversion for `avg_finalization_time` in `Query::block_metrics`. - Issue for `Query::transaction_metrics` producing an internal error when query period is beyond the genesis block. diff --git a/backend-rust/src/migrations/m0001-initialize.sql b/backend-rust/src/migrations/m0001-initialize.sql index 16fd4d1d..129ce4d4 100644 --- a/backend-rust/src/migrations/m0001-initialize.sql +++ b/backend-rust/src/migrations/m0001-initialize.sql @@ -841,6 +841,7 @@ CREATE INDEX block_special_transaction_outcomes_idx -- Function for generating a table where each row is a bucket. -- Used by metrics queries. +-- This function is replaced with a function of the same name in migration file `m0002`. CREATE OR REPLACE FUNCTION date_bin_series(bucket_size interval, starting TIMESTAMPTZ, ending TIMESTAMPTZ) RETURNS TABLE(bucket_start TIMESTAMPTZ, bucket_end TIMESTAMPTZ) AS $$ SELECT diff --git a/backend-rust/src/transaction_event/smart_contracts.rs b/backend-rust/src/transaction_event/smart_contracts.rs index 6e6fdf0e..192c4796 100644 --- a/backend-rust/src/transaction_event/smart_contracts.rs +++ b/backend-rust/src/transaction_event/smart_contracts.rs @@ -11,6 +11,7 @@ use concordium_rust_sdk::base::{ }, smart_contracts::ReceiveName, }; +use serde::Serialize; #[derive(Enum, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum ContractVersion { @@ -118,7 +119,7 @@ impl ContractInitialized { opt_event_schema.as_ref(), log, SmartContractSchemaNames::Event, - ); + )?; connection.edges.push(connection::Edge::new(index.to_string(), decoded_log)); } @@ -190,7 +191,7 @@ impl ContractUpdated { opt_receive_param_schema.as_ref(), &self.input_parameter, SmartContractSchemaNames::InputParameterReceiveFunction, - ); + )?; Ok(Some(decoded_input_parameter)) } @@ -250,7 +251,7 @@ impl ContractUpdated { opt_event_schema.as_ref(), log, SmartContractSchemaNames::Event, - ); + )?; connection.edges.push(connection::Edge::new(index.to_string(), decoded_log)); } @@ -331,7 +332,7 @@ impl ContractInterrupted { opt_event_schema.as_ref(), log, SmartContractSchemaNames::Event, - ); + )?; connection.edges.push(connection::Edge::new(index.to_string(), decoded_log)); } @@ -394,38 +395,55 @@ impl SmartContractSchemaNames { } } +/// Schema decoding error reported to the front-end. +#[derive(Serialize)] +struct SchemaDecodingError { + error: String, +} + fn decode_value_with_schema( opt_schema: Option<&Type>, value: &[u8], schema_name: SmartContractSchemaNames, -) -> String { +) -> Result { let Some(schema) = opt_schema else { // Note: There could be something better displayed than this string if no schema // is available for decoding at the frontend long-term. - return format!( - "No embedded {} schema in smart contract available for decoding", - schema_name.kind() - ); + return serde_json::to_string(&SchemaDecodingError { + error: format!( + "No embedded {} schema in smart contract available for decoding", + schema_name.kind() + ), + }).map_err(|_| ApiError::InternalError("Should be valid error string".to_string())); }; let mut cursor = Cursor::new(&value); + match schema.to_json(&mut cursor) { Ok(v) => { - serde_json::to_string(&v).unwrap_or_else(|e| { - // We don't return an error here since the query is correctly formed and - // the CCDScan backend is working as expected. - // A wrong/missing schema is a mistake by the smart contract - // developer which in general cannot be fixed after the deployment of - // the contract. We display the error message (instead of the decoded - // value) in the block explorer to make the info visible to the smart - // contract developer for debugging purposes here. - format!( - "Failed to deserialize {} with {} schema into string: {:?}", - schema_name.value(), - schema_name.kind(), - e - ) - }) + match serde_json::to_string(&v) { + Ok(v) => Ok(v), + Err(error) => { + // We don't return an error here since the query is correctly formed and + // the CCDScan backend is working as expected. + // A wrong/missing schema is a mistake by the smart contract + // developer which in general cannot be fixed after the deployment of + // the contract. We display the error message (instead of the decoded + // value) in the block explorer to make the info visible to the smart + // contract developer for debugging purposes here. + Ok(serde_json::to_string(&SchemaDecodingError { + error: format!( + "Failed to deserialize {} with {} schema into string: {:?}", + schema_name.value(), + schema_name.kind(), + error + ), + }) + .map_err(|_| { + ApiError::InternalError("Should be valid error string".to_string()) + })?) + } + } } Err(e) => { // We don't return an error here since the query is correctly formed and @@ -435,12 +453,15 @@ fn decode_value_with_schema( // the contract. We display the error message (instead of the decoded // value) in the block explorer to make the info visible to the smart // contract developer for debugging purposes here. - format!( - "Failed to deserialize {} with {} schema: {:?}", - schema_name.value(), - schema_name.kind(), - e.display(true) - ) + Ok(serde_json::to_string(&SchemaDecodingError { + error: format!( + "Failed to deserialize {} with {} schema: {:?}", + schema_name.value(), + schema_name.kind(), + e.display(true) + ), + }) + .map_err(|_| ApiError::InternalError("Should be valid error string".to_string()))?) } } }