Simplify slate (de)serialization (#103)

* Simplify slate (de)serialization

* rustfmt

* Cleanup

* Fix slate version tests

* Another fix for tests

* Fix slate deser in http adapter
This commit is contained in:
jaspervdm 2019-05-16 10:17:39 +02:00 committed by Yeastplume
parent 0b9b16d5cf
commit af6a6249e0
8 changed files with 189 additions and 150 deletions

44
Cargo.lock generated
View file

@ -280,16 +280,16 @@ dependencies = [
[[package]]
name = "croaring"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"croaring-sys 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"croaring-sys 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "croaring-sys"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bindgen 0.37.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -555,7 +555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "grin_api"
version = "1.1.0-beta.2"
source = "git+https://github.com/mimblewimble/grin#865b9b6b467d6aa0bfad1dee3df052b141ca3eab"
source = "git+https://github.com/mimblewimble/grin#884851cdeb59305b88300e9110500cbb15b51dc2"
dependencies = [
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -587,12 +587,12 @@ dependencies = [
[[package]]
name = "grin_chain"
version = "1.1.0-beta.2"
source = "git+https://github.com/mimblewimble/grin#865b9b6b467d6aa0bfad1dee3df052b141ca3eab"
source = "git+https://github.com/mimblewimble/grin#884851cdeb59305b88300e9110500cbb15b51dc2"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"croaring 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"croaring 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_core 1.1.0-beta.2 (git+https://github.com/mimblewimble/grin)",
@ -610,12 +610,12 @@ dependencies = [
[[package]]
name = "grin_core"
version = "1.1.0-beta.2"
source = "git+https://github.com/mimblewimble/grin#865b9b6b467d6aa0bfad1dee3df052b141ca3eab"
source = "git+https://github.com/mimblewimble/grin#884851cdeb59305b88300e9110500cbb15b51dc2"
dependencies = [
"blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"croaring 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"croaring 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -626,7 +626,7 @@ dependencies = [
"lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -636,7 +636,7 @@ dependencies = [
[[package]]
name = "grin_keychain"
version = "1.1.0-beta.2"
source = "git+https://github.com/mimblewimble/grin#865b9b6b467d6aa0bfad1dee3df052b141ca3eab"
source = "git+https://github.com/mimblewimble/grin#884851cdeb59305b88300e9110500cbb15b51dc2"
dependencies = [
"blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -646,7 +646,7 @@ dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"pbkdf2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ripemd160 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
@ -658,7 +658,7 @@ dependencies = [
[[package]]
name = "grin_p2p"
version = "1.1.0-beta.2"
source = "git+https://github.com/mimblewimble/grin#865b9b6b467d6aa0bfad1dee3df052b141ca3eab"
source = "git+https://github.com/mimblewimble/grin#884851cdeb59305b88300e9110500cbb15b51dc2"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
@ -671,15 +671,16 @@ dependencies = [
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "grin_pool"
version = "1.1.0-beta.2"
source = "git+https://github.com/mimblewimble/grin#865b9b6b467d6aa0bfad1dee3df052b141ca3eab"
source = "git+https://github.com/mimblewimble/grin#884851cdeb59305b88300e9110500cbb15b51dc2"
dependencies = [
"blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -690,7 +691,7 @@ dependencies = [
"grin_store 1.1.0-beta.2 (git+https://github.com/mimblewimble/grin)",
"grin_util 1.1.0-beta.2 (git+https://github.com/mimblewimble/grin)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -712,10 +713,10 @@ dependencies = [
[[package]]
name = "grin_store"
version = "1.1.0-beta.2"
source = "git+https://github.com/mimblewimble/grin#865b9b6b467d6aa0bfad1dee3df052b141ca3eab"
source = "git+https://github.com/mimblewimble/grin#884851cdeb59305b88300e9110500cbb15b51dc2"
dependencies = [
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"croaring 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"croaring 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -727,12 +728,13 @@ dependencies = [
"memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "grin_util"
version = "1.1.0-beta.2"
source = "git+https://github.com/mimblewimble/grin#865b9b6b467d6aa0bfad1dee3df052b141ca3eab"
source = "git+https://github.com/mimblewimble/grin#884851cdeb59305b88300e9110500cbb15b51dc2"
dependencies = [
"backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -742,7 +744,7 @@ dependencies = [
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2717,8 +2719,8 @@ dependencies = [
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
"checksum croaring 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b350ece8a9ba71eeb9c068a98a86dc420ca5c1d6bd4e1627a4581e9c843c38"
"checksum croaring-sys 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "546b00f33bdf591bce6410a8dca65047d126b1d5a9189190d085aa8c493d43a7"
"checksum croaring 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "71152d60cec9dfdc5d9d793bccfa9ad95927372b80cd00e983db5eb2ce103e3b"
"checksum croaring-sys 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ac84a4f975e67fc418be3911b7ca9aa74ee8a9717ca75452da7d6839421e2d67"
"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71"
"checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4"
"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"

View file

@ -776,9 +776,7 @@ where
err(e)
} else {
match api.receive_tx(&slate, None, None) {
Ok(s) => ok(s
.serialize_to_version(Some(s.version_info.orig_version))
.unwrap()),
Ok(s) => ok(serde_json::to_string(&s).unwrap()),
Err(e) => {
error!("receive_tx: failed with error: {}", e);
err(e)

View file

@ -17,7 +17,7 @@ use std::fs::File;
use std::io::{Read, Write};
use crate::config::WalletConfig;
use crate::libwallet::{Error, Slate};
use crate::libwallet::{Error, ErrorKind, Slate};
use crate::WalletCommAdapter;
use std::collections::HashMap;
@ -42,8 +42,11 @@ impl WalletCommAdapter for FileWalletCommAdapter {
fn send_tx_async(&self, dest: &str, slate: &Slate) -> Result<(), Error> {
let mut pub_tx = File::create(dest)?;
let slate_string = slate.serialize_to_version(Some(slate.version_info.orig_version))?;
pub_tx.write_all(slate_string.as_bytes())?;
pub_tx.write_all(
serde_json::to_string(slate)
.map_err(|_| ErrorKind::SlateSer)?
.as_bytes(),
)?;
pub_tx.sync_all()?;
Ok(())
}

View file

@ -16,11 +16,10 @@
/// HTTP Wallet 'plugin' implementation
use crate::api;
use crate::libwallet::slate_versions::{v0, v1};
use crate::libwallet::{Error, ErrorKind, Slate};
use crate::WalletCommAdapter;
use config::WalletConfig;
use failure::ResultExt;
use serde::Serialize;
use std::collections::HashMap;
#[derive(Clone)]
@ -49,66 +48,14 @@ impl WalletCommAdapter for HTTPWalletCommAdapter {
}
let url = format!("{}/v1/wallet/foreign/receive_tx", dest);
debug!("Posting transaction slate to {}", url);
//TODO: Use VersionedSlate when converting to V2 API
let slate = slate.serialize_to_version(Some(slate.version_info.orig_version))?;
// For compatibility with older clients
let res: Slate = {
if let None = slate.find("version_info") {
let version = Slate::parse_slate_version(&slate)?;
match version {
1 => {
let ver1: v1::SlateV1 =
serde_json::from_str(&slate).context(ErrorKind::SlateDeser)?;
let r: Result<v1::SlateV1, _> =
api::client::post(url.as_str(), None, &ver1);
match r {
Err(e) => {
let report = format!(
"Posting transaction slate (is recipient listening?): {}",
e
);
error!("{}", report);
return Err(ErrorKind::ClientCallback(report).into());
}
Ok(s) => Slate::deserialize_upgrade(
&serde_json::to_string(&s).context(ErrorKind::SlateDeser)?,
)?,
}
}
_ => {
let ver0: v0::SlateV0 =
serde_json::from_str(&slate).context(ErrorKind::SlateDeser)?;
let r: Result<v0::SlateV0, _> =
api::client::post(url.as_str(), None, &ver0);
match r {
Err(e) => {
let report = format!(
"Posting transaction slate (is recipient listening?): {}",
e
);
error!("{}", report);
return Err(ErrorKind::ClientCallback(report).into());
}
Ok(s) => Slate::deserialize_upgrade(
&serde_json::to_string(&s).context(ErrorKind::SlateDeser)?,
)?,
}
}
}
} else {
let res: Result<String, _> = api::client::post(url.as_str(), None, &slate);
match res {
Err(e) => {
let report =
format!("Posting transaction slate (is recipient listening?): {}", e);
error!("{}", report);
return Err(ErrorKind::ClientCallback(report).into());
}
Ok(r) => Slate::deserialize_upgrade(&r)?,
}
}
};
Ok(res)
let res: String = post(url.as_str(), None, &slate).map_err(|e| {
let report = format!("Posting transaction slate (is recipient listening?): {}", e);
error!("{}", report);
ErrorKind::ClientCallback(report)
})?;
let slate = Slate::deserialize_upgrade(&res).map_err(|_| ErrorKind::SlateDeser)?;
Ok(slate)
}
fn send_tx_async(&self, _dest: &str, _slate: &Slate) -> Result<(), Error> {
@ -130,3 +77,12 @@ impl WalletCommAdapter for HTTPWalletCommAdapter {
unimplemented!();
}
}
pub fn post<IN>(url: &str, api_secret: Option<String>, input: &IN) -> Result<String, api::Error>
where
IN: Serialize,
{
let req = api::client::create_post_request(url, api_secret, input)?;
let res = api::client::send_request(req)?;
Ok(res)
}

View file

@ -389,8 +389,7 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
// Reply to the same channel with topic SLATE_SIGNED
Ok(s) => {
let slate =
s.serialize_to_version(Some(slate.version_info.orig_version))?;
// TODO: Send the same version of slate that was sent to us
serde_json::to_string(&s).map_err(|_| ErrorKind::SlateSer)?;
let success = send(slate, channel, SLATE_SIGNED, TTL);
if success {

View file

@ -181,6 +181,10 @@ pub enum ErrorKind {
#[fail(display = "Can't parse slate version")]
SlateVersionParse,
/// Can't serialize slate
#[fail(display = "Can't Serialize slate")]
SlateSer,
/// Can't deserialize slate
#[fail(display = "Can't Deserialize slate")]
SlateDeser,

View file

@ -33,6 +33,7 @@ use crate::grin_util::{self, secp, RwLock};
use failure::ResultExt;
use rand::rngs::mock::StepRng;
use rand::thread_rng;
use serde::ser::{Serialize, Serializer};
use serde_json;
use std::fmt;
use std::sync::Arc;
@ -148,7 +149,7 @@ impl fmt::Display for ParticipantMessageData {
/// the slate around by whatever means they choose, (but we can provide some
/// binary or JSON serialization helpers here).
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Deserialize, Debug, Clone)]
pub struct Slate {
/// Versioning info
pub version_info: VersionCompatInfo,
@ -196,24 +197,11 @@ pub struct ParticipantMessages {
}
impl Slate {
/// TODO: Reduce the number of changes that need to occur below for each new
/// slate version
/// Attempt to find slate version
pub fn parse_slate_version(slate_json: &str) -> Result<u16, Error> {
// keep attempting to deser, working through known versions until we have
// enough to get the version out
let res: Result<SlateV2, serde_json::Error> = serde_json::from_str(slate_json);
if let Ok(s) = res {
return Ok(s.version_info.version);
}
let res: Result<SlateV1, serde_json::Error> = serde_json::from_str(slate_json);
if let Ok(s) = res {
return Ok(s.version as u16);
}
let res: Result<SlateV0, serde_json::Error> = serde_json::from_str(slate_json);
if let Ok(_) = res {
return Ok(0);
}
Err(ErrorKind::SlateVersionParse)?
let probe: SlateVersionProbe =
serde_json::from_str(slate_json).map_err(|_| ErrorKind::SlateVersionParse)?;
Ok(probe.version())
}
/// Recieve a slate, upgrade it to the latest version internally
@ -233,35 +221,9 @@ impl Slate {
let v1 = SlateV1::from(v0);
SlateV2::from(v1)
}
_ => return Err(ErrorKind::SlateVersion(version))?,
_ => return Err(ErrorKind::SlateVersion(version).into()),
};
let f = serde_json::to_string(&v2).context(ErrorKind::SlateDeser)?;
Ok(serde_json::from_str(&f).context(ErrorKind::SlateDeser)?)
}
/// Downgrate slate to desired version
pub fn serialize_to_version(&self, version: Option<u16>) -> Result<String, Error> {
let version = match version {
Some(v) => v,
None => CURRENT_SLATE_VERSION,
};
let ser_self = serde_json::to_string(&self).context(ErrorKind::SlateDeser)?;
match version {
2 => Ok(ser_self.clone()),
1 => {
let v2: SlateV2 = serde_json::from_str(&ser_self).context(ErrorKind::SlateDeser)?;
let v1 = SlateV1::from(v2);
let slate = serde_json::to_string(&v1).context(ErrorKind::SlateDeser)?;
Ok(slate)
}
0 => {
let v2: SlateV2 = serde_json::from_str(&ser_self).context(ErrorKind::SlateDeser)?;
let v1 = SlateV1::from(v2);
let v0 = SlateV0::from(v1);
Ok(serde_json::to_string(&v0).context(ErrorKind::SlateDeser)?)
}
_ => Err(ErrorKind::SlateVersion(version))?,
}
Ok(v2.into())
}
/// Create a new slate
@ -709,6 +671,50 @@ impl Slate {
}
}
impl Serialize for Slate {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::Error;
let v2 = SlateV2::from(self);
match self.version_info.orig_version {
2 => v2.serialize(serializer),
1 => {
let v1 = SlateV1::from(v2);
v1.serialize(serializer)
}
0 => {
let v1 = SlateV1::from(v2);
let v0 = SlateV0::from(v1);
v0.serialize(serializer)
}
v => Err(S::Error::custom(format!("Unknown slate version {}", v))),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SlateVersionProbe {
#[serde(default)]
version: Option<u64>,
#[serde(default)]
version_info: Option<VersionCompatInfo>,
}
impl SlateVersionProbe {
pub fn version(&self) -> u16 {
match &self.version_info {
Some(v) => v.version,
None => match self.version {
Some(_) => 1,
None => 0,
},
}
}
}
// Current slate version to versioned conversions
// Slate to versioned
@ -742,6 +748,42 @@ impl From<Slate> for SlateV2 {
}
}
impl From<&Slate> for SlateV2 {
fn from(slate: &Slate) -> SlateV2 {
let Slate {
num_participants,
id,
tx,
amount,
fee,
height,
lock_height,
participant_data,
version_info,
} = slate;
let num_participants = *num_participants;
let id = *id;
let tx = TransactionV2::from(tx);
let amount = *amount;
let fee = *fee;
let height = *height;
let lock_height = *lock_height;
let participant_data = map_vec!(participant_data, |data| ParticipantDataV2::from(data));
let version_info = VersionCompatInfoV2::from(version_info);
SlateV2 {
num_participants,
id,
tx,
amount,
fee,
height,
lock_height,
participant_data,
version_info,
}
}
}
impl From<&ParticipantData> for ParticipantDataV2 {
fn from(data: &ParticipantData) -> ParticipantDataV2 {
let ParticipantData {
@ -795,6 +837,15 @@ impl From<Transaction> for TransactionV2 {
}
}
impl From<&Transaction> for TransactionV2 {
fn from(tx: &Transaction) -> TransactionV2 {
let Transaction { offset, body } = tx;
let offset = *offset;
let body = TransactionBodyV2::from(body);
TransactionV2 { offset, body }
}
}
impl From<&TransactionBody> for TransactionBodyV2 {
fn from(body: &TransactionBody) -> TransactionBodyV2 {
let TransactionBody {

View file

@ -22,47 +22,73 @@ fn slate_conversions() {
let res = Slate::deserialize_upgrade(&v0);
assert!(res.is_ok());
// should serialize as latest
let res = res.unwrap();
let mut res = res.unwrap();
assert_eq!(res.version_info.orig_version, 0);
let s = res.serialize_to_version(Some(2));
res.version_info.orig_version = 2;
let s = serde_json::to_string(&res);
assert!(s.is_ok());
println!("v0 -> v2: {}", s.unwrap());
let s = s.unwrap();
let v = Slate::parse_slate_version(&s);
assert!(v.is_ok());
assert_eq!(v.unwrap(), 2);
println!("v0 -> v2: {}", s);
// Test V1 to V2
let v1 = include_str!("slates/v1.slate");
let res = Slate::deserialize_upgrade(&v1);
assert!(res.is_ok());
// should serialize as latest
let res = res.unwrap();
let mut res = res.unwrap();
assert_eq!(res.version_info.orig_version, 1);
let s = res.serialize_to_version(Some(2));
res.version_info.orig_version = 2;
let s = serde_json::to_string(&res);
assert!(s.is_ok());
println!("v1 -> v2: {}", s.unwrap());
let s = s.unwrap();
let v = Slate::parse_slate_version(&s);
assert!(v.is_ok());
assert_eq!(v.unwrap(), 2);
println!("v1 -> v2: {}", s);
// V2 -> V2, check version
let v2 = include_str!("slates/v2.slate");
let res = Slate::deserialize_upgrade(&v2);
assert!(res.is_ok());
let res = res.unwrap().serialize_to_version(Some(2));
let s = res.unwrap();
let res = Slate::deserialize_upgrade(&s).unwrap();
let res = res.unwrap();
assert_eq!(res.version_info.orig_version, 2);
let s = serde_json::to_string(&res);
assert!(s.is_ok());
let s = s.unwrap();
let v = Slate::parse_slate_version(&s);
assert!(v.is_ok());
assert_eq!(v.unwrap(), 2);
// Downgrade to V1
let v2 = include_str!("slates/v2.slate");
let res = Slate::deserialize_upgrade(&v2);
assert!(res.is_ok());
let mut res = res.unwrap();
// downgrade
let s = res.unwrap().serialize_to_version(Some(1));
res.version_info.orig_version = 1;
let s = serde_json::to_string(&res);
assert!(s.is_ok());
println!("v2 -> v1: {}", s.unwrap());
let s = s.unwrap();
let v = Slate::parse_slate_version(&s);
assert!(v.is_ok());
assert_eq!(v.unwrap(), 1);
println!("v2 -> v1: {}", s);
// Downgrade to V0
let v2 = include_str!("slates/v2.slate");
let res = Slate::deserialize_upgrade(&v2);
assert!(res.is_ok());
let mut res = res.unwrap();
// downgrade
let s = res.unwrap().serialize_to_version(Some(0));
res.version_info.orig_version = 0;
let s = serde_json::to_string(&res);
assert!(s.is_ok());
println!("v2 -> v0: {}", s.unwrap());
let s = s.unwrap();
let v = Slate::parse_slate_version(&s);
assert!(v.is_ok());
assert_eq!(v.unwrap(), 0);
println!("v2 -> v0: {}", s);
}