-
Notifications
You must be signed in to change notification settings - Fork 96
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
Avoid code duplication in implementation of ValidationContext
and ExecutionContext
#271
Comments
I based myself off of this to come up with the following, which I believe addresses all our problems:
Note that I don't worry about If you like it, I can implement this directly in #257. /// Abstracts all common variables between `validate()` and `execute()`
struct LocalVars {
conn_end_on_b: ConnectionEnd,
}
impl LocalVars {
fn new<Ctx>(ctx_b: &Ctx, msg: &MsgConnectionOpenConfirm) -> Result<Self, Error>
where
Ctx: ValidationContext,
{
Ok(Self {
conn_end_on_b: ctx_b.connection_end(&msg.conn_id_on_b)?,
})
}
fn conn_end_on_b(&self) -> &ConnectionEnd {
&self.conn_end_on_b
}
fn client_id_on_a(&self) -> &ClientId {
self.conn_end_on_b.counterparty().client_id()
}
fn client_id_on_b(&self) -> &ClientId {
self.conn_end_on_b.client_id()
}
fn conn_id_on_a(&self) -> Result<&ConnectionId, Error> {
self.conn_end_on_b
.counterparty()
.connection_id()
.ok_or_else(Error::invalid_counterparty)
}
}
pub(crate) fn validate<Ctx>(ctx_b: &Ctx, msg: MsgConnectionOpenConfirm) -> Result<(), Error>
where
Ctx: ValidationContext,
{
let vars = LocalVars::new(ctx_b, &msg)?;
validate_impl(ctx_b, msg, &vars)
}
fn validate_impl<Ctx>(
ctx_b: &Ctx,
msg: MsgConnectionOpenConfirm,
vars: &LocalVars,
) -> Result<(), Error>
where
Ctx: ValidationContext,
{
let conn_end_on_b = vars.conn_end_on_b();
if !conn_end_on_b.state_matches(&State::TryOpen) {
return Err(Error::connection_mismatch(msg.conn_id_on_b));
}
let client_id_on_a = vars.client_id_on_a();
let client_id_on_b = vars.client_id_on_b();
let conn_id_on_a = vars.conn_id_on_a()?;
// Verify proofs
{
let client_state_of_a_on_b = ctx_b
.client_state(client_id_on_b)
.map_err(|_| Error::other("failed to fetch client state".to_string()))?;
let consensus_state_of_a_on_b = ctx_b
.consensus_state(client_id_on_b, msg.proof_height_on_a)
.map_err(|_| Error::other("failed to fetch client consensus state".to_string()))?;
let prefix_on_a = conn_end_on_b.counterparty().prefix();
let prefix_on_b = ctx_b.commitment_prefix();
let expected_conn_end_on_a = ConnectionEnd::new(
State::Open,
client_id_on_a.clone(),
Counterparty::new(
client_id_on_b.clone(),
Some(msg.conn_id_on_b.clone()),
prefix_on_b,
),
conn_end_on_b.versions().to_vec(),
conn_end_on_b.delay_period(),
);
client_state_of_a_on_b
.verify_connection_state(
msg.proof_height_on_a,
prefix_on_a,
&msg.proof_conn_end_on_a,
consensus_state_of_a_on_b.root(),
conn_id_on_a,
&expected_conn_end_on_a,
)
.map_err(Error::verify_connection_state)?;
}
Ok(())
}
pub(crate) fn execute<Ctx>(ctx_b: &mut Ctx, msg: MsgConnectionOpenConfirm) -> Result<(), Error>
where
Ctx: ExecutionContext,
{
let vars = LocalVars::new(ctx_b, &msg)?;
execute_impl(ctx_b, msg, vars)
}
fn execute_impl<Ctx>(
ctx_b: &mut Ctx,
msg: MsgConnectionOpenConfirm,
vars: LocalVars,
) -> Result<(), Error>
where
Ctx: ExecutionContext,
{
let client_id_on_a = vars.client_id_on_a();
let client_id_on_b = vars.client_id_on_b();
let conn_id_on_a = vars.conn_id_on_a()?;
ctx_b.emit_ibc_event(IbcEvent::OpenConfirmConnection(OpenConfirm::new(
msg.conn_id_on_b.clone(),
client_id_on_b.clone(),
conn_id_on_a.clone(),
client_id_on_a.clone(),
)));
ctx_b.log_message("success: conn_open_confirm verification passed".to_string());
{
let new_conn_end_on_b = {
let mut new_conn_end_on_b = vars.conn_end_on_b;
new_conn_end_on_b.set_state(State::Open);
new_conn_end_on_b
};
ctx_b.store_connection(ConnectionsPath(msg.conn_id_on_b), &new_conn_end_on_b)?;
}
Ok(())
}
pub(crate) fn process<Ctx>(ctx_b: &mut Ctx, msg: MsgConnectionOpenConfirm) -> Result<(), Error>
where
Ctx: ExecutionContext,
{
// Note that this is a "zero-cost" refactor, since common variables are only built
// once in `LocalVars`
let vars = LocalVars::new(ctx_b, &msg)?;
// we can get rid of clone when we fix our `Error` structs
validate_impl(ctx_b, msg.clone(), &vars)?;
execute_impl(ctx_b, msg, vars)
} |
I like the idea to use |
Right; I was thinking we can keep |
I think this makes sense as an intermediary solution. 👍 We can remove the readers/keepers once we migrate the apps to use the new API. |
Originally posted by @hu55a1n1 in #257 (comment)
I have been thinking about how we can avoid the code duplication we currently have and it seems roughly speaking each of these handlers are composed of the following steps -
validate:
* get relevant state from host
* check current state is as expected (based on message)
* construct expected state
* verify proofs
execute:
* get relevant state from host
* emit event/log
* construct new state
* write state
process:
* get relevant state from host
* check current state is as expected (based on message)
* construct expected state
* verify proofs
* emit event/log
* construct new state
* (write state later on in
store_connection_result
)So there is some degree of overlap between them. Here's a potential way to avoid the duplication by defining a separate 'handler' trait and providing blanket impls for types that implement the
ValidationContext
andConnectionReader
->The text was updated successfully, but these errors were encountered: