Skip to content

Commit 21fe7da

Browse files
committed
feat: move serde serializers to stacks_common
1 parent c6c8199 commit 21fe7da

File tree

8 files changed

+149
-165
lines changed

8 files changed

+149
-165
lines changed

stacks-common/src/util/macros.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2-
// Copyright (C) 2020 Stacks Open Internet Foundation
2+
// Copyright (C) 2020-2025 Stacks Open Internet Foundation
33
//
44
// This program is free software: you can redistribute it and/or modify
55
// it under the terms of the GNU General Public License as published by
@@ -613,6 +613,12 @@ macro_rules! impl_byte_array_newtype {
613613
Self(o)
614614
}
615615
}
616+
617+
impl crate::util::HexDeser for $thing {
618+
fn try_from_hex(hex_str: &str) -> Result<Self, $crate::util::HexError> {
619+
$thing::from_hex(hex_str)
620+
}
621+
}
616622
};
617623
}
618624

stacks-common/src/util/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub mod pair;
2727
pub mod pipe;
2828
pub mod retry;
2929
pub mod secp256k1;
30+
pub mod serde_serializers;
3031
pub mod uint;
3132
pub mod vrf;
3233

@@ -111,6 +112,10 @@ impl error::Error for HexError {
111112
}
112113
}
113114

115+
pub trait HexDeser: Sized {
116+
fn try_from_hex(hex: &str) -> Result<Self, HexError>;
117+
}
118+
114119
/// Write any `serde_json` object directly to a file
115120
pub fn serialize_json_to_file<J, P>(json: &J, path: P) -> Result<(), std::io::Error>
116121
where
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/// This module serde encodes and decodes optional byte fields in RPC
2+
/// responses as Some(String) where the String is a `0x` prefixed
3+
/// hex string.
4+
pub mod prefix_opt_hex {
5+
pub fn serialize<S: serde::Serializer, T: std::fmt::LowerHex>(
6+
val: &Option<T>,
7+
s: S,
8+
) -> Result<S::Ok, S::Error> {
9+
match val {
10+
Some(ref some_val) => {
11+
let val_str = format!("0x{some_val:x}");
12+
s.serialize_some(&val_str)
13+
}
14+
None => s.serialize_none(),
15+
}
16+
}
17+
18+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: crate::util::HexDeser>(
19+
d: D,
20+
) -> Result<Option<T>, D::Error> {
21+
let opt_inst_str: Option<String> = serde::Deserialize::deserialize(d)?;
22+
let Some(inst_str) = opt_inst_str else {
23+
return Ok(None);
24+
};
25+
let Some(hex_str) = inst_str.get(2..) else {
26+
return Err(serde::de::Error::invalid_length(
27+
inst_str.len(),
28+
&"at least length 2 string",
29+
));
30+
};
31+
let val = T::try_from_hex(hex_str).map_err(serde::de::Error::custom)?;
32+
Ok(Some(val))
33+
}
34+
}
35+
36+
/// This module serde encodes and decodes byte fields in RPC
37+
/// responses as a String where the String is a `0x` prefixed
38+
/// hex string.
39+
pub mod prefix_hex {
40+
pub fn serialize<S: serde::Serializer, T: std::fmt::LowerHex>(
41+
val: &T,
42+
s: S,
43+
) -> Result<S::Ok, S::Error> {
44+
s.serialize_str(&format!("0x{val:x}"))
45+
}
46+
47+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: crate::util::HexDeser>(
48+
d: D,
49+
) -> Result<T, D::Error> {
50+
let inst_str: String = serde::Deserialize::deserialize(d)?;
51+
let Some(hex_str) = inst_str.get(2..) else {
52+
return Err(serde::de::Error::invalid_length(
53+
inst_str.len(),
54+
&"at least length 2 string",
55+
));
56+
};
57+
T::try_from_hex(hex_str).map_err(serde::de::Error::custom)
58+
}
59+
}
60+
61+
/// This module serde encode and decodes structs that
62+
/// implement StacksMessageCodec as a 0x-prefixed hex string.
63+
pub mod prefix_hex_codec {
64+
use crate::codec::StacksMessageCodec;
65+
use crate::util::hash::{hex_bytes, to_hex};
66+
67+
pub fn serialize<S: serde::Serializer, T: StacksMessageCodec>(
68+
val: &T,
69+
s: S,
70+
) -> Result<S::Ok, S::Error> {
71+
let mut bytes = vec![];
72+
val.consensus_serialize(&mut bytes)
73+
.map_err(serde::ser::Error::custom)?;
74+
s.serialize_str(&format!("0x{}", to_hex(&bytes)))
75+
}
76+
77+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: StacksMessageCodec>(
78+
d: D,
79+
) -> Result<T, D::Error> {
80+
let inst_str: String = serde::Deserialize::deserialize(d)?;
81+
let Some(hex_str) = inst_str.get(2..) else {
82+
return Err(serde::de::Error::invalid_length(
83+
inst_str.len(),
84+
&"at least length 2 string",
85+
));
86+
};
87+
let bytes = hex_bytes(hex_str).map_err(serde::de::Error::custom)?;
88+
T::consensus_deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom)
89+
}
90+
}
91+
92+
/// This module serde encode and decodes structs that
93+
/// implement StacksMessageCodec as a 0x-prefixed hex string.
94+
/// This is the same as prefix_hex_codec, but for Option<T>.
95+
pub mod prefix_opt_hex_codec {
96+
use crate::codec::StacksMessageCodec;
97+
use crate::util::hash::{hex_bytes, to_hex};
98+
99+
pub fn serialize<S: serde::Serializer, T: StacksMessageCodec>(
100+
val: &Option<T>,
101+
s: S,
102+
) -> Result<S::Ok, S::Error> {
103+
match val {
104+
Some(ref some_val) => {
105+
let mut bytes = vec![];
106+
some_val
107+
.consensus_serialize(&mut bytes)
108+
.map_err(serde::ser::Error::custom)?;
109+
let hex_string = format!("0x{}", to_hex(&bytes));
110+
s.serialize_some(&hex_string)
111+
}
112+
None => s.serialize_none(),
113+
}
114+
}
115+
116+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: StacksMessageCodec>(
117+
d: D,
118+
) -> Result<Option<T>, D::Error> {
119+
let opt_inst_str: Option<String> = serde::Deserialize::deserialize(d)?;
120+
let Some(inst_string) = opt_inst_str else {
121+
return Ok(None);
122+
};
123+
let Some(hex_str) = inst_string.get(2..) else {
124+
return Err(serde::de::Error::invalid_length(
125+
inst_string.len(),
126+
&"at least length 2 string",
127+
));
128+
};
129+
let bytes = hex_bytes(hex_str).map_err(serde::de::Error::custom)?;
130+
let val = T::consensus_deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom)?;
131+
Ok(Some(val))
132+
}
133+
}

stackslib/src/net/api/get_tenures_fork_info.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use stacks_common::types::chainstate::{
2525
use stacks_common::types::net::PeerHost;
2626
use stacks_common::types::StacksEpochId;
2727
use stacks_common::util::hash::{to_hex, Hash160};
28+
use stacks_common::util::serde_serializers::{prefix_hex, prefix_opt_hex, prefix_opt_hex_codec};
2829
use stacks_common::util::HexError;
2930
use {serde, serde_json};
3031

@@ -34,7 +35,6 @@ use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState, NakamotoSta
3435
use crate::chainstate::stacks::db::StacksChainState;
3536
use crate::chainstate::stacks::Error as ChainError;
3637
use crate::net::api::getblock_v3::NakamotoBlockStream;
37-
use crate::net::api::{prefix_hex, prefix_opt_hex, prefix_opt_hex_codec};
3838
use crate::net::http::{
3939
parse_bytes, parse_json, Error, HttpBadRequest, HttpChunkGenerator, HttpContentType,
4040
HttpNotFound, HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse,

stackslib/src/net/api/getsortition.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use stacks_common::types::chainstate::{
2626
};
2727
use stacks_common::types::net::PeerHost;
2828
use stacks_common::util::hash::{to_hex, Hash160};
29+
use stacks_common::util::serde_serializers::{prefix_hex, prefix_opt_hex};
2930
use stacks_common::util::HexError;
3031
use {serde, serde_json};
3132

@@ -37,7 +38,6 @@ use crate::chainstate::nakamoto::{
3738
use crate::chainstate::stacks::db::StacksChainState;
3839
use crate::chainstate::stacks::Error as ChainError;
3940
use crate::net::api::getblock_v3::NakamotoBlockStream;
40-
use crate::net::api::{prefix_hex, prefix_opt_hex};
4141
use crate::net::http::{
4242
parse_bytes, parse_json, Error, HttpBadRequest, HttpChunkGenerator, HttpContentType,
4343
HttpNotFound, HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse,

stackslib/src/net/api/mod.rs

Lines changed: 0 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -165,163 +165,3 @@ impl From<NetError> for Error {
165165
}
166166
}
167167
}
168-
169-
/// This module serde encodes and decodes optional byte fields in RPC
170-
/// responses as Some(String) where the String is a `0x` prefixed
171-
/// hex string.
172-
pub mod prefix_opt_hex {
173-
pub fn serialize<S: serde::Serializer, T: std::fmt::LowerHex>(
174-
val: &Option<T>,
175-
s: S,
176-
) -> Result<S::Ok, S::Error> {
177-
match val {
178-
Some(ref some_val) => {
179-
let val_str = format!("0x{some_val:x}");
180-
s.serialize_some(&val_str)
181-
}
182-
None => s.serialize_none(),
183-
}
184-
}
185-
186-
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: super::HexDeser>(
187-
d: D,
188-
) -> Result<Option<T>, D::Error> {
189-
let opt_inst_str: Option<String> = serde::Deserialize::deserialize(d)?;
190-
let Some(inst_str) = opt_inst_str else {
191-
return Ok(None);
192-
};
193-
let Some(hex_str) = inst_str.get(2..) else {
194-
return Err(serde::de::Error::invalid_length(
195-
inst_str.len(),
196-
&"at least length 2 string",
197-
));
198-
};
199-
let val = T::try_from(hex_str).map_err(serde::de::Error::custom)?;
200-
Ok(Some(val))
201-
}
202-
}
203-
204-
/// This module serde encodes and decodes byte fields in RPC
205-
/// responses as a String where the String is a `0x` prefixed
206-
/// hex string.
207-
pub mod prefix_hex {
208-
pub fn serialize<S: serde::Serializer, T: std::fmt::LowerHex>(
209-
val: &T,
210-
s: S,
211-
) -> Result<S::Ok, S::Error> {
212-
s.serialize_str(&format!("0x{val:x}"))
213-
}
214-
215-
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: super::HexDeser>(
216-
d: D,
217-
) -> Result<T, D::Error> {
218-
let inst_str: String = serde::Deserialize::deserialize(d)?;
219-
let Some(hex_str) = inst_str.get(2..) else {
220-
return Err(serde::de::Error::invalid_length(
221-
inst_str.len(),
222-
&"at least length 2 string",
223-
));
224-
};
225-
T::try_from(hex_str).map_err(serde::de::Error::custom)
226-
}
227-
}
228-
229-
/// This module serde encode and decodes structs that
230-
/// implement StacksMessageCodec as a 0x-prefixed hex string.
231-
pub mod prefix_hex_codec {
232-
use clarity::codec::StacksMessageCodec;
233-
use clarity::util::hash::{hex_bytes, to_hex};
234-
235-
pub fn serialize<S: serde::Serializer, T: StacksMessageCodec>(
236-
val: &T,
237-
s: S,
238-
) -> Result<S::Ok, S::Error> {
239-
let mut bytes = vec![];
240-
val.consensus_serialize(&mut bytes)
241-
.map_err(serde::ser::Error::custom)?;
242-
s.serialize_str(&format!("0x{}", to_hex(&bytes)))
243-
}
244-
245-
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: StacksMessageCodec>(
246-
d: D,
247-
) -> Result<T, D::Error> {
248-
let inst_str: String = serde::Deserialize::deserialize(d)?;
249-
let Some(hex_str) = inst_str.get(2..) else {
250-
return Err(serde::de::Error::invalid_length(
251-
inst_str.len(),
252-
&"at least length 2 string",
253-
));
254-
};
255-
let bytes = hex_bytes(hex_str).map_err(serde::de::Error::custom)?;
256-
T::consensus_deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom)
257-
}
258-
}
259-
260-
/// This module serde encode and decodes structs that
261-
/// implement StacksMessageCodec as a 0x-prefixed hex string.
262-
/// This is the same as prefix_hex_codec, but for Option<T>.
263-
pub mod prefix_opt_hex_codec {
264-
use clarity::codec::StacksMessageCodec;
265-
use clarity::util::hash::{hex_bytes, to_hex};
266-
267-
use super::prefix_hex_codec;
268-
269-
pub fn serialize<S: serde::Serializer, T: StacksMessageCodec>(
270-
val: &Option<T>,
271-
s: S,
272-
) -> Result<S::Ok, S::Error> {
273-
match val {
274-
Some(ref some_val) => {
275-
let mut bytes = vec![];
276-
some_val
277-
.consensus_serialize(&mut bytes)
278-
.map_err(serde::ser::Error::custom)?;
279-
let hex_string = format!("0x{}", to_hex(&bytes));
280-
s.serialize_some(&hex_string)
281-
}
282-
None => s.serialize_none(),
283-
}
284-
}
285-
286-
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: StacksMessageCodec>(
287-
d: D,
288-
) -> Result<Option<T>, D::Error> {
289-
let opt_inst_str: Option<String> = serde::Deserialize::deserialize(d)?;
290-
let Some(inst_string) = opt_inst_str else {
291-
return Ok(None);
292-
};
293-
let Some(hex_str) = inst_string.get(2..) else {
294-
return Err(serde::de::Error::invalid_length(
295-
inst_string.len(),
296-
&"at least length 2 string",
297-
));
298-
};
299-
let bytes = hex_bytes(hex_str).map_err(serde::de::Error::custom)?;
300-
let val = T::consensus_deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom)?;
301-
Ok(Some(val))
302-
}
303-
}
304-
305-
pub trait HexDeser: Sized {
306-
fn try_from(hex: &str) -> Result<Self, HexError>;
307-
}
308-
309-
macro_rules! impl_hex_deser {
310-
($thing:ident) => {
311-
impl HexDeser for $thing {
312-
fn try_from(hex: &str) -> Result<Self, HexError> {
313-
$thing::from_hex(hex)
314-
}
315-
}
316-
};
317-
}
318-
319-
impl_hex_deser!(BurnchainHeaderHash);
320-
impl_hex_deser!(StacksBlockId);
321-
impl_hex_deser!(SortitionId);
322-
impl_hex_deser!(VRFSeed);
323-
impl_hex_deser!(ConsensusHash);
324-
impl_hex_deser!(BlockHeaderHash);
325-
impl_hex_deser!(Hash160);
326-
impl_hex_deser!(Sha512Trunc256Sum);
327-
impl_hex_deser!(Txid);

stackslib/src/net/api/tests/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use stacks_common::types::chainstate::{
3131
use stacks_common::util::get_epoch_time_secs;
3232
use stacks_common::util::hash::{to_hex, Hash160, Sha512Trunc256Sum};
3333
use stacks_common::util::pipe::Pipe;
34+
use stacks_common::util::serde_serializers::{prefix_hex, prefix_opt_hex};
3435

3536
use crate::burnchains::bitcoin::indexer::BitcoinIndexer;
3637
use crate::burnchains::Txid;
@@ -44,7 +45,6 @@ use crate::chainstate::stacks::{
4445
TransactionAuth, TransactionPayload, TransactionPostConditionMode, TransactionVersion,
4546
};
4647
use crate::core::MemPoolDB;
47-
use crate::net::api::{prefix_hex, prefix_opt_hex};
4848
use crate::net::db::PeerDB;
4949
use crate::net::httpcore::{StacksHttpRequest, StacksHttpResponse};
5050
use crate::net::relay::Relayer;

0 commit comments

Comments
 (0)