From 568b1f639880ec713b20a551d3ea6a88f378f935 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Tue, 3 Dec 2024 19:26:05 -0500 Subject: [PATCH 1/9] relayer(requests): conversion for `QueryClientState`s --- crates/relayer/src/chain/requests.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/relayer/src/chain/requests.rs b/crates/relayer/src/chain/requests.rs index 3ded6c9ee5..d9ef156410 100644 --- a/crates/relayer/src/chain/requests.rs +++ b/crates/relayer/src/chain/requests.rs @@ -14,6 +14,7 @@ use ibc_proto::{ QueryUnreceivedPacketsRequest as RawQueryUnreceivedPacketsRequest, }, client::v1::{ + QueryClientStateRequest as RawQueryClientStateRequest, QueryClientStatesRequest as RawQueryClientStatesRequest, QueryConsensusStateHeightsRequest as RawQueryConsensusStateHeightsRequest, QueryConsensusStatesRequest as RawQueryConsensusStatesRequest, @@ -144,6 +145,14 @@ pub struct QueryClientStateRequest { pub height: QueryHeight, } +impl From for RawQueryClientStateRequest { + fn from(request: QueryClientStateRequest) -> Self { + Self { + client_id: request.client_id.to_string(), + } + } +} + /// gRPC query to fetch all client states associated with the chain. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct QueryClientStatesRequest { From 1fde73a1ac2e749859d7c87bc40ca9abd69645d7 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Tue, 3 Dec 2024 19:26:32 -0500 Subject: [PATCH 2/9] relayer(requests): conversion for `QueryConsensusState`s --- crates/relayer/src/chain/requests.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/relayer/src/chain/requests.rs b/crates/relayer/src/chain/requests.rs index d9ef156410..5d7136f6e1 100644 --- a/crates/relayer/src/chain/requests.rs +++ b/crates/relayer/src/chain/requests.rs @@ -17,6 +17,7 @@ use ibc_proto::{ QueryClientStateRequest as RawQueryClientStateRequest, QueryClientStatesRequest as RawQueryClientStatesRequest, QueryConsensusStateHeightsRequest as RawQueryConsensusStateHeightsRequest, + QueryConsensusStateRequest as RawQueryConsensusStateRequest, QueryConsensusStatesRequest as RawQueryConsensusStatesRequest, }, connection::v1::{ @@ -174,6 +175,28 @@ pub struct QueryConsensusStateRequest { pub query_height: QueryHeight, } +impl From for RawQueryConsensusStateRequest { + fn from(request: QueryConsensusStateRequest) -> Self { + // We extract the revision number and revision height, unless we are + // querying at the latest height. + if let QueryHeight::Specific(height) = request.query_height { + Self { + client_id: request.client_id.to_string(), + revision_number: height.revision_number(), + revision_height: height.revision_height(), + latest_height: false, + } + } else { + Self { + client_id: request.client_id.to_string(), + revision_number: 0, + revision_height: 0, + latest_height: true, + } + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct QueryUpgradedClientStateRequest { /// Height at which the chain is scheduled to halt for upgrade @@ -296,7 +319,7 @@ pub struct QueryChannelClientStateRequest { impl From for RawQueryChannelClientStateRequest { fn from(request: QueryChannelClientStateRequest) -> Self { - RawQueryChannelClientStateRequest { + Self { port_id: request.port_id.to_string(), channel_id: request.channel_id.to_string(), } From 7baca2497173a1da4577efacb5067b1e6d218e83 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Tue, 3 Dec 2024 19:27:45 -0500 Subject: [PATCH 3/9] chain(penumbra): use gRPC IBC service in consensus/client reqs --- crates/relayer/src/chain/penumbra/chain.rs | 87 ++++++++++++++++------ 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/crates/relayer/src/chain/penumbra/chain.rs b/crates/relayer/src/chain/penumbra/chain.rs index d698d9cb79..49a331f3ca 100644 --- a/crates/relayer/src/chain/penumbra/chain.rs +++ b/crates/relayer/src/chain/penumbra/chain.rs @@ -3,16 +3,20 @@ use bytes::{Buf, Bytes}; use futures::{FutureExt, StreamExt, TryStreamExt}; use http::Uri; use ibc_proto::ics23; + +use ibc_proto::ibc::core::client::v1::QueryClientStateRequest as RawQueryClientStateRequest; +use ibc_proto::ibc::core::client::v1::QueryConsensusStateRequest as RawQueryConsensusStatesRequest; + use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; use ibc_relayer_types::core::ics24_host::path::{ - AcksPath, ChannelEndsPath, ClientConsensusStatePath, ClientStatePath, CommitmentsPath, - ReceiptsPath, SeqRecvsPath, + AcksPath, ChannelEndsPath, CommitmentsPath, ReceiptsPath, SeqRecvsPath, }; use ibc_relayer_types::core::ics24_host::Path; use once_cell::sync::Lazy; use penumbra_proto::core::app::v1::AppParametersRequest; use penumbra_proto::core::component::ibc::v1::IbcRelay as ProtoIbcRelay; use penumbra_proto::DomainType as _; +use prost::Message; use std::str::FromStr; use std::sync::Arc; use std::thread; @@ -863,17 +867,42 @@ impl ChainEndpoint for PenumbraChain { include_proof: IncludeProof, ) -> Result<(AnyClientState, Option), Error> { crate::telemetry!(query, self.id(), "query_client_state"); + let mut client = self.ibc_client_grpc_client.clone(); - let res = self.rpc_query( - ClientStatePath(request.client_id.clone()), - request.height, - matches!(include_proof, IncludeProof::Yes), - )?; - let client_state = AnyClientState::decode_vec(&res.value).map_err(Error::decode)?; + let request: RawQueryClientStateRequest = request.into(); + + // TODO(erwan): for now, playing a bit fast-and-loose with the error handling. + let response = self + .rt + .block_on(client.client_state(request)) + .map_err(|e| Error::other(Box::new(e)))? + .into_inner(); + + let raw_client_state = response + .client_state + .ok_or_else(Error::empty_response_value)?; + let raw_proof_bytes = response.proof; + // let maybe_proof_height = response.proof_height; + + let client_state: AnyClientState = raw_client_state + .try_into() + .map_err(|e| Error::other(Box::new(e)))?; match include_proof { IncludeProof::Yes => { - let proof = res.proof.ok_or_else(Error::empty_response_proof)?; + // First, check that the raw proof is not empty. + if raw_proof_bytes.is_empty() { + return Err(Error::empty_response_proof()); + } + + // Only then, attempt to deserialize the proof. + let raw_proof = RawMerkleProof::decode::(raw_proof_bytes.into()) + .map_err(|e| Error::other(Box::new(e)))?; + + let proof = raw_proof + .try_into() + .map_err(|e| Error::other(Box::new(e)))?; + Ok((client_state, Some(proof))) } IncludeProof::No => Ok((client_state, None)), @@ -886,19 +915,25 @@ impl ChainEndpoint for PenumbraChain { include_proof: IncludeProof, ) -> Result<(AnyConsensusState, Option), Error> { crate::telemetry!(query, self.id(), "query_consensus_state"); + let mut client = self.ibc_client_grpc_client.clone(); - let res = self.rpc_query( - ClientConsensusStatePath { - client_id: request.client_id.clone(), - epoch: request.consensus_height.revision_number(), - height: request.consensus_height.revision_height(), - }, - request.query_height, - matches!(include_proof, IncludeProof::Yes), - )?; + let request: RawQueryConsensusStatesRequest = request.into(); + let response = self + .rt + .block_on(client.consensus_state(request)) + .map_err(|e| Error::other(Box::new(e)))? + .into_inner(); - let consensus_state = AnyConsensusState::decode_vec(&res.value).map_err(Error::decode)?; + let raw_consensus_state = response + .consensus_state + .ok_or_else(Error::empty_response_value)?; + let raw_proof_bytes = response.proof; + let consensus_state: AnyConsensusState = raw_consensus_state + .try_into() + .map_err(|e| Error::other(Box::new(e)))?; + + // Sanity check that we received a tendermint consensus state. if !matches!(consensus_state, AnyConsensusState::Tendermint(_)) { return Err(Error::consensus_state_type_mismatch( ClientType::Tendermint, @@ -907,11 +942,21 @@ impl ChainEndpoint for PenumbraChain { } match include_proof { + IncludeProof::No => Ok((consensus_state, None)), IncludeProof::Yes => { - let proof = res.proof.ok_or_else(Error::empty_response_proof)?; + if raw_proof_bytes.is_empty() { + return Err(Error::empty_response_proof()); + } + + let raw_proof = RawMerkleProof::decode::(raw_proof_bytes.into()) + .map_err(|e| Error::other(Box::new(e)))?; + + let proof = raw_proof + .try_into() + .map_err(|e| Error::other(Box::new(e)))?; + Ok((consensus_state, Some(proof))) } - IncludeProof::No => Ok((consensus_state, None)), } } From ce0c351d41a5e100677774df067e32c98f12cd73 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Wed, 4 Dec 2024 09:35:27 -0500 Subject: [PATCH 4/9] chain(penumbra): stuff `height` in grpc headers --- crates/relayer/src/chain/penumbra/chain.rs | 26 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/crates/relayer/src/chain/penumbra/chain.rs b/crates/relayer/src/chain/penumbra/chain.rs index 49a331f3ca..5d0871a0ab 100644 --- a/crates/relayer/src/chain/penumbra/chain.rs +++ b/crates/relayer/src/chain/penumbra/chain.rs @@ -863,13 +863,22 @@ impl ChainEndpoint for PenumbraChain { fn query_client_state( &self, - request: QueryClientStateRequest, + req: QueryClientStateRequest, include_proof: IncludeProof, ) -> Result<(AnyClientState, Option), Error> { crate::telemetry!(query, self.id(), "query_client_state"); let mut client = self.ibc_client_grpc_client.clone(); - let request: RawQueryClientStateRequest = request.into(); + let height = match req.height { + QueryHeight::Latest => 0.to_string(), + QueryHeight::Specific(h) => h.to_string(), + }; + + let proto_request: RawQueryClientStateRequest = req.into(); + let mut request = proto_request.into_request(); + request + .metadata_mut() + .insert("height", height.parse().expect("valid height")); // TODO(erwan): for now, playing a bit fast-and-loose with the error handling. let response = self @@ -911,13 +920,22 @@ impl ChainEndpoint for PenumbraChain { fn query_consensus_state( &self, - request: QueryConsensusStateRequest, + req: QueryConsensusStateRequest, include_proof: IncludeProof, ) -> Result<(AnyConsensusState, Option), Error> { crate::telemetry!(query, self.id(), "query_consensus_state"); let mut client = self.ibc_client_grpc_client.clone(); - let request: RawQueryConsensusStatesRequest = request.into(); + let height_str: String = match req.query_height { + QueryHeight::Latest => 0.to_string(), + QueryHeight::Specific(h) => h.to_string(), + }; + + let proto_request: RawQueryConsensusStatesRequest = req.into(); + let mut request = proto_request.into_request(); + request + .metadata_mut() + .insert("height", height_str.parse().expect("valid ascii string")); let response = self .rt .block_on(client.consensus_state(request)) From b393b54c15880b859fc4c60c813a88e2e1cc92d0 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Wed, 4 Dec 2024 10:42:00 -0500 Subject: [PATCH 5/9] chain(penumbra): override `latest_height` flag in consensus state reqs --- crates/relayer/src/chain/penumbra/chain.rs | 12 +++++++---- crates/relayer/src/chain/requests.rs | 23 +++++++--------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/crates/relayer/src/chain/penumbra/chain.rs b/crates/relayer/src/chain/penumbra/chain.rs index 5d0871a0ab..dd3c8e9b64 100644 --- a/crates/relayer/src/chain/penumbra/chain.rs +++ b/crates/relayer/src/chain/penumbra/chain.rs @@ -926,16 +926,21 @@ impl ChainEndpoint for PenumbraChain { crate::telemetry!(query, self.id(), "query_consensus_state"); let mut client = self.ibc_client_grpc_client.clone(); - let height_str: String = match req.query_height { + let height: String = match req.query_height { QueryHeight::Latest => 0.to_string(), QueryHeight::Specific(h) => h.to_string(), }; - let proto_request: RawQueryConsensusStatesRequest = req.into(); + let mut proto_request: RawQueryConsensusStatesRequest = req.into(); + // TODO(erwan): the connection handshake fails when we request the latest height. + // This is ostensibly a bug in hermes, in particular when we build the handshake message. + // However, for now, we can work around this by always overriding the flag to `false`. + proto_request.latest_height = false; + let mut request = proto_request.into_request(); request .metadata_mut() - .insert("height", height_str.parse().expect("valid ascii string")); + .insert("height", height.parse().unwrap()); let response = self .rt .block_on(client.consensus_state(request)) @@ -951,7 +956,6 @@ impl ChainEndpoint for PenumbraChain { .try_into() .map_err(|e| Error::other(Box::new(e)))?; - // Sanity check that we received a tendermint consensus state. if !matches!(consensus_state, AnyConsensusState::Tendermint(_)) { return Err(Error::consensus_state_type_mismatch( ClientType::Tendermint, diff --git a/crates/relayer/src/chain/requests.rs b/crates/relayer/src/chain/requests.rs index 5d7136f6e1..e4f18da511 100644 --- a/crates/relayer/src/chain/requests.rs +++ b/crates/relayer/src/chain/requests.rs @@ -177,22 +177,13 @@ pub struct QueryConsensusStateRequest { impl From for RawQueryConsensusStateRequest { fn from(request: QueryConsensusStateRequest) -> Self { - // We extract the revision number and revision height, unless we are - // querying at the latest height. - if let QueryHeight::Specific(height) = request.query_height { - Self { - client_id: request.client_id.to_string(), - revision_number: height.revision_number(), - revision_height: height.revision_height(), - latest_height: false, - } - } else { - Self { - client_id: request.client_id.to_string(), - revision_number: 0, - revision_height: 0, - latest_height: true, - } + Self { + client_id: request.client_id.to_string(), + // TODO(erwan): not a fan of having two different height representations in the same + // struct. We should probably refactor this. + revision_number: request.consensus_height.revision_number(), + revision_height: request.consensus_height.revision_height(), + latest_height: matches!(request.query_height, QueryHeight::Latest), } } } From c3c975cca60653347f21890f45b9e5880d70e4d5 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Wed, 4 Dec 2024 16:24:04 -0500 Subject: [PATCH 6/9] chain(penumbra): prefer gRPC for proof queries --- crates/relayer/src/chain/penumbra/chain.rs | 201 +++++++++++++++------ crates/relayer/src/chain/requests.rs | 42 +++++ 2 files changed, 189 insertions(+), 54 deletions(-) diff --git a/crates/relayer/src/chain/penumbra/chain.rs b/crates/relayer/src/chain/penumbra/chain.rs index dd3c8e9b64..40a0a0ba8c 100644 --- a/crates/relayer/src/chain/penumbra/chain.rs +++ b/crates/relayer/src/chain/penumbra/chain.rs @@ -4,13 +4,15 @@ use futures::{FutureExt, StreamExt, TryStreamExt}; use http::Uri; use ibc_proto::ics23; +use ibc_proto::ibc::core::channel::v1::QueryPacketAcknowledgementRequest as RawQueryPacketAcknowledgementRequest; +use ibc_proto::ibc::core::channel::v1::QueryPacketCommitmentRequest as RawQueryPacketCommitmentRequest; +use ibc_proto::ibc::core::channel::v1::QueryPacketReceiptRequest as RawQueryPacketReceiptRequest; use ibc_proto::ibc::core::client::v1::QueryClientStateRequest as RawQueryClientStateRequest; use ibc_proto::ibc::core::client::v1::QueryConsensusStateRequest as RawQueryConsensusStatesRequest; +use ibc_proto::ibc::core::connection::v1::QueryConnectionRequest as RawQueryConnectionRequest; use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; -use ibc_relayer_types::core::ics24_host::path::{ - AcksPath, ChannelEndsPath, CommitmentsPath, ReceiptsPath, SeqRecvsPath, -}; +use ibc_relayer_types::core::ics24_host::path::{ChannelEndsPath, SeqRecvsPath}; use ibc_relayer_types::core::ics24_host::Path; use once_cell::sync::Lazy; use penumbra_proto::core::app::v1::AppParametersRequest; @@ -1093,26 +1095,28 @@ impl ChainEndpoint for PenumbraChain { fn query_connection( &self, - request: QueryConnectionRequest, + req: QueryConnectionRequest, include_proof: IncludeProof, ) -> Result<(ConnectionEnd, Option), Error> { + crate::telemetry!(query, self.id(), "query_connection"); let mut client = self.ibc_connection_grpc_client.clone(); - let mut req = ibc_proto::ibc::core::connection::v1::QueryConnectionRequest { - connection_id: request.connection_id.to_string(), - // TODO height is ignored - } - .into_request(); - let map = req.metadata_mut(); - let height_str: String = match request.height { + let height = match req.height { QueryHeight::Latest => 0.to_string(), QueryHeight::Specific(h) => h.to_string(), }; - map.insert("height", height_str.parse().expect("valid ascii string")); + let connection_id = req.connection_id.clone(); + + let proto_request: RawQueryConnectionRequest = req.into(); + let mut request = proto_request.into_request(); + + request + .metadata_mut() + .insert("height", height.parse().unwrap()); - let response = self.rt.block_on(client.connection(req)).map_err(|e| { + let response = self.rt.block_on(client.connection(request)).map_err(|e| { if e.code() == tonic::Code::NotFound { - Error::connection_not_found(request.connection_id.clone()) + Error::connection_not_found(connection_id.clone()) } else { Error::grpc_status(e, "query_connection".to_owned()) } @@ -1126,7 +1130,7 @@ impl ChainEndpoint for PenumbraChain { // the NotFound error code. Nevertheless even if the call is successful, // the connection field may not be present, because in protobuf3 // everything is optional. - return Err(Error::connection_not_found(request.connection_id.clone())); + return Err(Error::connection_not_found(connection_id)); } }; @@ -1245,26 +1249,49 @@ impl ChainEndpoint for PenumbraChain { fn query_packet_commitment( &self, - request: QueryPacketCommitmentRequest, + req: QueryPacketCommitmentRequest, include_proof: IncludeProof, + // TODO(erwan): we can improve the api here. ) -> Result<(Vec, Option), Error> { - let res = self.rpc_query( - CommitmentsPath { - port_id: request.port_id, - channel_id: request.channel_id, - sequence: request.sequence, - }, - request.height, - matches!(include_proof, IncludeProof::Yes), - )?; + crate::telemetry!(query, self.id(), "query_packet_commitment"); + let mut client = self.ibc_channel_grpc_client.clone(); + + let height = match req.height { + QueryHeight::Latest => 0.to_string(), + QueryHeight::Specific(h) => h.to_string(), + }; + let proto_request: RawQueryPacketCommitmentRequest = req.into(); + + let mut request = proto_request.into_request(); + request + .metadata_mut() + .insert("height", height.parse().unwrap()); + + let response = self + .rt + .block_on(client.packet_commitment(request)) + .map_err(|e| Error::grpc_status(e, "query_packet_commitment".to_owned()))? + .into_inner(); + + let packet_commitment = response.commitment; + let raw_proof_bytes = response.proof; match include_proof { + IncludeProof::No => Ok((packet_commitment, None)), IncludeProof::Yes => { - let proof = res.proof.ok_or_else(Error::empty_response_proof)?; + if raw_proof_bytes.is_empty() { + return Err(Error::empty_response_proof()); + } + + let raw_proof = RawMerkleProof::decode::(raw_proof_bytes.into()) + .map_err(|e| Error::other(Box::new(e)))?; + + let proof = raw_proof + .try_into() + .map_err(|e| Error::other(Box::new(e)))?; - Ok((res.value, Some(proof))) + Ok((packet_commitment, Some(proof))) } - IncludeProof::No => Ok((res.value, None)), } } @@ -1298,26 +1325,70 @@ impl ChainEndpoint for PenumbraChain { fn query_packet_receipt( &self, - request: QueryPacketReceiptRequest, + req: QueryPacketReceiptRequest, include_proof: IncludeProof, + // TODO(erwan): This API is very wrong. But I will only fix it later. + // We are querying for a packet receipt, this should be:ƒ + // -> Result, Error> ) -> Result<(Vec, Option), Error> { - let res = self.rpc_query( - ReceiptsPath { - port_id: request.port_id, - channel_id: request.channel_id, - sequence: request.sequence, - }, - request.height, - matches!(include_proof, IncludeProof::Yes), - )?; + crate::telemetry!(query, self.id(), "query_packet_receipt"); + let mut client = self.ibc_channel_grpc_client.clone(); + let height = match req.height { + QueryHeight::Latest => 0.to_string(), + QueryHeight::Specific(h) => h.to_string(), + }; + let port_id = req.port_id.clone(); + let channel_id = req.channel_id.clone(); + let sequence = req.sequence; + + let proto_request: RawQueryPacketReceiptRequest = req.into(); + + let mut request = proto_request.into_request(); + request + .metadata_mut() + .insert("height", height.parse().expect("valid ascii")); + + let response = self + .rt + .block_on(client.packet_receipt(request)) + .map_err(|e| { + if e.code() == tonic::Code::NotFound { + Error::other_with_string(format!( + "packet receipt not found for port_id: {}, channel_id: {}, sequence: {}", + port_id, channel_id, sequence + )) + } else { + Error::grpc_status(e, "query_packet_receipt".to_owned()) + } + })? + .into_inner(); + + let raw_proof_bytes = response.proof; + if !response.received { + // TODO(erwan): not completely clear this should be an error, but this match + // the behavior based on the current implementation where the tonic 404 is treated as an error. + return Err(Error::other_with_string(format!( + "packet receipt not found for port_id: {}, channel_id: {}, sequence: {}", + port_id, channel_id, sequence + ))); + } match include_proof { + IncludeProof::No => Ok((vec![], None)), IncludeProof::Yes => { - let proof = res.proof.ok_or_else(Error::empty_response_proof)?; + if raw_proof_bytes.is_empty() { + return Err(Error::empty_response_proof()); + } + + let raw_proof = RawMerkleProof::decode::(raw_proof_bytes.into()) + .map_err(|e| Error::other(Box::new(e)))?; + + let proof = raw_proof + .try_into() + .map_err(|e| Error::other(Box::new(e)))?; - Ok((res.value, Some(proof))) + Ok((vec![], Some(proof))) } - IncludeProof::No => Ok((res.value, None)), } } @@ -1345,26 +1416,48 @@ impl ChainEndpoint for PenumbraChain { fn query_packet_acknowledgement( &self, - request: QueryPacketAcknowledgementRequest, + req: QueryPacketAcknowledgementRequest, include_proof: IncludeProof, + // TODO(erwan): This API should change. Why are we thrashing raw bytes around? ) -> Result<(Vec, Option), Error> { - let res = self.rpc_query( - AcksPath { - port_id: request.port_id, - channel_id: request.channel_id, - sequence: request.sequence, - }, - request.height, - matches!(include_proof, IncludeProof::Yes), - )?; + crate::telemetry!(query, self.id(), "query_packet_acknowledgement"); + let mut client = self.ibc_channel_grpc_client.clone(); + + let height = match req.height { + QueryHeight::Latest => 0.to_string(), + QueryHeight::Specific(h) => h.to_string(), + }; + + let proto_request: RawQueryPacketAcknowledgementRequest = req.into(); + let mut request = proto_request.into_request(); + request + .metadata_mut() + .insert("height", height.parse().unwrap()); + let response = self + .rt + .block_on(client.packet_acknowledgement(request)) + .map_err(|e| Error::grpc_status(e, "query_packet_acknowledgement".to_owned()))? + .into_inner(); + + let raw_ack = response.acknowledgement; + let raw_proof_bytes = response.proof; match include_proof { + IncludeProof::No => Ok((raw_ack, None)), IncludeProof::Yes => { - let proof = res.proof.ok_or_else(Error::empty_response_proof)?; + if raw_proof_bytes.is_empty() { + return Err(Error::empty_response_proof()); + } + + let raw_proof = RawMerkleProof::decode::(raw_proof_bytes.into()) + .map_err(|e| Error::other(Box::new(e)))?; + + let proof = raw_proof + .try_into() + .map_err(|e| Error::other(Box::new(e)))?; - Ok((res.value, Some(proof))) + Ok((raw_ack, Some(proof))) } - IncludeProof::No => Ok((res.value, None)), } } diff --git a/crates/relayer/src/chain/requests.rs b/crates/relayer/src/chain/requests.rs index e4f18da511..878ef05059 100644 --- a/crates/relayer/src/chain/requests.rs +++ b/crates/relayer/src/chain/requests.rs @@ -8,8 +8,11 @@ use ibc_proto::{ QueryChannelsRequest as RawQueryChannelsRequest, QueryConnectionChannelsRequest as RawQueryConnectionChannelsRequest, QueryNextSequenceReceiveRequest as RawQueryNextSequenceReceiveRequest, + QueryPacketAcknowledgementRequest as RawQueryPacketAcknowledgementRequest, QueryPacketAcknowledgementsRequest as RawQueryPacketAcknowledgementsRequest, + QueryPacketCommitmentRequest as RawQueryPacketCommitmentRequest, QueryPacketCommitmentsRequest as RawQueryPacketCommitmentsRequest, + QueryPacketReceiptRequest as RawQueryPacketReceiptRequest, QueryUnreceivedAcksRequest as RawQueryUnreceivedAcksRequest, QueryUnreceivedPacketsRequest as RawQueryUnreceivedPacketsRequest, }, @@ -22,6 +25,7 @@ use ibc_proto::{ }, connection::v1::{ QueryClientConnectionsRequest as RawQueryClientConnectionsRequest, + QueryConnectionRequest as RawQueryConnectionRequest, QueryConnectionsRequest as RawQueryConnectionsRequest, }, }, @@ -264,6 +268,14 @@ pub struct QueryConnectionRequest { pub height: QueryHeight, } +impl From for RawQueryConnectionRequest { + fn from(request: QueryConnectionRequest) -> Self { + Self { + connection_id: request.connection_id.to_string(), + } + } +} + /// gRPC query to fetch all channels associated with the specified connection. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct QueryConnectionChannelsRequest { @@ -325,6 +337,16 @@ pub struct QueryPacketCommitmentRequest { pub height: QueryHeight, } +impl From for RawQueryPacketCommitmentRequest { + fn from(request: QueryPacketCommitmentRequest) -> Self { + RawQueryPacketCommitmentRequest { + port_id: request.port_id.to_string(), + channel_id: request.channel_id.to_string(), + sequence: request.sequence.into(), + } + } +} + /// gRPC query to fetch the packet commitment hashes associated with the specified channel. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct QueryPacketCommitmentsRequest { @@ -351,6 +373,16 @@ pub struct QueryPacketReceiptRequest { pub height: QueryHeight, } +impl From for RawQueryPacketReceiptRequest { + fn from(request: QueryPacketReceiptRequest) -> Self { + Self { + port_id: request.port_id.to_string(), + channel_id: request.channel_id.to_string(), + sequence: request.sequence.as_u64(), + } + } +} + /// gRPC query to fetch all unreceived packet sequences associated with the specified channel. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct QueryUnreceivedPacketsRequest { @@ -381,6 +413,16 @@ pub struct QueryPacketAcknowledgementRequest { pub height: QueryHeight, } +impl From for RawQueryPacketAcknowledgementRequest { + fn from(request: QueryPacketAcknowledgementRequest) -> Self { + RawQueryPacketAcknowledgementRequest { + port_id: request.port_id.to_string(), + channel_id: request.channel_id.to_string(), + sequence: request.sequence.as_u64(), + } + } +} + /// gRPC query to fetch all packet acknowledgements associated with the specified channel. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct QueryPacketAcknowledgementsRequest { From d12bf4d4fe0c975eeea57b5c83e02628035ed07e Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Thu, 5 Dec 2024 12:16:38 -0500 Subject: [PATCH 7/9] chain(requests): add conversion trait for `QueryChannelRequest` --- crates/relayer/src/chain/requests.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/relayer/src/chain/requests.rs b/crates/relayer/src/chain/requests.rs index 878ef05059..e19faf726e 100644 --- a/crates/relayer/src/chain/requests.rs +++ b/crates/relayer/src/chain/requests.rs @@ -5,6 +5,7 @@ use ibc_proto::{ ibc::core::{ channel::v1::{ QueryChannelClientStateRequest as RawQueryChannelClientStateRequest, + QueryChannelRequest as RawQueryChannelRequest, QueryChannelsRequest as RawQueryChannelsRequest, QueryConnectionChannelsRequest as RawQueryConnectionChannelsRequest, QueryNextSequenceReceiveRequest as RawQueryNextSequenceReceiveRequest, @@ -313,6 +314,15 @@ pub struct QueryChannelRequest { pub height: QueryHeight, } +impl From for RawQueryChannelRequest { + fn from(request: QueryChannelRequest) -> Self { + Self { + port_id: request.port_id.to_string(), + channel_id: request.channel_id.to_string(), + } + } +} + /// gRPC request to fetch the client state associated with a specified channel. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct QueryChannelClientStateRequest { From 48940c4c2c4dc3ed65b4099ac91db4b17eab3d04 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Thu, 5 Dec 2024 12:18:27 -0500 Subject: [PATCH 8/9] chain(penumbra): rely on gRPC for chan_open handshake --- crates/relayer/src/chain/penumbra/chain.rs | 109 ++++++++++++++------- 1 file changed, 74 insertions(+), 35 deletions(-) diff --git a/crates/relayer/src/chain/penumbra/chain.rs b/crates/relayer/src/chain/penumbra/chain.rs index 40a0a0ba8c..87be2625b4 100644 --- a/crates/relayer/src/chain/penumbra/chain.rs +++ b/crates/relayer/src/chain/penumbra/chain.rs @@ -1203,27 +1203,52 @@ impl ChainEndpoint for PenumbraChain { Ok(channels) } + /// identifier. A proof can optionally be returned along with the result. fn query_channel( &self, - request: QueryChannelRequest, + req: QueryChannelRequest, include_proof: IncludeProof, ) -> Result<(ChannelEnd, Option), Error> { - crate::telemetry!(query, self.id(), "query_channel"); + let mut client = self.ibc_channel_grpc_client.clone(); - let res = self.rpc_query( - ChannelEndsPath(request.port_id, request.channel_id), - request.height, - matches!(include_proof, IncludeProof::Yes), - )?; + let height = match req.height { + QueryHeight::Latest => 0.to_string(), + QueryHeight::Specific(h) => h.to_string(), + }; + + let proto_request: RawQueryChannelRequest = req.into(); + let mut request = proto_request.into_request(); + request + .metadata_mut() + .insert("height", height.parse().unwrap()); + + let response = self + .rt + .block_on(client.channel(request)) + .map_err(|e| Error::grpc_status(e, "query_channel".to_owned()))? + .into_inner(); + + let channel = response.channel.ok_or_else(Error::empty_response_value)?; + let channel_end: ChannelEnd = channel.try_into().map_err(|e| Error::other(Box::new(e)))?; - let channel_end = ChannelEnd::decode_vec(&res.value).map_err(Error::decode)?; + let raw_proof_bytes = response.proof; match include_proof { + IncludeProof::No => Ok((channel_end, None)), IncludeProof::Yes => { - let proof = res.proof.ok_or_else(Error::empty_response_proof)?; + if raw_proof_bytes.is_empty() { + return Err(Error::empty_response_proof()); + } + + let raw_proof = RawMerkleProof::decode::(raw_proof_bytes.into()) + .map_err(|e| Error::other(Box::new(e)))?; + + let proof = raw_proof + .try_into() + .map_err(|e| Error::other(Box::new(e)))?; + Ok((channel_end, Some(proof))) } - IncludeProof::No => Ok((channel_end, None)), } } @@ -1511,7 +1536,7 @@ impl ChainEndpoint for PenumbraChain { fn query_next_sequence_receive( &self, - request: QueryNextSequenceReceiveRequest, + req: QueryNextSequenceReceiveRequest, include_proof: IncludeProof, ) -> Result<(Sequence, Option), Error> { crate::time!( @@ -1521,38 +1546,52 @@ impl ChainEndpoint for PenumbraChain { } ); crate::telemetry!(query, self.id(), "query_next_sequence_receive"); + let mut client = self.ibc_channel_grpc_client.clone(); + + let height = match req.height { + QueryHeight::Latest => 0.to_string(), + QueryHeight::Specific(h) => h.to_string(), + }; + + let proto_request: RawQueryNextSequenceReceiveRequest = req.into(); + let mut request = proto_request.into_request(); + request + .metadata_mut() + .insert("height", height.parse().unwrap()); + + let response = self + .rt + .block_on(client.next_sequence_receive(request)) + .map_err(|e| Error::grpc_status(e, "query_next_sequence_receive".to_owned()))? + .into_inner(); + + // TODO(erwan): previously, there was a comment explaining that we expect + // a u64 encoded in big-endian in the ABCI query branch. Now that we use + // gRPC for this query, we shouldn't have to worry about that (this also match previous behavior + // when a proof is *not* requested). Nevertheless. I will keep the comment here for now: + // ``` + // Note: We expect the return to be a u64 encoded in big-endian. Refer to ibc-go: + // https://github.com/cosmos/ibc-go/blob/25767f6bdb5bab2c2a116b41d92d753c93e18121/modules/core/04-channel/client/utils/utils.go#L191 + // ``` + let next_seq: Sequence = response.next_sequence_receive.into(); + let raw_proof_bytes = response.proof; match include_proof { IncludeProof::Yes => { - let res = self.rpc_query( - SeqRecvsPath(request.port_id, request.channel_id), - request.height, - true, - )?; - - // Note: We expect the return to be a u64 encoded in big-endian. Refer to ibc-go: - // https://github.com/cosmos/ibc-go/blob/25767f6bdb5bab2c2a116b41d92d753c93e18121/modules/core/04-channel/client/utils/utils.go#L191 - if res.value.len() != 8 { - return Err(Error::query("next_sequence_receive".into())); + if raw_proof_bytes.is_empty() { + return Err(Error::empty_response_proof()); } - let seq: Sequence = Bytes::from(res.value).get_u64().into(); - let proof = res.proof.ok_or_else(Error::empty_response_proof)?; - - Ok((seq, Some(proof))) - } - IncludeProof::No => { - let mut client = self.ibc_channel_grpc_client.clone(); - let request = tonic::Request::new(request.into()); + let raw_proof = RawMerkleProof::decode::(raw_proof_bytes.into()) + .map_err(|e| Error::other(Box::new(e)))?; - let response = self - .rt - .block_on(client.next_sequence_receive(request)) - .map_err(|e| Error::grpc_status(e, "query_next_sequence_receive".to_owned()))? - .into_inner(); + let proof = raw_proof + .try_into() + .map_err(|e| Error::other(Box::new(e)))?; - Ok((response.next_sequence_receive.into(), None)) + Ok((next_seq, Some(proof))) } + IncludeProof::No => Ok((next_seq, None)), } } From b18603b3640403661f1ffc7c4e5b88eee19b5462 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Thu, 5 Dec 2024 12:18:49 -0500 Subject: [PATCH 9/9] chain(penumbra): fmt --- crates/relayer/src/chain/penumbra/chain.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/relayer/src/chain/penumbra/chain.rs b/crates/relayer/src/chain/penumbra/chain.rs index 87be2625b4..7b3fb02553 100644 --- a/crates/relayer/src/chain/penumbra/chain.rs +++ b/crates/relayer/src/chain/penumbra/chain.rs @@ -1,9 +1,11 @@ use anyhow::Context; -use bytes::{Buf, Bytes}; +use bytes::Bytes; use futures::{FutureExt, StreamExt, TryStreamExt}; use http::Uri; use ibc_proto::ics23; +use ibc_proto::ibc::core::channel::v1::QueryChannelRequest as RawQueryChannelRequest; +use ibc_proto::ibc::core::channel::v1::QueryNextSequenceReceiveRequest as RawQueryNextSequenceReceiveRequest; use ibc_proto::ibc::core::channel::v1::QueryPacketAcknowledgementRequest as RawQueryPacketAcknowledgementRequest; use ibc_proto::ibc::core::channel::v1::QueryPacketCommitmentRequest as RawQueryPacketCommitmentRequest; use ibc_proto::ibc::core::channel::v1::QueryPacketReceiptRequest as RawQueryPacketReceiptRequest; @@ -12,8 +14,6 @@ use ibc_proto::ibc::core::client::v1::QueryConsensusStateRequest as RawQueryCons use ibc_proto::ibc::core::connection::v1::QueryConnectionRequest as RawQueryConnectionRequest; use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; -use ibc_relayer_types::core::ics24_host::path::{ChannelEndsPath, SeqRecvsPath}; -use ibc_relayer_types::core::ics24_host::Path; use once_cell::sync::Lazy; use penumbra_proto::core::app::v1::AppParametersRequest; use penumbra_proto::core::component::ibc::v1::IbcRelay as ProtoIbcRelay; @@ -23,11 +23,10 @@ use std::str::FromStr; use std::sync::Arc; use std::thread; use std::time::Duration; -use tendermint_proto::Protobuf; use tracing::info; use crate::chain::client::ClientSettings; -use crate::chain::cosmos::query::{abci_query, fetch_version_specs, QueryResponse}; +use crate::chain::cosmos::query::fetch_version_specs; use crate::chain::endpoint::ChainStatus; use crate::chain::requests::*; use crate::chain::tracking::TrackedMsgs;