diff --git a/Cargo.lock b/Cargo.lock index c64fb8cdf7..b3c3cd8ba1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1180,6 +1180,7 @@ dependencies = [ "parity-scale-codec", "proptest", "ref-cast", + "regex", "rstest", "script", "serde", @@ -1192,6 +1193,7 @@ dependencies = [ "thiserror", "typename", "utils", + "variant_count", ] [[package]] diff --git a/chainstate/src/rpc/mod.rs b/chainstate/src/rpc/mod.rs index 57bd74104d..4c07d24ce0 100644 --- a/chainstate/src/rpc/mod.rs +++ b/chainstate/src/rpc/mod.rs @@ -17,14 +17,19 @@ mod types; -use std::io::{Read, Write}; +use std::{ + convert::Infallible, + io::{Read, Write}, + sync::Arc, +}; use crate::{Block, BlockSource, ChainInfo, GenBlock}; use chainstate_types::BlockIndex; use common::{ + address::dehexify::dehexify_all_addresses, chain::{ tokens::{RPCTokenInfo, TokenId}, - DelegationId, PoolId, SignedTransaction, Transaction, + ChainConfig, DelegationId, PoolId, SignedTransaction, Transaction, }, primitives::{Amount, BlockHeight, Id}, }; @@ -168,7 +173,19 @@ impl ChainstateRpcServer for super::ChainstateHandle { .await, )?; let rpc_blk = both.map(|(block, block_index)| RpcBlock::new(block, block_index)); - Ok(rpc_blk.map(JsonEncoded::new).map(|blk| blk.to_string())) + let result = rpc_blk.map(JsonEncoded::new).map(|blk| blk.to_string()); + + let chain_config: Arc = rpc::handle_result( + self.call(move |this| { + let chain_config = Arc::clone(this.get_chain_config()); + Ok::<_, Infallible>(chain_config) + }) + .await, + )?; + + let result: Option = result.map(|res| dehexify_all_addresses(&chain_config, &res)); + + Ok(result) } async fn get_transaction( @@ -184,7 +201,20 @@ impl ChainstateRpcServer for super::ChainstateHandle { let tx: Option = rpc::handle_result(self.call(move |this| this.get_transaction(&id)).await)?; let rpc_tx = tx.map(RpcSignedTransaction::new); - Ok(rpc_tx.map(JsonEncoded::new).map(|tx| tx.to_string())) + + let chain_config: Arc = rpc::handle_result( + self.call(move |this| { + let chain_config = Arc::clone(this.get_chain_config()); + Ok::<_, Infallible>(chain_config) + }) + .await, + )?; + + let result = rpc_tx.map(JsonEncoded::new).map(|tx| tx.to_string()); + + let result: Option = result.map(|res| dehexify_all_addresses(&chain_config, &res)); + + Ok(result) } async fn get_mainchain_blocks( diff --git a/common/Cargo.toml b/common/Cargo.toml index e1600d290c..b113af65b0 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -29,6 +29,9 @@ ref-cast.workspace = true serde = { workspace = true, features = ["derive"] } static_assertions.workspace = true thiserror.workspace = true +variant_count.workspace = true + +regex = "1.9" [dev-dependencies] test-utils = {path = '../test-utils'} diff --git a/common/src/address/dehexify.rs b/common/src/address/dehexify.rs new file mode 100644 index 0000000000..635dba0ffe --- /dev/null +++ b/common/src/address/dehexify.rs @@ -0,0 +1,30 @@ +// Copyright (c) 2023 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::chain::{tokens::TokenId, ChainConfig, DelegationId, Destination, PoolId}; + +use super::hexified::HexifiedAddress; + +#[allow(clippy::let_and_return)] +pub fn dehexify_all_addresses(conf: &ChainConfig, input: &str) -> String { + let result = HexifiedAddress::::replace_with_address(conf, input).to_string(); + let result = HexifiedAddress::::replace_with_address(conf, &result).to_string(); + let result = HexifiedAddress::::replace_with_address(conf, &result).to_string(); + let result = HexifiedAddress::::replace_with_address(conf, &result).to_string(); + + result +} + +// TODO: add tests that create blocks, and ensure the replacement in json works properly. diff --git a/common/src/address/hexified.rs b/common/src/address/hexified.rs new file mode 100644 index 0000000000..50ffe41e6f --- /dev/null +++ b/common/src/address/hexified.rs @@ -0,0 +1,410 @@ +// Copyright (c) 2023 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::chain::ChainConfig; + +use super::{traits::Addressable, Address}; +use regex::Regex; +use serde::{de::Error, Deserialize}; +use serialization::DecodeAll; + +const REGEX_SUFFIX: &str = r"\{0x([0-9a-fA-F]+)\}"; + +/// A hexified address is an address that's formatted in such a way that it can be safely replaced with a real address using the object Address. +/// This whole thing is a workaround due to the fact that serde doesn't support stateful serialization, so the ChainConfig cannot be passed while +/// serializing. +pub struct HexifiedAddress<'a, A> { + addressable: &'a A, +} + +impl<'a, A: Addressable + DecodeAll + 'a> HexifiedAddress<'a, A> { + pub fn new(addressable: &'a A) -> Self { + Self { addressable } + } + + fn make_regex_pattern() -> String { + A::json_wrapper_prefix().to_string() + REGEX_SUFFIX + } + + fn make_regex_object() -> Regex { + Regex::new(&Self::make_regex_pattern()).expect("Regex pattern cannot fail") + } + + pub fn is_hexified_address(target_str: &str) -> bool { + let matcher = Self::make_regex_object(); + matcher.is_match(target_str) + } + + pub fn extract_hexified_address(target_str: impl AsRef) -> Option { + let matcher = Self::make_regex_object(); + let caps = matcher.captures(target_str.as_ref())?; + let hex_data = caps.get(1)?.as_str(); + Some(hex_data.to_string()) + } + + #[must_use] + pub fn replace_with_address(chain_config: &ChainConfig, target_str: &str) -> String { + let matcher = Self::make_regex_object(); + let replacer = AddressableReplacer::::new(chain_config); + let result = matcher.replace_all(target_str, replacer); + + result.to_string() + } + + /// Deserialize a hex string with proper error reporting + fn serde_hex_deserialize<'de, D>( + hex_string: impl AsRef + std::fmt::Display, + ) -> Result + where + D: serde::Deserializer<'de>, + { + let hex_string = if hex_string.as_ref().starts_with("0x") { + // Get rid of the 0x prefix + hex_string.as_ref().trim_start_matches("0x") + } else { + hex_string.as_ref() + }; + + let bytes = hex::decode(hex_string).map_err(|e| { + D::Error::custom(format!( + "Failed to decode hex to bytes for address from string {hex_string} with hexified json prefix {} with error {e}", + A::json_wrapper_prefix() + )) + })?; + let obj = A::decode_all(&mut &*bytes).map_err(|e| { + D::Error::custom(format!( + "Failed to decode bytes to object for address from string {hex_string} with hexified json prefix {} with error {e}", + A::json_wrapper_prefix() + )) + })?; + + Ok(obj) + } + + pub fn serde_serialize(addressable: &'a A, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&Self::new(addressable).to_string()) + } + + pub fn serde_deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if Self::is_hexified_address(&s) { + // If the object is hexified and isn't an address, we de-hexify it + + let hex_string = + Self::extract_hexified_address(&s).ok_or(D::Error::custom(format!( + "Failed to extract hexified address from string {s} with hexified json prefix {}", + A::json_wrapper_prefix() + )))?; + + Self::serde_hex_deserialize::(&hex_string) + } else if s.starts_with("0x") { + Self::serde_hex_deserialize::(&s) + } else { + Address::::from_str_no_hrp_verify(&s).map_err(D::Error::custom) + } + } +} + +impl<'a, A: Addressable> std::fmt::Display for HexifiedAddress<'a, A> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let result = format!( + "{}{{0x{}}}", + A::json_wrapper_prefix(), + hex::ToHex::encode_hex::(&self.addressable.encode_to_bytes_for_address()) + ); + result.fmt(f) + } +} + +struct AddressableReplacer<'a, A> { + chain_config: &'a ChainConfig, + _marker: std::marker::PhantomData, +} + +impl<'a, A: Addressable> AddressableReplacer<'a, A> { + pub fn new(chain_config: &'a ChainConfig) -> Self { + Self { + chain_config, + _marker: std::marker::PhantomData, + } + } +} + +impl<'a, A: Addressable + DecodeAll> regex::Replacer for AddressableReplacer<'a, A> { + fn replace_append(&mut self, caps: ®ex::Captures<'_>, dst: &mut String) { + let hex_data = caps.get(1).expect("It's already verified it exists").as_str(); + let bytes = match hex::decode(hex_data) { + Ok(bytes) => bytes, + Err(_) => { + logging::log::error!( + "While de-hexifying, failed to decode hex to bytes for a {}", + A::json_wrapper_prefix() + ); + // replace with hex value + dst.push_str("0x"); + dst.push_str(hex_data); + return; + } + }; + let obj = match A::decode_all(&mut &*bytes) { + Ok(obj) => obj, + Err(_) => { + logging::log::error!( + "While de-hexifying, failed to decode bytes to data for object for a {}", + A::json_wrapper_prefix() + ); + // replace with hex value + dst.push_str("0x"); + dst.push_str(hex_data); + return; + } + }; + let address = match Address::new(self.chain_config, &obj) { + Ok(address) => address, + Err(_) => { + logging::log::error!( + "While de-hexifying, failed to create address for object for a {}", + A::json_wrapper_prefix() + ); + // replace with hex value + dst.push_str("0x"); + dst.push_str(hex_data); + return; + } + }; + dst.push_str(&address.to_string()) + } +} + +#[cfg(test)] +mod tests { + use crypto::{ + key::{KeyKind, PrivateKey}, + random, + }; + use rstest::rstest; + use serialization::Encode; + use test_utils::random::{make_seedable_rng, Rng, Seed}; + + use crate::{ + address::{ + hexified::HexifiedAddress, pubkeyhash::PublicKeyHash, traits::Addressable, Address, + }, + chain::{config::create_regtest, Destination}, + primitives::H256, + }; + + fn random_string(length: usize, rng: &mut impl Rng) -> String { + rng.sample_iter(&random::distributions::Alphanumeric) + .take(length) + .map(char::from) + .collect() + } + + #[rstest] + #[trace] + #[case(Seed::from_entropy())] + fn basic_search_and_replace(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); + let (_private_key, public_key) = + PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); + let address = Destination::PublicKey(public_key); + + let chain_config = create_regtest(); + + let s = format!("{}", HexifiedAddress::new(&address)); + let res = HexifiedAddress::::replace_with_address(&chain_config, &s); + assert_eq!( + res, + format!("{}", Address::new(&chain_config, &address).unwrap()) + ); + } + + #[test] + fn basic_search_and_replace_simple_invalid_wont_change() { + let chain_config = create_regtest(); + + let s = "some-random-stuff"; + let res = HexifiedAddress::::replace_with_address(&chain_config, s); + assert_eq!(res, s) + } + + #[rstest] + #[trace] + #[case(Seed::from_entropy())] + fn many_random_instances(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); + let chain_config = create_regtest(); + + let strings = (0..100) + .map(|_| { + let size = rng.gen::() % 50; + random_string(size, &mut rng) + }) + .collect::>(); + + let keys = (0..strings.len()) + .map(|_| match rng.gen::() % Destination::VARIANT_COUNT { + 0..=1 => { + let (_private_key, public_key) = + PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); + Destination::PublicKey(public_key) + } + 2 => { + let (_private_key, public_key) = + PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); + Destination::Address(PublicKeyHash::from(&public_key)) + } + 3 => Destination::ScriptHash(crate::primitives::Id::new(H256::random_using( + &mut rng, + ))), + 4 => { + let (_private_key, public_key) = + PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); + Destination::ClassicMultisig(PublicKeyHash::from(&public_key)) + } + _ => unreachable!(), + }) + .collect::>(); + + let final_str = strings + .iter() + .zip(keys.iter()) + .map(|(s, k)| { + let hexified = HexifiedAddress::new(k); + s.clone() + &hexified.to_string() + }) + .collect::>() + .join(""); + + let to_test = + HexifiedAddress::::replace_with_address(&chain_config, &final_str); + + let expected = strings + .iter() + .zip(keys.iter()) + .map(|(s, k)| { + let address_str = Address::new(&chain_config, k).unwrap(); + s.clone() + &address_str.to_string() + }) + .collect::>() + .join(""); + + assert_eq!(to_test, expected); + } + + #[test] + fn invalid_match_should_replace_with_hex_case_invalid_hex() { + let chain_config = create_regtest(); + + let s = format!("{}{{0xabc}}", Destination::json_wrapper_prefix()); // Invalid hex + + let re = HexifiedAddress::::make_regex_object(); + assert!(re.is_match(&s)); + + let res = HexifiedAddress::::replace_with_address(&chain_config, &s); + assert_eq!(res, "0xabc"); + } + + #[test] + fn invalid_match_should_replace_with_hex_case_invalid_obj() { + let chain_config = create_regtest(); + + let s = format!("{}{{0xabcd}}", Destination::json_wrapper_prefix()); // Invalid object + + let re = HexifiedAddress::::make_regex_object(); + assert!(re.is_match(&s)); + + let res = HexifiedAddress::::replace_with_address(&chain_config, &s); + assert_eq!(res, "0xabcd"); + } + + #[test] + fn invalid_match_should_replace_with_hex_creating_case_address_creation_error() { + let chain_config = create_regtest(); + + let s = format!("{}", HexifiedAddress::new(&Destination::AnyoneCanSpend)); // AnyoneCanSpend cannot be converted to an address + + let re = HexifiedAddress::::make_regex_object(); + assert!(re.is_match(&s)); + + let res = HexifiedAddress::::replace_with_address(&chain_config, &s); + assert_eq!( + res, + "0x".to_string() + + &hex::ToHex::encode_hex::(&Destination::AnyoneCanSpend.encode()) + ); + } + + #[test] + fn serde_serialize_something_that_cannot_go_to_address() { + let chain_config = create_regtest(); + + // AnyoneCanSpend is too short to go to an address + let obj = Destination::AnyoneCanSpend; + let obj_json = serde_json::to_string(&obj).unwrap(); + + { + assert_eq!(obj_json, "\"HexifiedDestination{0x00}\""); + let obj_deserialized: Destination = serde_json::from_str(&obj_json).unwrap(); + assert_eq!(obj_deserialized, obj); + } + + { + // Do the replacement, which will make it a hex starting with 0x, and deserialization will still succeed + let obj_json_replaced = + HexifiedAddress::::replace_with_address(&chain_config, &obj_json); + assert_eq!(obj_json_replaced, "\"0x00\""); + let obj_deserialized: Destination = serde_json::from_str(&obj_json_replaced).unwrap(); + assert_eq!(obj_deserialized, obj); + } + } + + #[rstest] + #[trace] + #[case(Seed::from_entropy())] + fn serde_serialize_something_that_can_be_an_address(#[case] seed: Seed) { + let chain_config = create_regtest(); + + let mut rng = make_seedable_rng(seed); + let (_private_key, public_key) = + PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); + let obj = Destination::PublicKey(public_key); + let obj_json = serde_json::to_string(&obj).unwrap(); + + { + let obj_hex: String = hex::ToHex::encode_hex(&obj.encode()); + assert_eq!(obj_json, format!("\"HexifiedDestination{{0x{obj_hex}}}\"")); + let obj_deserialized: Destination = serde_json::from_str(&obj_json).unwrap(); + assert_eq!(obj_deserialized, obj); + } + + { + // Do the replacement, which will make the hexified address become a real address + let expected_address = Address::new(&chain_config, &obj).unwrap(); + let obj_json_replaced = + HexifiedAddress::::replace_with_address(&chain_config, &obj_json); + assert_eq!(obj_json_replaced, format!("\"{expected_address}\"")); + let obj_deserialized: Destination = serde_json::from_str(&obj_json_replaced).unwrap(); + assert_eq!(obj_deserialized, obj); + } + } +} diff --git a/common/src/address/mod.rs b/common/src/address/mod.rs index 4283aa1b2c..3119d0bcfd 100644 --- a/common/src/address/mod.rs +++ b/common/src/address/mod.rs @@ -13,12 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::Display; +pub mod dehexify; +pub mod hexified; +pub mod pubkeyhash; +pub mod traits; use crate::chain::ChainConfig; use crate::primitives::{encoding, Bech32Error}; -pub mod pubkeyhash; -pub mod traits; +use std::fmt::Display; use utils::qrcode::{qrcode_from_str, QrCode, QrCodeError}; use self::traits::Addressable; @@ -81,6 +83,17 @@ impl Address { Ok(result) } + /// Decode an address without verifying the hrp + /// This is used only for the case of json deserialization, which is done as a compromise as the alternative + /// would be to not serialize at all. This is because chain config cannot be passed to the json serializer/deserializer. + fn from_str_no_hrp_verify(address: impl AsRef) -> Result { + let data = encoding::decode(address)?; + let raw_data = data.data(); + let result = T::decode_from_bytes_from_address(raw_data) + .map_err(|e| AddressError::DecodingError(e.to_string()))?; + Ok(result) + } + pub fn from_str(cfg: &ChainConfig, address: &str) -> Result { let address = Self { address: address.to_owned(), diff --git a/common/src/address/traits.rs b/common/src/address/traits.rs index 41c720f236..8f19276b8c 100644 --- a/common/src/address/traits.rs +++ b/common/src/address/traits.rs @@ -29,4 +29,7 @@ pub trait Addressable { ) -> Result where Self: Sized; + + /// Returns the prefix to be used for json conversions that cannot be done with a ChainConfig + fn json_wrapper_prefix() -> &'static str; } diff --git a/common/src/chain/block/mod.rs b/common/src/chain/block/mod.rs index 539c76a694..94b22eceb6 100644 --- a/common/src/chain/block/mod.rs +++ b/common/src/chain/block/mod.rs @@ -218,6 +218,18 @@ impl PartialEq for WithId { } } +impl serde::Serialize for Id { + fn serialize(&self, s: S) -> Result { + self.serde_serialize(s) + } +} + +impl<'de> serde::Deserialize<'de> for Id { + fn deserialize>(d: D) -> Result { + Self::serde_deserialize(d) + } +} + impl Eq for WithId {} #[cfg(test)] diff --git a/common/src/chain/gen_block.rs b/common/src/chain/gen_block.rs index be8463598f..f951b38a15 100644 --- a/common/src/chain/gen_block.rs +++ b/common/src/chain/gen_block.rs @@ -76,6 +76,18 @@ impl PartialEq> for Id { } } +impl serde::Serialize for Id { + fn serialize(&self, s: S) -> Result { + self.serde_serialize(s) + } +} + +impl<'de> serde::Deserialize<'de> for Id { + fn deserialize>(d: D) -> Result { + Self::serde_deserialize(d) + } +} + impl Id { /// Figure out if this [Id] refers to a [Genesis] or a proper [Block]. pub fn classify(&self, c: &crate::chain::config::ChainConfig) -> GenBlockId { diff --git a/common/src/chain/genesis.rs b/common/src/chain/genesis.rs index 818b4e1e95..988b078537 100644 --- a/common/src/chain/genesis.rs +++ b/common/src/chain/genesis.rs @@ -63,3 +63,9 @@ impl Idable for Genesis { Id::new(id::hash_encoded(&self)) } } + +impl serde::Serialize for Id { + fn serialize(&self, s: S) -> Result { + self.serde_serialize(s) + } +} diff --git a/common/src/chain/pos.rs b/common/src/chain/pos.rs index cdb28112ba..a9a9e193f7 100644 --- a/common/src/chain/pos.rs +++ b/common/src/chain/pos.rs @@ -19,7 +19,7 @@ use serialization::{DecodeAll, Encode}; use typename::TypeName; use crate::{ - address::{traits::Addressable, AddressError}, + address::{hexified::HexifiedAddress, traits::Addressable, AddressError}, primitives::{per_thousand::PerThousand, BlockDistance, Id, H256}, Uint256, }; @@ -62,6 +62,18 @@ pub struct PoSChainConfig { consensus_version: PoSConsensusVersion, } +impl serde::Serialize for PoolId { + fn serialize(&self, serializer: S) -> Result { + HexifiedAddress::serde_serialize(self, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for PoolId { + fn deserialize>(deserializer: D) -> Result { + HexifiedAddress::::serde_deserialize(deserializer) + } +} + impl Addressable for PoolId { type Error = AddressError; @@ -80,6 +92,10 @@ impl Addressable for PoolId { Self::decode_all(&mut address_bytes.as_ref()) .map_err(|e| AddressError::DecodingError(e.to_string())) } + + fn json_wrapper_prefix() -> &'static str { + "HexifiedPoolId" + } } impl Addressable for DelegationId { @@ -100,6 +116,22 @@ impl Addressable for DelegationId { Self::decode_all(&mut address_bytes.as_ref()) .map_err(|e| AddressError::DecodingError(e.to_string())) } + + fn json_wrapper_prefix() -> &'static str { + "HexifiedDelegationId" + } +} + +impl serde::Serialize for DelegationId { + fn serialize(&self, serializer: S) -> Result { + HexifiedAddress::serde_serialize(self, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for DelegationId { + fn deserialize>(deserializer: D) -> Result { + HexifiedAddress::::serde_deserialize(deserializer) + } } impl PoSChainConfig { diff --git a/common/src/chain/tokens/mod.rs b/common/src/chain/tokens/mod.rs index 47aaa49e19..4044afb208 100644 --- a/common/src/chain/tokens/mod.rs +++ b/common/src/chain/tokens/mod.rs @@ -23,7 +23,7 @@ pub enum Token {} pub type TokenId = Id; pub type NftDataHash = Vec; use crate::{ - address::{traits::Addressable, AddressError}, + address::{hexified::HexifiedAddress, traits::Addressable, AddressError}, primitives::{Amount, Id, H256}, }; @@ -55,6 +55,22 @@ impl Addressable for TokenId { Self::decode_all(&mut address_bytes.as_ref()) .map_err(|e| AddressError::DecodingError(e.to_string())) } + + fn json_wrapper_prefix() -> &'static str { + "HexifiedTokenId" + } +} + +impl serde::Serialize for TokenId { + fn serialize(&self, serializer: S) -> Result { + HexifiedAddress::serde_serialize(self, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for TokenId { + fn deserialize>(deserializer: D) -> Result { + HexifiedAddress::::serde_deserialize(deserializer) + } } mod nft; diff --git a/common/src/chain/transaction/mod.rs b/common/src/chain/transaction/mod.rs index 47c85d1320..5bf0cfd0a6 100644 --- a/common/src/chain/transaction/mod.rs +++ b/common/src/chain/transaction/mod.rs @@ -144,6 +144,18 @@ impl Transaction { } } +impl serde::Serialize for Id { + fn serialize(&self, s: S) -> Result { + self.serde_serialize(s) + } +} + +impl<'de> serde::Deserialize<'de> for Id { + fn deserialize>(d: D) -> Result { + Self::serde_deserialize(d) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/common/src/chain/transaction/output/mod.rs b/common/src/chain/transaction/output/mod.rs index 075c31dcd3..d5f03bdb58 100644 --- a/common/src/chain/transaction/output/mod.rs +++ b/common/src/chain/transaction/output/mod.rs @@ -14,12 +14,15 @@ // limitations under the License. use crate::{ - address::{pubkeyhash::PublicKeyHash, traits::Addressable, AddressError}, + address::{ + hexified::HexifiedAddress, pubkeyhash::PublicKeyHash, traits::Addressable, AddressError, + }, chain::{output_value::OutputValue, tokens::TokenData, ChainConfig, DelegationId, PoolId}, primitives::{Amount, Id}, }; use script::Script; -use serialization::{hex::HexEncode, Decode, DecodeAll, Encode}; +use serialization::{Decode, DecodeAll, Encode}; +use variant_count::VariantCount; use self::{stakelock::StakePoolData, timelock::OutputTimeLock}; @@ -28,7 +31,7 @@ pub mod output_value; pub mod stakelock; pub mod timelock; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, VariantCount)] pub enum Destination { #[codec(index = 0)] AnyoneCanSpend, // zero verification; used primarily for testing. Never use this for real money @@ -44,7 +47,13 @@ pub enum Destination { impl serde::Serialize for Destination { fn serialize(&self, serializer: S) -> Result { - serializer.serialize_str(&format!("0x{}", self.hex_encode())) + HexifiedAddress::serde_serialize(self, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Destination { + fn deserialize>(deserializer: D) -> Result { + HexifiedAddress::::serde_deserialize(deserializer) } } @@ -66,6 +75,10 @@ impl Addressable for Destination { Self::decode_all(&mut address_bytes.as_ref()) .map_err(|e| AddressError::DecodingError(e.to_string())) } + + fn json_wrapper_prefix() -> &'static str { + "HexifiedDestination" + } } #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, serde::Serialize)] diff --git a/common/src/primitives/id/mod.rs b/common/src/primitives/id/mod.rs index 6aeae63ad7..a7251e2239 100644 --- a/common/src/primitives/id/mod.rs +++ b/common/src/primitives/id/mod.rs @@ -94,12 +94,10 @@ impl<'de> serde::Deserialize<'de> for H256 { } } -#[derive(PartialEq, Eq, Encode, Decode, serde::Serialize, serde::Deserialize, RefCast)] +#[derive(PartialEq, Eq, Encode, Decode, RefCast)] #[repr(transparent)] -#[serde(transparent)] pub struct Id { hash: H256, - #[serde(skip)] _shadow: std::marker::PhantomData T>, } @@ -159,6 +157,26 @@ impl Id { _shadow: std::marker::PhantomData, } } + + pub fn serde_serialize(&self, s: S) -> Result { + ::serialize(&self.hash, s) + } + + pub fn serde_deserialize<'de, D: serde::Deserializer<'de>>(d: D) -> Result { + >::deserialize(d).map(Self::new) + } +} + +impl serde::Serialize for Id<()> { + fn serialize(&self, s: S) -> Result { + self.serde_serialize(s) + } +} + +impl<'de> serde::Deserialize<'de> for Id<()> { + fn deserialize>(d: D) -> Result { + Self::serde_deserialize(d) + } } impl AsRef<[u8]> for Id {