penumbra_ibc/component/msg_handler/
acknowledgement.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use anyhow::{Context, Result};
use async_trait::async_trait;
use cnidarium::StateWrite;
use ibc_types::core::{
    channel::channel::Order as ChannelOrder, channel::channel::State as ChannelState,
    channel::events, channel::msgs::MsgAcknowledgement, channel::PortId,
    connection::State as ConnectionState,
};

use crate::component::{
    app_handler::{AppHandlerCheck, AppHandlerExecute},
    channel::{StateReadExt as _, StateWriteExt as _},
    connection::StateReadExt as _,
    proof_verification::{commit_packet, PacketProofVerifier},
    HostInterface, MsgHandler,
};

#[async_trait]
impl MsgHandler for MsgAcknowledgement {
    async fn check_stateless<H: AppHandlerCheck>(&self) -> Result<()> {
        // NOTE: no additional stateless validation is possible

        Ok(())
    }

    async fn try_execute<
        S: StateWrite,
        AH: AppHandlerCheck + AppHandlerExecute,
        HI: HostInterface,
    >(
        &self,
        mut state: S,
    ) -> Result<()> {
        tracing::debug!(msg = ?self);
        let channel = state
            .get_channel(&self.packet.chan_on_a, &self.packet.port_on_a)
            .await?
            .ok_or_else(|| anyhow::anyhow!("channel not found"))?;
        if !channel.state_matches(&ChannelState::Open) {
            anyhow::bail!("channel is not open");
        }

        // TODO: capability authentication?

        if channel.counterparty().port_id().ne(&self.packet.port_on_b) {
            anyhow::bail!("packet destination port does not match channel");
        }
        if channel
            .counterparty()
            .channel_id()
            .ok_or_else(|| {
                anyhow::anyhow!("missing channel id for counterparty channel in acknowledgement")
            })?
            .ne(&self.packet.chan_on_b)
        {
            anyhow::bail!("packet destination channel does not match channel");
        }

        let connection = state
            .get_connection(&channel.connection_hops[0])
            .await?
            .ok_or_else(|| anyhow::anyhow!("connection not found for channel"))?;
        if !connection.state_matches(&ConnectionState::Open) {
            anyhow::bail!("connection for channel is not open");
        }

        // verify we sent this packet and haven't cleared it yet
        let commitment = state
            .get_packet_commitment(&self.packet)
            .await?
            .ok_or_else(|| anyhow::anyhow!("packet commitment not found"))?;
        if commitment != commit_packet(&self.packet) {
            anyhow::bail!("packet commitment does not match");
        }

        state
            .verify_packet_ack_proof::<HI>(&connection, self)
            .await
            .with_context(|| "packet ack proof verification failed")?;

        if channel.ordering == ChannelOrder::Ordered {
            let next_sequence_ack = state
                .get_ack_sequence(&self.packet.chan_on_a, &self.packet.port_on_a)
                .await?;
            if self.packet.sequence != next_sequence_ack.into() {
                anyhow::bail!("packet sequence number does not match");
            }
        }

        let transfer = PortId::transfer();
        if self.packet.port_on_b == transfer {
            AH::acknowledge_packet_check(&mut state, self).await?;
        } else {
            anyhow::bail!("invalid port id");
        }
        if channel.ordering == ChannelOrder::Ordered {
            let mut next_sequence_ack = state
                .get_ack_sequence(&self.packet.chan_on_a, &self.packet.port_on_a)
                .await?;
            next_sequence_ack += 1;
            state.put_ack_sequence(
                &self.packet.chan_on_a,
                &self.packet.port_on_a,
                next_sequence_ack,
            );
        }

        // delete our commitment so we can't ack it again
        state.delete_packet_commitment(
            &self.packet.chan_on_a,
            &self.packet.port_on_a,
            self.packet.sequence.into(),
        );

        state.record(
            events::packet::AcknowledgePacket {
                timeout_height: self.packet.timeout_height_on_b,
                timeout_timestamp: self.packet.timeout_timestamp_on_b,
                sequence: self.packet.sequence,
                src_port_id: self.packet.port_on_a.clone(),
                src_channel_id: self.packet.chan_on_a.clone(),
                dst_port_id: self.packet.port_on_b.clone(),
                dst_channel_id: self.packet.chan_on_b.clone(),
                channel_ordering: channel.ordering,
                src_connection_id: channel.connection_hops[0].clone(),
            }
            .into(),
        );

        let transfer = PortId::transfer();
        if self.packet.port_on_b == transfer {
            AH::acknowledge_packet_execute(state, self).await?;
        } else {
            anyhow::bail!("invalid port id");
        }

        Ok(())
    }
}