Skip to main content

dubp_block/
parser.rs

1//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Parsers for block.
17
18use crate::*;
19use json_pest_parser::*;
20use std::num::TryFromIntError;
21
22/// Default hasher
23pub type DefaultHasher = std::hash::BuildHasherDefault<std::collections::hash_map::DefaultHasher>;
24
25#[derive(Copy, Clone, Debug, Error)]
26#[error("Fail to convert serde_json::Value into json_pest_parser::JSONValue")]
27/// Error on conversion of serde_json value into pest_json value
28pub struct JsonValueConversionError;
29
30/// Convert serde_json value into pest_json value
31pub fn serde_json_value_to_pest_json_value(
32    value: &Value,
33) -> Result<JSONValue<DefaultHasher>, JsonValueConversionError> {
34    match value {
35        Value::Null => Ok(JSONValue::Null),
36        Value::Bool(boolean) => Ok(JSONValue::Boolean(*boolean)),
37        Value::Number(number) => Ok(JSONValue::Number(if let Some(u64_) = number.as_u64() {
38            Number::U64(u64_)
39        } else if let Some(f64_) = number.as_f64() {
40            Number::F64(f64_)
41        } else {
42            return Err(JsonValueConversionError);
43        })),
44        Value::String(string) => Ok(JSONValue::String(string)),
45        Value::Array(values) => Ok(JSONValue::Array(
46            values
47                .iter()
48                .map(serde_json_value_to_pest_json_value)
49                .collect::<Result<Vec<JSONValue<DefaultHasher>>, JsonValueConversionError>>()?,
50        )),
51        Value::Object(map) => Ok(JSONValue::Object(
52            map.into_iter()
53                .map(|(k, v)| match serde_json_value_to_pest_json_value(v) {
54                    Ok(v) => Ok((k.as_str(), v)),
55                    Err(e) => Err(e),
56                })
57                .collect::<Result<
58                    HashMap<&str, JSONValue<DefaultHasher>, DefaultHasher>,
59                    JsonValueConversionError,
60                >>()?,
61        )),
62    }
63}
64
65#[derive(Debug, Error)]
66pub enum ParseJsonBlockError {
67    #[error("{0}")]
68    BaseConversionError(BaseConversionError),
69    #[error("{0}")]
70    JsonValueConversion(JsonValueConversionError),
71    #[error("{0}")]
72    ParseCompactDocError(ParseCompactDocError),
73    #[error("{0}")]
74    ParseParamsError(ParseParamsError),
75    #[error("{0}")]
76    ParseJsonError(ParseJsonError),
77    #[error("{0}")]
78    ParseJsonTxError(ParseJsonTxError),
79}
80
81impl From<BaseConversionError> for ParseJsonBlockError {
82    fn from(e: BaseConversionError) -> Self {
83        ParseJsonBlockError::BaseConversionError(e)
84    }
85}
86
87impl From<ParseCompactDocError> for ParseJsonBlockError {
88    fn from(e: ParseCompactDocError) -> Self {
89        ParseJsonBlockError::ParseCompactDocError(e)
90    }
91}
92
93impl From<ParseJsonError> for ParseJsonBlockError {
94    fn from(e: ParseJsonError) -> Self {
95        ParseJsonBlockError::ParseJsonError(e)
96    }
97}
98
99impl From<TryFromIntError> for ParseJsonBlockError {
100    fn from(e: TryFromIntError) -> Self {
101        Self::ParseJsonError(ParseJsonError {
102            cause: format!("{}", e),
103        })
104    }
105}
106
107pub fn parse_json_block_from_serde_value(
108    serde_json_value: &serde_json::Value,
109) -> Result<DubpBlock, ParseJsonBlockError> {
110    parse_json_block(
111        &serde_json_value_to_pest_json_value(serde_json_value)
112            .map_err(ParseJsonBlockError::JsonValueConversion)?,
113    )
114}
115
116pub fn parse_json_block(
117    json_block: &JSONValue<DefaultHasher>,
118) -> Result<DubpBlock, ParseJsonBlockError> {
119    let json_block = if let JSONValue::Object(json_block) = json_block {
120        json_block
121    } else {
122        return Err(ParseJsonError {
123            cause: "Json block must be an object !".to_owned(),
124        }
125        .into());
126    };
127
128    let currency = get_str(json_block, "currency")?;
129
130    let block_number = u32::try_from(get_u64(json_block, "number")?)?;
131
132    let block_content = DubpBlockV10Content {
133        version: get_u64(json_block, "version")? as usize,
134        number: BlockNumber(block_number),
135        pow_min: get_u64(json_block, "powMin")? as usize,
136        time: get_u64(json_block, "time")?,
137        median_time: get_u64(json_block, "medianTime")?,
138        members_count: get_u64(json_block, "membersCount")? as usize,
139        monetary_mass: get_u64(json_block, "monetaryMass").unwrap_or(0),
140        unit_base: get_u64(json_block, "unitbase")? as usize,
141        issuers_count: get_u64(json_block, "issuersCount")? as usize,
142        issuers_frame: get_u64(json_block, "issuersFrame")? as usize,
143        issuers_frame_var: get_number(json_block, "issuersFrameVar")?.trunc() as isize,
144        currency: CurrencyName(currency.to_owned()),
145        issuer: ed25519::PublicKey::from_base58(get_str(json_block, "issuer")?)?,
146        parameters: get_optional_str_not_empty(json_block, "parameters")?
147            .map(BlockV10Parameters::from_str)
148            .transpose()
149            .map_err(ParseJsonBlockError::ParseParamsError)?,
150        previous_hash: if block_number == 0 {
151            Hash::default()
152        } else {
153            Hash::from_hex(get_str(json_block, "previousHash")?)?
154        },
155        previous_issuer: if block_number == 0 {
156            ed25519::PublicKey::default()
157        } else {
158            ed25519::PublicKey::from_base58(get_str(json_block, "previousIssuer")?)?
159        },
160        dividend: get_optional_usize(json_block, "dividend")?,
161        identities: parse_compact_identities(
162            currency,
163            &get_str_array(json_block, "identities")?[..],
164        )?,
165        joiners: parse_compact_memberships(
166            currency,
167            MembershipType::In(),
168            &get_str_array(json_block, "joiners")?,
169        )?,
170        actives: parse_compact_memberships(
171            currency,
172            MembershipType::In(),
173            &get_str_array(json_block, "actives")?,
174        )?,
175        leavers: parse_compact_memberships(
176            currency,
177            MembershipType::Out(),
178            &get_str_array(json_block, "leavers")?,
179        )?,
180        revoked: parse_compact_revocations(&get_str_array(json_block, "revoked")?)?,
181        excluded: get_str_array(json_block, "excluded")?
182            .iter()
183            .map(|p| ed25519::PublicKey::from_base58(p))
184            .collect::<Result<Vec<_>, BaseConversionError>>()?,
185        certifications: parse_compact_certifications(&get_str_array(
186            json_block,
187            "certifications",
188        )?)?,
189        transactions: parse_json_transactions(&get_array(json_block, "transactions")?)
190            .map_err(ParseJsonBlockError::ParseJsonTxError)?,
191    };
192    Ok(DubpBlock::V10(
193        DubpBlockV10Builder::new(block_content).build_unchecked(DubpBlockV10AfterPowData {
194            nonce: get_u64(json_block, "nonce")?,
195            signature: ed25519::Signature::from_base64(get_str(json_block, "signature")?)?,
196            hash: BlockHash(Hash::from_hex(get_str(json_block, "hash")?)?),
197        }),
198    ))
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use crate::tests::*;
205    use dubp_documents::dubp_wallet::prelude::*;
206    use dubp_documents::smallvec::smallvec;
207    use dubp_documents::transaction::*;
208    use unwrap::unwrap;
209
210    #[test]
211    fn parse_empty_json_block() {
212        let block_json_str = r#"{
213   "version": 10,
214   "nonce": 10200000037108,
215   "number": 7,
216   "powMin": 70,
217   "time": 1488987677,
218   "medianTime": 1488987394,
219   "membersCount": 59,
220   "monetaryMass": 59000,
221   "unitbase": 0,
222   "issuersCount": 1,
223   "issuersFrame": 6,
224   "issuersFrameVar": 0,
225   "currency": "g1",
226   "issuer": "2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ",
227   "signature": "xaWNjdFeE4yr9+AKckgR6QuAvMzmKUWfY+uIlC3HKjn2apJqG70Gf59A71W+Ucz6E9WPXRzDDF/xOrf6GCGHCA==",
228   "hash": "0000407900D981FC17B5A6FBCF8E8AFA4C00FAD7AFC5BEA9A96FF505E5D105EC",
229   "parameters": "",
230   "previousHash": "0000379BBE6ABC18DCFD6E4733F9F76CB06593D10FAEDF722BE190C277AC16EA",
231   "previousIssuer": "2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ",
232   "inner_hash": "CF2701092D5A34A55802E343B5F8D61D9B7E8089F1F13A19721234DF5B2F0F38",
233   "dividend": null,
234   "identities": [],
235   "joiners": [],
236   "actives": [],
237   "leavers": [],
238   "revoked": [],
239   "excluded": [],
240   "certifications": [],
241   "transactions": [],
242   "raw": "Version: 10\nType: Block\nCurrency: g1\nNumber: 7\nPoWMin: 70\nTime: 1488987677\nMedianTime: 1488987394\nUnitBase: 0\nIssuer: 2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ\nIssuersFrame: 6\nIssuersFrameVar: 0\nDifferentIssuersCount: 1\nPreviousHash: 0000379BBE6ABC18DCFD6E4733F9F76CB06593D10FAEDF722BE190C277AC16EA\nPreviousIssuer: 2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ\nMembersCount: 59\nIdentities:\nJoiners:\nActives:\nLeavers:\nRevoked:\nExcluded:\nCertifications:\nTransactions:\nInnerHash: CF2701092D5A34A55802E343B5F8D61D9B7E8089F1F13A19721234DF5B2F0F38\nNonce: 10200000037108\n"
243  }"#;
244
245        let block_json_pest = unwrap!(json_pest_parser::parse_json_string(block_json_str));
246        let block_json_serde = unwrap!(serde_json::Value::from_str(block_json_str));
247
248        let expected_block_content = DubpBlockV10Content {
249            version: 10,
250            number: BlockNumber(7),
251            pow_min: 70,
252            time: 1_488_987_677,
253            median_time: 1_488_987_394,
254            members_count: 59,
255            monetary_mass: 59000,
256            unit_base: 0,
257            issuers_count: 1,
258            issuers_frame: 6,
259            issuers_frame_var: 0,
260            currency: CurrencyName("g1".to_owned()),
261            issuer: pk("2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ"),
262            parameters: None,
263            previous_hash: unwrap!(Hash::from_hex(
264                "0000379BBE6ABC18DCFD6E4733F9F76CB06593D10FAEDF722BE190C277AC16EA",
265            )),
266            previous_issuer: pk("2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ"),
267            dividend: None,
268            identities: vec![],
269            joiners: vec![],
270            actives: vec![],
271            leavers: vec![],
272            revoked: vec![],
273            excluded: vec![],
274            certifications: vec![],
275            transactions: vec![],
276        };
277        let expected_block = DubpBlock::V10(DubpBlockV10Builder::new(expected_block_content).build_unchecked(DubpBlockV10AfterPowData {
278            nonce: 10_200_000_037_108,
279            signature: unwrap!(ed25519::Signature::from_base64("xaWNjdFeE4yr9+AKckgR6QuAvMzmKUWfY+uIlC3HKjn2apJqG70Gf59A71W+Ucz6E9WPXRzDDF/xOrf6GCGHCA==")),
280            hash: BlockHash(
281                unwrap!(Hash::from_hex(
282                    "0000407900D981FC17B5A6FBCF8E8AFA4C00FAD7AFC5BEA9A96FF505E5D105EC"
283                ))
284            ),
285        }));
286        assert_eq!(
287            expected_block.inner_hash(),
288            unwrap!(Hash::from_hex(
289                "CF2701092D5A34A55802E343B5F8D61D9B7E8089F1F13A19721234DF5B2F0F38"
290            ))
291        );
292
293        assert_eq!(expected_block, unwrap!(parse_json_block(&block_json_pest)));
294        assert_eq!(
295            expected_block,
296            unwrap!(parse_json_block_from_serde_value(&block_json_serde))
297        );
298    }
299
300    #[test]
301    fn parse_json_block_with_one_tx() -> Result<(), VerifyBlockHashError> {
302        let block_json_str = r#"{
303   "version": 10,
304   "nonce": 10100000033688,
305   "number": 52,
306   "powMin": 74,
307   "time": 1488990898,
308   "medianTime": 1488990117,
309   "membersCount": 59,
310   "monetaryMass": 59000,
311   "unitbase": 0,
312   "issuersCount": 1,
313   "issuersFrame": 6,
314   "issuersFrameVar": 0,
315   "currency": "g1",
316   "issuer": "2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ",
317   "signature": "4/UIwXzWQekbYw7fpD8ueMH4GnDEwCM+DvDaTfquBXOvFXLRYo/S+Vrk5u7so/98gYaZ2O7Myh20xgQvhh5FDQ==",
318   "hash": "000057D4B29AF6DADB16F841F19C54C00EB244CECA9C8F2D4839D54E5F91451C",
319   "parameters": "",
320   "previousHash": "00000FEDA61240DD125A26886FEB2E6995B52A94778C71224CAF8492FF257D47",
321   "previousIssuer": "2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ",
322   "inner_hash": "6B27ACDA51F416449E5A61FC69438F8974D11FC27EB7A992410C276FC0B9BA5F",
323   "dividend": null,
324   "identities": [],
325   "joiners": [],
326   "actives": [],
327   "leavers": [],
328   "revoked": [],
329   "excluded": [],
330   "certifications": [],
331   "transactions": [
332    {
333     "version": 10,
334     "currency": "g1",
335     "locktime": 0,
336     "blockstamp": "50-00001DAA4559FEDB8320D1040B0F22B631459F36F237A0D9BC1EB923C12A12E7",
337     "blockstampTime": 1488990016,
338     "issuers": [
339      "2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ"
340     ],
341     "inputs": [
342      "1000:0:D:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:1"
343     ],
344     "outputs": [
345      "1:0:SIG(Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm)",
346      "999:0:SIG(2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ)"
347     ],
348     "unlocks": [
349      "0:SIG(0)"
350     ],
351     "signatures": [
352      "fAH5Gor+8MtFzQZ++JaJO6U8JJ6+rkqKtPrRr/iufh3MYkoDGxmjzj6jCADQL+hkWBt8y8QzlgRkz0ixBcKHBw=="
353     ],
354     "comment": "TEST",
355     "block_number": 0,
356     "time": 0
357    }
358   ],
359   "raw": "Version: 10\nType: Block\nCurrency: g1\nNumber: 52\nPoWMin: 74\nTime: 1488990898\nMedianTime: 1488990117\nUnitBase: 0\nIssuer: 2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ\nIssuersFrame: 6\nIssuersFrameVar: 0\nDifferentIssuersCount: 1\nPreviousHash: 00000FEDA61240DD125A26886FEB2E6995B52A94778C71224CAF8492FF257D47\nPreviousIssuer: 2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ\nMembersCount: 59\nIdentities:\nJoiners:\nActives:\nLeavers:\nRevoked:\nExcluded:\nCertifications:\nTransactions:\nTX:10:1:1:1:2:1:0\n50-00001DAA4559FEDB8320D1040B0F22B631459F36F237A0D9BC1EB923C12A12E7\n2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ\n1000:0:D:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:1\n0:SIG(0)\n1:0:SIG(Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm)\n999:0:SIG(2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ)\nTEST\nfAH5Gor+8MtFzQZ++JaJO6U8JJ6+rkqKtPrRr/iufh3MYkoDGxmjzj6jCADQL+hkWBt8y8QzlgRkz0ixBcKHBw==\nInnerHash: 6B27ACDA51F416449E5A61FC69438F8974D11FC27EB7A992410C276FC0B9BA5F\nNonce: 10100000033688\n"
360  }"#;
361
362        let block_json_value = unwrap!(json_pest_parser::parse_json_string(block_json_str));
363
364        let expected_block_content = DubpBlockV10Content {
365            version: 10,
366            number: BlockNumber(52),
367            pow_min: 74,
368            time: 1_488_990_898,
369            median_time: 1_488_990_117,
370            members_count: 59,
371            monetary_mass: 59000,
372            unit_base: 0,
373            issuers_count: 1,
374            issuers_frame: 6,
375            issuers_frame_var: 0,
376            currency: CurrencyName("g1".to_owned()),
377            issuer: pk("2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ"),
378            parameters: None,
379            previous_hash: unwrap!(Hash::from_hex(
380                "00000FEDA61240DD125A26886FEB2E6995B52A94778C71224CAF8492FF257D47",
381            )),
382            previous_issuer: pk("2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ"),
383            dividend: None,
384            identities: vec![],
385            joiners: vec![],
386            actives: vec![],
387            leavers: vec![],
388            revoked: vec![],
389            excluded: vec![],
390            certifications: vec![],
391            transactions: vec![match first_g1_tx_doc() {
392                TransactionDocument::V10(tx_doc) => tx_doc,
393            }],
394        };
395        let expected_block = DubpBlock::V10(DubpBlockV10Builder::new(expected_block_content).build_unchecked(DubpBlockV10AfterPowData {
396            nonce: 10_100_000_033_688,
397            signature: unwrap!(ed25519::Signature::from_base64("4/UIwXzWQekbYw7fpD8ueMH4GnDEwCM+DvDaTfquBXOvFXLRYo/S+Vrk5u7so/98gYaZ2O7Myh20xgQvhh5FDQ==")),
398            hash: BlockHash(
399                unwrap!(Hash::from_hex(
400                    "000057D4B29AF6DADB16F841F19C54C00EB244CECA9C8F2D4839D54E5F91451C"
401                ))
402            ),
403        }));
404        assert_eq!(
405            expected_block.inner_hash(),
406            unwrap!(Hash::from_hex(
407                "6B27ACDA51F416449E5A61FC69438F8974D11FC27EB7A992410C276FC0B9BA5F"
408            ))
409        );
410
411        assert_eq!(expected_block, unwrap!(parse_json_block(&block_json_value)));
412
413        expected_block.verify_inner_hash()?;
414        Ok(())
415    }
416
417    fn first_g1_tx_doc() -> TransactionDocument {
418        let expected_tx_builder = TransactionDocumentV10Builder {
419            currency: &"g1",
420            blockstamp: unwrap!(Blockstamp::from_str(
421                "50-00001DAA4559FEDB8320D1040B0F22B631459F36F237A0D9BC1EB923C12A12E7",
422            )),
423            locktime: 0,
424            issuers: smallvec![pk("2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ")],
425            inputs: &[TransactionInputV10 {
426                amount: SourceAmount::with_base0(1_000),
427                id: SourceIdV10::Ud(UdSourceIdV10 {
428                    block_number: BlockNumber(1),
429                    issuer: pk("2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ"),
430                }),
431            }],
432            unlocks: &[TransactionInputUnlocksV10::default()],
433            outputs: smallvec![
434                TransactionOutputV10 {
435                    amount: SourceAmount::with_base0(1),
436                    conditions: UTXOConditions::from(WalletScriptV10::single(
437                        WalletConditionV10::Sig(pk("Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm"))
438                    ))
439                },
440                TransactionOutputV10 {
441                    amount: SourceAmount::with_base0(999),
442                    conditions: UTXOConditions::from(WalletScriptV10::single(
443                        WalletConditionV10::Sig(pk("2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ"))
444                    ))
445                },
446            ],
447            comment: "TEST",
448            hash: None,
449        };
450
451        TransactionDocumentBuilder::V10(expected_tx_builder).build_with_signature(smallvec![Sig::Ed25519(
452            unwrap!(ed25519::Signature::from_base64("fAH5Gor+8MtFzQZ++JaJO6U8JJ6+rkqKtPrRr/iufh3MYkoDGxmjzj6jCADQL+hkWBt8y8QzlgRkz0ixBcKHBw=="))
453        )])
454    }
455}