Add transaction type (#50)

* Add transaction type
This commit is contained in:
Giacomo Pasini 2023-01-18 15:02:58 +01:00 committed by GitHub
parent 677d4a245c
commit efb1690872
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 238 additions and 5 deletions

View File

@ -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"

View File

@ -0,0 +1,4 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AccountId;

View File

@ -1,2 +1,3 @@
pub type PublicKey = [u8; 32];
pub type PrivateKey = [u8; 32];
pub type Signature = [u8; 32];

View File

@ -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;

View File

@ -1,5 +0,0 @@
#[derive(Clone, Debug)]
pub struct Tx;
#[derive(Clone, Debug)]
pub struct Id;

8
nomos-core/src/tx/mod.rs Normal file
View File

@ -0,0 +1,8 @@
mod transaction;
use serde::{Deserialize, Serialize};
pub use transaction::Transaction;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Tx {
Transfer(Transaction),
}

View File

@ -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<D>(deserializer: D) -> Result<Self, D::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
WireTransaction {
from: self.from.clone(),
to: self.to.clone(),
value: self.value,
signature: self._signature,
}
.serialize(serializer)
}
}
}

150
nomos-core/src/wire.rs Normal file
View File

@ -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<WithOtherEndian<DefaultOptions, LittleEndian>, Bounded>,
FixintEncoding,
>,
RejectTrailing,
>;
const DATA_LIMIT: u64 = 2048; // Do not serialize/deserialize more than 2Kb
static OPTIONS: Lazy<BincodeOptions> = Lazy::new(|| {
bincode::DefaultOptions::new()
.with_little_endian()
.with_limit(DATA_LIMIT)
.with_fixint_encoding()
.reject_trailing_bytes()
});
type BincodeDeserializer<'de> = bincode::Deserializer<SliceReader<'de>, BincodeOptions>;
type BincodeSerializer<T> = bincode::Serializer<T, BincodeOptions>;
pub struct Deserializer<'de> {
inner: BincodeDeserializer<'de>,
}
pub struct Serializer<T> {
inner: BincodeSerializer<T>,
}
impl<'de> Deserializer<'de> {
pub fn get_deserializer(&mut self) -> impl serde::Deserializer<'de> + '_ {
&mut self.inner
}
pub fn deserialize<T: Deserialize<'de>>(&mut self) -> Result<T, Error> {
<T>::deserialize(&mut self.inner)
}
}
impl<T: std::io::Write> Serializer<T> {
pub fn get_serializer(&mut self) -> impl serde::Serializer + '_ {
&mut self.inner
}
pub fn serialize_into<U: Serialize>(
&mut self,
item: &U,
) -> Result<<&mut BincodeSerializer<T> 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<u8>) -> Serializer<&'_ mut Vec<u8>> {
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<T: Serialize>(item: &T) -> Result<Vec<u8>, 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::<String>().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::<String>().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::<String>().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 = <String>::deserialize(deserializer.get_deserializer()).unwrap();
assert_eq!(tmp, deserialized);
}
}