fedimint_meta_common/
lib.rs

1#![deny(clippy::pedantic)]
2#![allow(clippy::missing_errors_doc)]
3#![allow(clippy::module_name_repetitions)]
4#![allow(clippy::must_use_candidate)]
5#![allow(clippy::needless_lifetimes)]
6
7pub mod endpoint;
8
9use std::fmt;
10use std::str::FromStr;
11
12use config::MetaClientConfig;
13use fedimint_core::core::{Decoder, ModuleInstanceId, ModuleKind};
14use fedimint_core::encoding::{Decodable, DecodeError, Encodable};
15use fedimint_core::module::{CommonModuleInit, ModuleCommon, ModuleConsensusVersion};
16use fedimint_core::plugin_types_trait_impl_common;
17use fedimint_logging::LOG_MODULE_META;
18use serde::de::{self, Visitor};
19use serde::{Deserialize, Deserializer, Serialize, Serializer};
20use thiserror::Error;
21use tracing::warn;
22// Common contains types shared by both the client and server
23
24// The client and server configuration
25pub mod config;
26
27/// Unique name for this module
28pub const KIND: ModuleKind = ModuleKind::from_static_str("meta");
29
30/// Modules are non-compatible with older versions
31pub const MODULE_CONSENSUS_VERSION: ModuleConsensusVersion = ModuleConsensusVersion::new(0, 0);
32
33/// The meta module was built with flexibility and upgradability in mind. We
34/// currently only intend to use one key, which is defined here.
35pub const DEFAULT_META_KEY: MetaKey = MetaKey(0);
36
37/// A key identifying a value in the meta module consensus
38///
39/// Intentionally small (`u8`) to avoid problems with malicious peers
40/// submitting lots of votes to waste storage and memory. Since values
41/// in the meta module are supposed to be larger aggregates (e.g. json),
42/// 256 keys should be plenty.
43#[derive(
44    Debug,
45    Copy,
46    Clone,
47    Encodable,
48    Decodable,
49    PartialEq,
50    Eq,
51    PartialOrd,
52    Ord,
53    Hash,
54    Serialize,
55    Deserialize,
56)]
57pub struct MetaKey(pub u8);
58
59impl fmt::Display for MetaKey {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        self.0.fmt(f)
62    }
63}
64
65impl FromStr for MetaKey {
66    type Err = <u8 as FromStr>::Err;
67
68    fn from_str(s: &str) -> Result<Self, Self::Err> {
69        Ok(Self(FromStr::from_str(s)?))
70    }
71}
72/// A value of the [`MetaKey`] peers are trying to establish consensus on
73///
74/// Mostly a newtype around a `Vec<u8>` as meta module does not ever interpret
75/// it. Serialized as a hex string, with [`Decodable`] and [`Deserialize`]
76/// implementations enforcing size limit of [`Self::MAX_LEN_BYTES`].
77#[derive(Debug, Clone, Encodable, PartialEq, Eq, PartialOrd, Ord, Hash)]
78pub struct MetaValue(Vec<u8>);
79
80impl FromStr for MetaValue {
81    type Err = anyhow::Error;
82
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        Ok(Self(hex::decode(s)?))
85    }
86}
87
88impl fmt::Display for MetaValue {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        f.write_str(&hex::encode(&self.0))
91    }
92}
93impl From<&[u8]> for MetaValue {
94    fn from(value: &[u8]) -> Self {
95        Self(value.to_vec())
96    }
97}
98
99impl MetaValue {
100    /// Maximum size of a [`MetaValue`]
101    /// More than 1MB would lead to problems.
102    pub const MAX_LEN_BYTES: usize = 1024 * 1024 * 1024;
103
104    pub fn as_slice(&self) -> &[u8] {
105        &self.0
106    }
107
108    pub fn to_json(&self) -> anyhow::Result<serde_json::Value> {
109        Ok(serde_json::from_slice(&self.0)?)
110    }
111
112    /// Converts the value to a JSON value, ignoring invalid utf-8.
113    pub fn to_json_lossy(&self) -> anyhow::Result<serde_json::Value> {
114        let maybe_lossy_str = String::from_utf8_lossy(self.as_slice());
115
116        if maybe_lossy_str.as_bytes() != self.as_slice() {
117            warn!(target: LOG_MODULE_META, "Value contains invalid utf-8, converting to lossy string");
118        }
119
120        Ok(serde_json::from_str(&maybe_lossy_str)?)
121    }
122}
123
124impl Decodable for MetaValue {
125    fn consensus_decode_partial<R: std::io::Read>(
126        r: &mut R,
127        modules: &fedimint_core::module::registry::ModuleDecoderRegistry,
128    ) -> Result<Self, fedimint_core::encoding::DecodeError> {
129        let bytes = Vec::consensus_decode_partial(r, modules)?;
130
131        if Self::MAX_LEN_BYTES < bytes.len() {
132            return Err(DecodeError::new_custom(anyhow::format_err!("Too long")));
133        }
134
135        Ok(Self(bytes))
136    }
137}
138impl Serialize for MetaValue {
139    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
140    where
141        S: Serializer,
142    {
143        assert!(self.0.len() <= Self::MAX_LEN_BYTES);
144        serializer.serialize_str(&hex::encode(&self.0))
145    }
146}
147
148// Implement Deserialize for MetaValue
149impl<'de> Deserialize<'de> for MetaValue {
150    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
151    where
152        D: Deserializer<'de>,
153    {
154        struct MetaValueVisitor;
155
156        impl<'de> Visitor<'de> for MetaValueVisitor {
157            type Value = MetaValue;
158
159            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
160                formatter.write_str("a hex string")
161            }
162
163            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
164            where
165                E: de::Error,
166            {
167                let val = hex::decode(value).map_err(de::Error::custom)?;
168
169                if MetaValue::MAX_LEN_BYTES < val.len() {
170                    return Err(de::Error::custom("Too long"));
171                }
172
173                Ok(MetaValue(val))
174            }
175        }
176
177        deserializer.deserialize_str(MetaValueVisitor)
178    }
179}
180
181#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
182pub struct MetaConsensusItem {
183    // Since AlephBft will merge and not re-submit the exact same item twice within one session,
184    // changing submitted item in sequence `a -> b -> a` will simply ignore the second `a`.
185    // To avoid this behavior, an otherwise meaningless `salt` field is used.
186    pub salt: u64,
187    pub key: MetaKey,
188    pub value: MetaValue,
189}
190
191/// A [`MetaValue`] in a consensus (which means it has a revision number)
192#[derive(Debug, Clone, Encodable, Decodable, Serialize, Deserialize, PartialEq, Eq)]
193pub struct MetaConsensusValue {
194    pub revision: u64,
195    pub value: MetaValue,
196}
197
198/// Input for a fedimint transaction
199#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
200pub struct MetaInput;
201
202/// Output for a fedimint transaction
203#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
204pub struct MetaOutput;
205
206/// Information needed by a client to update output funds
207#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
208pub struct MetaOutputOutcome;
209
210/// Errors that might be returned by the server
211#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
212pub enum MetaInputError {
213    #[error("This module does not support inputs")]
214    NotSupported,
215}
216
217/// Errors that might be returned by the server
218#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
219pub enum MetaOutputError {
220    #[error("This module does not support outputs")]
221    NotSupported,
222}
223
224/// Contains the types defined above
225pub struct MetaModuleTypes;
226
227// Wire together the types for this module
228plugin_types_trait_impl_common!(
229    KIND,
230    MetaModuleTypes,
231    MetaClientConfig,
232    MetaInput,
233    MetaOutput,
234    MetaOutputOutcome,
235    MetaConsensusItem,
236    MetaInputError,
237    MetaOutputError
238);
239
240#[derive(Debug)]
241pub struct MetaCommonInit;
242
243impl CommonModuleInit for MetaCommonInit {
244    const CONSENSUS_VERSION: ModuleConsensusVersion = MODULE_CONSENSUS_VERSION;
245    const KIND: ModuleKind = KIND;
246
247    type ClientConfig = MetaClientConfig;
248
249    fn decoder() -> Decoder {
250        MetaModuleTypes::decoder_builder().build()
251    }
252}
253
254impl fmt::Display for MetaClientConfig {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        write!(f, "MetaClientConfig")
257    }
258}
259impl fmt::Display for MetaInput {
260    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261        write!(f, "MetaInput")
262    }
263}
264
265impl fmt::Display for MetaOutput {
266    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267        write!(f, "MetaOutput")
268    }
269}
270
271impl fmt::Display for MetaOutputOutcome {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        write!(f, "MetaOutputOutcome")
274    }
275}
276
277impl fmt::Display for MetaConsensusItem {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        write!(f, "MetaConsensusItem")
280    }
281}