namada_vote_ext/
lib.rs

1//! This module contains types necessary for processing vote extensions.
2
3#![doc(html_favicon_url = "https://dev.namada.net/master/favicon.png")]
4#![doc(html_logo_url = "https://dev.namada.net/master/rustdoc-logo.png")]
5#![deny(rustdoc::broken_intra_doc_links)]
6#![deny(rustdoc::private_intra_doc_links)]
7#![warn(
8    missing_docs,
9    rust_2018_idioms,
10    clippy::cast_sign_loss,
11    clippy::cast_possible_truncation,
12    clippy::cast_possible_wrap,
13    clippy::cast_lossless,
14    clippy::arithmetic_side_effects,
15    clippy::dbg_macro,
16    clippy::print_stdout,
17    clippy::print_stderr
18)]
19
20pub mod bridge_pool_roots;
21pub mod ethereum_events;
22pub mod validator_set_update;
23
24use namada_core::borsh::{
25    BorshDeserialize, BorshSchema, BorshSerialize, BorshSerializeExt,
26};
27use namada_core::chain::ChainId;
28use namada_core::key::common;
29use namada_macros::BorshDeserializer;
30#[cfg(feature = "migrations")]
31use namada_migrations::*;
32use namada_tx::data::TxType;
33use namada_tx::data::protocol::{ProtocolTx, ProtocolTxType};
34use namada_tx::{Authorization, Signed, Tx, TxError};
35
36/// This type represents the data we pass to the extension of
37/// a vote at the PreCommit phase of Tendermint.
38#[derive(
39    Debug,
40    Clone,
41    PartialEq,
42    Eq,
43    BorshSerialize,
44    BorshDeserialize,
45    BorshDeserializer,
46    BorshSchema,
47)]
48pub struct VoteExtension {
49    /// Vote extension data related with Ethereum events.
50    pub ethereum_events: Option<Signed<ethereum_events::Vext>>,
51    /// A signature of the Ethereum bridge pool root and nonce.
52    pub bridge_pool_root: Option<bridge_pool_roots::SignedVext>,
53    /// Vote extension data related with validator set updates.
54    pub validator_set_update: Option<validator_set_update::SignedVext>,
55}
56
57macro_rules! ethereum_tx_data_deserialize_inner {
58    ($variant:ty) => {
59        impl TryFrom<&Tx> for $variant {
60            type Error = TxError;
61
62            fn try_from(tx: &Tx) -> Result<Self, TxError> {
63                let tx_data = tx
64                    .data(tx.commitments().first().ok_or_else(|| {
65                        TxError::Deserialization(
66                            "Missing inner protocol tx commitments".into(),
67                        )
68                    })?)
69                    .ok_or_else(|| {
70                        TxError::Deserialization(
71                            "Expected protocol tx type associated data".into(),
72                        )
73                    })?;
74                Self::try_from_slice(&tx_data)
75                    .map_err(|err| TxError::Deserialization(err.to_string()))
76            }
77        }
78    };
79}
80
81macro_rules! ethereum_tx_data_declare {
82        (
83            $( #[$outer_attrs:meta] )*
84            {
85                $(
86                    $(#[$inner_attrs:meta])*
87                    $variant:ident ($inner_ty:ty)
88                ),* $(,)?
89            }
90        ) => {
91            $( #[$outer_attrs] )*
92            pub enum EthereumTxData {
93                $(
94                    $(#[$inner_attrs])*
95                    $variant ( $inner_ty )
96                ),*
97            }
98
99            /// All the variants of [`EthereumTxData`], stored
100            /// in a trait.
101            #[allow(missing_docs)]
102            pub trait EthereumTxDataVariants {
103                $( type $variant; )*
104            }
105
106            impl EthereumTxDataVariants for EthereumTxData {
107                $( type $variant = $inner_ty; )*
108            }
109
110            #[allow(missing_docs)]
111            pub mod ethereum_tx_data_variants {
112                //! All the variants of [`EthereumTxData`], stored
113                //! in a module.
114                use super::*;
115
116                $( pub type $variant = $inner_ty; )*
117            }
118
119            $( ethereum_tx_data_deserialize_inner!($inner_ty); )*
120        };
121    }
122
123ethereum_tx_data_declare! {
124    /// Data associated with Ethereum protocol transactions.
125    #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, BorshSchema)]
126    {
127        /// Ethereum events contained in vote extensions that
128        /// are compressed before being included on chain
129        EthereumEvents(ethereum_events::VextDigest),
130        /// Collection of signatures over the Ethereum bridge
131        /// pool merkle root and nonce.
132        BridgePool(bridge_pool_roots::MultiSignedVext),
133        /// Validator set updates contained in vote extensions
134        ValidatorSetUpdate(validator_set_update::VextDigest),
135        /// Ethereum events seen by some validator
136        EthEventsVext(ethereum_events::SignedVext),
137        /// Signature over the Ethereum bridge pool merkle root and nonce.
138        BridgePoolVext(bridge_pool_roots::SignedVext),
139        /// Validator set update signed by some validator
140        ValSetUpdateVext(validator_set_update::SignedVext),
141    }
142}
143
144impl TryFrom<&Tx> for EthereumTxData {
145    type Error = TxError;
146
147    fn try_from(tx: &Tx) -> Result<Self, TxError> {
148        let TxType::Protocol(protocol_tx) = tx.header().tx_type else {
149            return Err(TxError::Deserialization(
150                "Expected protocol tx type".into(),
151            ));
152        };
153        let Some(tx_data) =
154            tx.data(tx.commitments().first().ok_or_else(|| {
155                TxError::Deserialization(
156                    "Missing inner protocol tx commitments".into(),
157                )
158            })?)
159        else {
160            return Err(TxError::Deserialization(
161                "Expected protocol tx type associated data".into(),
162            ));
163        };
164        Self::deserialize(&protocol_tx.tx, &tx_data)
165    }
166}
167
168impl EthereumTxData {
169    /// Sign transaction Ethereum data and wrap it in a [`Tx`].
170    pub fn sign(
171        &self,
172        signing_key: &common::SecretKey,
173        chain_id: ChainId,
174    ) -> Tx {
175        let (tx_data, tx_type) = self.serialize();
176        let mut outer_tx =
177            Tx::from_type(TxType::Protocol(Box::new(ProtocolTx {
178                pk: signing_key.to_public(),
179                tx: tx_type,
180            })));
181        outer_tx.header.chain_id = chain_id;
182        outer_tx.set_data(namada_tx::Data::new(tx_data));
183        outer_tx.add_section(namada_tx::Section::Authorization(
184            Authorization::new(
185                outer_tx.sechashes(),
186                [(0, signing_key.clone())].into_iter().collect(),
187                None,
188            ),
189        ));
190        outer_tx
191    }
192
193    /// Serialize Ethereum protocol transaction data.
194    pub fn serialize(&self) -> (Vec<u8>, ProtocolTxType) {
195        macro_rules! match_of_type {
196                ( $( $type:ident ),* $(,)?) => {
197                    match self {
198                        $( EthereumTxData::$type(x) =>
199                           (x.serialize_to_vec(), ProtocolTxType::$type)),*
200                    }
201                }
202            }
203        match_of_type! {
204            EthereumEvents,
205            BridgePool,
206            ValidatorSetUpdate,
207            EthEventsVext,
208            BridgePoolVext,
209            ValSetUpdateVext,
210        }
211    }
212
213    /// Deserialize Ethereum protocol transaction data.
214    pub fn deserialize(
215        tx_type: &ProtocolTxType,
216        data: &[u8],
217    ) -> Result<Self, TxError> {
218        let deserialize: fn(&[u8]) -> _ = match tx_type {
219            ProtocolTxType::EthereumEvents => |data| {
220                BorshDeserialize::try_from_slice(data)
221                    .map(EthereumTxData::EthereumEvents)
222            },
223            ProtocolTxType::BridgePool => |data| {
224                BorshDeserialize::try_from_slice(data)
225                    .map(EthereumTxData::BridgePool)
226            },
227            ProtocolTxType::ValidatorSetUpdate => |data| {
228                BorshDeserialize::try_from_slice(data)
229                    .map(EthereumTxData::ValidatorSetUpdate)
230            },
231            ProtocolTxType::EthEventsVext => |data| {
232                BorshDeserialize::try_from_slice(data)
233                    .map(EthereumTxData::EthEventsVext)
234            },
235            ProtocolTxType::BridgePoolVext => |data| {
236                BorshDeserialize::try_from_slice(data)
237                    .map(EthereumTxData::BridgePoolVext)
238            },
239            ProtocolTxType::ValSetUpdateVext => |data| {
240                BorshDeserialize::try_from_slice(data)
241                    .map(EthereumTxData::ValSetUpdateVext)
242            },
243        };
244        deserialize(data)
245            .map_err(|err| TxError::Deserialization(err.to_string()))
246    }
247}