Skip to main content

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