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
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 = hex::FromHexError;
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    /// The value read as JSON.
109    ///
110    /// # Errors
111    ///
112    /// Fails with a [`serde_json::Error`] if the bytes are not valid JSON,
113    /// including when they are not valid UTF-8.
114    pub fn to_json(&self) -> Result<serde_json::Value, serde_json::Error> {
115        serde_json::from_slice(&self.0)
116    }
117
118    /// Converts the value to a JSON value, ignoring invalid utf-8.
119    ///
120    /// # Errors
121    ///
122    /// Fails with a [`serde_json::Error`] if the bytes are not valid JSON once
123    /// invalid UTF-8 has been replaced.
124    pub fn to_json_lossy(&self) -> Result<serde_json::Value, serde_json::Error> {
125        let maybe_lossy_str = String::from_utf8_lossy(self.as_slice());
126
127        if maybe_lossy_str.as_bytes() != self.as_slice() {
128            warn!(target: LOG_MODULE_META, "Value contains invalid utf-8, converting to lossy string");
129        }
130
131        serde_json::from_str(&maybe_lossy_str)
132    }
133}
134
135impl Decodable for MetaValue {
136    fn consensus_decode_partial<R: std::io::Read>(
137        r: &mut R,
138        modules: &fedimint_core::module::registry::ModuleDecoderRegistry,
139    ) -> Result<Self, fedimint_core::encoding::DecodeError> {
140        let bytes = Vec::consensus_decode_partial(r, modules)?;
141
142        if Self::MAX_LEN_BYTES < bytes.len() {
143            return Err(DecodeError::from_str("Too long"));
144        }
145
146        Ok(Self(bytes))
147    }
148}
149impl Serialize for MetaValue {
150    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
151    where
152        S: Serializer,
153    {
154        assert!(self.0.len() <= Self::MAX_LEN_BYTES);
155        serializer.serialize_str(&hex::encode(&self.0))
156    }
157}
158
159// Implement Deserialize for MetaValue
160impl<'de> Deserialize<'de> for MetaValue {
161    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162    where
163        D: Deserializer<'de>,
164    {
165        struct MetaValueVisitor;
166
167        impl Visitor<'_> for MetaValueVisitor {
168            type Value = MetaValue;
169
170            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
171                formatter.write_str("a hex string")
172            }
173
174            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
175            where
176                E: de::Error,
177            {
178                let val = hex::decode(value).map_err(de::Error::custom)?;
179
180                if MetaValue::MAX_LEN_BYTES < val.len() {
181                    return Err(de::Error::custom("Too long"));
182                }
183
184                Ok(MetaValue(val))
185            }
186        }
187
188        deserializer.deserialize_str(MetaValueVisitor)
189    }
190}
191
192#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
193pub struct MetaConsensusItem {
194    // Since AlephBft will merge and not re-submit the exact same item twice within one session,
195    // changing submitted item in sequence `a -> b -> a` will simply ignore the second `a`.
196    // To avoid this behavior, an otherwise meaningless `salt` field is used.
197    pub salt: u64,
198    pub key: MetaKey,
199    pub value: MetaValue,
200}
201
202/// A [`MetaValue`] in a consensus (which means it has a revision number)
203#[derive(Debug, Clone, Encodable, Decodable, Serialize, Deserialize, PartialEq, Eq)]
204pub struct MetaConsensusValue {
205    pub revision: u64,
206    pub value: MetaValue,
207}
208
209/// Input for a fedimint transaction
210#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
211pub struct MetaInput;
212
213/// Output for a fedimint transaction
214#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
215pub struct MetaOutput;
216
217/// Information needed by a client to update output funds
218#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
219pub struct MetaOutputOutcome;
220
221/// Errors that might be returned by the server
222#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
223pub enum MetaInputError {
224    #[error("This module does not support inputs")]
225    NotSupported,
226}
227
228/// Errors that might be returned by the server
229#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
230pub enum MetaOutputError {
231    #[error("This module does not support outputs")]
232    NotSupported,
233}
234
235/// Contains the types defined above
236pub struct MetaModuleTypes;
237
238// Wire together the types for this module
239plugin_types_trait_impl_common!(
240    KIND,
241    MetaModuleTypes,
242    MetaClientConfig,
243    MetaInput,
244    MetaOutput,
245    MetaOutputOutcome,
246    MetaConsensusItem,
247    MetaInputError,
248    MetaOutputError
249);
250
251#[derive(Debug)]
252pub struct MetaCommonInit;
253
254impl CommonModuleInit for MetaCommonInit {
255    const CONSENSUS_VERSION: ModuleConsensusVersion = MODULE_CONSENSUS_VERSION;
256    const KIND: ModuleKind = KIND;
257
258    type ClientConfig = MetaClientConfig;
259
260    fn decoder() -> Decoder {
261        MetaModuleTypes::decoder_builder().build()
262    }
263}
264
265impl fmt::Display for MetaClientConfig {
266    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267        write!(f, "MetaClientConfig")
268    }
269}
270impl fmt::Display for MetaInput {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        write!(f, "MetaInput")
273    }
274}
275
276impl fmt::Display for MetaOutput {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        write!(f, "MetaOutput")
279    }
280}
281
282impl fmt::Display for MetaOutputOutcome {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284        write!(f, "MetaOutputOutcome")
285    }
286}
287
288impl fmt::Display for MetaConsensusItem {
289    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290        write!(f, "Meta - len: {}", self.value.0.len())
291    }
292}
293
294#[cfg(test)]
295mod tests;