1#![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#[derive(
39 Debug,
40 Clone,
41 PartialEq,
42 Eq,
43 BorshSerialize,
44 BorshDeserialize,
45 BorshDeserializer,
46 BorshSchema,
47)]
48pub struct VoteExtension {
49 pub ethereum_events: Option<Signed<ethereum_events::Vext>>,
51 pub bridge_pool_root: Option<bridge_pool_roots::SignedVext>,
53 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 #[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 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 #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, BorshSchema)]
126 {
127 EthereumEvents(ethereum_events::VextDigest),
130 BridgePool(bridge_pool_roots::MultiSignedVext),
133 ValidatorSetUpdate(validator_set_update::VextDigest),
135 EthEventsVext(ethereum_events::SignedVext),
137 BridgePoolVext(bridge_pool_roots::SignedVext),
139 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 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 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 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}