mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-12-22 19:49:28 +00:00
types: JSON representation types (#300)
Some checks failed
Audit / audit (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / typo (push) Has been cancelled
Deny / audit (push) Has been cancelled
Doc / build (push) Has been cancelled
CI / ci (macos-latest, stable, bash) (push) Has been cancelled
CI / ci (ubuntu-latest, stable, bash) (push) Has been cancelled
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Has been cancelled
Doc / deploy (push) Has been cancelled
Some checks failed
Audit / audit (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / typo (push) Has been cancelled
Deny / audit (push) Has been cancelled
Doc / build (push) Has been cancelled
CI / ci (macos-latest, stable, bash) (push) Has been cancelled
CI / ci (ubuntu-latest, stable, bash) (push) Has been cancelled
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Has been cancelled
Doc / deploy (push) Has been cancelled
* add `cuprate_types::json` * docs * `Option` -> flattened enums + prefix structs * output enum * docs * todo!() epee impl * cuprate-rpc-types: add comments * cuprate-rpc-types: common `TxEntry` fields into prefix struct * remove epee * docs * add `hex` module * `From` serai types * cleanup * proofs * tx from impls * fix tx timelock * add block value tests * add ringct types * add tx_v1, tx_rct_3 test * clsag bulletproofs tx test * clsag bulletproofs plus tx test * docs * fix hex bytes * typo * docs
This commit is contained in:
parent
a003e0588d
commit
80bfe0a34c
14 changed files with 2004 additions and 26 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -917,11 +917,16 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"cuprate-epee-encoding",
|
"cuprate-epee-encoding",
|
||||||
"cuprate-fixed-bytes",
|
"cuprate-fixed-bytes",
|
||||||
|
"cuprate-helper",
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
|
"hex",
|
||||||
|
"hex-literal",
|
||||||
"monero-serai",
|
"monero-serai",
|
||||||
|
"pretty_assertions",
|
||||||
"proptest",
|
"proptest",
|
||||||
"proptest-derive",
|
"proptest-derive",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1412,6 +1417,9 @@ name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex-literal"
|
name = "hex-literal"
|
||||||
|
|
|
@ -6,12 +6,16 @@ This allows all workspace crates to share, and aids compile times.
|
||||||
If a 3rd party's crate/functions/types are small enough, it could be moved here to trim dependencies and allow easy modifications.
|
If a 3rd party's crate/functions/types are small enough, it could be moved here to trim dependencies and allow easy modifications.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
Code can be selectively used/compiled with cargo's `--feature` or `features = ["..."]`.
|
Modules can be selectively used/compiled with cargo's `--feature` or `features = ["..."]`.
|
||||||
|
|
||||||
All features on by default.
|
All features are off by default.
|
||||||
|
|
||||||
See [`Cargo.toml`](Cargo.toml)'s `[features]` table to see what features there are and what they enable.
|
See [`Cargo.toml`](Cargo.toml)'s `[features]` table to see what features there are and what they enable.
|
||||||
|
|
||||||
|
Special non-module related features:
|
||||||
|
- `serde`: Enables serde implementations on applicable types
|
||||||
|
- `std`: Enables usage of `std`
|
||||||
|
|
||||||
## `#[no_std]`
|
## `#[no_std]`
|
||||||
Each modules documents whether it requires `std` or not.
|
Each modules documents whether it requires `std` or not.
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ epee = ["dep:cuprate-epee-encoding"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true }
|
cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true }
|
||||||
cuprate-fixed-bytes = { path = "../../net/fixed-bytes" }
|
cuprate-fixed-bytes = { path = "../../net/fixed-bytes" }
|
||||||
cuprate-types = { path = "../../types" }
|
cuprate-types = { path = "../../types", default-features = false, features = ["epee", "serde"] }
|
||||||
|
|
||||||
paste = { workspace = true }
|
paste = { workspace = true }
|
||||||
serde = { workspace = true, optional = true }
|
serde = { workspace = true, optional = true }
|
||||||
|
|
|
@ -635,7 +635,9 @@ define_request_and_response! {
|
||||||
AccessResponseBase {
|
AccessResponseBase {
|
||||||
blob: String,
|
blob: String,
|
||||||
block_header: BlockHeader,
|
block_header: BlockHeader,
|
||||||
json: String, // FIXME: this should be defined in a struct, it has many fields.
|
/// `cuprate_rpc_types::json::block::Block` should be used
|
||||||
|
/// to create this JSON string in a type-safe manner.
|
||||||
|
json: String,
|
||||||
miner_tx_hash: String,
|
miner_tx_hash: String,
|
||||||
tx_hashes: Vec<String> = default_vec::<String>(), "default_vec",
|
tx_hashes: Vec<String> = default_vec::<String>(), "default_vec",
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,32 +71,24 @@ use cuprate_epee_encoding::{
|
||||||
pub enum TxEntry {
|
pub enum TxEntry {
|
||||||
/// This entry exists in the transaction pool.
|
/// This entry exists in the transaction pool.
|
||||||
InPool {
|
InPool {
|
||||||
as_hex: String,
|
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
|
||||||
as_json: String,
|
#[cfg_attr(feature = "serde", serde(flatten))]
|
||||||
|
prefix: TxEntryPrefix,
|
||||||
block_height: u64,
|
block_height: u64,
|
||||||
block_timestamp: u64,
|
block_timestamp: u64,
|
||||||
confirmations: u64,
|
confirmations: u64,
|
||||||
double_spend_seen: bool,
|
|
||||||
output_indices: Vec<u64>,
|
output_indices: Vec<u64>,
|
||||||
prunable_as_hex: String,
|
|
||||||
prunable_hash: String,
|
|
||||||
pruned_as_hex: String,
|
|
||||||
tx_hash: String,
|
|
||||||
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))]
|
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))]
|
||||||
/// Will always be serialized as `true`.
|
/// Will always be serialized as `true`.
|
||||||
in_pool: bool,
|
in_pool: bool,
|
||||||
},
|
},
|
||||||
/// This entry _does not_ exist in the transaction pool.
|
/// This entry _does not_ exist in the transaction pool.
|
||||||
NotInPool {
|
NotInPool {
|
||||||
as_hex: String,
|
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
|
||||||
as_json: String,
|
#[cfg_attr(feature = "serde", serde(flatten))]
|
||||||
double_spend_seen: bool,
|
prefix: TxEntryPrefix,
|
||||||
prunable_as_hex: String,
|
|
||||||
prunable_hash: String,
|
|
||||||
pruned_as_hex: String,
|
|
||||||
received_timestamp: u64,
|
received_timestamp: u64,
|
||||||
relayed: bool,
|
relayed: bool,
|
||||||
tx_hash: String,
|
|
||||||
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))]
|
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))]
|
||||||
/// Will always be serialized as `false`.
|
/// Will always be serialized as `false`.
|
||||||
in_pool: bool,
|
in_pool: bool,
|
||||||
|
@ -106,20 +98,29 @@ pub enum TxEntry {
|
||||||
impl Default for TxEntry {
|
impl Default for TxEntry {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::NotInPool {
|
Self::NotInPool {
|
||||||
as_hex: String::default(),
|
prefix: Default::default(),
|
||||||
as_json: String::default(),
|
|
||||||
double_spend_seen: bool::default(),
|
|
||||||
prunable_as_hex: String::default(),
|
|
||||||
prunable_hash: String::default(),
|
|
||||||
pruned_as_hex: String::default(),
|
|
||||||
received_timestamp: u64::default(),
|
received_timestamp: u64::default(),
|
||||||
relayed: bool::default(),
|
relayed: bool::default(),
|
||||||
tx_hash: String::default(),
|
|
||||||
in_pool: false,
|
in_pool: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Common fields in all [`TxEntry`] variants.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct TxEntryPrefix {
|
||||||
|
as_hex: String,
|
||||||
|
/// `cuprate_rpc_types::json::tx::Transaction` should be used
|
||||||
|
/// to create this JSON string in a type-safe manner.
|
||||||
|
as_json: String,
|
||||||
|
double_spend_seen: bool,
|
||||||
|
tx_hash: String,
|
||||||
|
prunable_as_hex: String,
|
||||||
|
prunable_hash: String,
|
||||||
|
pruned_as_hex: String,
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Epee
|
//---------------------------------------------------------------------------------------------------- Epee
|
||||||
#[cfg(feature = "epee")]
|
#[cfg(feature = "epee")]
|
||||||
impl EpeeObjectBuilder<TxEntry> for () {
|
impl EpeeObjectBuilder<TxEntry> for () {
|
||||||
|
|
|
@ -140,6 +140,8 @@ define_request_and_response! {
|
||||||
#[doc = serde_doc_test!(GET_TRANSACTIONS_RESPONSE)]
|
#[doc = serde_doc_test!(GET_TRANSACTIONS_RESPONSE)]
|
||||||
AccessResponseBase {
|
AccessResponseBase {
|
||||||
txs_as_hex: Vec<String> = default_vec::<String>(), "default_vec",
|
txs_as_hex: Vec<String> = default_vec::<String>(), "default_vec",
|
||||||
|
/// `cuprate_rpc_types::json::tx::Transaction` should be used
|
||||||
|
/// to create this JSON string in a type-safe manner.
|
||||||
txs_as_json: Vec<String> = default_vec::<String>(), "default_vec",
|
txs_as_json: Vec<String> = default_vec::<String>(), "default_vec",
|
||||||
missed_tx: Vec<String> = default_vec::<String>(), "default_vec",
|
missed_tx: Vec<String> = default_vec::<String>(), "default_vec",
|
||||||
txs: Vec<TxEntry> = default_vec::<TxEntry>(), "default_vec",
|
txs: Vec<TxEntry> = default_vec::<TxEntry>(), "default_vec",
|
||||||
|
|
|
@ -9,19 +9,23 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/types"
|
||||||
keywords = ["cuprate", "types"]
|
keywords = ["cuprate", "types"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["blockchain", "epee", "serde"]
|
default = ["blockchain", "epee", "serde", "json", "hex"]
|
||||||
blockchain = []
|
blockchain = []
|
||||||
epee = ["dep:cuprate-epee-encoding"]
|
epee = ["dep:cuprate-epee-encoding"]
|
||||||
serde = ["dep:serde"]
|
serde = ["dep:serde"]
|
||||||
proptest = ["dep:proptest", "dep:proptest-derive"]
|
proptest = ["dep:proptest", "dep:proptest-derive"]
|
||||||
|
json = ["hex", "dep:cuprate-helper"]
|
||||||
|
hex = ["dep:hex"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true }
|
cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true }
|
||||||
|
cuprate-helper = { path = "../helper", optional = true, features = ["cast"] }
|
||||||
cuprate-fixed-bytes = { path = "../net/fixed-bytes" }
|
cuprate-fixed-bytes = { path = "../net/fixed-bytes" }
|
||||||
|
|
||||||
bytes = { workspace = true }
|
bytes = { workspace = true }
|
||||||
curve25519-dalek = { workspace = true }
|
curve25519-dalek = { workspace = true }
|
||||||
monero-serai = { workspace = true }
|
monero-serai = { workspace = true }
|
||||||
|
hex = { workspace = true, features = ["serde", "alloc"], optional = true }
|
||||||
serde = { workspace = true, features = ["derive"], optional = true }
|
serde = { workspace = true, features = ["derive"], optional = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
|
@ -29,6 +33,9 @@ proptest = { workspace = true, optional = true }
|
||||||
proptest-derive = { workspace = true, optional = true }
|
proptest-derive = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
hex-literal = { workspace = true }
|
||||||
|
pretty_assertions = { workspace = true }
|
||||||
|
serde_json = { workspace = true, features = ["std"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
|
@ -10,3 +10,5 @@ This crate is a kitchen-sink for data types that are shared across Cuprate.
|
||||||
| `serde` | Enables `serde` on types where applicable
|
| `serde` | Enables `serde` on types where applicable
|
||||||
| `epee` | Enables `cuprate-epee-encoding` on types where applicable
|
| `epee` | Enables `cuprate-epee-encoding` on types where applicable
|
||||||
| `proptest` | Enables `proptest::arbitrary::Arbitrary` on some types
|
| `proptest` | Enables `proptest::arbitrary::Arbitrary` on some types
|
||||||
|
| `json` | Enables the `json` module, containing JSON representations of common Monero types
|
||||||
|
| `hex` | Enables the `hex` module, containing the `HexBytes` type
|
74
types/src/hex.rs
Normal file
74
types/src/hex.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
//! Hexadecimal serde wrappers for arrays.
|
||||||
|
//!
|
||||||
|
//! This module provides transparent wrapper types for
|
||||||
|
//! arrays that (de)serialize from hexadecimal input/output.
|
||||||
|
|
||||||
|
#[cfg(feature = "epee")]
|
||||||
|
use cuprate_epee_encoding::{error, macros::bytes, EpeeValue, Marker};
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Wrapper type for a byte array that (de)serializes from/to hexadecimal strings.
|
||||||
|
///
|
||||||
|
/// # Deserialization
|
||||||
|
/// This struct has a custom deserialization that only applies to certain
|
||||||
|
/// `N` lengths because [`hex::FromHex`] does not implement for a generic `N`:
|
||||||
|
/// <https://docs.rs/hex/0.4.3/src/hex/lib.rs.html#220-230>
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct HexBytes<const N: usize>(
|
||||||
|
#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] pub [u8; N],
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<'de, const N: usize> Deserialize<'de> for HexBytes<N>
|
||||||
|
where
|
||||||
|
[u8; N]: hex::FromHex,
|
||||||
|
<[u8; N] as hex::FromHex>::Error: std::fmt::Display,
|
||||||
|
{
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ok(Self(hex::serde::deserialize(deserializer)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "epee")]
|
||||||
|
impl<const N: usize> EpeeValue for HexBytes<N> {
|
||||||
|
const MARKER: Marker = <[u8; N] as EpeeValue>::MARKER;
|
||||||
|
|
||||||
|
fn read<B: bytes::Buf>(r: &mut B, marker: &Marker) -> error::Result<Self> {
|
||||||
|
Ok(Self(<[u8; N] as EpeeValue>::read(r, marker)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<B: bytes::BufMut>(self, w: &mut B) -> error::Result<()> {
|
||||||
|
<[u8; N] as EpeeValue>::write(self.0, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default is not implemented for arrays >32, so we must do it manually.
|
||||||
|
impl<const N: usize> Default for HexBytes<N> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self([0; N])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hex_bytes_32() {
|
||||||
|
let hash = [1; 32];
|
||||||
|
let hex_bytes = HexBytes::<32>(hash);
|
||||||
|
let expected_json = r#""0101010101010101010101010101010101010101010101010101010101010101""#;
|
||||||
|
|
||||||
|
let to_string = serde_json::to_string(&hex_bytes).unwrap();
|
||||||
|
assert_eq!(to_string, expected_json);
|
||||||
|
|
||||||
|
let from_str = serde_json::from_str::<HexBytes<32>>(expected_json).unwrap();
|
||||||
|
assert_eq!(hex_bytes, from_str);
|
||||||
|
}
|
||||||
|
}
|
359
types/src/json/block.rs
Normal file
359
types/src/json/block.rs
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
//! JSON block types.
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use monero_serai::{block, transaction};
|
||||||
|
|
||||||
|
use cuprate_helper::cast::usize_to_u64;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
hex::HexBytes,
|
||||||
|
json::output::{Output, TaggedKey, Target},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// JSON representation of a block.
|
||||||
|
///
|
||||||
|
/// Used in:
|
||||||
|
/// - [`/get_block` -> `json`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_block)
|
||||||
|
#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct Block {
|
||||||
|
pub major_version: u8,
|
||||||
|
pub minor_version: u8,
|
||||||
|
pub timestamp: u64,
|
||||||
|
pub prev_id: HexBytes<32>,
|
||||||
|
pub nonce: u32,
|
||||||
|
pub miner_tx: MinerTransaction,
|
||||||
|
pub tx_hashes: Vec<HexBytes<32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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(HexBytes::<32>).collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
major_version: b.header.hardfork_version,
|
||||||
|
minor_version: b.header.hardfork_signal,
|
||||||
|
timestamp: b.header.timestamp,
|
||||||
|
prev_id: HexBytes::<32>(b.header.previous),
|
||||||
|
nonce: b.header.nonce,
|
||||||
|
miner_tx,
|
||||||
|
tx_hashes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`Block::miner_tx`].
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum MinerTransaction {
|
||||||
|
V1 {
|
||||||
|
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
|
||||||
|
#[serde(flatten)]
|
||||||
|
prefix: MinerTransactionPrefix,
|
||||||
|
signatures: [(); 0],
|
||||||
|
},
|
||||||
|
V2 {
|
||||||
|
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
|
||||||
|
#[serde(flatten)]
|
||||||
|
prefix: MinerTransactionPrefix,
|
||||||
|
rct_signatures: MinerTransactionRctSignatures,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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: HexBytes::<32>(o.key.0),
|
||||||
|
view_tag: HexBytes::<1>([view_tag]),
|
||||||
|
};
|
||||||
|
|
||||||
|
Target::TaggedKey { tagged_key }
|
||||||
|
}
|
||||||
|
None => Target::Key {
|
||||||
|
key: HexBytes::<32>(o.key.0),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Output { amount, target }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let unlock_time = match prefix.additional_timelock {
|
||||||
|
transaction::Timelock::None => 0,
|
||||||
|
transaction::Timelock::Block(x) => usize_to_u64(x),
|
||||||
|
transaction::Timelock::Time(x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
prefix: Default::default(),
|
||||||
|
signatures: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`MinerTransaction::V1::prefix`] & [`MinerTransaction::V2::prefix`].
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct MinerTransactionPrefix {
|
||||||
|
pub version: u8,
|
||||||
|
pub unlock_time: u64,
|
||||||
|
pub vin: [Input; 1],
|
||||||
|
pub vout: Vec<Output>,
|
||||||
|
pub extra: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`MinerTransaction::V2::rct_signatures`].
|
||||||
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct MinerTransactionRctSignatures {
|
||||||
|
pub r#type: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`MinerTransactionPrefix::vin`].
|
||||||
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct Input {
|
||||||
|
pub r#gen: Gen,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`Input::gen`].
|
||||||
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct Gen {
|
||||||
|
pub height: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use hex_literal::hex;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[expect(clippy::needless_pass_by_value)]
|
||||||
|
fn test(block: Block, block_json: &'static str) {
|
||||||
|
let json = serde_json::from_str::<Block>(block_json).unwrap();
|
||||||
|
assert_eq!(block, json);
|
||||||
|
let string = serde_json::to_string(&json).unwrap();
|
||||||
|
assert_eq!(block_json, &string);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_300000() {
|
||||||
|
const JSON: &str = r#"{"major_version":1,"minor_version":0,"timestamp":1415690591,"prev_id":"e97a0ab6307de9b9f9a9872263ef3e957976fb227eb9422c6854e989e5d5d34c","nonce":2147484616,"miner_tx":{"version":1,"unlock_time":300060,"vin":[{"gen":{"height":300000}}],"vout":[{"amount":47019296802,"target":{"key":"3c1dcbf5b485987ecef4596bb700e32cbc7bd05964e3888ffc05f8a46bf5fc33"}},{"amount":200000000000,"target":{"key":"5810afc7a1b01a1c913eb6aab15d4a851cbc4a8cf0adf90bb80ac1a7ca9928aa"}},{"amount":3000000000000,"target":{"key":"520f49c5f2ce8456dc1a565f35ed3a5ccfff3a1210b340870a57d2749a81a2df"}},{"amount":10000000000000,"target":{"key":"44d7705e62c76c2e349a474df6724aa1d9932092002b03a94f9c19d9d12b9427"}}],"extra":[1,251,8,189,254,12,213,173,108,61,156,198,144,151,31,130,141,211,120,55,81,98,32,247,111,127,254,170,170,240,124,190,223,2,8,0,0,0,64,184,115,46,246],"signatures":[]},"tx_hashes":[]}"#;
|
||||||
|
|
||||||
|
let block = Block {
|
||||||
|
major_version: 1,
|
||||||
|
minor_version: 0,
|
||||||
|
timestamp: 1415690591,
|
||||||
|
prev_id: HexBytes::<32>(hex!(
|
||||||
|
"e97a0ab6307de9b9f9a9872263ef3e957976fb227eb9422c6854e989e5d5d34c"
|
||||||
|
)),
|
||||||
|
nonce: 2147484616,
|
||||||
|
miner_tx: MinerTransaction::V1 {
|
||||||
|
prefix: MinerTransactionPrefix {
|
||||||
|
version: 1,
|
||||||
|
unlock_time: 300060,
|
||||||
|
vin: [Input {
|
||||||
|
r#gen: Gen { height: 300000 },
|
||||||
|
}],
|
||||||
|
vout: vec![
|
||||||
|
Output {
|
||||||
|
amount: 47019296802,
|
||||||
|
target: Target::Key {
|
||||||
|
key: HexBytes::<32>(hex!("3c1dcbf5b485987ecef4596bb700e32cbc7bd05964e3888ffc05f8a46bf5fc33")),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Output {
|
||||||
|
amount: 200000000000,
|
||||||
|
target: Target::Key {
|
||||||
|
key: HexBytes::<32>(hex!("5810afc7a1b01a1c913eb6aab15d4a851cbc4a8cf0adf90bb80ac1a7ca9928aa")),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Output {
|
||||||
|
amount: 3000000000000,
|
||||||
|
target: Target::Key {
|
||||||
|
key: HexBytes::<32>(hex!("520f49c5f2ce8456dc1a565f35ed3a5ccfff3a1210b340870a57d2749a81a2df")),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Output {
|
||||||
|
amount: 10000000000000,
|
||||||
|
target: Target::Key {
|
||||||
|
key: HexBytes::<32>(hex!("44d7705e62c76c2e349a474df6724aa1d9932092002b03a94f9c19d9d12b9427")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
extra: vec![
|
||||||
|
1, 251, 8, 189, 254, 12, 213, 173, 108, 61, 156, 198, 144, 151, 31, 130,
|
||||||
|
141, 211, 120, 55, 81, 98, 32, 247, 111, 127, 254, 170, 170, 240, 124, 190,
|
||||||
|
223, 2, 8, 0, 0, 0, 64, 184, 115, 46, 246,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
signatures: [],
|
||||||
|
},
|
||||||
|
tx_hashes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
test(block, JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_3245409() {
|
||||||
|
const JSON: &str = r#"{"major_version":16,"minor_version":16,"timestamp":1727293028,"prev_id":"41b56c273d69def3294e56179de71c61808042d54c1e085078d21dbe99e81b6f","nonce":311,"miner_tx":{"version":2,"unlock_time":3245469,"vin":[{"gen":{"height":3245409}}],"vout":[{"amount":601012280000,"target":{"tagged_key":{"key":"8c0b16c6df02b9944b49f375d96a958a0fc5431c048879bb5bf25f64a1163b9e","view_tag":"88"}}}],"extra":[1,39,23,182,203,58,48,15,217,9,13,147,104,133,206,176,185,56,237,179,136,72,84,129,113,98,206,4,18,50,130,162,94,2,17,73,18,21,33,32,112,5,0,0,0,0,0,0,0,0,0,0],"rct_signatures":{"type":0}},"tx_hashes":["eab76986a0cbcae690d8499f0f616f783fd2c89c6f611417f18011950dbdab2e","57b19aa8c2cdbb6836cf13dd1e321a67860965c12e4418f3c30f58c8899a851e","5340185432ab6b74fb21379f7e8d8f0e37f0882b2a7121fd7c08736f079e2edc","01dc6d31db56d68116f5294c1b4f80b33b048b5cdfefcd904f23e6c0de3daff5","c9fb6a2730678203948fef2a49fa155b63f35a3649f3d32ed405a6806f3bbd56","af965cdd2a2315baf1d4a3d242f44fe07b1fd606d5f4853c9ff546ca6c12a5af","97bc9e047d25fae8c14ce6ec882224e7b722f5e79b62a2602a6bacebdac8547b","28c46992eaf10dc0cceb313c30572d023432b7bd26e85e679bc8fe419533a7bf","c32e3acde2ff2885c9cc87253b40d6827d167dfcc3022c72f27084fd98788062","19e66a47f075c7cccde8a7b52803119e089e33e3a4847cace0bd1d17b0d22bab","8e8ac560e77a1ee72e82a5eb6887adbe5979a10cd29cb2c2a3720ce87db43a70","b7ff5141524b5cca24de6780a5dbfdf71e7de1e062fd85f557fb3b43b8e285dc","f09df0f113763ef9b9a2752ac293b478102f7cab03ef803a3d9db7585aea8912"]}"#;
|
||||||
|
|
||||||
|
let block = Block {
|
||||||
|
major_version: 16,
|
||||||
|
minor_version: 16,
|
||||||
|
timestamp: 1727293028,
|
||||||
|
prev_id: HexBytes::<32>(hex!(
|
||||||
|
"41b56c273d69def3294e56179de71c61808042d54c1e085078d21dbe99e81b6f"
|
||||||
|
)),
|
||||||
|
nonce: 311,
|
||||||
|
miner_tx: MinerTransaction::V2 {
|
||||||
|
prefix: MinerTransactionPrefix {
|
||||||
|
version: 2,
|
||||||
|
unlock_time: 3245469,
|
||||||
|
vin: [Input {
|
||||||
|
r#gen: Gen { height: 3245409 },
|
||||||
|
}],
|
||||||
|
vout: vec![Output {
|
||||||
|
amount: 601012280000,
|
||||||
|
target: Target::TaggedKey {
|
||||||
|
tagged_key: TaggedKey {
|
||||||
|
key: HexBytes::<32>(hex!(
|
||||||
|
"8c0b16c6df02b9944b49f375d96a958a0fc5431c048879bb5bf25f64a1163b9e"
|
||||||
|
)),
|
||||||
|
view_tag: HexBytes::<1>(hex!("88")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
extra: vec![
|
||||||
|
1, 39, 23, 182, 203, 58, 48, 15, 217, 9, 13, 147, 104, 133, 206, 176, 185,
|
||||||
|
56, 237, 179, 136, 72, 84, 129, 113, 98, 206, 4, 18, 50, 130, 162, 94, 2,
|
||||||
|
17, 73, 18, 21, 33, 32, 112, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
rct_signatures: MinerTransactionRctSignatures { r#type: 0 },
|
||||||
|
},
|
||||||
|
tx_hashes: vec![
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"eab76986a0cbcae690d8499f0f616f783fd2c89c6f611417f18011950dbdab2e"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"57b19aa8c2cdbb6836cf13dd1e321a67860965c12e4418f3c30f58c8899a851e"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"5340185432ab6b74fb21379f7e8d8f0e37f0882b2a7121fd7c08736f079e2edc"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"01dc6d31db56d68116f5294c1b4f80b33b048b5cdfefcd904f23e6c0de3daff5"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"c9fb6a2730678203948fef2a49fa155b63f35a3649f3d32ed405a6806f3bbd56"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"af965cdd2a2315baf1d4a3d242f44fe07b1fd606d5f4853c9ff546ca6c12a5af"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"97bc9e047d25fae8c14ce6ec882224e7b722f5e79b62a2602a6bacebdac8547b"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"28c46992eaf10dc0cceb313c30572d023432b7bd26e85e679bc8fe419533a7bf"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"c32e3acde2ff2885c9cc87253b40d6827d167dfcc3022c72f27084fd98788062"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"19e66a47f075c7cccde8a7b52803119e089e33e3a4847cace0bd1d17b0d22bab"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"8e8ac560e77a1ee72e82a5eb6887adbe5979a10cd29cb2c2a3720ce87db43a70"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"b7ff5141524b5cca24de6780a5dbfdf71e7de1e062fd85f557fb3b43b8e285dc"
|
||||||
|
)),
|
||||||
|
HexBytes::<32>(hex!(
|
||||||
|
"f09df0f113763ef9b9a2752ac293b478102f7cab03ef803a3d9db7585aea8912"
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
test(block, JSON);
|
||||||
|
}
|
||||||
|
}
|
15
types/src/json/mod.rs
Normal file
15
types/src/json/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//! This module contains types mappings for other common types
|
||||||
|
//! to allow for easier JSON (de)serialization.
|
||||||
|
//!
|
||||||
|
//! The main types are:
|
||||||
|
//! - [`block::Block`]
|
||||||
|
//! - [`tx::Transaction`]
|
||||||
|
//!
|
||||||
|
//! Modules exist within this module as the JSON representation
|
||||||
|
//! of types sometimes differs, thus, the modules hold the types
|
||||||
|
//! that match the specific schema, for example [`block::Input`]
|
||||||
|
//! is different than [`tx::Input`].
|
||||||
|
|
||||||
|
pub mod block;
|
||||||
|
pub mod output;
|
||||||
|
pub mod tx;
|
43
types/src/json/output.rs
Normal file
43
types/src/json/output.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
//! JSON output types.
|
||||||
|
//!
|
||||||
|
//! The same [`Output`] is used in both
|
||||||
|
//! [`crate::json::block::MinerTransactionPrefix::vout`]
|
||||||
|
//! and [`crate::json::tx::TransactionPrefix::vout`].
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::hex::HexBytes;
|
||||||
|
|
||||||
|
/// JSON representation of an output.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct Output {
|
||||||
|
pub amount: u64,
|
||||||
|
pub target: Target,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`Output::target`].
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Target {
|
||||||
|
Key { key: HexBytes<32> },
|
||||||
|
TaggedKey { tagged_key: TaggedKey },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Target {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Key {
|
||||||
|
key: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`Target::TaggedKey::tagged_key`].
|
||||||
|
#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct TaggedKey {
|
||||||
|
pub key: HexBytes<32>,
|
||||||
|
pub view_tag: HexBytes<1>,
|
||||||
|
}
|
1455
types/src/json/tx.rs
Normal file
1455
types/src/json/tx.rs
Normal file
File diff suppressed because one or more lines are too long
|
@ -28,4 +28,10 @@ pub use types::{
|
||||||
#[cfg(feature = "blockchain")]
|
#[cfg(feature = "blockchain")]
|
||||||
pub mod blockchain;
|
pub mod blockchain;
|
||||||
|
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
pub mod json;
|
||||||
|
|
||||||
|
#[cfg(feature = "hex")]
|
||||||
|
pub mod hex;
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Private
|
//---------------------------------------------------------------------------------------------------- Private
|
||||||
|
|
Loading…
Reference in a new issue