use crate::config::{TorConfig, WalletConfig};
use crate::error::{Error, ErrorKind};
use crate::grin_core::core::hash::Hash;
use crate::grin_core::core::{Output, Transaction, TxKernel};
use crate::grin_core::libtx::{aggsig, secp_ser};
use crate::grin_core::{global, ser};
use crate::grin_keychain::{Identifier, Keychain};
use crate::grin_util::logger::LoggingConfig;
use crate::grin_util::secp::key::{PublicKey, SecretKey};
use crate::grin_util::secp::{self, pedersen, Secp256k1};
use crate::grin_util::{ToHex, ZeroingString};
use crate::slate_versions::ser as dalek_ser;
use chrono::prelude::*;
use ed25519_dalek::PublicKey as DalekPublicKey;
use ed25519_dalek::Signature as DalekSignature;
use failure::ResultExt;
use serde;
use serde_json;
use std::collections::HashMap;
use std::fmt;
use std::time::Duration;
use uuid::Uuid;
pub trait WalletInst<'a, L, C, K>: Send + Sync
where
L: WalletLCProvider<'a, C, K> + Send + Sync,
C: NodeClient + 'a,
K: Keychain + 'a,
{
fn lc_provider(&mut self) -> Result<&mut (dyn WalletLCProvider<'a, C, K> + 'a), Error>;
}
pub trait WalletLCProvider<'a, C, K>: Send + Sync
where
C: NodeClient + 'a,
K: Keychain + 'a,
{
fn set_top_level_directory(&mut self, dir: &str) -> Result<(), Error>;
fn get_top_level_directory(&self) -> Result<String, Error>;
fn create_config(
&self,
chain_type: &global::ChainTypes,
file_name: &str,
wallet_config: Option<WalletConfig>,
logging_config: Option<LoggingConfig>,
tor_config: Option<TorConfig>,
) -> Result<(), Error>;
fn create_wallet(
&mut self,
name: Option<&str>,
mnemonic: Option<ZeroingString>,
mnemonic_length: usize,
password: ZeroingString,
test_mode: bool,
) -> Result<(), Error>;
fn open_wallet(
&mut self,
name: Option<&str>,
password: ZeroingString,
create_mask: bool,
use_test_rng: bool,
) -> Result<Option<SecretKey>, Error>;
fn close_wallet(&mut self, name: Option<&str>) -> Result<(), Error>;
fn wallet_exists(&self, name: Option<&str>) -> Result<bool, Error>;
fn get_mnemonic(
&self,
name: Option<&str>,
password: ZeroingString,
) -> Result<ZeroingString, Error>;
fn validate_mnemonic(&self, mnemonic: ZeroingString) -> Result<(), Error>;
fn recover_from_mnemonic(
&self,
mnemonic: ZeroingString,
password: ZeroingString,
) -> Result<(), Error>;
fn change_password(
&self,
name: Option<&str>,
old: ZeroingString,
new: ZeroingString,
) -> Result<(), Error>;
fn delete_wallet(&self, name: Option<&str>) -> Result<(), Error>;
fn wallet_inst(&mut self) -> Result<&mut Box<dyn WalletBackend<'a, C, K> + 'a>, Error>;
}
pub trait WalletBackend<'ck, C, K>: Send + Sync
where
C: NodeClient + 'ck,
K: Keychain + 'ck,
{
fn set_keychain(
&mut self,
k: Box<K>,
mask: bool,
use_test_rng: bool,
) -> Result<Option<SecretKey>, Error>;
fn close(&mut self) -> Result<(), Error>;
fn keychain(&self, mask: Option<&SecretKey>) -> Result<K, Error>;
fn w2n_client(&mut self) -> &mut C;
fn calc_commit_for_cache(
&mut self,
keychain_mask: Option<&SecretKey>,
amount: u64,
id: &Identifier,
) -> Result<Option<String>, Error>;
fn set_parent_key_id_by_name(&mut self, label: &str) -> Result<(), Error>;
fn set_parent_key_id(&mut self, _: Identifier);
fn parent_key_id(&mut self) -> Identifier;
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = OutputData> + 'a>;
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
fn get_tx_log_entry(&self, uuid: &Uuid) -> Result<Option<TxLogEntry>, Error>;
fn get_private_context(
&mut self,
keychain_mask: Option<&SecretKey>,
slate_id: &[u8],
) -> Result<Context, Error>;
fn tx_log_iter<'a>(&'a self) -> Box<dyn Iterator<Item = TxLogEntry> + 'a>;
fn acct_path_iter<'a>(&'a self) -> Box<dyn Iterator<Item = AcctPathMapping> + 'a>;
fn get_acct_path(&self, label: String) -> Result<Option<AcctPathMapping>, Error>;
fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error>;
fn get_stored_tx(&self, uuid: &str) -> Result<Option<Transaction>, Error>;
fn batch<'a>(
&'a mut self,
keychain_mask: Option<&SecretKey>,
) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error>;
fn batch_no_mask<'a>(&'a mut self) -> Result<Box<dyn WalletOutputBatch<K> + 'a>, Error>;
fn current_child_index(&mut self, parent_key_id: &Identifier) -> Result<u32, Error>;
fn next_child(&mut self, keychain_mask: Option<&SecretKey>) -> Result<Identifier, Error>;
fn last_confirmed_height(&mut self) -> Result<u64, Error>;
fn last_scanned_block(&mut self) -> Result<ScannedBlockInfo, Error>;
fn init_status(&mut self) -> Result<WalletInitStatus, Error>;
}
pub trait WalletOutputBatch<K>
where
K: Keychain,
{
fn keychain(&mut self) -> &mut K;
fn save(&mut self, out: OutputData) -> Result<(), Error>;
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
fn iter(&self) -> Box<dyn Iterator<Item = OutputData>>;
fn delete(&mut self, id: &Identifier, mmr_index: &Option<u64>) -> Result<(), Error>;
fn save_child_index(&mut self, parent_key_id: &Identifier, child_n: u32) -> Result<(), Error>;
fn save_last_confirmed_height(
&mut self,
parent_key_id: &Identifier,
height: u64,
) -> Result<(), Error>;
fn save_last_scanned_block(&mut self, block: ScannedBlockInfo) -> Result<(), Error>;
fn save_init_status(&mut self, value: WalletInitStatus) -> Result<(), Error>;
fn next_tx_log_id(&mut self, parent_key_id: &Identifier) -> Result<u32, Error>;
fn tx_log_iter(&self) -> Box<dyn Iterator<Item = TxLogEntry>>;
fn save_tx_log_entry(&mut self, t: TxLogEntry, parent_id: &Identifier) -> Result<(), Error>;
fn save_acct_path(&mut self, mapping: AcctPathMapping) -> Result<(), Error>;
fn acct_path_iter(&self) -> Box<dyn Iterator<Item = AcctPathMapping>>;
fn lock_output(&mut self, out: &mut OutputData) -> Result<(), Error>;
fn save_private_context(&mut self, slate_id: &[u8], ctx: &Context) -> Result<(), Error>;
fn delete_private_context(&mut self, slate_id: &[u8]) -> Result<(), Error>;
fn commit(&self) -> Result<(), Error>;
}
pub trait NodeClient: Send + Sync + Clone {
fn node_url(&self) -> &str;
fn set_node_url(&mut self, node_url: &str);
fn node_api_secret(&self) -> Option<String>;
fn set_node_api_secret(&mut self, node_api_secret: Option<String>);
fn post_tx(&self, tx: &Transaction, fluff: bool) -> Result<(), Error>;
fn get_version_info(&mut self) -> Option<NodeVersionInfo>;
fn get_chain_tip(&self) -> Result<(u64, String), Error>;
fn get_kernel(
&mut self,
excess: &pedersen::Commitment,
min_height: Option<u64>,
max_height: Option<u64>,
) -> Result<Option<(TxKernel, u64, u64)>, Error>;
fn get_outputs_from_node(
&self,
wallet_outputs: Vec<pedersen::Commitment>,
) -> Result<HashMap<pedersen::Commitment, (String, u64, u64)>, Error>;
fn get_outputs_by_pmmr_index(
&self,
start_height: u64,
end_height: Option<u64>,
max_outputs: u64,
) -> Result<
(
u64,
u64,
Vec<(pedersen::Commitment, pedersen::RangeProof, bool, u64, u64)>,
),
Error,
>;
fn height_range_to_pmmr_indices(
&self,
start_height: u64,
end_height: Option<u64>,
) -> Result<(u64, u64), Error>;
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NodeVersionInfo {
pub node_version: String,
pub block_header_version: u16,
pub verified: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct OutputData {
pub root_key_id: Identifier,
pub key_id: Identifier,
pub n_child: u32,
pub commit: Option<String>,
#[serde(with = "secp_ser::opt_string_or_u64")]
pub mmr_index: Option<u64>,
#[serde(with = "secp_ser::string_or_u64")]
pub value: u64,
pub status: OutputStatus,
#[serde(with = "secp_ser::string_or_u64")]
pub height: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub lock_height: u64,
pub is_coinbase: bool,
pub tx_log_entry: Option<u32>,
}
impl ser::Writeable for OutputData {
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
}
}
impl ser::Readable for OutputData {
fn read<R: ser::Reader>(reader: &mut R) -> Result<OutputData, ser::Error> {
let data = reader.read_bytes_len_prefix()?;
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
}
}
impl OutputData {
pub fn lock(&mut self) {
self.status = OutputStatus::Locked;
}
pub fn num_confirmations(&self, current_height: u64) -> u64 {
if self.height > current_height {
return 0;
}
if self.status == OutputStatus::Unconfirmed {
0
} else {
1 + (current_height - self.height)
}
}
pub fn eligible_to_spend(&self, current_height: u64, minimum_confirmations: u64) -> bool {
if [OutputStatus::Spent, OutputStatus::Locked].contains(&self.status)
|| self.status == OutputStatus::Unconfirmed && self.is_coinbase
|| self.lock_height > current_height
{
false
} else {
(self.status == OutputStatus::Unspent
&& self.num_confirmations(current_height) >= minimum_confirmations)
|| self.status == OutputStatus::Unconfirmed && minimum_confirmations == 0
}
}
pub fn mark_unspent(&mut self) {
match self.status {
OutputStatus::Unconfirmed | OutputStatus::Reverted => {
self.status = OutputStatus::Unspent
}
_ => {}
}
}
pub fn mark_spent(&mut self) {
match self.status {
OutputStatus::Unspent | OutputStatus::Locked => self.status = OutputStatus::Spent,
_ => (),
}
}
pub fn mark_reverted(&mut self) {
match self.status {
OutputStatus::Unspent => self.status = OutputStatus::Reverted,
_ => (),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub enum OutputStatus {
Unconfirmed,
Unspent,
Locked,
Spent,
Reverted,
}
impl fmt::Display for OutputStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
OutputStatus::Unconfirmed => write!(f, "Unconfirmed"),
OutputStatus::Unspent => write!(f, "Unspent"),
OutputStatus::Locked => write!(f, "Locked"),
OutputStatus::Spent => write!(f, "Spent"),
OutputStatus::Reverted => write!(f, "Reverted"),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Context {
pub parent_key_id: Identifier,
pub sec_key: SecretKey,
pub sec_nonce: SecretKey,
pub initial_sec_key: SecretKey,
pub initial_sec_nonce: SecretKey,
pub output_ids: Vec<(Identifier, Option<u64>, u64)>,
pub input_ids: Vec<(Identifier, Option<u64>, u64)>,
pub amount: u64,
pub fee: u64,
pub payment_proof_derivation_index: Option<u32>,
pub is_invoice: bool,
pub calculated_excess: Option<pedersen::Commitment>,
}
impl Context {
pub fn new(
secp: &secp::Secp256k1,
sec_key: SecretKey,
parent_key_id: &Identifier,
use_test_rng: bool,
is_invoice: bool,
) -> Context {
let sec_nonce = match use_test_rng {
false => aggsig::create_secnonce(secp).unwrap(),
true => SecretKey::from_slice(secp, &[1; 32]).unwrap(),
};
Context {
parent_key_id: parent_key_id.clone(),
sec_key: sec_key.clone(),
sec_nonce: sec_nonce.clone(),
initial_sec_key: sec_key.clone(),
initial_sec_nonce: sec_nonce.clone(),
input_ids: vec![],
output_ids: vec![],
amount: 0,
fee: 0,
payment_proof_derivation_index: None,
is_invoice,
calculated_excess: None,
}
}
}
impl Context {
pub fn add_output(&mut self, output_id: &Identifier, mmr_index: &Option<u64>, amount: u64) {
self.output_ids
.push((output_id.clone(), *mmr_index, amount));
}
pub fn get_outputs(&self) -> Vec<(Identifier, Option<u64>, u64)> {
self.output_ids.clone()
}
pub fn add_input(&mut self, input_id: &Identifier, mmr_index: &Option<u64>, amount: u64) {
self.input_ids.push((input_id.clone(), *mmr_index, amount));
}
pub fn get_inputs(&self) -> Vec<(Identifier, Option<u64>, u64)> {
self.input_ids.clone()
}
pub fn get_private_keys(&self) -> (SecretKey, SecretKey) {
(self.sec_key.clone(), self.sec_nonce.clone())
}
pub fn get_public_keys(&self, secp: &Secp256k1) -> (PublicKey, PublicKey) {
(
PublicKey::from_secret_key(secp, &self.sec_key).unwrap(),
PublicKey::from_secret_key(secp, &self.sec_nonce).unwrap(),
)
}
}
impl ser::Writeable for Context {
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
}
}
impl ser::Readable for Context {
fn read<R: ser::Reader>(reader: &mut R) -> Result<Context, ser::Error> {
let data = reader.read_bytes_len_prefix()?;
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct BlockIdentifier(pub Hash);
impl BlockIdentifier {
pub fn hash(&self) -> Hash {
self.0
}
pub fn from_hex(hex: &str) -> Result<BlockIdentifier, Error> {
let hash =
Hash::from_hex(hex).context(ErrorKind::GenericError("Invalid hex".to_owned()))?;
Ok(BlockIdentifier(hash))
}
}
impl serde::ser::Serialize for BlockIdentifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&self.0.to_hex())
}
}
impl<'de> serde::de::Deserialize<'de> for BlockIdentifier {
fn deserialize<D>(deserializer: D) -> Result<BlockIdentifier, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_str(BlockIdentifierVisitor)
}
}
struct BlockIdentifierVisitor;
impl<'de> serde::de::Visitor<'de> for BlockIdentifierVisitor {
type Value = BlockIdentifier;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a block hash")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let block_hash = Hash::from_hex(s).unwrap();
Ok(BlockIdentifier(block_hash))
}
}
#[derive(Serialize, Eq, PartialEq, Deserialize, Debug, Clone)]
pub struct WalletInfo {
#[serde(with = "secp_ser::string_or_u64")]
pub last_confirmed_height: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub minimum_confirmations: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub total: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub amount_awaiting_finalization: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub amount_awaiting_confirmation: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub amount_immature: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub amount_currently_spendable: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub amount_locked: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub amount_reverted: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub enum TxLogEntryType {
ConfirmedCoinbase,
TxReceived,
TxSent,
TxReceivedCancelled,
TxSentCancelled,
TxReverted,
}
impl fmt::Display for TxLogEntryType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
TxLogEntryType::ConfirmedCoinbase => write!(f, "Confirmed \nCoinbase"),
TxLogEntryType::TxReceived => write!(f, "Received Tx"),
TxLogEntryType::TxSent => write!(f, "Sent Tx"),
TxLogEntryType::TxReceivedCancelled => write!(f, "Received Tx\n- Cancelled"),
TxLogEntryType::TxSentCancelled => write!(f, "Sent Tx\n- Cancelled"),
TxLogEntryType::TxReverted => write!(f, "Received Tx\n- Reverted"),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TxLogEntry {
pub parent_key_id: Identifier,
pub id: u32,
pub tx_slate_id: Option<Uuid>,
pub tx_type: TxLogEntryType,
pub creation_ts: DateTime<Utc>,
pub confirmation_ts: Option<DateTime<Utc>>,
pub confirmed: bool,
pub num_inputs: usize,
pub num_outputs: usize,
#[serde(with = "secp_ser::string_or_u64")]
pub amount_credited: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub amount_debited: u64,
#[serde(with = "secp_ser::opt_string_or_u64")]
pub fee: Option<u64>,
#[serde(with = "secp_ser::opt_string_or_u64")]
#[serde(default)]
pub ttl_cutoff_height: Option<u64>,
pub stored_tx: Option<String>,
#[serde(with = "secp_ser::option_commitment_serde")]
#[serde(default)]
pub kernel_excess: Option<pedersen::Commitment>,
#[serde(default)]
pub kernel_lookup_min_height: Option<u64>,
#[serde(default)]
pub payment_proof: Option<StoredProofInfo>,
#[serde(with = "option_duration_as_secs", default)]
pub reverted_after: Option<Duration>,
}
impl ser::Writeable for TxLogEntry {
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
}
}
impl ser::Readable for TxLogEntry {
fn read<R: ser::Reader>(reader: &mut R) -> Result<TxLogEntry, ser::Error> {
let data = reader.read_bytes_len_prefix()?;
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
}
}
impl TxLogEntry {
pub fn new(parent_key_id: Identifier, t: TxLogEntryType, id: u32) -> Self {
TxLogEntry {
parent_key_id: parent_key_id,
tx_type: t,
id: id,
tx_slate_id: None,
creation_ts: Utc::now(),
confirmation_ts: None,
confirmed: false,
amount_credited: 0,
amount_debited: 0,
num_inputs: 0,
num_outputs: 0,
fee: None,
ttl_cutoff_height: None,
stored_tx: None,
kernel_excess: None,
kernel_lookup_min_height: None,
payment_proof: None,
reverted_after: None,
}
}
pub fn sum_confirmed(txs: &[TxLogEntry]) -> (u64, u64) {
txs.iter().fold((0, 0), |acc, tx| match tx.confirmed {
true => (acc.0 + tx.amount_credited, acc.1 + tx.amount_debited),
false => acc,
})
}
pub fn update_confirmation_ts(&mut self) {
self.confirmation_ts = Some(Utc::now());
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct StoredProofInfo {
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
pub receiver_address: DalekPublicKey,
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
pub receiver_signature: Option<DalekSignature>,
pub sender_address_path: u32,
#[serde(with = "dalek_ser::dalek_pubkey_serde")]
pub sender_address: DalekPublicKey,
#[serde(with = "dalek_ser::option_dalek_sig_serde")]
pub sender_signature: Option<DalekSignature>,
}
impl ser::Writeable for StoredProofInfo {
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
}
}
impl ser::Readable for StoredProofInfo {
fn read<R: ser::Reader>(reader: &mut R) -> Result<StoredProofInfo, ser::Error> {
let data = reader.read_bytes_len_prefix()?;
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AcctPathMapping {
pub label: String,
pub path: Identifier,
}
impl ser::Writeable for AcctPathMapping {
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
}
}
impl ser::Readable for AcctPathMapping {
fn read<R: ser::Reader>(reader: &mut R) -> Result<AcctPathMapping, ser::Error> {
let data = reader.read_bytes_len_prefix()?;
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
}
}
#[derive(Serialize, Deserialize)]
pub struct TxWrapper {
pub tx_hex: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ScannedBlockInfo {
pub height: u64,
pub hash: String,
pub start_pmmr_index: u64,
pub last_pmmr_index: u64,
}
impl ser::Writeable for ScannedBlockInfo {
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
}
}
impl ser::Readable for ScannedBlockInfo {
fn read<R: ser::Reader>(reader: &mut R) -> Result<ScannedBlockInfo, ser::Error> {
let data = reader.read_bytes_len_prefix()?;
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
}
}
#[derive(Debug, Clone)]
pub struct CbData {
pub output: Output,
pub kernel: TxKernel,
pub key_id: Option<Identifier>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WalletInitStatus {
InitNeedsScanning,
InitNoScanning,
InitComplete,
}
impl ser::Writeable for WalletInitStatus {
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
}
}
impl ser::Readable for WalletInitStatus {
fn read<R: ser::Reader>(reader: &mut R) -> Result<WalletInitStatus, ser::Error> {
let data = reader.read_bytes_len_prefix()?;
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
}
}
pub mod option_duration_as_secs {
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serializer};
use std::time::Duration;
pub fn serialize<S>(dur: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match dur {
Some(dur) => serializer.serialize_str(&format!("{}", dur.as_secs())),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
where
D: Deserializer<'de>,
{
match Option::<String>::deserialize(deserializer)? {
Some(s) => {
let secs = s
.parse::<u64>()
.map_err(|err| Error::custom(err.to_string()))?;
Ok(Some(Duration::from_secs(secs)))
}
None => Ok(None),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::Value;
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
struct TestSer {
#[serde(with = "option_duration_as_secs", default)]
dur: Option<Duration>,
}
#[test]
fn duration_serde() {
let some = TestSer {
dur: Some(Duration::from_secs(100)),
};
let val = serde_json::to_value(some.clone()).unwrap();
if let Value::Object(o) = &val {
if let Value::String(s) = o.get("dur").unwrap() {
assert_eq!(s, "100");
} else {
panic!("Invalid type");
}
} else {
panic!("Invalid type")
}
assert_eq!(some, serde_json::from_value(val).unwrap());
let none = TestSer { dur: None };
let val = serde_json::to_value(none.clone()).unwrap();
if let Value::Object(o) = &val {
if let Value::Null = o.get("dur").unwrap() {
} else {
panic!("Invalid type");
}
} else {
panic!("Invalid type")
}
assert_eq!(none, serde_json::from_value(val).unwrap());
let none2 = serde_json::from_str::<TestSer>("{}").unwrap();
assert_eq!(none, none2);
}
}