413 lines
13 KiB
Rust
413 lines
13 KiB
Rust
use ghost_traits::networks::NetworkRpcResolver;
|
|
use serde::{Deserialize, Deserializer};
|
|
|
|
use sp_std::vec;
|
|
use serde_json::json;
|
|
use sp_std::vec::Vec;
|
|
use sp_core::{H256, U256};
|
|
use sp_runtime::offchain::http::{Error as HttpError, HttpResult, PendingRequest};
|
|
|
|
use crate::{
|
|
ExternalBlockNumber, NetworkType, RequestId, RpcResolverError, WeavingError, WeavingSession,
|
|
LOG_TARGET,
|
|
};
|
|
|
|
fn parse_response(
|
|
http_result: HttpResult,
|
|
request_id: RequestId,
|
|
network_type: &NetworkType,
|
|
) -> Result<NetworkResponseType, RpcResolverError> {
|
|
let response = http_result.map_err(|err| match err {
|
|
HttpError::DeadlineReached => RpcResolverError::DeadlineReached,
|
|
HttpError::IoError => RpcResolverError::IoError,
|
|
HttpError::Unknown => RpcResolverError::Unknown,
|
|
})?;
|
|
|
|
if response.code != 200 {
|
|
return Err(RpcResolverError::Non200HttpCode);
|
|
}
|
|
|
|
let response_body = response.body().collect::<Vec<u8>>();
|
|
let response_str = sp_std::str::from_utf8(&response_body)
|
|
.map_err(|_| RpcResolverError::UnparsableResponseBody)?;
|
|
|
|
let response_data: NetworkResponse = match network_type {
|
|
NetworkType::Evm => {
|
|
let evm_response: EvmResponse = serde_json::from_str(response_str)
|
|
.map_err(|_| RpcResolverError::JsonDeserializationFailed)?;
|
|
|
|
evm_response.into()
|
|
}
|
|
NetworkType::Utxo => {
|
|
let utxo_response: UtxoResponse = serde_json::from_str(response_str)
|
|
.map_err(|_| RpcResolverError::JsonDeserializationFailed)?;
|
|
|
|
utxo_response.into()
|
|
}
|
|
_ => return Err(RpcResolverError::UnknownNetworkType),
|
|
};
|
|
|
|
if request_id != response_data.id {
|
|
return Err(RpcResolverError::InvalidResponseId);
|
|
}
|
|
|
|
if response_data.error.is_some() {
|
|
return Err(RpcResolverError::RpcProtocolError);
|
|
}
|
|
|
|
response_data
|
|
.result
|
|
.ok_or(RpcResolverError::RpcEmptyResultField)
|
|
}
|
|
|
|
fn parse_pending_requests<'a, R: Copy + Into<RequestId> + 'static>(
|
|
pending_requests: Vec<PendingRequest>,
|
|
deadline: sp_runtime::offchain::Timestamp,
|
|
request_id: R,
|
|
network_type: &'a NetworkType,
|
|
) -> impl Iterator<Item = NetworkResponseType> + 'a {
|
|
PendingRequest::try_wait_all(pending_requests, Some(deadline))
|
|
.into_iter()
|
|
.filter_map(move |wait_result| {
|
|
let http_result = wait_result
|
|
.inspect_err(|_| {
|
|
log::error!(
|
|
target: LOG_TARGET,
|
|
"🕸️ Error during request preparation: {:?}",
|
|
WeavingError::<u32>::DeadlineReached,
|
|
)
|
|
})
|
|
.ok()?;
|
|
|
|
parse_response(http_result, request_id.into(), network_type)
|
|
.inspect_err(|err| {
|
|
log::error!(
|
|
target: LOG_TARGET,
|
|
"🕸️ Error during response parsing: {:?}",
|
|
err,
|
|
)
|
|
})
|
|
.ok()
|
|
})
|
|
}
|
|
|
|
impl NetworkRpcResolver<WeavingError<u32>, ExternalBlockNumber, H256, RequestId, WeavingSession>
|
|
for NetworkType
|
|
{
|
|
fn get_block_request_body(&self, id: u64) -> Vec<u8> {
|
|
let method = match self {
|
|
NetworkType::Evm => "eth_blockNumber",
|
|
NetworkType::Utxo => "getblockcount",
|
|
_ => "unknown",
|
|
};
|
|
|
|
serde_json::to_vec(&json!({"id": id, "jsonrpc": "2.0", "method": method}))
|
|
.unwrap_or_else(|_| b"{}".to_vec())
|
|
}
|
|
|
|
fn get_hash_request_body(
|
|
&self,
|
|
id: u64,
|
|
target_block: ExternalBlockNumber,
|
|
weaving_session: WeavingSession,
|
|
contract_ref: &[u8],
|
|
selector_ref: &[u8],
|
|
) -> Vec<u8> {
|
|
extern crate alloc;
|
|
|
|
let (method, params) = match self {
|
|
NetworkType::Evm => {
|
|
let contract_hex = hex::encode(contract_ref);
|
|
let selector_hex = hex::encode(selector_ref);
|
|
|
|
let encoded_session = alloc::format!("{:064x}", weaving_session);
|
|
let encoded_block = alloc::format!("{:064x}", target_block);
|
|
|
|
let to_field = alloc::format!("0x{}", contract_hex);
|
|
let data_field =
|
|
alloc::format!("0x{}{}{}", selector_hex, encoded_session, encoded_block,);
|
|
|
|
(
|
|
"eth_call",
|
|
json!([{"to": to_field, "data": data_field}, "latest"]),
|
|
)
|
|
}
|
|
NetworkType::Utxo => ("getblockhash", json!([target_block])),
|
|
_ => ("unknown", json!([])),
|
|
};
|
|
|
|
serde_json::to_vec(&json!({"id": id, "jsonrpc": "2.0", "method": method, "params": params}))
|
|
.unwrap_or_else(|_| b"{}".to_vec())
|
|
}
|
|
|
|
fn parse_block_requests(
|
|
&self,
|
|
pending_requests: Vec<PendingRequest>,
|
|
deadline: sp_runtime::offchain::Timestamp,
|
|
request_id: RequestId,
|
|
) -> Vec<ExternalBlockNumber> {
|
|
parse_pending_requests(pending_requests, deadline, request_id, self)
|
|
.filter_map(|response| match response {
|
|
NetworkResponseType::BlockNumber(block) => Some(block),
|
|
NetworkResponseType::TreeRoot(_, _) => None,
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn parse_hash_requests(
|
|
&self,
|
|
pending_requests: Vec<PendingRequest>,
|
|
deadline: sp_runtime::offchain::Timestamp,
|
|
request_id: RequestId,
|
|
) -> Vec<(H256, WeavingSession)> {
|
|
parse_pending_requests(pending_requests, deadline, request_id, self)
|
|
.filter_map(|response| match response {
|
|
NetworkResponseType::TreeRoot(hash, next) => Some((hash, next)),
|
|
NetworkResponseType::BlockNumber(_) => None,
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Deserialize)]
|
|
struct NetworkResponse {
|
|
pub id: RequestId,
|
|
pub error: Option<serde_json::Value>,
|
|
pub result: Option<NetworkResponseType>,
|
|
}
|
|
|
|
impl From<EvmResponse> for NetworkResponse {
|
|
fn from(evm_response: EvmResponse) -> Self {
|
|
Self {
|
|
id: evm_response.id,
|
|
error: evm_response.error,
|
|
result: evm_response.result.map(Into::into),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<UtxoResponse> for NetworkResponse {
|
|
fn from(utxo_response: UtxoResponse) -> Self {
|
|
Self {
|
|
id: utxo_response.id,
|
|
error: utxo_response.error,
|
|
result: utxo_response.result.map(Into::into),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Deserialize)]
|
|
enum NetworkResponseType {
|
|
BlockNumber(ExternalBlockNumber),
|
|
TreeRoot(H256, WeavingSession),
|
|
}
|
|
|
|
impl From<EvmResponseType> for NetworkResponseType {
|
|
fn from(evm_type: EvmResponseType) -> Self {
|
|
match evm_type {
|
|
EvmResponseType::BlockNumber(block) => Self::BlockNumber(block),
|
|
EvmResponseType::TreeRoot(root, next) => Self::TreeRoot(root, next),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<UtxoResponseType> for NetworkResponseType {
|
|
fn from(utxo_type: UtxoResponseType) -> Self {
|
|
match utxo_type {
|
|
UtxoResponseType::BlockNumber(block) => Self::BlockNumber(block),
|
|
UtxoResponseType::TreeRoot(root) => Self::TreeRoot(root, WeavingSession::MAX),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize, Eq, PartialEq, Clone)]
|
|
struct EvmResponse {
|
|
pub id: RequestId,
|
|
pub error: Option<serde_json::Value>,
|
|
pub result: Option<EvmResponseType>,
|
|
}
|
|
|
|
#[derive(Deserialize, Eq, PartialEq, Clone)]
|
|
struct UtxoResponse {
|
|
pub id: RequestId,
|
|
pub error: Option<serde_json::Value>,
|
|
pub result: Option<UtxoResponseType>,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, PartialEq, Eq)]
|
|
#[serde(untagged)]
|
|
enum EvmResponseType {
|
|
#[serde(deserialize_with = "de_hex_string_to_block_number")]
|
|
BlockNumber(ExternalBlockNumber),
|
|
#[serde(deserialize_with = "de_contract_tuple")]
|
|
TreeRoot(H256, WeavingSession),
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, PartialEq, Eq)]
|
|
#[serde(untagged)]
|
|
enum UtxoResponseType {
|
|
BlockNumber(ExternalBlockNumber),
|
|
#[serde(deserialize_with = "de_string_to_h256")]
|
|
TreeRoot(H256),
|
|
}
|
|
|
|
fn de_hex_string_to_block_number<'de, D>(de: D) -> Result<ExternalBlockNumber, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s: &'de str = Deserialize::deserialize(de)?;
|
|
|
|
let clean_hex = s.strip_prefix("0x").unwrap_or(s);
|
|
let trimmed_hex = clean_hex.trim_start_matches('0');
|
|
|
|
if trimmed_hex.is_empty() {
|
|
return Ok(Default::default());
|
|
}
|
|
|
|
ExternalBlockNumber::from_str_radix(trimmed_hex, 16)
|
|
.map_err(|_| serde::de::Error::custom("Failed to parse block number hex"))
|
|
}
|
|
|
|
fn de_string_to_h256<'de, D>(de: D) -> Result<H256, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s: &'de str = Deserialize::deserialize(de)?;
|
|
let clean_hex = s.strip_prefix("0x").unwrap_or(s);
|
|
|
|
let mut bytes = [0u8; 32];
|
|
hex::decode_to_slice(clean_hex, &mut bytes)
|
|
.map_err(|_| serde::de::Error::custom("Failed to parse hex string into H256"))?;
|
|
|
|
Ok(H256::from(bytes))
|
|
}
|
|
|
|
fn de_contract_tuple<'de, D>(de: D) -> Result<(H256, WeavingSession), D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s: &str = Deserialize::deserialize(de)?;
|
|
let clean_hex = s.strip_prefix("0x").unwrap_or(s);
|
|
|
|
if clean_hex.len() != 128 {
|
|
return Err(serde::de::Error::custom(
|
|
"Invalid hex string length for (bytes32, uint256)",
|
|
));
|
|
}
|
|
|
|
let (bytes32_hex, uint256_hex) = clean_hex.split_at(64);
|
|
|
|
let mut bytes = [0u8; 32];
|
|
hex::decode_to_slice(bytes32_hex, &mut bytes)
|
|
.map_err(|_| serde::de::Error::custom("Failed to parse hex string into H256"))?;
|
|
|
|
let root_hash = H256::from(bytes);
|
|
|
|
let uint256_val = U256::from_str_radix(uint256_hex, 16)
|
|
.map_err(|_| serde::de::Error::custom("Failed to parse uint256"))?;
|
|
|
|
let next_weaving_session: WeavingSession = uint256_val
|
|
.try_into()
|
|
.map_err(|_| serde::de::Error::custom("EVM uint256 value overflow bounds"))?;
|
|
|
|
Ok((root_hash, next_weaving_session))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod parsing_tests {
|
|
use super::*;
|
|
use serde::Deserialize;
|
|
use sp_core::H256;
|
|
|
|
#[derive(Deserialize)]
|
|
struct TestBlock {
|
|
#[serde(deserialize_with = "de_hex_string_to_block_number")]
|
|
val: ExternalBlockNumber,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct TestHash {
|
|
#[serde(deserialize_with = "de_string_to_h256")]
|
|
val: H256,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct TestTuple {
|
|
#[serde(deserialize_with = "de_contract_tuple")]
|
|
val: (H256, WeavingSession),
|
|
}
|
|
|
|
#[test]
|
|
fn test_de_hex_string_to_block_number_success() {
|
|
let json = r#"{"val": "0x64"}"#;
|
|
let parsed: TestBlock = serde_json::from_str(json).unwrap();
|
|
assert_eq!(parsed.val, 100);
|
|
|
|
let json_zeros = r#"{"val": "0x00000a"}"#;
|
|
let parsed_zeros: TestBlock = serde_json::from_str(json_zeros).unwrap();
|
|
assert_eq!(parsed_zeros.val, 10);
|
|
|
|
let json_zero = r#"{"val": "0x0"}"#;
|
|
let parsed_zero: TestBlock = serde_json::from_str(json_zero).unwrap();
|
|
assert_eq!(parsed_zero.val, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_de_hex_string_to_block_number_invalid() {
|
|
let json = r#"{"val": "0x6G"}"#;
|
|
let result: Result<TestBlock, _> = serde_json::from_str(json);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_de_string_to_h256_success() {
|
|
let raw_hash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
|
let json = format!(r#"{{"val": "0x{}"}}"#, raw_hash);
|
|
|
|
let parsed: TestHash = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(parsed.val, H256::repeat_byte(0xAA));
|
|
}
|
|
|
|
#[test]
|
|
fn test_de_string_to_h256_invalid_length() {
|
|
let json = r#"{"val": "0x00aa"}"#;
|
|
let result: Result<TestHash, _> = serde_json::from_str(json);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_de_contract_tuple_success() {
|
|
let hash_part = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
|
let session_part = "000000000000000000000000000000000000000000000000000000000000002a";
|
|
let json = format!(r#"{{"val": "0x{}{}"}}"#, hash_part, session_part);
|
|
|
|
let parsed: TestTuple = serde_json::from_str(&json).unwrap();
|
|
|
|
assert_eq!(parsed.val.0, H256::repeat_byte(0xBB));
|
|
assert_eq!(parsed.val.1, 42u64);
|
|
}
|
|
|
|
#[test]
|
|
fn test_de_contract_tuple_invalid_length() {
|
|
let json = r#"{"val": "0xbbbb002a"}"#;
|
|
let result: Result<TestTuple, _> = serde_json::from_str(json);
|
|
|
|
assert!(result.is_err());
|
|
let err_msg = result.err().unwrap().to_string();
|
|
assert!(err_msg.contains("Invalid hex string length"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_de_contract_tuple_overflow() {
|
|
let hash_part = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
|
let session_overflow = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
|
|
let json = format!(r#"{{"val": "0x{}{}"}}"#, hash_part, session_overflow);
|
|
|
|
let result: Result<TestTuple, _> = serde_json::from_str(&json);
|
|
|
|
assert!(result.is_err());
|
|
let err_msg = result.err().unwrap().to_string();
|
|
assert!(err_msg.contains("EVM uint256 value overflow bounds"));
|
|
}
|
|
}
|