1use crate::*;
19use json_pest_parser::*;
20use std::num::TryFromIntError;
21
22pub 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")]
27pub struct JsonValueConversionError;
29
30pub 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}