From serai types

This commit is contained in:
hinto.janai 2024-09-27 16:01:07 -04:00
parent 4a1821e5e7
commit a5316ad331
No known key found for this signature in database
GPG key ID: D47CE05FA175A499
5 changed files with 226 additions and 18 deletions

2
Cargo.lock generated
View file

@ -885,9 +885,11 @@ dependencies = [
name = "cuprate-types"
version = "0.0.0"
dependencies = [
"bytemuck",
"bytes",
"cuprate-epee-encoding",
"cuprate-fixed-bytes",
"cuprate-helper",
"curve25519-dalek",
"hex",
"monero-serai",

View file

@ -13,14 +13,17 @@ default = ["blockchain", "epee", "serde", "json", "hex"]
blockchain = []
epee = ["dep:cuprate-epee-encoding"]
serde = ["dep:serde"]
bytemuck = ["dep:bytemuck"]
proptest = ["dep:proptest", "dep:proptest-derive"]
json = ["hex"]
json = ["hex", "bytemuck", "dep:cuprate-helper"]
hex = ["dep:hex", "dep:paste"]
[dependencies]
cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true }
cuprate-helper = { path = "../helper", optional = true, features = ["cast"] }
cuprate-fixed-bytes = { path = "../net/fixed-bytes" }
bytemuck = { workspace = true, features = ["derive"], optional = true }
bytes = { workspace = true }
curve25519-dalek = { workspace = true }
monero-serai = { workspace = true }

View file

@ -3,6 +3,16 @@
//! This module provides transparent wrapper types for
//! arrays that (de)serialize from hexadecimal input/output.
#![cfg_attr(
feature = "bytemuck",
expect(
clippy::allow_attributes,
reason = "bytemuck uses allow in its derive macros"
)
)]
#[cfg(feature = "bytemuck")]
use bytemuck::{Pod, TransparentWrapper, Zeroable};
#[cfg(feature = "epee")]
use cuprate_epee_encoding::{error, macros::bytes, EpeeValue, Marker};
#[cfg(feature = "serde")]
@ -19,6 +29,7 @@ macro_rules! generate_hex_array {
$(
#[doc = concat!("Wrapper type that (de)serializes from/to a ", stringify!($array_len), "-byte array.")]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable, TransparentWrapper))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]

View file

@ -3,7 +3,14 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{hex::HexBytes32, json::output::Output};
use monero_serai::{block, transaction};
use cuprate_helper::cast::usize_to_u64;
use crate::{
hex::{HexBytes1, HexBytes32},
json::output::{Output, TaggedKey, Target},
};
/// JSON representation of a block.
///
@ -21,19 +28,25 @@ pub struct Block {
pub tx_hashes: Vec<HexBytes32>,
}
// impl From<monero_serai::block::Block> for Block {
// fn from(b: monero_serai::block::Block) -> Self {
// Self {
// major_version: todo!(),
// minor_version: todo!(),
// timestamp: todo!(),
// prev_id: todo!(),
// nonce: todo!(),
// miner_tx: todo!(),
// tx_hashes: todo!(),
// }
// }
// }
impl From<block::Block> for Block {
fn from(b: block::Block) -> Self {
let Ok(miner_tx) = MinerTransaction::try_from(b.miner_transaction) else {
unreachable!("input is a miner tx, this should never fail");
};
let tx_hashes = b.transactions.into_iter().map(HexBytes32).collect();
Self {
major_version: b.header.hardfork_version,
minor_version: b.header.hardfork_signal,
timestamp: b.header.timestamp,
prev_id: HexBytes32(b.header.previous),
nonce: b.header.nonce,
miner_tx,
tx_hashes,
}
}
}
/// [`Block::miner_tx`].
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -54,6 +67,98 @@ pub enum MinerTransaction {
},
}
impl TryFrom<transaction::Transaction> for MinerTransaction {
type Error = transaction::Transaction;
/// # Errors
/// This function errors if the input is not a miner transaction.
fn try_from(tx: transaction::Transaction) -> Result<Self, transaction::Transaction> {
fn map_prefix(
prefix: transaction::TransactionPrefix,
version: u8,
) -> Result<MinerTransactionPrefix, transaction::TransactionPrefix> {
let Some(input) = prefix.inputs.first() else {
return Err(prefix);
};
let height = match input {
transaction::Input::Gen(height) => usize_to_u64(*height),
transaction::Input::ToKey { .. } => return Err(prefix),
};
let vin = {
let r#gen = Gen { height };
let input = Input { r#gen };
[input]
};
let vout = prefix
.outputs
.into_iter()
.map(|o| {
let amount = o.amount.unwrap_or(0);
let target = match o.view_tag {
Some(view_tag) => {
let tagged_key = TaggedKey {
key: HexBytes32(o.key.0),
view_tag: HexBytes1([view_tag]),
};
Target::TaggedKey { tagged_key }
}
None => Target::Key {
key: HexBytes32(o.key.0),
},
};
Output { amount, target }
})
.collect();
// TODO: use cuprate_constants target time
let unlock_time = match prefix.additional_timelock {
transaction::Timelock::None => height,
transaction::Timelock::Block(height_lock) => height + usize_to_u64(height_lock),
transaction::Timelock::Time(seconds) => height + (seconds * 120),
} + 60;
Ok(MinerTransactionPrefix {
version,
unlock_time,
vin,
vout,
extra: prefix.extra,
})
}
Ok(match tx {
transaction::Transaction::V1 { prefix, signatures } => {
let prefix = match map_prefix(prefix, 1) {
Ok(p) => p,
Err(prefix) => return Err(transaction::Transaction::V1 { prefix, signatures }),
};
Self::V1 {
prefix,
signatures: [(); 0],
}
}
transaction::Transaction::V2 { prefix, proofs } => {
let prefix = match map_prefix(prefix, 2) {
Ok(p) => p,
Err(prefix) => return Err(transaction::Transaction::V2 { prefix, proofs }),
};
Self::V2 {
prefix,
rct_signatures: MinerTransactionRctSignatures { r#type: 0 },
}
}
})
}
}
impl Default for MinerTransaction {
fn default() -> Self {
Self::V1 {
@ -69,7 +174,7 @@ impl Default for MinerTransaction {
pub struct MinerTransactionPrefix {
pub version: u8,
pub unlock_time: u64,
pub vin: Vec<Input>,
pub vin: [Input; 1],
pub vout: Vec<Output>,
pub extra: Vec<u8>,
}

View file

@ -8,9 +8,13 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use cuprate_helper::cast::usize_to_u64;
use monero_serai::transaction;
use crate::{
hex::{HexBytes32, HexBytes64, HexBytes8},
json::output::Output,
hex::{HexBytes1, HexBytes32, HexBytes64, HexBytes8},
json::output::{Output, TaggedKey, Target},
};
/// JSON representation of a non-miner transaction.
@ -48,6 +52,89 @@ pub struct TransactionPrefix {
pub extra: Vec<u8>,
}
impl From<transaction::Transaction> for Transaction {
fn from(tx: transaction::Transaction) -> Self {
fn map_prefix(prefix: transaction::TransactionPrefix, version: u8) -> TransactionPrefix {
let mut height = 0;
let vin = prefix
.inputs
.into_iter()
.filter_map(|input| match input {
transaction::Input::ToKey {
amount,
key_offsets,
key_image,
} => {
let key = Key {
amount: amount.unwrap_or(0),
key_offsets,
k_image: HexBytes32(key_image.compress().0),
};
Some(Input { key })
}
transaction::Input::Gen(h) => {
height = usize_to_u64(h);
None
}
})
.collect();
let vout = prefix
.outputs
.into_iter()
.map(|o| {
let amount = o.amount.unwrap_or(0);
let target = match o.view_tag {
Some(view_tag) => {
let tagged_key = TaggedKey {
key: HexBytes32(o.key.0),
view_tag: HexBytes1([view_tag]),
};
Target::TaggedKey { tagged_key }
}
None => Target::Key {
key: HexBytes32(o.key.0),
},
};
Output { amount, target }
})
.collect();
// TODO: use cuprate_constants target time
let unlock_time = match prefix.additional_timelock {
transaction::Timelock::None => height,
transaction::Timelock::Block(height_lock) => height + usize_to_u64(height_lock),
transaction::Timelock::Time(seconds) => height + (seconds * 120),
} + 60;
TransactionPrefix {
version,
unlock_time,
vin,
vout,
extra: prefix.extra,
}
}
match tx {
transaction::Transaction::V1 { prefix, signatures } => Self::V1 {
prefix: map_prefix(prefix, 1),
signatures: todo!(),
},
transaction::Transaction::V2 { prefix, proofs } => Self::V2 {
prefix: map_prefix(prefix, 2),
rct_signatures: todo!(),
rctsig_prunable: todo!(),
},
}
}
}
/// [`Transaction::V2::rct_signatures`].
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]