From efb169087224d47db38d90be048019267796713f Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Wed, 18 Jan 2023 15:02:58 +0100 Subject: [PATCH] Add transaction type (#50) * Add transaction type --- nomos-core/Cargo.toml | 2 + nomos-core/src/account.rs | 4 + nomos-core/src/crypto.rs | 1 + nomos-core/src/lib.rs | 2 + nomos-core/src/tx.rs | 5 -- nomos-core/src/tx/mod.rs | 8 ++ nomos-core/src/tx/transaction.rs | 71 +++++++++++++++ nomos-core/src/wire.rs | 150 +++++++++++++++++++++++++++++++ 8 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 nomos-core/src/account.rs delete mode 100644 nomos-core/src/tx.rs create mode 100644 nomos-core/src/tx/mod.rs create mode 100644 nomos-core/src/tx/transaction.rs create mode 100644 nomos-core/src/wire.rs diff --git a/nomos-core/Cargo.toml b/nomos-core/Cargo.toml index a297134a..bf00ef32 100644 --- a/nomos-core/Cargo.toml +++ b/nomos-core/Cargo.toml @@ -15,6 +15,8 @@ futures = "0.3" raptorq = { version = "1.7", optional = true } serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" +bincode = "1.3" +once_cell = "1.0" [dev-dependencies] rand = "0.8" diff --git a/nomos-core/src/account.rs b/nomos-core/src/account.rs new file mode 100644 index 00000000..b2dacf92 --- /dev/null +++ b/nomos-core/src/account.rs @@ -0,0 +1,4 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AccountId; diff --git a/nomos-core/src/crypto.rs b/nomos-core/src/crypto.rs index 416ea347..297984da 100644 --- a/nomos-core/src/crypto.rs +++ b/nomos-core/src/crypto.rs @@ -1,2 +1,3 @@ pub type PublicKey = [u8; 32]; pub type PrivateKey = [u8; 32]; +pub type Signature = [u8; 32]; diff --git a/nomos-core/src/lib.rs b/nomos-core/src/lib.rs index 986613e2..39aec6ca 100644 --- a/nomos-core/src/lib.rs +++ b/nomos-core/src/lib.rs @@ -1,5 +1,7 @@ +pub mod account; pub mod block; pub mod crypto; pub mod fountain; pub mod staking; pub mod tx; +pub mod wire; diff --git a/nomos-core/src/tx.rs b/nomos-core/src/tx.rs deleted file mode 100644 index 06016845..00000000 --- a/nomos-core/src/tx.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[derive(Clone, Debug)] -pub struct Tx; - -#[derive(Clone, Debug)] -pub struct Id; diff --git a/nomos-core/src/tx/mod.rs b/nomos-core/src/tx/mod.rs new file mode 100644 index 00000000..0942384d --- /dev/null +++ b/nomos-core/src/tx/mod.rs @@ -0,0 +1,8 @@ +mod transaction; +use serde::{Deserialize, Serialize}; +pub use transaction::Transaction; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Tx { + Transfer(Transaction), +} diff --git a/nomos-core/src/tx/transaction.rs b/nomos-core/src/tx/transaction.rs new file mode 100644 index 00000000..d0a0d846 --- /dev/null +++ b/nomos-core/src/tx/transaction.rs @@ -0,0 +1,71 @@ +use crate::account::AccountId; +use crate::crypto::Signature; + +/// Verified transactions +/// +/// Can only be constructed if the signature is valid, +/// but does not imply that it can be successfully applied +/// to the ledger. +#[derive(Clone, Debug)] +pub struct Transaction { + pub from: AccountId, + pub to: AccountId, + pub value: u64, + // TODO: here for the moment because I still want to retain the ability + // to go from `Transaction` to wire format. We could otherwise + // save the id and rely on some storage + _signature: Signature, +} + +mod serde { + use super::*; + use ::serde::{Deserialize, Deserializer, Serialize, Serializer}; + // We have this additional definition so that we can automatically derive + // Serialize/Deserialize for the type while still being able to check + // the signature while deserializing. + // This would also allow to control ser/de independently from the Rust + // representation. + #[derive(Serialize, Deserialize)] + struct WireTransaction { + from: AccountId, + to: AccountId, + value: u64, + signature: Signature, + } + + impl<'de> Deserialize<'de> for Transaction { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let WireTransaction { + from, + to, + value, + signature, + } = WireTransaction::deserialize(deserializer)?; + //TODO: check signature + Ok(Transaction { + from, + to, + value, + _signature: signature, + }) + } + } + + impl Serialize for Transaction { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + WireTransaction { + from: self.from.clone(), + to: self.to.clone(), + value: self.value, + signature: self._signature, + } + .serialize(serializer) + } + } +} diff --git a/nomos-core/src/wire.rs b/nomos-core/src/wire.rs new file mode 100644 index 00000000..962f1a5c --- /dev/null +++ b/nomos-core/src/wire.rs @@ -0,0 +1,150 @@ +//! Serializer and Deserializer for wire formats. + +// TODO: we're using bincode for now, but might need strong guarantees about +// the underlying format in the future for standardization. +use bincode::{ + config::{ + Bounded, DefaultOptions, FixintEncoding, LittleEndian, RejectTrailing, WithOtherEndian, + WithOtherIntEncoding, WithOtherLimit, WithOtherTrailing, + }, + de::read::SliceReader, + Options, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +pub type Error = bincode::Error; +// type composition is cool but also makes naming types a bit akward +type BincodeOptions = WithOtherTrailing< + WithOtherIntEncoding< + WithOtherLimit, Bounded>, + FixintEncoding, + >, + RejectTrailing, +>; + +const DATA_LIMIT: u64 = 2048; // Do not serialize/deserialize more than 2Kb +static OPTIONS: Lazy = Lazy::new(|| { + bincode::DefaultOptions::new() + .with_little_endian() + .with_limit(DATA_LIMIT) + .with_fixint_encoding() + .reject_trailing_bytes() +}); + +type BincodeDeserializer<'de> = bincode::Deserializer, BincodeOptions>; +type BincodeSerializer = bincode::Serializer; + +pub struct Deserializer<'de> { + inner: BincodeDeserializer<'de>, +} + +pub struct Serializer { + inner: BincodeSerializer, +} + +impl<'de> Deserializer<'de> { + pub fn get_deserializer(&mut self) -> impl serde::Deserializer<'de> + '_ { + &mut self.inner + } + + pub fn deserialize>(&mut self) -> Result { + ::deserialize(&mut self.inner) + } +} + +impl Serializer { + pub fn get_serializer(&mut self) -> impl serde::Serializer + '_ { + &mut self.inner + } + + pub fn serialize_into( + &mut self, + item: &U, + ) -> Result<<&mut BincodeSerializer as serde::Serializer>::Ok, Error> { + item.serialize(&mut self.inner) + } +} + +/// Return a deserializer for wire format +/// +/// We only operator on in-memory slices as to abstract +/// any underlying protocol. See https://sans-io.readthedocs.io/how-to-sans-io.html +pub fn deserializer(data: &[u8]) -> Deserializer<'_> { + Deserializer { + inner: bincode::de::Deserializer::from_slice(data, *OPTIONS), + } +} + +/// Return a serializer for wire format. +/// +/// We only operator on in-memory slices as to abstract +/// any underlying protocol. See https://sans-io.readthedocs.io/how-to-sans-io.html +pub fn serializer(buffer: &mut Vec) -> Serializer<&'_ mut Vec> { + Serializer { + inner: bincode::Serializer::new(buffer, *OPTIONS), + } +} + +/// Return a serializer for wire format that overwrites (but now grow) the provided +/// buffer. +/// +/// We only operator on in-memory slices as to abstract +/// any underlying protocol. See https://sans-io.readthedocs.io/how-to-sans-io.html +pub fn serializer_into_buffer(buffer: &mut [u8]) -> Serializer<&'_ mut [u8]> { + Serializer { + inner: bincode::Serializer::new(buffer, *OPTIONS), + } +} + +/// Serialize an object directly into a vec +pub fn serialize(item: &T) -> Result, Error> { + let size = OPTIONS.serialized_size(item)?; + let mut buf = Vec::with_capacity(size as usize); + serializer(&mut buf).serialize_into(item)?; + Ok(buf) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ser_de() { + let tmp = String::from("much wow, very cool"); + let mut buf = Vec::new(); + let _ = serializer(&mut buf).serialize_into(&tmp).unwrap(); + let deserialized = deserializer(&buf).deserialize::().unwrap(); + assert_eq!(tmp, deserialized); + } + + #[test] + fn ser_de_slice() { + let tmp = String::from("much wow, very cool"); + let mut buf = vec![0; 1024]; + let _ = serializer_into_buffer(&mut buf) + .serialize_into(&tmp) + .unwrap(); + let deserialized = deserializer(&buf).deserialize::().unwrap(); + assert_eq!(tmp, deserialized); + } + + #[test] + fn ser_de_owned() { + let tmp = String::from("much wow, very cool"); + let serialized = serialize(&tmp).unwrap(); + let deserialized = deserializer(&serialized).deserialize::().unwrap(); + assert_eq!(tmp, deserialized); + } + + #[test] + fn ser_de_inner() { + let tmp = String::from("much wow, very cool"); + let mut buf = Vec::new(); + let mut serializer = serializer(&mut buf); + tmp.serialize(serializer.get_serializer()).unwrap(); + let mut deserializer = deserializer(&buf); + let deserialized = ::deserialize(deserializer.get_deserializer()).unwrap(); + assert_eq!(tmp, deserialized); + } +}