Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

penumbra-ibc: ⏎ invert early return condition #3691

Merged
merged 1 commit into from
Jan 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 117 additions & 116 deletions crates/core/component/ibc/src/component/msg_handler/update_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,124 +35,125 @@ impl MsgHandler for MsgUpdateClient {
// to Ok(()) rather than erroring to avoid having two "racing" relay
// transactions fail just because they both contain the same client
// update.
if !update_is_already_committed(&state, self).await? {
tracing::debug!(msg = ?self);

let client_state = client_is_present(&state, self).await?;

client_is_not_frozen(&client_state)?;
client_is_not_expired::<&S, HI>(&state, &self.client_id, &client_state).await?;

let trusted_client_state = client_state;

let untrusted_header =
ics02_validation::get_tendermint_header(self.client_message.clone())?;

header_revision_matches_client_state(&trusted_client_state, &untrusted_header)?;
header_height_is_consistent(&untrusted_header)?;

// The (still untrusted) header uses the `trusted_height` field to
// specify the trusted anchor data it is extending.
let trusted_height = untrusted_header.trusted_height;

// We use the specified trusted height to query the trusted
// consensus state the update extends.
let last_trusted_consensus_state = state
.get_verified_consensus_state(&trusted_height, &self.client_id)
.await?;

// We also have to convert from an IBC height, which has two
// components, to a Tendermint height, which has only one.
let trusted_height = trusted_height
.revision_height()
.try_into()
.context("invalid header height")?;

let trusted_validator_set =
verify_header_validator_set(&untrusted_header, &last_trusted_consensus_state)?;

// Now we build the trusted and untrusted states to feed to the Tendermint light client.

let trusted_state = TrustedBlockState {
// TODO(erwan): do we need an additional check on `chain_id`
chain_id: &trusted_client_state.chain_id.clone().into(),
header_time: last_trusted_consensus_state.timestamp,
height: trusted_height,
next_validators: trusted_validator_set,
next_validators_hash: last_trusted_consensus_state.next_validators_hash,
};

let untrusted_state = UntrustedBlockState {
signed_header: &untrusted_header.signed_header,
validators: &untrusted_header.validator_set,
next_validators: None, // TODO: do we need this?
};

let options = trusted_client_state.as_light_client_options()?;
let verifier = ProdVerifier::default();

let verdict = verifier.verify_update_header(
untrusted_state,
trusted_state,
&options,
HI::get_block_timestamp(&state).await?,
);

match verdict {
Verdict::Success => Ok(()),
Verdict::NotEnoughTrust(voting_power_tally) => Err(anyhow::anyhow!(
"not enough trust, voting power tally: {:?}",
voting_power_tally
)),
Verdict::Invalid(detail) => Err(anyhow::anyhow!(
"could not verify tendermint header: invalid: {:?}",
detail
)),
}?;

let trusted_header = untrusted_header;

// get the latest client state
let client_state = state
.get_client_state(&self.client_id)
.await
.context("unable to get client state")?;

// NOTE: next_tendermint_state will freeze the client on equivocation.
let (next_tm_client_state, next_tm_consensus_state) = state
.next_tendermint_state(
self.client_id.clone(),
client_state.clone(),
trusted_header.clone(),
)
.await;

// store the updated client and consensus states
state.put_client(&self.client_id, next_tm_client_state);
state
.put_verified_consensus_state::<HI>(
trusted_header.height(),
self.client_id.clone(),
next_tm_consensus_state,
)
.await?;

state.record(
UpdateClient {
client_id: self.client_id.clone(),
client_type: ibc_types::core::client::ClientType(
TENDERMINT_CLIENT_TYPE.to_string(),
), // TODO: hardcoded
consensus_height: trusted_header.height(),
header: <ibc_types::lightclients::tendermint::header::Header as ibc_proto::Protobuf<ibc_proto::ibc::lightclients::tendermint::v1::Header>>::encode_vec(trusted_header),
}
.into(),
);
return Ok(());
} else {
if update_is_already_committed(&state, self).await? {
tracing::debug!("skipping duplicate update");
return Ok(());
}

tracing::debug!(msg = ?self);

let client_state = client_is_present(&state, self).await?;

client_is_not_frozen(&client_state)?;
client_is_not_expired::<&S, HI>(&state, &self.client_id, &client_state).await?;

let trusted_client_state = client_state;

let untrusted_header =
ics02_validation::get_tendermint_header(self.client_message.clone())?;

header_revision_matches_client_state(&trusted_client_state, &untrusted_header)?;
header_height_is_consistent(&untrusted_header)?;

// The (still untrusted) header uses the `trusted_height` field to
// specify the trusted anchor data it is extending.
let trusted_height = untrusted_header.trusted_height;

// We use the specified trusted height to query the trusted
// consensus state the update extends.
let last_trusted_consensus_state = state
.get_verified_consensus_state(&trusted_height, &self.client_id)
.await?;

// We also have to convert from an IBC height, which has two
// components, to a Tendermint height, which has only one.
let trusted_height = trusted_height
.revision_height()
.try_into()
.context("invalid header height")?;

let trusted_validator_set =
verify_header_validator_set(&untrusted_header, &last_trusted_consensus_state)?;

// Now we build the trusted and untrusted states to feed to the Tendermint light client.

let trusted_state = TrustedBlockState {
// TODO(erwan): do we need an additional check on `chain_id`
chain_id: &trusted_client_state.chain_id.clone().into(),
header_time: last_trusted_consensus_state.timestamp,
height: trusted_height,
next_validators: trusted_validator_set,
next_validators_hash: last_trusted_consensus_state.next_validators_hash,
};

let untrusted_state = UntrustedBlockState {
signed_header: &untrusted_header.signed_header,
validators: &untrusted_header.validator_set,
next_validators: None, // TODO: do we need this?
};

let options = trusted_client_state.as_light_client_options()?;
let verifier = ProdVerifier::default();

let verdict = verifier.verify_update_header(
untrusted_state,
trusted_state,
&options,
HI::get_block_timestamp(&state).await?,
);

match verdict {
Verdict::Success => Ok(()),
Verdict::NotEnoughTrust(voting_power_tally) => Err(anyhow::anyhow!(
"not enough trust, voting power tally: {:?}",
voting_power_tally
)),
Verdict::Invalid(detail) => Err(anyhow::anyhow!(
"could not verify tendermint header: invalid: {:?}",
detail
)),
}?;

let trusted_header = untrusted_header;

// get the latest client state
let client_state = state
.get_client_state(&self.client_id)
.await
.context("unable to get client state")?;

// NOTE: next_tendermint_state will freeze the client on equivocation.
let (next_tm_client_state, next_tm_consensus_state) = state
.next_tendermint_state(
self.client_id.clone(),
client_state.clone(),
trusted_header.clone(),
)
.await;

// store the updated client and consensus states
state.put_client(&self.client_id, next_tm_client_state);
state
.put_verified_consensus_state::<HI>(
trusted_header.height(),
self.client_id.clone(),
next_tm_consensus_state,
)
.await?;

state.record(
UpdateClient {
client_id: self.client_id.clone(),
client_type: ibc_types::core::client::ClientType(
TENDERMINT_CLIENT_TYPE.to_string(),
), // TODO: hardcoded
consensus_height: trusted_header.height(),
header:
<ibc_types::lightclients::tendermint::header::Header as ibc_proto::Protobuf<
ibc_proto::ibc::lightclients::tendermint::v1::Header,
>>::encode_vec(trusted_header),
}
.into(),
);
Ok(())
}
}
Expand Down
Loading