Skip to main content

dubp_documents_parser/json/
transactions.rs

1//  Copyright (C) 2020  Éloïs SANCHEZ.
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
16use crate::*;
17use json_pest_parser::*;
18
19#[derive(Debug, Error)]
20pub enum ParseJsonTxError {
21    #[error("wrong blockstamp : {0}")]
22    Blockstamp(BlockstampParseError),
23    #[error("wrong hash : {0}")]
24    Hash(BaseConversionError),
25    #[error("wrong issuer : {0}")]
26    Issuer(BaseConversionError),
27    #[error("wrong input : {0}")]
28    Input(TextParseError),
29    #[error("wrong unlock : {0}")]
30    Unlock(TextParseError),
31    #[error("wrong output : {0}")]
32    Output(TextParseError),
33    #[error("wrong sig : {0}")]
34    Sig(BaseConversionError),
35    #[error("json error: {0}")]
36    JsonErr(ParseJsonError),
37    #[error("wrong format")]
38    WrongFormat,
39}
40
41impl From<ParseJsonError> for ParseJsonTxError {
42    fn from(e: ParseJsonError) -> Self {
43        ParseJsonTxError::JsonErr(e)
44    }
45}
46
47/// Parse transactions documents from array of str
48pub fn parse_json_transactions(
49    array_transactions: &[&JSONValue<DefaultHasher>],
50) -> Result<Vec<TransactionDocumentV10>, ParseJsonTxError> {
51    array_transactions
52        .iter()
53        .map(|tx| {
54            parse_json_transaction(tx).map(|tx_doc| match tx_doc {
55                TransactionDocument::V10(tx_doc_v10) => tx_doc_v10,
56            })
57        })
58        .collect::<Result<Vec<TransactionDocumentV10>, ParseJsonTxError>>()
59}
60
61/// Parse transaction from json value
62fn parse_json_transaction(
63    json_tx: &JSONValue<DefaultHasher>,
64) -> Result<TransactionDocument, ParseJsonTxError> {
65    let json_tx = if let JSONValue::Object(json_tx) = json_tx {
66        json_tx
67    } else {
68        return Err(ParseJsonError {
69            cause: "Json transaction must be an object !".to_owned(),
70        }
71        .into());
72    };
73
74    match get_u64(json_tx, "version")? {
75        10 => Ok(
76            TransactionDocumentBuilder::V10(TransactionDocumentV10Builder {
77                currency: get_str(json_tx, "currency")?,
78                blockstamp: Blockstamp::from_str(get_str(json_tx, "blockstamp")?)
79                    .map_err(ParseJsonTxError::Blockstamp)?,
80                locktime: (get_number(json_tx, "locktime")?.trunc() as u64),
81                issuers: get_str_array(json_tx, "issuers")?
82                    .iter()
83                    .map(|p| ed25519::PublicKey::from_base58(p))
84                    .collect::<Result<SmallVec<_>, BaseConversionError>>()
85                    .map_err(ParseJsonTxError::Issuer)?,
86                inputs: &get_str_array(json_tx, "inputs")?
87                    .iter()
88                    .map(|i| tx_input_v10_from_str(i))
89                    .collect::<Result<Vec<TransactionInputV10>, TextParseError>>()
90                    .map_err(ParseJsonTxError::Input)?[..],
91                unlocks: &get_str_array(json_tx, "unlocks")?
92                    .iter()
93                    .map(|i| tx_unlock_v10_from_str(i))
94                    .collect::<Result<Vec<TransactionInputUnlocksV10>, TextParseError>>()
95                    .map_err(ParseJsonTxError::Unlock)?[..],
96                outputs: get_str_array(json_tx, "outputs")?
97                    .iter()
98                    .map(|i| tx_output_v10_from_str(i))
99                    .collect::<Result<SmallVec<_>, TextParseError>>()
100                    .map_err(ParseJsonTxError::Output)?,
101                comment: &unescape_str(get_str(json_tx, "comment")?),
102                hash: get_optional_str(json_tx, "hash")?
103                    .map(Hash::from_hex)
104                    .transpose()
105                    .map_err(ParseJsonTxError::Hash)?,
106            })
107            .build_with_signature(
108                get_str_array(json_tx, "signatures")?
109                    .iter()
110                    .map(|p| ed25519::Signature::from_base64(p))
111                    .map(|p| p.map(Sig::Ed25519))
112                    .collect::<Result<SmallVec<[Sig; 1]>, BaseConversionError>>()
113                    .map_err(ParseJsonTxError::Sig)?,
114            ),
115        ),
116        version => Err(ParseJsonError {
117            cause: format!("Unhandled json transaction version: {} !", version),
118        }
119        .into()),
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use dubp_documents::smallvec::smallvec;
127    use unwrap::unwrap;
128
129    pub fn first_g1_tx_doc() -> TransactionDocument {
130        let expected_tx_builder = TransactionDocumentV10Builder {
131            currency: &"g1",
132            blockstamp: unwrap!(Blockstamp::from_str(
133                "50-00001DAA4559FEDB8320D1040B0F22B631459F36F237A0D9BC1EB923C12A12E7",
134            )),
135            locktime: 0,
136            issuers: svec![unwrap!(ed25519::PublicKey::from_base58(
137                "2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ",
138            ))],
139            inputs: &[unwrap!(tx_input_v10_from_str(
140                "1000:0:D:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:1",
141            ))],
142            unlocks: &[unwrap!(tx_unlock_v10_from_str("0:SIG(0)"))],
143            outputs: smallvec![
144                unwrap!(tx_output_v10_from_str(
145                    "1:0:SIG(Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm)",
146                )),
147                unwrap!(tx_output_v10_from_str(
148                    "999:0:SIG(2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ)"
149                )),
150            ],
151            comment: "TEST",
152            hash: None,
153        };
154
155        TransactionDocumentBuilder::V10(expected_tx_builder).build_with_signature(svec![Sig::Ed25519(
156            unwrap!(ed25519::Signature::from_base64("fAH5Gor+8MtFzQZ++JaJO6U8JJ6+rkqKtPrRr/iufh3MYkoDGxmjzj6jCADQL+hkWBt8y8QzlgRkz0ixBcKHBw=="))
157        )])
158    }
159
160    #[test]
161    fn test_parse_json_tx() {
162        let tx_json_str = r#"{
163     "version": 10,
164     "currency": "g1",
165     "locktime": 0,
166     "blockstamp": "50-00001DAA4559FEDB8320D1040B0F22B631459F36F237A0D9BC1EB923C12A12E7",
167     "blockstampTime": 1488990016,
168     "issuers": [
169      "2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ"
170     ],
171     "inputs": [
172      "1000:0:D:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:1"
173     ],
174     "outputs": [
175      "1:0:SIG(Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm)",
176      "999:0:SIG(2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ)"
177     ],
178     "unlocks": [
179      "0:SIG(0)"
180     ],
181     "signatures": [
182      "fAH5Gor+8MtFzQZ++JaJO6U8JJ6+rkqKtPrRr/iufh3MYkoDGxmjzj6jCADQL+hkWBt8y8QzlgRkz0ixBcKHBw=="
183     ],
184     "comment": "TEST",
185     "block_number": 0,
186     "time": 0
187    }"#;
188
189        let tx_json_value = unwrap!(json_pest_parser::parse_json_string(tx_json_str));
190
191        assert_eq!(
192            first_g1_tx_doc(),
193            unwrap!(parse_json_transaction(&tx_json_value))
194        );
195    }
196}