-
Notifications
You must be signed in to change notification settings - Fork 100
Avoid code duplication in implementation of ValidationContext
and ExecutionContext
#271
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
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: