feat(primitives): Implement canonical JSON serialization (#90)
This commit is contained in:
parent
956743e484
commit
4ce608ea6f
7 changed files with 167 additions and 1 deletions
|
|
@ -20,3 +20,9 @@ pallas-crypto = { version = "0.8.0", path = "../pallas-crypto" }
|
|||
pallas-codec = { version = "0.8.0", path = "../pallas-codec" }
|
||||
base58 = "0.2.0"
|
||||
bech32 = "0.8.1"
|
||||
serde = { version ="1.0.136", optional = true }
|
||||
serde_json = { version ="1.0.79", optional = true }
|
||||
|
||||
[features]
|
||||
json = ["serde", "serde_json"]
|
||||
default = ["json"]
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use super::{AuxiliaryData, Header, PlutusData, TransactionBody};
|
||||
use super::{AuxiliaryData, Header, NativeScript, PlutusData, TransactionBody};
|
||||
use pallas_crypto::hash::{Hash, Hasher};
|
||||
|
||||
pub fn hash_block_header(data: &Header) -> Hash<32> {
|
||||
|
|
@ -14,10 +14,23 @@ pub fn hash_transaction(data: &TransactionBody) -> Hash<32> {
|
|||
Hasher::<256>::hash_cbor(data)
|
||||
}
|
||||
|
||||
#[deprecated(note = "use PlutusData::to_hash instead")]
|
||||
pub fn hash_plutus_data(data: &PlutusData) -> Hash<32> {
|
||||
Hasher::<256>::hash_cbor(data)
|
||||
}
|
||||
|
||||
impl NativeScript {
|
||||
pub fn to_hash(&self) -> Hash<28> {
|
||||
Hasher::<224>::hash_cbor(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PlutusData {
|
||||
pub fn to_hash(&self) -> Hash<32> {
|
||||
Hasher::<256>::hash_cbor(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionBody {
|
||||
pub fn to_hash(&self) -> Hash<32> {
|
||||
Hasher::<256>::hash_cbor(self)
|
||||
|
|
|
|||
134
pallas-primitives/src/alonzo/json.rs
Normal file
134
pallas-primitives/src/alonzo/json.rs
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
use serde_json::json;
|
||||
|
||||
use crate::ToCanonicalJson;
|
||||
|
||||
// infered from https://github.com/input-output-hk/cardano-node/blob/c1efb2f97134c0607c982246a36e3da7266ac194/cardano-api/src/Cardano/Api/ScriptData.hs#L254
|
||||
impl ToCanonicalJson for super::PlutusData {
|
||||
fn to_json(&self) -> serde_json::Value {
|
||||
match self {
|
||||
super::PlutusData::Constr(x) => {
|
||||
let constructor = x.prefix.map(|x| x as u64).unwrap_or(x.tag);
|
||||
let fields: Vec<_> = x.values.iter().map(|i| i.to_json()).collect();
|
||||
json!({ "constructor": constructor, "fields": fields })
|
||||
}
|
||||
super::PlutusData::Map(x) => {
|
||||
let map: Vec<_> = x
|
||||
.iter()
|
||||
.map(|(k, v)| json!({ "k": k.to_json(), "v": v.to_json() }))
|
||||
.collect();
|
||||
json!({ "map": map })
|
||||
}
|
||||
super::PlutusData::BigInt(int) => match int {
|
||||
super::BigInt::Int(n) => match i64::try_from(*n) {
|
||||
Ok(x) => json!({ "int": x }),
|
||||
Err(_) => json!({ "bignint": hex::encode(i128::from(*n).to_be_bytes()) }),
|
||||
},
|
||||
// WARNING / TODO: the CDDL shows a bignum variants of arbitrary length expressed as
|
||||
// bytes, but I can't find the corresponding mapping to JSON in the
|
||||
// Haskell implementation. Not sure what I'm missing. For the time
|
||||
// being, I'll invent a new JSON expression that uses hex strings as
|
||||
// a way to express the values.
|
||||
super::BigInt::BigUInt(x) => json!({ "biguint": hex::encode(x.as_slice())}),
|
||||
super::BigInt::BigNInt(x) => json!({ "bignint": hex::encode(x.as_slice())}),
|
||||
},
|
||||
super::PlutusData::BoundedBytes(x) => json!({ "bytes": hex::encode(x.as_slice())}),
|
||||
super::PlutusData::Array(x) => {
|
||||
let list: Vec<_> = x.iter().map(|i| i.to_json()).collect();
|
||||
json!({ "list": list })
|
||||
}
|
||||
super::PlutusData::ArrayIndef(x) => {
|
||||
let list: Vec<_> = x.iter().map(|i| i.to_json()).collect();
|
||||
json!({ "list": list })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCanonicalJson for super::NativeScript {
|
||||
fn to_json(&self) -> serde_json::Value {
|
||||
match self {
|
||||
super::NativeScript::ScriptPubkey(x) => {
|
||||
json!({ "keyHash": x.to_string(), "type": "sig"})
|
||||
}
|
||||
super::NativeScript::ScriptAll(x) => {
|
||||
let scripts: Vec<_> = x.iter().map(|i| i.to_json()).collect();
|
||||
json!({ "type": "all", "scripts": scripts})
|
||||
}
|
||||
super::NativeScript::ScriptAny(x) => {
|
||||
let scripts: Vec<_> = x.iter().map(|i| i.to_json()).collect();
|
||||
json!({ "type": "any", "scripts": scripts})
|
||||
}
|
||||
super::NativeScript::ScriptNOfK(n, k) => {
|
||||
let scripts: Vec<_> = k.iter().map(|i| i.to_json()).collect();
|
||||
json!({ "type": "atLeast", "required": n, "scripts" : scripts })
|
||||
}
|
||||
super::NativeScript::InvalidBefore(slot) => json!({ "type": "before", "slot": slot }),
|
||||
super::NativeScript::InvalidHereafter(slot) => json!({"type": "after", "slot": slot }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{alonzo::BlockWrapper, Fragment, ToCanonicalJson};
|
||||
|
||||
#[test]
|
||||
fn test_datums_serialize_as_expected() {
|
||||
let test_blocks = vec![(
|
||||
include_str!("test_data/test9.block"),
|
||||
include_str!("test_data/test9.datums"),
|
||||
)];
|
||||
|
||||
for (idx, (block_str, jsonl_str)) in test_blocks.iter().enumerate() {
|
||||
println!("decoding json block {}", idx + 1);
|
||||
|
||||
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
||||
|
||||
let BlockWrapper(_, block) = BlockWrapper::decode_fragment(&bytes[..])
|
||||
.expect(&format!("error decoding cbor for file {}", idx));
|
||||
|
||||
let mut datums = jsonl_str.lines();
|
||||
|
||||
for ws in block.transaction_witness_sets.iter() {
|
||||
if let Some(pds) = &ws.plutus_data {
|
||||
for pd in pds.iter() {
|
||||
let expected: serde_json::Value =
|
||||
serde_json::from_str(datums.next().unwrap()).unwrap();
|
||||
let current = pd.to_json();
|
||||
assert_eq!(current, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_scripts_serialize_as_expected() {
|
||||
let test_blocks = vec![(
|
||||
include_str!("test_data/test9.block"),
|
||||
include_str!("test_data/test9.native"),
|
||||
)];
|
||||
|
||||
for (idx, (block_str, jsonl_str)) in test_blocks.iter().enumerate() {
|
||||
println!("decoding json block {}", idx + 1);
|
||||
|
||||
let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx));
|
||||
|
||||
let BlockWrapper(_, block) = BlockWrapper::decode_fragment(&bytes[..])
|
||||
.expect(&format!("error decoding cbor for file {}", idx));
|
||||
|
||||
let mut scripts = jsonl_str.lines();
|
||||
|
||||
for ws in block.transaction_witness_sets.iter() {
|
||||
if let Some(nss) = &ws.native_script {
|
||||
for ns in nss.iter() {
|
||||
let expected: serde_json::Value =
|
||||
serde_json::from_str(scripts.next().unwrap()).unwrap();
|
||||
let current = ns.to_json();
|
||||
assert_eq!(current, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,4 +3,7 @@ mod model;
|
|||
pub mod address;
|
||||
pub mod crypto;
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
pub mod json;
|
||||
|
||||
pub use model::*;
|
||||
|
|
|
|||
4
pallas-primitives/src/alonzo/test_data/test9.datums
Normal file
4
pallas-primitives/src/alonzo/test_data/test9.datums
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{"constructor":0,"fields":[{"bytes":"11f76aa14dae8aa6ac6578af2287bfc4c8e7a3da566548da747e026c"},{"int":250000000},{"bytes":"c56d4cceb8a8550534968e1bf165137ca41e908d2d780cc1402079bd"},{"bytes":"4368696c6c65644b6f6e6734373636"},{"bytes":"1656abe748903d4610c6db6373f036c8aec1b19ece5c93b6f25b5233"},{"int":30}]}
|
||||
{"constructor":0,"fields":[{"bytes":"11f76aa14dae8aa6ac6578af2287bfc4c8e7a3da566548da747e026c"},{"int":190000000},{"bytes":"c56d4cceb8a8550534968e1bf165137ca41e908d2d780cc1402079bd"},{"bytes":"4368696c6c65644b6f6e6734373636"},{"bytes":"1656abe748903d4610c6db6373f036c8aec1b19ece5c93b6f25b5233"},{"int":30}]}
|
||||
{"constructor":0,"fields":[{"bytes":"92f5aef1645f377ce472eb092ac8a572f9811d3ab2d5dd39cbe07064"},{"int":380000000},{"bytes":"bff82d31352d9bdfdb49e243ab74af715488631f330b2cf064178f90"},{"bytes":"426c6f636b4f776c734269747479303833"},{"bytes":"68b82dc4fb2d515501728de5bfcb1fd0a47eb3dd628d3340e10afa28"},{"biguint":"00"}]}
|
||||
{"constructor":0,"fields":[{"bytes":"92f5aef1645f377ce472eb092ac8a572f9811d3ab2d5dd39cbe07064"},{"int":350000000},{"bytes":"bff82d31352d9bdfdb49e243ab74af715488631f330b2cf064178f90"},{"bytes":"426c6f636b4f776c734269747479303833"},{"bytes":"68b82dc4fb2d515501728de5bfcb1fd0a47eb3dd628d3340e10afa28"},{"biguint":"00"}]}
|
||||
1
pallas-primitives/src/alonzo/test_data/test9.native
Normal file
1
pallas-primitives/src/alonzo/test_data/test9.native
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"scripts":[{"keyHash":"4d04380dcb9fbad5aff8e2f4e19394ef4e5e11b37932838f01984a12","type":"sig"},{"slot":112500819,"type":"after"}],"type":"all"}
|
||||
|
|
@ -23,6 +23,11 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
pub trait ToCanonicalJson {
|
||||
fn to_json(&self) -> serde_json::Value;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Era {
|
||||
Byron,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue