Repository: ergoplatform/sigma-rust (language: rust)

Issue:
Implement ContextExtension serialization (+JSON)



Discussion / hints:
I had a quick look at implementing this. I think this requires full value serialisation support for each item value.
@jasondavies Yes, you're right. I was too quick with "good first issue" label. 
To be fair, if we have some simple value types that can be (de)serialised (let's say numbers to start with), we can fail if an unsupported value type is (de)serialised.

Fix patch:
diff --git a/sigma-tree/Cargo.toml b/sigma-tree/Cargo.toml
index e6b56bed7..15ad57f98 100644
--- a/sigma-tree/Cargo.toml
+++ b/sigma-tree/Cargo.toml
@@ -28,7 +28,7 @@ with-serde = ["serde"]
 [dev-dependencies]
 wasm-bindgen-test = "0.3.10"
 proptest-derive = "0.2"
-sigma-testutil = { path = "../sigma-testutil"}
+# sigma-testutil = { path = "../sigma-testutil"}
 rand = "0.7.3"
 
 [dev-dependencies.proptest]
diff --git a/sigma-tree/src/chain/context_extension.rs b/sigma-tree/src/chain/context_extension.rs
index 66531f855..b741523f2 100644
--- a/sigma-tree/src/chain/context_extension.rs
+++ b/sigma-tree/src/chain/context_extension.rs
@@ -1,38 +1,123 @@
 //! ContextExtension type
-use crate::serialization::{
-    sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
+use crate::{
+    ast::Constant,
+    serialization::{
+        sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
+        SigmaSerializable,
+    },
 };
+use indexmap::IndexMap;
 #[cfg(feature = "with-serde")]
 use serde::{Deserialize, Serialize};
-use sigma_ser::vlq_encode;
 use std::collections::HashMap;
-use std::io;
+use std::{convert::TryFrom, io, num::ParseIntError};
 
 /// User-defined variables to be put into context
 #[derive(Debug, PartialEq, Eq, Clone)]
-#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+    feature = "with-serde",
+    derive(Serialize, Deserialize),
+    serde(
+        into = "HashMap<String, Constant>",
+        try_from = "HashMap<String, Constant>"
+    )
+)]
 pub struct ContextExtension {
     /// key-value pairs of variable id and it's value
-    pub values: HashMap<u8, Vec<u8>>,
+    pub values: IndexMap<u8, Constant>,
 }
 
 impl ContextExtension {
     /// Returns an empty ContextExtension
     pub fn empty() -> Self {
         Self {
-            values: HashMap::new(),
+            values: IndexMap::new(),
         }
     }
 }
 
 impl SigmaSerializable for ContextExtension {
-    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
         w.put_u8(self.values.len() as u8)?;
-        assert!(self.values.is_empty(), "implemented only for empty");
+        let mut sorted_values: Vec<(&u8, &Constant)> = self.values.iter().collect();
+        // stable order is important for tx id generation
+        // since JSON encoding does not preserve the order, JSON roundtrip would result in different order
+        // of values and thus a different tx id
+        // see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/681
+        sorted_values.sort_by_key(|(k, _)| *k);
+        sorted_values.iter().try_for_each(|(idx, c)| {
+            w.put_u8(**idx)?;
+            c.sigma_serialize(w)
+        })?;
         Ok(())
     }
+
     fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        let _ = r.get_u8()?;
-        Ok(ContextExtension::empty())
+        let values_count = r.get_u8()?;
+        let mut values: IndexMap<u8, Constant> = IndexMap::with_capacity(values_count as usize);
+        for _ in 0..values_count {
+            let idx = r.get_u8()?;
+            values.insert(idx, Constant::sigma_parse(r)?);
+        }
+        Ok(ContextExtension { values })
+    }
+}
+
+impl Into<HashMap<String, Constant>> for ContextExtension {
+    fn into(self) -> HashMap<String, Constant> {
+        self.values
+            .into_iter()
+            .map(|(k, v)| (format!("{}", k), v))
+            .collect()
+    }
+}
+
+impl TryFrom<HashMap<String, Constant>> for ContextExtension {
+    type Error = ParseIntError;
+    fn try_from(values_str: HashMap<String, Constant>) -> Result<Self, Self::Error> {
+        let values = values_str.iter().try_fold(
+            IndexMap::with_capacity(values_str.len()),
+            |mut acc, pair| {
+                let idx: u8 = pair.0.parse()?;
+                acc.insert(idx, pair.1.clone());
+                Ok(acc)
+            },
+        )?;
+        Ok(ContextExtension { values })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
+
+    impl Arbitrary for ContextExtension {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            vec(any::<Constant>(), 0..10)
+                .prop_map(|constants| {
+                    let pairs: Vec<(u8, Constant)> = constants
+                        .into_iter()
+                        .enumerate()
+                        .map(|(idx, c)| (idx as u8, c))
+                        .collect();
+                    Self {
+                        values: pairs.into_iter().collect(),
+                    }
+                })
+                .boxed()
+        }
+    }
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any::<ContextExtension>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
     }
 }
diff --git a/sigma-tree/src/chain/json.rs b/sigma-tree/src/chain/json.rs
index 145abe34c..323c22758 100644
--- a/sigma-tree/src/chain/json.rs
+++ b/sigma-tree/src/chain/json.rs
@@ -100,6 +100,7 @@ mod tests {
     use super::super::ergo_box::*;
     use super::super::transaction::*;
     use super::*;
+    use crate::chain::ContextExtension;
     use proptest::prelude::*;
     use register::NonMandatoryRegisters;
 
@@ -141,4 +142,21 @@ mod tests {
         let b: ergo_box::ErgoBoxFromJson = serde_json::from_str(json).unwrap();
         assert!(b.ergo_tree.proposition().is_ok())
     }
+
+    #[test]
+    fn parse_empty_context_extension() {
+        let c: ContextExtension = serde_json::from_str("{}").unwrap();
+        assert_eq!(c, ContextExtension::empty());
+    }
+
+    #[test]
+    fn parse_context_extension() {
+        let json = r#"
+        {"1" :"05b0b5cad8e6dbaef44a", "3":"048ce5d4e505"}
+        "#;
+        let c: ContextExtension = serde_json::from_str(json).unwrap();
+        assert_eq!(c.values.len(), 2);
+        assert!(c.values.get(&1u8).is_some());
+        assert!(c.values.get(&3u8).is_some());
+    }
 }
diff --git a/sigma-tree/src/chain/prover_result.rs b/sigma-tree/src/chain/prover_result.rs
index bb9d4787e..c05f4617f 100644
--- a/sigma-tree/src/chain/prover_result.rs
+++ b/sigma-tree/src/chain/prover_result.rs
@@ -47,11 +47,8 @@ mod tests {
         type Parameters = ();
 
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            (vec(any::<u8>(), 0..100))
-                .prop_map(|v| Self {
-                    proof: v,
-                    extension: ContextExtension::empty(),
-                })
+            (vec(any::<u8>(), 0..100), any::<ContextExtension>())
+                .prop_map(|(proof, extension)| Self { proof, extension })
                 .boxed()
         }
 
diff --git a/sigma-tree/src/chain/transaction.rs b/sigma-tree/src/chain/transaction.rs
index 0d277f209..c32deda1d 100644
--- a/sigma-tree/src/chain/transaction.rs
+++ b/sigma-tree/src/chain/transaction.rs
@@ -301,7 +301,7 @@ mod tests {
           "boxId": "9126af0675056b80d1fda7af9bf658464dbfa0b128afca7bf7dae18c27fe8456",
           "spendingProof": {
             "proofBytes": [],
-            "extension": {"values": {}}
+            "extension": {}
           }
         }
       ],
diff --git a/sigma-tree/src/sigma_protocol/verifier.rs b/sigma-tree/src/sigma_protocol/verifier.rs
index 1fefa8af5..d48a0c8d9 100644
--- a/sigma-tree/src/sigma_protocol/verifier.rs
+++ b/sigma-tree/src/sigma_protocol/verifier.rs
@@ -164,14 +164,14 @@ mod tests {
           "boxId": "f353ae1b2027e40ea318e7a2673ea4bbaa281b7acee518a0994c5cbdefb05f55",
           "spendingProof": {
             "proofBytes":[],
-            "extension": {"values": {}}
+            "extension": {}
           }
         },
         {
           "boxId": "56111b039b86f71004b768d2e8b4579f1d79e28e7a617fd5add57a5239498c26",
           "spendingProof": {
             "proofBytes":[],
-            "extension": {"values": {}}
+            "extension": {}
           }
         }
       ],


Test patch: