From 6f33817bba216445e6dbdb17af1502e339d61a79 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Fri, 15 Dec 2023 00:12:45 +0900 Subject: [PATCH 1/6] implement ELC message aggregation * add context aggregation tests * add UpdateClientMessage aggregation tests * improve handler validations * improve error messages * improve update client aggregation tests Signed-off-by: Jun Kimura --- .../src/light_client/aggregate_messages.rs | 63 ++++ .../ecall-handler/src/light_client/errors.rs | 12 + .../ecall-handler/src/light_client/mod.rs | 2 + .../ecall-handler/src/light_client/router.rs | 4 +- modules/commitments/src/context.rs | 190 +++++++++++- modules/commitments/src/errors.rs | 16 + modules/commitments/src/lib.rs | 3 +- modules/commitments/src/message.rs | 2 +- .../commitments/src/message/update_client.rs | 284 ++++++++++++++++++ modules/ecall-commands/src/lib.rs | 9 +- modules/ecall-commands/src/light_client.rs | 15 +- modules/ecall-commands/src/msgs.rs | 30 +- modules/enclave-api/src/api/command.rs | 25 +- modules/enclave-api/src/api/proto.rs | 16 +- modules/service/src/elc.rs | 18 +- proto/definitions/lcp/service/elc/v1/tx.proto | 21 ++ proto/src/descriptor.bin | Bin 98399 -> 99425 bytes proto/src/prost/lcp.service.elc.v1.rs | 88 ++++++ 18 files changed, 772 insertions(+), 26 deletions(-) create mode 100644 enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs diff --git a/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs b/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs new file mode 100644 index 00000000..b57eb2c7 --- /dev/null +++ b/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs @@ -0,0 +1,63 @@ +use crate::light_client::Error; +use crate::prelude::*; +use context::Context; +use crypto::{EnclavePublicKey, Signer, Verifier}; +use ecall_commands::{AggregateMessagesInput, AggregateMessagesResult, LightClientResult}; +use light_client::{ + commitments::{self, prove_commitment, Message, UpdateClientMessage}, + LightClientResolver, +}; +use store::KVStore; + +pub fn aggregate_messages( + ctx: &mut Context, + input: AggregateMessagesInput, +) -> Result { + ctx.set_timestamp(input.current_timestamp); + + if input.messages.len() < 2 { + return Err(Error::invalid_argument( + "messages and signatures must have at least 2 elements".into(), + )); + } + if input.messages.len() != input.signatures.len() { + return Err(Error::invalid_argument( + "messages and signatures must have the same length".into(), + )); + } + + let ek = ctx.get_enclave_key(); + let pk = ek.pubkey().map_err(Error::crypto)?; + + let messages = input + .messages + .into_iter() + .map(|c| Message::from_bytes(&c)?.try_into()) + .collect::, _>>()? + .into_iter() + .zip(input.signatures.iter()) + .map(|(c, s)| -> Result<_, Error> { + verify_commitment(&pk, &c, s)?; + Ok(c) + }) + .collect::, _>>()?; + + let message = Message::from(commitments::aggregate_messages(messages)?); + let proof = prove_commitment(ek, input.signer, message)?; + + Ok(LightClientResult::AggregateMessages( + AggregateMessagesResult(proof), + )) +} + +fn verify_commitment( + verifier: &EnclavePublicKey, + commitment: &UpdateClientMessage, + signature: &[u8], +) -> Result<(), Error> { + let message_bytes = Message::UpdateClient(commitment.clone()).to_bytes(); + verifier + .verify(&message_bytes, signature) + .map_err(Error::crypto)?; + Ok(()) +} diff --git a/enclave-modules/ecall-handler/src/light_client/errors.rs b/enclave-modules/ecall-handler/src/light_client/errors.rs index 6f505d8e..c1b4644d 100644 --- a/enclave-modules/ecall-handler/src/light_client/errors.rs +++ b/enclave-modules/ecall-handler/src/light_client/errors.rs @@ -4,6 +4,14 @@ use flex_error::*; define_error! { #[derive(Debug, PartialEq, Eq)] Error { + InvalidArgument + { + descr: String + } + |e| { + format_args!("invalid argument: descr={}", e.descr) + }, + SealedEnclaveKeyNotFound |_| { "Sealed EnclaveKey not found" }, @@ -19,6 +27,10 @@ define_error! { [light_client::commitments::Error] |_| { "Commitment error" }, + Crypto + [crypto::Error] + |_| { "Crypto error" }, + LcpType {} [lcp_types::TypeError] diff --git a/enclave-modules/ecall-handler/src/light_client/mod.rs b/enclave-modules/ecall-handler/src/light_client/mod.rs index 41dc3fcc..f8ad250d 100644 --- a/enclave-modules/ecall-handler/src/light_client/mod.rs +++ b/enclave-modules/ecall-handler/src/light_client/mod.rs @@ -1,3 +1,4 @@ +pub use aggregate_messages::aggregate_messages; pub use errors::Error; pub use init_client::init_client; pub use query::query_client; @@ -5,6 +6,7 @@ pub use router::dispatch; pub use update_client::update_client; pub use verify_state::{verify_membership, verify_non_membership}; +mod aggregate_messages; mod errors; mod init_client; mod query; diff --git a/enclave-modules/ecall-handler/src/light_client/router.rs b/enclave-modules/ecall-handler/src/light_client/router.rs index fc3c7dca..972910f0 100644 --- a/enclave-modules/ecall-handler/src/light_client/router.rs +++ b/enclave-modules/ecall-handler/src/light_client/router.rs @@ -1,5 +1,6 @@ use crate::light_client::{ - init_client, query_client, update_client, verify_membership, verify_non_membership, Error, + aggregate_messages, init_client, query_client, update_client, verify_membership, + verify_non_membership, Error, }; use context::Context; use crypto::NopSigner; @@ -25,6 +26,7 @@ pub fn dispatch( match cmd { InitClient(input) => init_client(&mut ctx, input)?, UpdateClient(input) => update_client(&mut ctx, input)?, + AggregateMessages(input) => aggregate_messages(&mut ctx, input)?, VerifyMembership(input) => verify_membership(&mut ctx, input)?, VerifyNonMembership(input) => verify_non_membership(&mut ctx, input)?, } diff --git a/modules/commitments/src/context.rs b/modules/commitments/src/context.rs index 6a4e310d..21c3f943 100644 --- a/modules/commitments/src/context.rs +++ b/modules/commitments/src/context.rs @@ -8,7 +8,7 @@ pub const VALIDATION_CONTEXT_TYPE_EMPTY_EMPTY: u16 = 0; pub const VALIDATION_CONTEXT_TYPE_EMPTY_WITHIN_TRUSTING_PERIOD: u16 = 1; pub const VALIDATION_CONTEXT_HEADER_SIZE: usize = 32; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ValidationContext { Empty, TrustingPeriod(TrustingPeriodContext), @@ -41,6 +41,42 @@ impl ValidationContext { header } + pub fn aggregate(self, other: Self) -> Result { + match (self, other) { + (Self::Empty, Self::Empty) => Ok(Self::Empty), + (Self::Empty, Self::TrustingPeriod(ctx)) => Ok(Self::TrustingPeriod(ctx)), + (Self::TrustingPeriod(ctx), Self::Empty) => Ok(Self::TrustingPeriod(ctx)), + (Self::TrustingPeriod(ctx1), Self::TrustingPeriod(ctx2)) => { + if ctx1.trusting_period != ctx2.trusting_period { + return Err(Error::context_aggregation_failed(format!( + "trusting_period mismatch: ctx1={:?} ctx2={:?}", + ctx1.trusting_period, ctx2.trusting_period, + ))); + } + if ctx1.clock_drift != ctx2.clock_drift { + return Err(Error::context_aggregation_failed(format!( + "clock_drift mismatch: ctx1={:?} ctx2={:?}", + ctx1.clock_drift, ctx2.clock_drift + ))); + } + Ok(Self::TrustingPeriod(TrustingPeriodContext::new( + ctx1.trusting_period, + ctx1.clock_drift, + if ctx1.untrusted_header_timestamp > ctx2.untrusted_header_timestamp { + ctx1.untrusted_header_timestamp + } else { + ctx2.untrusted_header_timestamp + }, + if ctx1.trusted_state_timestamp < ctx2.trusted_state_timestamp { + ctx1.trusted_state_timestamp + } else { + ctx2.trusted_state_timestamp + }, + ))) + } + } + } + fn parse_context_type_from_header(header_bytes: &[u8]) -> Result { if header_bytes.len() != VALIDATION_CONTEXT_HEADER_SIZE { return Err(Error::invalid_validation_context_header(format!( @@ -150,7 +186,7 @@ impl Display for ValidationContext { } /// NOTE: time precision is in seconds (i.e. nanoseconds are truncated) -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct TrustingPeriodContext { /// How long a validator set is trusted for (must be shorter than the chain's /// unbonding period) @@ -537,4 +573,154 @@ mod tests { validate_and_assert_no_error(ctx, current_timestamp); } } + + #[test] + fn test_validation_context_aggregation() { + { + let ctx0 = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-19 0:00 UTC), + datetime!(2023-08-19 0:00 UTC), + )); + let ctx1 = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-20 0:00 UTC), + )); + let expected = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-19 0:00 UTC), + )); + let res = ctx0.aggregate(ctx1); + if let Ok(ctx) = res { + assert_eq!(ctx, expected); + } else { + panic!("{:?}", res); + } + } + + { + let ctx0 = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-20 0:00 UTC), + )); + let ctx1 = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-19 0:00 UTC), + datetime!(2023-08-19 0:00 UTC), + )); + let expected = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-19 0:00 UTC), + )); + let res = ctx0.aggregate(ctx1); + if let Ok(ctx) = res { + assert_eq!(ctx, expected); + } else { + panic!("{:?}", res); + } + } + + { + let ctx0 = ValidationContext::from(build_trusting_period_context( + 2, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-20 0:00 UTC), + )); + let ctx1 = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-20 0:00 UTC), + )); + let res = ctx0.aggregate(ctx1); + assert!(res.is_err()); + } + + { + let ctx0 = ValidationContext::from(build_trusting_period_context( + 1, + 2, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-20 0:00 UTC), + )); + let ctx1 = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-20 0:00 UTC), + )); + let res = ctx0.aggregate(ctx1); + assert!(res.is_err()); + } + } + + #[test] + fn test_validation_context_and_empty_aggregation() { + { + let ctx0 = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-20 0:00 UTC), + )); + let ctx1 = ValidationContext::Empty; + let expected = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-20 0:00 UTC), + )); + let res = ctx0.aggregate(ctx1); + if let Ok(ctx) = res { + assert_eq!(ctx, expected); + } else { + panic!("{:?}", res); + } + } + + { + let ctx0 = ValidationContext::Empty; + let ctx1 = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-20 0:00 UTC), + )); + let expected = ValidationContext::from(build_trusting_period_context( + 1, + 1, + datetime!(2023-08-20 0:00 UTC), + datetime!(2023-08-20 0:00 UTC), + )); + let res = ctx0.aggregate(ctx1); + if let Ok(ctx) = res { + assert_eq!(ctx, expected); + } else { + panic!("{:?}", res); + } + } + } + + #[test] + fn test_empty_context_aggregation() { + let ctx0 = ValidationContext::Empty; + let ctx1 = ValidationContext::Empty; + let res = ctx0.aggregate(ctx1); + if let Ok(ctx) = res { + assert_eq!(ctx, ValidationContext::Empty); + } else { + panic!("{:?}", res); + } + } } diff --git a/modules/commitments/src/errors.rs b/modules/commitments/src/errors.rs index 321bb0fb..7b677125 100644 --- a/modules/commitments/src/errors.rs +++ b/modules/commitments/src/errors.rs @@ -95,6 +95,22 @@ define_error! { format_args!("not truncated timestamp: timestamp_nanos={}", e.timestamp_nanos) }, + MessageAggregationFailed + { + descr: String + } + |e| { + format_args!("message aggregation failed: descr={}", e.descr) + }, + + ContextAggregationFailed + { + descr: String + } + |e| { + format_args!("context aggregation failed: descr={}", e.descr) + }, + LcpType {} [lcp_types::TypeError] diff --git a/modules/commitments/src/lib.rs b/modules/commitments/src/lib.rs index 918cb3b6..a1de0530 100644 --- a/modules/commitments/src/lib.rs +++ b/modules/commitments/src/lib.rs @@ -23,7 +23,8 @@ pub use context::{TrustingPeriodContext, ValidationContext}; pub use encoder::EthABIEncoder; pub use errors::Error; pub use message::{ - CommitmentPrefix, EmittedState, Message, UpdateClientMessage, VerifyMembershipMessage, + aggregate_messages, CommitmentPrefix, EmittedState, Message, UpdateClientMessage, + VerifyMembershipMessage, }; pub use proof::CommitmentProof; pub use prover::prove_commitment; diff --git a/modules/commitments/src/message.rs b/modules/commitments/src/message.rs index 7ffc560d..7176bb9f 100644 --- a/modules/commitments/src/message.rs +++ b/modules/commitments/src/message.rs @@ -1,4 +1,4 @@ -pub use self::update_client::{EmittedState, UpdateClientMessage}; +pub use self::update_client::{aggregate_messages, EmittedState, UpdateClientMessage}; pub use self::verify_membership::{CommitmentPrefix, VerifyMembershipMessage}; use crate::encoder::EthABIEncoder; use crate::prelude::*; diff --git a/modules/commitments/src/message/update_client.rs b/modules/commitments/src/message/update_client.rs index eb8c5a25..57d10980 100644 --- a/modules/commitments/src/message/update_client.rs +++ b/modules/commitments/src/message/update_client.rs @@ -21,6 +21,34 @@ pub struct UpdateClientMessage { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct EmittedState(pub Height, pub Any); +impl UpdateClientMessage { + pub fn aggregate(self, other: Self) -> Result { + if self.post_state_id != other.prev_state_id.unwrap_or_default() { + return Err(Error::message_aggregation_failed(format!( + "invalid prev_state_id: expected={} actual={}", + self.post_state_id, + other.prev_state_id.unwrap_or_default() + ))); + } + if self.post_height != other.prev_height.unwrap_or_default() { + return Err(Error::message_aggregation_failed(format!( + "invalid prev_height: expected={} actual={}", + self.post_height, + other.prev_height.unwrap_or_default() + ))); + } + Ok(Self { + prev_height: self.prev_height, + prev_state_id: self.prev_state_id, + post_height: other.post_height, + post_state_id: other.post_state_id, + timestamp: other.timestamp, + context: self.context.aggregate(other.context)?, + emitted_states: [self.emitted_states, other.emitted_states].concat(), + }) + } +} + impl Display for UpdateClientMessage { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( @@ -37,6 +65,23 @@ impl Display for UpdateClientMessage { } } +/// Aggregate a list of messages into a single message +pub fn aggregate_messages( + messages: Vec, +) -> Result { + if messages.is_empty() { + return Err(Error::message_aggregation_failed( + "cannot aggregate empty messages".to_string(), + )); + } + let mut messages = messages.into_iter(); + let mut message = messages.next().unwrap(); + for m in messages { + message = message.aggregate(m)?; + } + Ok(message) +} + /// the struct is encoded as a tuple of 7 elements pub(crate) struct EthABIUpdateClientMessage { pub prev_height: EthABIHeight, // (u64, u64) @@ -161,3 +206,242 @@ impl EthABIEncoder for UpdateClientMessage { EthABIUpdateClientMessage::decode(bz).and_then(|v| v.try_into()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::TrustingPeriodContext; + use core::time::Duration; + + #[test] + fn test_update_client_message_aggregation() { + { + let msg0 = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(2, 2), + post_state_id: StateID::from([2u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(1).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + let msg1 = UpdateClientMessage { + prev_height: Some(Height::new(2, 2)), + prev_state_id: Some(StateID::from([2u8; 32])), + post_height: Height::new(3, 3), + post_state_id: StateID::from([3u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(2).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + let expected = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(3, 3), + post_state_id: StateID::from([3u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(2).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + assert_eq!(aggregate_messages(vec![msg0, msg1]).unwrap(), expected); + } + { + let msg0 = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(2, 2), + post_state_id: StateID::from([2u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(1).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![EmittedState( + Height::new(1, 1), + Any::new("/foo".to_string(), vec![1u8; 32]), + )], + }; + let msg1 = UpdateClientMessage { + prev_height: Some(Height::new(2, 2)), + prev_state_id: Some(StateID::from([2u8; 32])), + post_height: Height::new(3, 3), + post_state_id: StateID::from([3u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(2).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![EmittedState( + Height::new(2, 2), + Any::new("/bar".to_string(), vec![2u8; 32]), + )], + }; + let expected = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(3, 3), + post_state_id: StateID::from([3u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(2).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![ + EmittedState( + Height::new(1, 1), + Any::new("/foo".to_string(), vec![1u8; 32]), + ), + EmittedState( + Height::new(2, 2), + Any::new("/bar".to_string(), vec![2u8; 32]), + ), + ], + }; + assert_eq!(aggregate_messages(vec![msg0, msg1]).unwrap(), expected); + } + { + // trusting period aggregation + let msg0 = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(2, 2), + post_state_id: StateID::from([2u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(1).unwrap(), + context: TrustingPeriodContext::new( + Duration::from_secs(1), + Duration::from_secs(2), + Time::from_unix_timestamp_nanos(1).unwrap(), + Time::from_unix_timestamp_nanos(2).unwrap(), + ) + .into(), + emitted_states: vec![], + }; + let msg1 = UpdateClientMessage { + prev_height: Some(Height::new(2, 2)), + prev_state_id: Some(StateID::from([2u8; 32])), + post_height: Height::new(3, 3), + post_state_id: StateID::from([3u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(2).unwrap(), + context: TrustingPeriodContext::new( + Duration::from_secs(1), + Duration::from_secs(2), + Time::from_unix_timestamp_nanos(2).unwrap(), + Time::from_unix_timestamp_nanos(3).unwrap(), + ) + .into(), + emitted_states: vec![], + }; + let expected = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(3, 3), + post_state_id: StateID::from([3u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(2).unwrap(), + context: TrustingPeriodContext::new( + Duration::from_secs(1), + Duration::from_secs(2), + Time::from_unix_timestamp_nanos(2).unwrap(), + Time::from_unix_timestamp_nanos(2).unwrap(), + ) + .into(), + emitted_states: vec![], + }; + assert_eq!(aggregate_messages(vec![msg0, msg1]).unwrap(), expected); + } + { + // invalid prev_state_id + let msg0 = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(2, 2), + post_state_id: StateID::from([2u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(1).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + let msg1 = UpdateClientMessage { + prev_height: Some(Height::new(2, 2)), + prev_state_id: Some(StateID::from([3u8; 32])), + post_height: Height::new(3, 3), + post_state_id: StateID::from([3u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(2).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + assert!(msg0.aggregate(msg1).is_err()); + } + { + // invalid prev_height + let msg0 = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(2, 2), + post_state_id: StateID::from([2u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(1).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + let msg1 = UpdateClientMessage { + prev_height: Some(Height::new(3, 3)), + prev_state_id: Some(StateID::from([2u8; 32])), + post_height: Height::new(3, 3), + post_state_id: StateID::from([3u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(2).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + assert!(msg0.aggregate(msg1).is_err()); + } + { + // empty messages + assert!(aggregate_messages(vec![]).is_err()); + } + { + // single message + let msg0 = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(2, 2), + post_state_id: StateID::from([2u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(1).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + assert_eq!(aggregate_messages(vec![msg0.clone()]).unwrap(), msg0); + } + { + // three messages + let msg0 = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(2, 2), + post_state_id: StateID::from([2u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(1).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + let msg1 = UpdateClientMessage { + prev_height: Some(Height::new(2, 2)), + prev_state_id: Some(StateID::from([2u8; 32])), + post_height: Height::new(3, 3), + post_state_id: StateID::from([3u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(2).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + let msg2 = UpdateClientMessage { + prev_height: Some(Height::new(3, 3)), + prev_state_id: Some(StateID::from([3u8; 32])), + post_height: Height::new(4, 4), + post_state_id: StateID::from([4u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(3).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + let expected = UpdateClientMessage { + prev_height: Some(Height::new(1, 1)), + prev_state_id: Some(StateID::from([1u8; 32])), + post_height: Height::new(4, 4), + post_state_id: StateID::from([4u8; 32]), + timestamp: Time::from_unix_timestamp_nanos(3).unwrap(), + context: ValidationContext::default(), + emitted_states: vec![], + }; + assert_eq!( + aggregate_messages(vec![msg0, msg1, msg2]).unwrap(), + expected + ); + } + } +} diff --git a/modules/ecall-commands/src/lib.rs b/modules/ecall-commands/src/lib.rs index 6a6d04e6..fd30e5ef 100644 --- a/modules/ecall-commands/src/lib.rs +++ b/modules/ecall-commands/src/lib.rs @@ -30,10 +30,11 @@ pub use enclave_manage::{ pub use enclave_manage::{SimulateRemoteAttestationInput, SimulateRemoteAttestationResult}; pub use errors::InputValidationError; pub use light_client::{ - CommitmentProofPair, InitClientInput, InitClientResult, LightClientCommand, - LightClientExecuteCommand, LightClientQueryCommand, LightClientResult, QueryClientInput, - QueryClientResult, UpdateClientInput, UpdateClientResult, VerifyMembershipInput, - VerifyMembershipResult, VerifyNonMembershipInput, VerifyNonMembershipResult, + AggregateMessagesInput, AggregateMessagesResult, CommitmentProofPair, InitClientInput, + InitClientResult, LightClientCommand, LightClientExecuteCommand, LightClientQueryCommand, + LightClientResult, QueryClientInput, QueryClientResult, UpdateClientInput, UpdateClientResult, + VerifyMembershipInput, VerifyMembershipResult, VerifyNonMembershipInput, + VerifyNonMembershipResult, }; mod commands; diff --git a/modules/ecall-commands/src/light_client.rs b/modules/ecall-commands/src/light_client.rs index b64f71cf..7499ebbc 100644 --- a/modules/ecall-commands/src/light_client.rs +++ b/modules/ecall-commands/src/light_client.rs @@ -14,6 +14,7 @@ pub enum LightClientCommand { pub enum LightClientExecuteCommand { InitClient(InitClientInput), UpdateClient(UpdateClientInput), + AggregateMessages(AggregateMessagesInput), VerifyMembership(VerifyMembershipInput), VerifyNonMembership(VerifyNonMembershipInput), } @@ -29,6 +30,7 @@ impl EnclaveKeySelector for LightClientCommand { Self::Execute(cmd) => match cmd { LightClientExecuteCommand::InitClient(input) => Some(input.signer), LightClientExecuteCommand::UpdateClient(input) => Some(input.signer), + LightClientExecuteCommand::AggregateMessages(input) => Some(input.signer), LightClientExecuteCommand::VerifyMembership(input) => Some(input.signer), LightClientExecuteCommand::VerifyNonMembership(input) => Some(input.signer), }, @@ -54,6 +56,14 @@ pub struct UpdateClientInput { pub signer: Address, } +#[derive(Serialize, Deserialize, Debug)] +pub struct AggregateMessagesInput { + pub signer: Address, + pub messages: Vec>, + pub signatures: Vec>, + pub current_timestamp: Time, +} + #[derive(Serialize, Deserialize, Debug)] pub struct VerifyMembershipInput { pub client_id: ClientId, @@ -85,6 +95,7 @@ pub struct QueryClientInput { pub enum LightClientResult { InitClient(InitClientResult), UpdateClient(UpdateClientResult), + AggregateMessages(AggregateMessagesResult), VerifyMembership(VerifyMembershipResult), VerifyNonMembership(VerifyNonMembershipResult), @@ -99,9 +110,11 @@ pub struct InitClientResult { } #[derive(Serialize, Deserialize, Debug)] -#[serde(transparent)] pub struct UpdateClientResult(pub CommitmentProof); +#[derive(Serialize, Deserialize, Debug)] +pub struct AggregateMessagesResult(pub CommitmentProof); + #[derive(Serialize, Deserialize, Debug)] pub struct VerifyMembershipResult(pub CommitmentProof); diff --git a/modules/ecall-commands/src/msgs.rs b/modules/ecall-commands/src/msgs.rs index 7b3a425a..3ecfd122 100644 --- a/modules/ecall-commands/src/msgs.rs +++ b/modules/ecall-commands/src/msgs.rs @@ -4,9 +4,10 @@ use crate::prelude::*; use core::str::FromStr; use crypto::Address; use lcp_types::proto::lcp::service::elc::v1::{ - MsgCreateClient, MsgCreateClientResponse, MsgUpdateClient, MsgUpdateClientResponse, - MsgVerifyMembership, MsgVerifyMembershipResponse, MsgVerifyNonMembership, - MsgVerifyNonMembershipResponse, QueryClientRequest, QueryClientResponse, + MsgAggregateMessages, MsgAggregateMessagesResponse, MsgCreateClient, MsgCreateClientResponse, + MsgUpdateClient, MsgUpdateClientResponse, MsgVerifyMembership, MsgVerifyMembershipResponse, + MsgVerifyNonMembership, MsgVerifyNonMembershipResponse, QueryClientRequest, + QueryClientResponse, }; use lcp_types::{ClientId, Time}; @@ -48,6 +49,19 @@ impl TryFrom for UpdateClientInput { } } +impl TryFrom for AggregateMessagesInput { + type Error = Error; + fn try_from(msg: MsgAggregateMessages) -> Result { + let signer = Address::try_from(msg.signer.as_slice())?; + Ok(Self { + signer, + messages: msg.messages, + signatures: msg.signatures, + current_timestamp: Time::now(), + }) + } +} + impl TryFrom for VerifyMembershipInput { type Error = Error; @@ -120,6 +134,16 @@ impl From for MsgUpdateClientResponse { } } +impl From for MsgAggregateMessagesResponse { + fn from(res: AggregateMessagesResult) -> Self { + Self { + message: res.0.message, + signer: res.0.signer.into(), + signature: res.0.signature, + } + } +} + impl From for MsgVerifyMembershipResponse { fn from(res: VerifyMembershipResult) -> Self { Self { diff --git a/modules/enclave-api/src/api/command.rs b/modules/enclave-api/src/api/command.rs index 993b4240..30e2f65d 100644 --- a/modules/enclave-api/src/api/command.rs +++ b/modules/enclave-api/src/api/command.rs @@ -1,11 +1,12 @@ use crate::{EnclavePrimitiveAPI, Result}; use ecall_commands::{ - Command, CommandResult, EnclaveManageCommand, EnclaveManageResult, GenerateEnclaveKeyInput, - GenerateEnclaveKeyResult, IASRemoteAttestationInput, IASRemoteAttestationResult, - InitClientInput, InitClientResult, LightClientCommand, LightClientExecuteCommand, - LightClientQueryCommand, LightClientResult, QueryClientInput, QueryClientResult, - UpdateClientInput, UpdateClientResult, VerifyMembershipInput, VerifyMembershipResult, - VerifyNonMembershipInput, VerifyNonMembershipResult, + AggregateMessagesInput, AggregateMessagesResult, Command, CommandResult, EnclaveManageCommand, + EnclaveManageResult, GenerateEnclaveKeyInput, GenerateEnclaveKeyResult, + IASRemoteAttestationInput, IASRemoteAttestationResult, InitClientInput, InitClientResult, + LightClientCommand, LightClientExecuteCommand, LightClientQueryCommand, LightClientResult, + QueryClientInput, QueryClientResult, UpdateClientInput, UpdateClientResult, + VerifyMembershipInput, VerifyMembershipResult, VerifyNonMembershipInput, + VerifyNonMembershipResult, }; use store::transaction::CommitStore; @@ -109,6 +110,18 @@ pub trait EnclaveCommandAPI: EnclavePrimitiveAPI { } } + fn aggregate_messages(&self, input: AggregateMessagesInput) -> Result { + match self.execute_command( + Command::LightClient(LightClientCommand::Execute( + LightClientExecuteCommand::AggregateMessages(input), + )), + None, + )? { + CommandResult::LightClient(LightClientResult::AggregateMessages(res)) => Ok(res), + _ => unreachable!(), + } + } + /// verify_membership verifies the existence of the state in the upstream chain and generates a message that represents membership of value in the state fn verify_membership(&self, input: VerifyMembershipInput) -> Result { match self.execute_command( diff --git a/modules/enclave-api/src/api/proto.rs b/modules/enclave-api/src/api/proto.rs index 63870a00..f7199fa7 100644 --- a/modules/enclave-api/src/api/proto.rs +++ b/modules/enclave-api/src/api/proto.rs @@ -1,9 +1,10 @@ use super::command::EnclaveCommandAPI; use crate::Result; use lcp_proto::lcp::service::elc::v1::{ - MsgCreateClient, MsgCreateClientResponse, MsgUpdateClient, MsgUpdateClientResponse, - MsgVerifyMembership, MsgVerifyMembershipResponse, MsgVerifyNonMembership, - MsgVerifyNonMembershipResponse, QueryClientRequest, QueryClientResponse, + MsgAggregateMessages, MsgAggregateMessagesResponse, MsgCreateClient, MsgCreateClientResponse, + MsgUpdateClient, MsgUpdateClientResponse, MsgVerifyMembership, MsgVerifyMembershipResponse, + MsgVerifyNonMembership, MsgVerifyNonMembershipResponse, QueryClientRequest, + QueryClientResponse, }; use log::*; use store::transaction::CommitStore; @@ -30,6 +31,15 @@ pub trait EnclaveProtoAPI: EnclaveCommandAPI { Ok(res.into()) } + fn proto_aggregate_messages( + &self, + msg: MsgAggregateMessages, + ) -> Result { + let res = self.aggregate_messages(msg.try_into()?)?; + info!("aggregate_commitments: message={{{}}}", res.0.message()?); + Ok(res.into()) + } + fn proto_verify_membership( &self, msg: MsgVerifyMembership, diff --git a/modules/service/src/elc.rs b/modules/service/src/elc.rs index c5e7f723..572b420b 100644 --- a/modules/service/src/elc.rs +++ b/modules/service/src/elc.rs @@ -1,10 +1,10 @@ use crate::service::AppService; use enclave_api::EnclaveProtoAPI; use lcp_proto::lcp::service::elc::v1::{ - msg_server::Msg, query_server::Query, MsgCreateClient, MsgCreateClientResponse, - MsgUpdateClient, MsgUpdateClientResponse, MsgVerifyMembership, MsgVerifyMembershipResponse, - MsgVerifyNonMembership, MsgVerifyNonMembershipResponse, QueryClientRequest, - QueryClientResponse, + msg_server::Msg, query_server::Query, MsgAggregateMessages, MsgAggregateMessagesResponse, + MsgCreateClient, MsgCreateClientResponse, MsgUpdateClient, MsgUpdateClientResponse, + MsgVerifyMembership, MsgVerifyMembershipResponse, MsgVerifyNonMembership, + MsgVerifyNonMembershipResponse, QueryClientRequest, QueryClientResponse, }; use store::transaction::CommitStore; use tonic::{Request, Response, Status}; @@ -35,6 +35,16 @@ where } } + async fn aggregate_messages( + &self, + request: Request, + ) -> Result, Status> { + match self.enclave.proto_aggregate_messages(request.into_inner()) { + Ok(res) => Ok(Response::new(res)), + Err(e) => Err(Status::aborted(e.to_string())), + } + } + async fn verify_membership( &self, request: Request, diff --git a/proto/definitions/lcp/service/elc/v1/tx.proto b/proto/definitions/lcp/service/elc/v1/tx.proto index d413f657..0b916a90 100644 --- a/proto/definitions/lcp/service/elc/v1/tx.proto +++ b/proto/definitions/lcp/service/elc/v1/tx.proto @@ -16,6 +16,9 @@ service Msg { // UpdateClient defines a rpc handler method for MsgUpdateClient. rpc UpdateClient(MsgUpdateClient) returns (MsgUpdateClientResponse); + // AggregateMessages defines a rpc handler method for MsgAggregateMessages + rpc AggregateMessages(MsgAggregateMessages) returns (MsgAggregateMessagesResponse); + // VerifyMembership defines a rpc handler method for MsgVerifyMembership rpc VerifyMembership(MsgVerifyMembership) returns (MsgVerifyMembershipResponse); @@ -74,6 +77,24 @@ message MsgUpdateClientResponse { bytes signature = 3; } +message MsgAggregateMessages { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + bytes signer = 1; + repeated bytes messages = 2; + repeated bytes signatures = 3; +} + +message MsgAggregateMessagesResponse { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + bytes message = 1; + bytes signer = 2; + bytes signature = 3; +} + message MsgVerifyMembership { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; diff --git a/proto/src/descriptor.bin b/proto/src/descriptor.bin index 7f2bc85d493a64a76b36c9048616ec621817d7e4..c5a23196108e22cbfbf1f5c7ba8a4df2b19acf1a 100644 GIT binary patch delta 3597 zcmZWrOH&+G80~xePS52+9yDZt2?3mBfIxVrQqf37ATh|RNCLzp0uiiXfIuD$C{aRV zti(KY-OK34DpzhS*|>45Kf-dQZd_UA!j0#<{dKFwf~q)7% z9SicWGkaYNqTLNPO!auFI8ZFE7mD*6h4DhEG+!)~{EYER%f&l|^;*#+S}U$H^0P*- z@tIoL|q?aMQjj;lOQ?AKLfWmDI^t!*tb z%@o>8i#`}5q-&}4%@yb(eAVup4!^u3t_1D6(GD-5%PAgSM7j_pT`1I%gsd(E%TZa7 zE?v?tpFoO`wk3hkKv$U`BQ(%;B*Qd>x@EPg2q2`80=#Z+9EgJY5c5cfZoi6o7_>)L zaY%yJlhA119-dc730e>4jk*NrmDX;k=7QGCv>YIKy}FWfx^%Sqh*^YA(pP7l3!#5c zEM7dz{*e&lRTe`=IuRtDDD+wqvN{oLfliWi>I=WWCr%Fcu|Rl+%6%*l5GwaUAo3Ov z7T#BL(Jq{j^-o{z1=DTQfq6dcyf1p&&a;2;e@G8A^_+loJKxx3PT2F%tv`HzUo^M% zOOH>GVEt(enYQ8)(Z%%L6#W^tU#Ix7cz9|Zwz|Z& za-qo(YvM+75hS@N^i~qGauKA3xg^&J`2(#1pn(x?00<3?uswj#zzEyJrHMw#9%L&Z z@J16J$ur80BL?0m`@^NnZ;br`0NPkWqnu-G55zzlgFOuFp?o#rToZN}x{@Yzee^r+ z2kvE_OhrQzq@fjtzLJFO&;)5{MMF=rxdG6{BzFOX{FB@~5W1M;<`p$KB^#_(0Jt!x zWMD66q$CNZ$`8juI$CKjPKqJRRQ)k74hg1Z6CaewGA-jIB*=7x|m@Xq|jjC&a#j|;LRpHa=|PMi5PgZEM$sYFvl(c0BtUz(a>}30>nU@V;6Y7 zns?SB7kD@|ua9DWOy*&-1)j`{h9*cuQ|P@UWQQh5LwhuIfp1g*bWz|70|@yGd|?2g zivr)M9yPegGkY?ip%=M(#3;x^1A({5LwhuIQAU?E0BFUe9OW#Q9k82547B3mO70S1 zDWTB~v6NK94H39YI_A+8v6S#&tYtaIDV0YkvXV#dC3m^LHI9L{+-^T4Ad13uxZE*n zuR}t=)9ZA(bA-D={gtGfRLXA$jNXHuBqOdQ-K4-TZo%f@Kr*Z18K5mzq#!~O`Qq$4C0mFeUQ5Dz~-{%#lX~iY>H2~_C zxGoSfme|)os9R!Rr)eD*HWC_bc!S$T473g2a2gv9+)eg15O|vjk8*CZuMq=p z6TU9TnA%TmIiHAVPK{$*8ehCHug07YcupP7NiYt5n1t-m1j_@6t!e6QHZ}m-*k-2z zA^$cT8whP|v#~XG_>iXt0PP`n4@5DZ8VIz9JhdiY@319wV5i+1B6mQa9fsW|YL1SU+ksy~g1W_CY$CHE&g9Abaj)MJ2K!*x!vCQ`#3w-|z?9^3d zIQdxU<_N&ZPXyL;5TC+jc99XbClUPg-yp3LIKXL|rwqGP&jc(?C4tZoU^EUO$~+S= zF-O6DCg9?Tir{mBg+@UvlweH7Mge)2GFmC1orS9Q;*0EsfR!UIDFYaDu~k5rnWNyo L5PR7N;?49w#%6RD delta 2744 zcmX|?O-~zF6o%*C`D7ZJFa(T zM7$k3j-{>0F2tiR)tzQR5Aa@ohc*hiVoEq)7@tTf0NvdTuZICo27mv@aOtyf+oXfC z82|WE?KX>9I>j$@L7U38pAHR=}~+O6q+39;W= zCS$)f=~rnDSq=RrRRX59aUE{6A)6Li1qj`A&?h-<9=$mbKF6GNM}17 z3aMjWG#m&nsOJ}q3rNOP^}+dY#-xL?5l3h0!*WARrOg-{xe-agF*XXf!ZC9mW1NkT z&(uVDR(o=S^PBat6LTTwH=7?cE;GZNqsvO=IT;+j!}&RxHwfqF#9wyg%v;OxjdS>G zUi@Wa8VfQFI~f#_gJS4G7BYh(a&tBZwMe6&C;&FMCw5)i6bl}F{Uz?M7?0AwxQ zO@0l43`)}==vvY=+!fhtI(er6=+?69m~*YC*UTR=$kuN3NtJ;0OvXpGF0*osH`W8l zIDa6IW;!Rb+j>P(Iv!~_C0y4YDWwEid#aKWoE(f8CZE?BFzcx0J4MZHl{r2=_dX_5V`|#t4HXFTLExON8Ad6t|M**@uavF1h;m? ztt8{tj=0s+cms!7HSiOtlsr|$koHjA3PRdLajVC=j}o`yGa>7pK0L~LccD5GxB8KD zV!cWh=i}5#Fk!aiV>{o-cKl>, + #[prost(bytes = "vec", repeated, tag = "2")] + pub messages: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(bytes = "vec", repeated, tag = "3")] + pub signatures: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, +} +#[derive(::serde::Serialize, ::serde::Deserialize)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgAggregateMessagesResponse { + #[prost(bytes = "vec", tag = "1")] + pub message: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "2")] + pub signer: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "3")] + pub signature: ::prost::alloc::vec::Vec, +} +#[derive(::serde::Serialize, ::serde::Deserialize)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct MsgVerifyMembership { #[prost(string, tag = "1")] pub client_id: ::prost::alloc::string::String, @@ -503,6 +525,29 @@ pub mod msg_client { ); self.inner.unary(request.into_request(), path, codec).await } + /// AggregateMessages defines a rpc handler method for MsgAggregateMessages + pub async fn aggregate_messages( + &mut self, + request: impl tonic::IntoRequest, + ) -> Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/lcp.service.elc.v1.Msg/AggregateMessages", + ); + self.inner.unary(request.into_request(), path, codec).await + } /// VerifyMembership defines a rpc handler method for MsgVerifyMembership pub async fn verify_membership( &mut self, @@ -566,6 +611,11 @@ pub mod msg_server { &self, request: tonic::Request, ) -> Result, tonic::Status>; + /// AggregateMessages defines a rpc handler method for MsgAggregateMessages + async fn aggregate_messages( + &self, + request: tonic::Request, + ) -> Result, tonic::Status>; /// VerifyMembership defines a rpc handler method for MsgVerifyMembership async fn verify_membership( &self, @@ -716,6 +766,44 @@ pub mod msg_server { }; Box::pin(fut) } + "/lcp.service.elc.v1.Msg/AggregateMessages" => { + #[allow(non_camel_case_types)] + struct AggregateMessagesSvc(pub Arc); + impl tonic::server::UnaryService + for AggregateMessagesSvc { + type Response = super::MsgAggregateMessagesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = self.0.clone(); + let fut = async move { + (*inner).aggregate_messages(request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = AggregateMessagesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/lcp.service.elc.v1.Msg/VerifyMembership" => { #[allow(non_camel_case_types)] struct VerifyMembershipSvc(pub Arc); From f540ee34c88e36d4f325ce929fa069db136e63ce Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sun, 17 Dec 2023 00:26:06 +0900 Subject: [PATCH 2/6] fix to emit state if the option is enabled Signed-off-by: Jun Kimura --- .../ecall-handler/src/light_client/update_client.rs | 7 ++----- tests/integration/src/lib.rs | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/enclave-modules/ecall-handler/src/light_client/update_client.rs b/enclave-modules/ecall-handler/src/light_client/update_client.rs index a091a4af..9c577e12 100644 --- a/enclave-modules/ecall-handler/src/light_client/update_client.rs +++ b/enclave-modules/ecall-handler/src/light_client/update_client.rs @@ -22,11 +22,8 @@ pub fn update_client( let message: Message = { let mut msg = UpdateClientMessage::try_from(res.message)?; - if input.include_state && !msg.emitted_states.is_empty() { - msg.emitted_states = vec![EmittedState( - res.height, - res.new_any_consensus_state.clone(), - )]; + if input.include_state && msg.emitted_states.is_empty() { + msg.emitted_states = vec![EmittedState(res.height, res.new_any_client_state.clone())]; } msg.into() }; diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index cbcaa24f..79a4ceec 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -180,6 +180,7 @@ mod tests { assert!(res.0.is_proven()); let msg: UpdateClientMessage = res.0.message().unwrap().try_into()?; + assert!(msg.emitted_states.len() == 1); let height = msg.post_height; info!("current height is {}", height); From cc4d34df65ca3f0e8aba1a8d6e5468b15a55741b Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Fri, 22 Dec 2023 23:25:24 +0900 Subject: [PATCH 3/6] improve Display for message Signed-off-by: Jun Kimura --- modules/commitments/src/context.rs | 2 +- .../commitments/src/message/update_client.rs | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/modules/commitments/src/context.rs b/modules/commitments/src/context.rs index 21c3f943..c69208ab 100644 --- a/modules/commitments/src/context.rs +++ b/modules/commitments/src/context.rs @@ -272,7 +272,7 @@ impl Display for TrustingPeriodContext { write!( f, "trusting_period={} clock_drift={} untrusted_header_timestamp={} trusted_state_timestamp={}", - self.trusting_period.as_secs(), self.clock_drift.as_secs(), self.untrusted_header_timestamp, self.trusted_state_timestamp + self.trusting_period.as_nanos(), self.clock_drift.as_nanos(), self.untrusted_header_timestamp.as_unix_timestamp_nanos(), self.trusted_state_timestamp.as_unix_timestamp_nanos() ) } } diff --git a/modules/commitments/src/message/update_client.rs b/modules/commitments/src/message/update_client.rs index 57d10980..fb3d459a 100644 --- a/modules/commitments/src/message/update_client.rs +++ b/modules/commitments/src/message/update_client.rs @@ -5,6 +5,7 @@ use crate::prelude::*; use crate::{Error, StateID}; use core::fmt::Display; use lcp_types::{Any, Height, Time}; +use prost::Message; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -21,6 +22,17 @@ pub struct UpdateClientMessage { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct EmittedState(pub Height, pub Any); +impl Display for EmittedState { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "EmittedState(height: {}, state: {})", + self.0, + hex::encode(self.1.encode_to_vec()) + ) + } +} + impl UpdateClientMessage { pub fn aggregate(self, other: Self) -> Result { if self.post_state_id != other.prev_state_id.unwrap_or_default() { @@ -53,14 +65,14 @@ impl Display for UpdateClientMessage { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, - "UpdateClient(prev_height: {}, prev_state_id: {}, post_height: {}, post_state_id: {}, timestamp: {}, context: {}, emitted_states: {})", + "UpdateClient(prev_height: {}, prev_state_id: {}, post_height: {}, post_state_id: {}, timestamp: {}, context: {}, emitted_states: [{}])", self.prev_height.as_ref().map_or("None".to_string(), |h| h.to_string()), self.prev_state_id.as_ref().map_or("None".to_string(), |id| id.to_string()), self.post_height, self.post_state_id, - self.timestamp, + self.timestamp.as_unix_timestamp_nanos(), self.context, - self.emitted_states.len(), + self.emitted_states.iter().map(|v| v.to_string()).collect::>().join(", ") ) } } From 9955b0e3ca538f9fc6fb86fe80fe476746cd28e3 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sat, 23 Dec 2023 00:43:10 +0900 Subject: [PATCH 4/6] add test for `aggregate_messages` Signed-off-by: Jun Kimura --- tests/integration/src/lib.rs | 206 ++++++++++++++++++++++------------- 1 file changed, 132 insertions(+), 74 deletions(-) diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index 79a4ceec..439fd62e 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -12,18 +12,21 @@ mod tests { use anyhow::{anyhow, bail}; use commitments::UpdateClientMessage; use ecall_commands::{ - CommitmentProofPair, GenerateEnclaveKeyInput, InitClientInput, UpdateClientInput, - VerifyMembershipInput, + AggregateMessagesInput, CommitmentProofPair, GenerateEnclaveKeyInput, InitClientInput, + UpdateClientInput, VerifyMembershipInput, }; use enclave_api::{Enclave, EnclaveCommandAPI}; use host_environment::Environment; - use ibc::core::{ - ics23_commitment::{commitment::CommitmentProofBytes, merkle::MerkleProof}, - ics24_host::{ - identifier::{ChannelId, PortId}, - path::ChannelEndPath, - Path, + use ibc::{ + core::{ + ics23_commitment::{commitment::CommitmentProofBytes, merkle::MerkleProof}, + ics24_host::{ + identifier::{ChannelId, PortId}, + path::ChannelEndPath, + Path, + }, }, + Height as IBCHeight, }; use ibc_test_framework::prelude::{ run_binary_channel_test, BinaryChannelTest, ChainHandle, Config, ConnectedChains, @@ -31,10 +34,10 @@ mod tests { }; use keymanager::EnclaveKeyManager; use lcp_proto::protobuf::Protobuf; - use lcp_types::Time; + use lcp_types::{Height, Time}; use log::*; - use std::str::FromStr; use std::sync::{Arc, RwLock}; + use std::{ops::Add, str::FromStr, time::Duration}; use store::{host::HostStore, memory::MemStore}; use tempfile::TempDir; use tokio::runtime::Runtime as TokioRuntime; @@ -144,74 +147,129 @@ mod tests { }; } - // XXX use non-latest height here - let initial_height = rly - .query_latest_height()? - .decrement()? - .decrement()? - .decrement()?; - - let (client_state, consensus_state) = rly.fetch_state_as_any(initial_height)?; - info!( - "initial_height: {:?} client_state: {:?}, consensus_state: {:?}", - initial_height, client_state, consensus_state - ); - - let res = enclave.init_client(InitClientInput { - any_client_state: client_state, - any_consensus_state: consensus_state, - current_timestamp: Time::now(), - signer, - })?; - assert!(!res.proof.is_proven()); - let client_id = res.client_id; - - info!("generated client id is {}", client_id.as_str().to_string()); - - let target_header = rly.create_header(initial_height, initial_height.increment())?; - let res = enclave.update_client(UpdateClientInput { - client_id: client_id.clone(), - any_header: target_header, - current_timestamp: Time::now(), - include_state: true, - signer, - })?; - info!("update_client's result is {:?}", res); - assert!(res.0.is_proven()); - - let msg: UpdateClientMessage = res.0.message().unwrap().try_into()?; - assert!(msg.emitted_states.len() == 1); - let height = msg.post_height; - - info!("current height is {}", height); - - let (port_id, channel_id) = ( - PortId::from_str("transfer")?, - ChannelId::from_str("channel-0")?, - ); - let res = rly.query_channel_proof( - port_id.clone(), - channel_id.clone(), - Some(height.try_into().map_err(|e| anyhow!("{:?}", e))?), - )?; - - info!("expected channel is {:?}", res.0); - - let _ = enclave.verify_membership(VerifyMembershipInput { - client_id, - prefix: "ibc".into(), - path: Path::ChannelEnd(ChannelEndPath(port_id, channel_id)).to_string(), - value: res.0.encode_vec()?, - proof: CommitmentProofPair( - res.2.try_into().map_err(|e| anyhow!("{:?}", e))?, - merkle_proof_to_bytes(res.1)?, - ), - signer, - })?; + let (client_id, last_height) = { + // XXX use non-latest height here + let initial_height = rly.query_latest_height()?.decrement()?.decrement()?; + + let (client_state, consensus_state) = rly.fetch_state_as_any(initial_height)?; + info!( + "initial_height: {:?} client_state: {:?}, consensus_state: {:?}", + initial_height, client_state, consensus_state + ); + + let res = enclave.init_client(InitClientInput { + any_client_state: client_state, + any_consensus_state: consensus_state, + current_timestamp: Time::now(), + signer, + })?; + assert!(!res.proof.is_proven()); + let client_id = res.client_id; + + (client_id, initial_height) + }; + info!("generated client: id={} height={}", client_id, last_height); + + let last_height = { + let post_height = last_height.increment(); + let target_header = rly.create_header(last_height, post_height)?; + let res = enclave.update_client(UpdateClientInput { + client_id: client_id.clone(), + any_header: target_header, + current_timestamp: Time::now(), + include_state: true, + signer, + })?; + info!("update_client's result is {:?}", res); + assert!(res.0.is_proven()); + + let msg: UpdateClientMessage = res.0.message().unwrap().try_into()?; + assert!(msg.prev_height == Some(Height::from(last_height))); + assert!(msg.post_height == Height::from(post_height)); + assert!(msg.emitted_states.len() == 1); + post_height + }; + info!("current last_height is {}", last_height); + + { + let (port_id, channel_id) = ( + PortId::from_str("transfer")?, + ChannelId::from_str("channel-0")?, + ); + let res = rly.query_channel_proof( + port_id.clone(), + channel_id.clone(), + Some(last_height.into()), + )?; + + info!("expected channel is {:?}", res.0); + + let _ = enclave.verify_membership(VerifyMembershipInput { + client_id: client_id.clone(), + prefix: "ibc".into(), + path: Path::ChannelEnd(ChannelEndPath(port_id, channel_id)).to_string(), + value: res.0.encode_vec()?, + proof: CommitmentProofPair( + res.2.try_into().map_err(|e| anyhow!("{:?}", e))?, + merkle_proof_to_bytes(res.1)?, + ), + signer, + })?; + } + + let last_height = { + let mut lh = last_height; + let mut proofs = vec![]; + for _ in 0..10 { + let target_height = wait_block_advance(&mut rly)?; + let target_header = rly.create_header(lh, target_height)?; + let res = enclave.update_client(UpdateClientInput { + client_id: client_id.clone(), + any_header: target_header, + current_timestamp: Time::now().add(Duration::from_secs(10))?, // for gaiad's clock drift + include_state: false, + signer, + })?; + info!("update_client's result is {:?}", res); + lh = target_height; + proofs.push(res.0); + } + let messages = proofs + .iter() + .map(|p| p.message().map(|m| m.to_bytes())) + .collect::>()?; + let signatures = proofs.into_iter().map(|p| p.signature).collect(); + + let res = enclave.aggregate_messages(AggregateMessagesInput { + messages, + signatures, + signer, + current_timestamp: Time::now().add(Duration::from_secs(10))?, + })?; + let msg: UpdateClientMessage = res.0.message().unwrap().try_into()?; + assert!(msg.prev_height == Some(Height::from(last_height))); + assert!(msg.post_height == Height::from(lh)); + assert!(msg.emitted_states.len() == 0); + lh + }; + info!("current last_height is {}", last_height); Ok(()) } + fn wait_block_advance(rly: &mut Relayer) -> Result { + let mut height = rly.query_latest_height()?; + loop { + let next_height = rly.query_latest_height()?; + if next_height > height { + height = next_height; + break; + } + std::thread::sleep(std::time::Duration::from_secs(1)); + } + Ok(height) + } + fn merkle_proof_to_bytes(proof: MerkleProof) -> Result, anyhow::Error> { let proof = CommitmentProofBytes::try_from(proof)?; Ok(proof.into()) From e6c4519e9caae8569fe0c124f3f91faf579e850d Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sat, 23 Dec 2023 11:42:58 +0900 Subject: [PATCH 5/6] evaluate validation context in `aggregate_messages` Signed-off-by: Jun Kimura --- .../ecall-handler/src/light_client/aggregate_messages.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs b/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs index b57eb2c7..3dd6016f 100644 --- a/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs +++ b/enclave-modules/ecall-handler/src/light_client/aggregate_messages.rs @@ -5,7 +5,7 @@ use crypto::{EnclavePublicKey, Signer, Verifier}; use ecall_commands::{AggregateMessagesInput, AggregateMessagesResult, LightClientResult}; use light_client::{ commitments::{self, prove_commitment, Message, UpdateClientMessage}, - LightClientResolver, + HostContext, LightClientResolver, }; use store::KVStore; @@ -38,6 +38,7 @@ pub fn aggregate_messages( .zip(input.signatures.iter()) .map(|(c, s)| -> Result<_, Error> { verify_commitment(&pk, &c, s)?; + c.context.validate(ctx.host_timestamp())?; Ok(c) }) .collect::, _>>()?; From a9cc4caf68250517d9058509d8ff86e6c1b18f03 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sat, 23 Dec 2023 12:12:40 +0900 Subject: [PATCH 6/6] fix lint errors Signed-off-by: Jun Kimura --- tests/integration/src/lib.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index 439fd62e..4e06c215 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -196,11 +196,8 @@ mod tests { PortId::from_str("transfer")?, ChannelId::from_str("channel-0")?, ); - let res = rly.query_channel_proof( - port_id.clone(), - channel_id.clone(), - Some(last_height.into()), - )?; + let res = + rly.query_channel_proof(port_id.clone(), channel_id.clone(), Some(last_height))?; info!("expected channel is {:?}", res.0); @@ -249,7 +246,7 @@ mod tests { let msg: UpdateClientMessage = res.0.message().unwrap().try_into()?; assert!(msg.prev_height == Some(Height::from(last_height))); assert!(msg.post_height == Height::from(lh)); - assert!(msg.emitted_states.len() == 0); + assert!(msg.emitted_states.is_empty()); lh }; info!("current last_height is {}", last_height);