eosio_client_api/
json_rpc.rs

1use crate::api_types::*;
2use crate::errors::{Error, ErrorKind, Result};
3use crate::wallet_types::Wallet;
4use crate::wasm::WASM;
5use eosio_client_keys::EOSPublicKey;
6use libabieos_sys::{AbiFiles, ABIEOS, vec_u8_to_hex};
7use reqwest::header::{HeaderValue, CONTENT_TYPE};
8use reqwest::Client;
9use reqwest::StatusCode;
10use serde_json::Value;
11//use rust_embed::RustEmbed;
12
13use chrono::{DateTime, Utc};
14
15pub const ERROR_TXN_SET_EXACT_CODE: usize = 3_160_008;
16
17pub struct EOSRPC {
18    pub client: Client,
19    pub host: String,
20    pub abi_abi_js: String,
21    pub transaction_abi_js: String,
22}
23
24impl EOSRPC {
25    pub async fn non_blocking(host: String) -> Result<EOSRPC> {
26        let client = reqwest::Client::new();
27        let abi_f = AbiFiles::get("abi_rv.abi.json").unwrap();
28        let abi_abi_js: String = String::from_utf8(abi_f.as_ref().to_vec())?;
29        let transaction_abi_js: String = String::from_utf8(
30            AbiFiles::get("transaction.abi.json")
31                .unwrap()
32                .as_ref()
33                .to_vec(),
34        )?;
35        Ok(EOSRPC {
36            client,
37            host,
38            abi_abi_js,
39            transaction_abi_js,
40        })
41    }
42
43    pub async fn non_blocking_ex(
44        host: String,
45        abi_abi_js: &str,
46        transaction_abi_js: &str,
47    ) -> EOSRPC {
48        let client = reqwest::Client::new();
49        EOSRPC {
50            client,
51            host,
52            abi_abi_js: abi_abi_js.to_string(),
53            transaction_abi_js: transaction_abi_js.to_string(),
54        }
55    }
56
57    pub async fn non_blocking_req(&self, url: &str, in_json: Value) -> Result<String> {
58        let full_url = [&self.host, url].concat();
59        let req = self.client.post(&full_url).json(&in_json);
60        let response = req.send().await?;
61        let content_type = response.headers().get(CONTENT_TYPE).unwrap();
62        let hv_json = HeaderValue::from_static("application/json");
63        if content_type != hv_json {
64            return Err(ErrorKind::InvalidResponseContentType.into());
65        }
66        let status = response.status();
67
68        if status == StatusCode::OK
69            || status == StatusCode::CREATED
70            || status == StatusCode::ACCEPTED
71        {
72            Ok(response.text().await?)
73        } else {
74            let tx: &str = &response.text().await?;
75            let error_reply: serde_json::Result<ErrorReply> = serde_json::from_str(tx);
76            match error_reply {
77                Ok (e) => {
78                    Err(ErrorKind::InvalidResponseStatus(e.error).into())
79                },
80                _ => {
81                    Err(ErrorKind::InvalidResponseErr(tx.to_string()).into())
82                }
83            }
84
85
86        }
87    }
88
89    pub async fn get_account(&self, account_name: &str) -> Result<GetAccount> {
90        let value = serde_json::json!({ "account_name": account_name });
91        let res = self
92            .non_blocking_req("/v1/chain/get_account", value)
93            .await?;
94        let ga: GetAccount = serde_json::from_str(&res).unwrap();
95        Ok(ga)
96    }
97    pub async fn get_abi(&self, account_name: &str) -> Result<GetAbi> {
98        let value = serde_json::json!({ "account_name": account_name });
99        let res = self.non_blocking_req("/v1/chain/get_abi", value).await?;
100        let ga: GetAbi = serde_json::from_str(&res).unwrap();
101        Ok(ga)
102    }
103
104    pub async fn get_info(&self) -> Result<GetInfo> {
105        let value = serde_json::json!({});
106        let res = self.non_blocking_req("/v1/chain/get_info", value).await?;
107        let ga: GetInfo = serde_json::from_str(&res).unwrap();
108        Ok(ga)
109    }
110
111    pub async fn get_code_hash(&self, account_name: &str) -> Result<GetCodeHash> {
112        let value = serde_json::json!({ "account_name": account_name });
113        let res = self
114            .non_blocking_req("/v1/chain/get_code_hash", value)
115            .await?;
116        let gc: GetCodeHash = serde_json::from_str(&res)?;
117        Ok(gc)
118    }
119
120    pub async fn get_raw_abi(&self, account_name: &str) -> Result<GetRawABI> {
121        let value = serde_json::json!({ "account_name": account_name });
122
123        let res = self
124            .non_blocking_req("/v1/chain/get_raw_abi", value)
125            .await?;
126        let gr: GetRawABI = serde_json::from_str(&res)?;
127        Ok(gr)
128    }
129    pub async fn get_block_num(&self, block_num: usize) -> Result<GetBlock> {
130        let value = serde_json::json!({ "block_num_or_id": block_num });
131
132        let res = self.non_blocking_req("/v1/chain/get_block", value).await?;
133        let gb: GetBlock = serde_json::from_str(&res)?;
134        Ok(gb)
135    }
136    pub async fn get_block_id(&self, block_id: &str) -> Result<GetBlock> {
137        let value = serde_json::json!({ "block_num_or_id": block_id });
138
139        let res = self.non_blocking_req("/v1/chain/get_block", value).await?;
140        let gb: GetBlock = serde_json::from_str(&res)?;
141        Ok(gb)
142    }
143
144    pub async fn get_required_keys(
145        &self,
146        transaction: &TransactionIn,
147        keys: Vec<EOSPublicKey>,
148    ) -> Result<RequiredKeys> {
149        let mut key_str: Vec<String> = vec![];
150        for key in keys {
151            let x = key.to_eos_string()?;
152            key_str.push(x);
153        }
154
155        let value = serde_json::json!({ "transaction": transaction, "available_keys":key_str});
156        let res = self
157            .non_blocking_req("/v1/chain/get_required_keys", value)
158            .await?;
159        let rk: RequiredKeys = serde_json::from_str(&res).unwrap();
160        Ok(rk)
161    }
162
163    pub async fn get_abi_from_account(
164        &self,
165        abieos_eosio: &ABIEOS,
166        account_name: &str,
167    ) -> Result<ABIEOS> {
168        let rawabi_r = self.get_raw_abi(account_name).await;
169        let account_abi = rawabi_r?.decode_abi()?;
170
171        let account_abi_json = abieos_eosio.bin_to_json("eosio", "abi_def", &account_abi)?;
172        Ok(ABIEOS::new_with_abi(account_name, &account_abi_json)?)
173    }
174
175    pub async fn push_transaction(
176        &self,
177        abieos: &ABIEOS,
178        wallet: &Wallet,
179        actions: Vec<ActionIn>,
180        ref_block: &str,
181        exp_time: DateTime<Utc>,
182    ) -> Result<TransactionResponse> {
183        let ti = TransactionIn::simple(actions, ref_block, exp_time)?;
184
185        let trx_json = serde_json::to_string(&ti)?;
186        let trx = abieos.json_to_hex("eosio", "transaction", &trx_json)?;
187
188        let pubkeys = wallet.keys().await?;
189        let required_keys = self.get_required_keys(&ti, pubkeys).await?;
190        let eospubs: Vec<EOSPublicKey> =
191            EOSPublicKey::from_eos_strings(&required_keys.required_keys)?;
192
193        let signed_transaction = wallet.sign_transaction(ti, eospubs).await?;
194        let pti = PackedTransactionIn {
195            signatures: signed_transaction.signatures,
196            compression: "none".to_string(),
197            packed_context_free_data: "".to_string(),
198            packed_trx: trx.to_string(),
199        };
200
201        let in_val = serde_json::json!(pti);
202        let res = self
203            .non_blocking_req("/v1/chain/push_transaction", in_val)
204            .await?;
205        let tr: TransactionResponse = serde_json::from_str(&res).unwrap();
206        Ok(tr)
207    }
208
209    pub async fn get_table_rows(
210        &self,
211        code: &str,
212        scope: &str,
213        table: &str,
214        table_key: &str,
215        lower_bound: &str,
216        upper_bound: &str,
217        limit: usize,
218        key_type: &str,
219        index_position: &str,
220        encode_type: &str,
221        reverse: bool,
222        show_payer: bool,
223    ) -> Result<GetTableRows> {
224        let in_j = GetTableRowsIn {
225            json: false,
226            code: code.parse().unwrap(),
227            scope: scope.parse().unwrap(),
228            table: table.parse().unwrap(),
229            table_key: table_key.parse().unwrap(),
230            lower_bound: lower_bound.parse().unwrap(),
231            upper_bound: upper_bound.parse().unwrap(),
232            limit,
233            key_type: key_type.parse().unwrap(),
234            index_position: index_position.parse().unwrap(),
235            encode_type: encode_type.parse().unwrap(),
236            reverse,
237            show_payer,
238        };
239        let in_val = serde_json::json!(in_j);
240        let res = self
241            .non_blocking_req("/v1/chain/get_table_rows", in_val)
242            .await?;
243        let tr: GetTableRows = serde_json::from_str(&res).unwrap();
244        Ok(tr)
245    }
246    pub async fn get_table_by_scope(
247        &self,
248        code: &str,
249        table: &str,
250        lower_bound: &str,
251        upper_bound: &str,
252        limit: usize,
253        reverse: bool,
254    ) -> Result<GetTableByScope> {
255        let pti = GetTableByScopeIn {
256            code: code.parse().unwrap(),
257            table: table.parse().unwrap(),
258            lower_bound: lower_bound.parse().unwrap(),
259            upper_bound: upper_bound.parse().unwrap(),
260            limit,
261            reverse,
262        };
263        let in_val = serde_json::json!(pti);
264        let res = self
265            .non_blocking_req("/v1/chain/get_table_by_scope", in_val)
266            .await?;
267        let tr: GetTableByScope = serde_json::from_str(&res).unwrap();
268        Ok(tr)
269    }
270}
271
272pub fn create_setcode_action(acct_abieos: &ABIEOS, name: &str, code: &WASM) -> Result<ActionIn> {
273    let auth = AuthorizationIn {
274        permission: "active".to_string(),
275        actor: String::from(name),
276    };
277    let v_auth: Vec<AuthorizationIn> = vec![auth];
278    let data = ActionSetcodeData {
279        account: String::from(name),
280        vmtype: 0,
281        vmversion: 0,
282        code: vec_u8_to_hex(&code.code)?,
283    }
284    .to_hex(acct_abieos)?;
285
286    Ok(ActionIn {
287        name: "setcode".to_string(),
288        account: "eosio".to_string(),
289        authorization: v_auth,
290        data,
291    })
292}
293
294pub fn create_setcode_clear_action(acct_abieos: &ABIEOS, name: &str) -> Result<ActionIn> {
295    let auth = AuthorizationIn {
296        permission: "active".to_string(),
297        actor: String::from(name),
298    };
299    let v_auth: Vec<AuthorizationIn> = vec![auth];
300    let data = ActionSetcodeData {
301        account: String::from(name),
302        vmtype: 0,
303        vmversion: 0,
304        code: vec_u8_to_hex(&WASM::dummy())?,
305    }
306    .to_hex(acct_abieos)?;
307
308    Ok(ActionIn {
309        name: "setcode".to_string(),
310        account: "eosio".to_string(),
311        authorization: v_auth,
312        data,
313    })
314}
315
316pub struct AbiTrio {
317    pub sys_abi: ABIEOS,
318    pub txn_abi: ABIEOS,
319    pub acct_abi: ABIEOS,
320}
321
322impl AbiTrio {
323    pub async fn create(sys_name: &str, sys_acct_name: &str, eos: &EOSRPC) -> Result<AbiTrio> {
324        let sys_abi = ABIEOS::new_with_abi(sys_name, &eos.abi_abi_js)?;
325        let txn_abi: ABIEOS =
326            ABIEOS::new_with_abi(sys_name, &eos.transaction_abi_js).map_err(|e| {
327                sys_abi.destroy();
328                Error::with_chain(e, "AbiTrio_txn")
329            })?;
330        let acct_abi: ABIEOS = eos
331            .get_abi_from_account(&sys_abi, sys_acct_name)
332            .await
333            .map_err(|e| {
334                sys_abi.destroy();
335                txn_abi.destroy();
336                Error::with_chain(e, "AbiTrio_act")
337            })?;
338
339        Ok(AbiTrio {
340            sys_abi,
341            txn_abi,
342            acct_abi,
343        })
344    }
345    pub fn destroy(&self) {
346        self.acct_abi.destroy();
347        self.txn_abi.destroy();
348        self.sys_abi.destroy()
349    }
350}
351
352pub fn create_setabi_action(
353    sys_abieos: &ABIEOS,
354    acct_abieos: &ABIEOS,
355    name: &str,
356    abi: &str,
357) -> Result<ActionIn> {
358    let auth = AuthorizationIn {
359        permission: "active".to_string(),
360        actor: String::from(name),
361    };
362    let v_auth: Vec<AuthorizationIn> = vec![auth];
363    let abi_hex = sys_abieos.json_to_hex("eosio", "abi_def", abi)?;
364    // let abi_s = String::from(abi);
365    let data = ActionSetData {
366        account: String::from(name),
367        abi: String::from(abi_hex),
368    }
369    .to_hex(acct_abieos)?;
370
371    Ok(ActionIn {
372        name: "setabi".to_string(),
373        account: "eosio".to_string(),
374        data,
375        authorization: v_auth,
376    })
377}
378
379#[cfg(test)]
380mod test {
381    use super::*;
382
383    //use crate::api_types::GetAccount;
384    use crate::wallet_types::{get_wallet_pass, EOSIO_CHAIN_ID};
385    use chrono::{Duration, NaiveDateTime};
386    use std::fs;
387
388    //const TEST_HOST: &str = "http://127.0.0.1:8888";
389    //const TEST_HOST: &str = "http://tempest.local:8888";
390    const TEST_HOST: &str = "https://api.testnet.eos.io";
391    const TEST_KEOSD: &str = "http://127.0.0.1:3888";
392
393    const TEST_WALLET_NAME: &str = "default";
394    const TEST_ACCOUNT_NAME: &str = "fwonhjnefmps";
395    const TEST_ACCOUNT_NO_ABI: &str = "tafoacvsqlmw";
396
397    //const TEST_HOST: &str = "https://eos.greymass.com";
398    //const TEST_HOST: &str = "https://chain.wax.io";
399
400    #[tokio::test]
401    async fn non_blocking_req_test() -> Result<()> {
402        let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
403        let _ga = eos.get_account("eosio").await?;
404
405        let _abi = eos.get_abi("eosio").await?;
406        Ok(())
407    }
408
409    #[tokio::test]
410    async fn non_blocking_get_info() -> Result<()> {
411        let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
412        let _gi = eos.get_info().await?;
413        Ok(())
414    }
415
416    #[test]
417    fn datetime_format() {
418        let s = "2020-05-16T05:12:03";
419        const FORMAT: &'static str = "%Y-%m-%dT%H:%M:%S";
420        // let _tz_offset = FixedOffset::east(0);
421        match NaiveDateTime::parse_from_str(s, FORMAT) {
422            Err(_e) => {
423                eprintln!("{:#?}", _e);
424                assert!(false)
425            }
426            Ok(_dt) => assert!(true),
427        }
428    }
429
430    #[tokio::test]
431    async fn non_blocking_get_required_keys() -> Result<()> {
432        let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
433        let keys = vec![
434            EOSPublicKey::from_eos_string("EOS6zUgp7uAV1pCTXZMGJyH3dLUSWJUkZWGA9WpWxyP2pCT3mAkNX")
435                .unwrap(),
436            EOSPublicKey::from_eos_string("EOS7ctUUZhtCGHnxUnh4Rg5eethj3qNS5S9fijyLMKgRsBLh8eMBB")
437                .unwrap(),
438            EOSPublicKey::from_eos_string("EOS8fdsPr1aKsmszNHeY4RrgupbabNQ5nmLgQWMEkTn2dENrPbRgP")
439                .unwrap(),
440        ];
441        let gi: GetInfo = eos.get_info().await?;
442        let exp_time = gi.set_exp_time(Duration::seconds(1800));
443
444        let wasm = WASM::read_file("test/good-2.wasm")?;
445
446        let name = TEST_ACCOUNT_NAME;
447
448        let abi_trio = AbiTrio::create("eosio", "eosio", &eos).await?;
449        let action_r = create_setcode_action(&abi_trio.acct_abi, &name, &wasm);
450        abi_trio.destroy();
451
452        let ti = TransactionIn::simple(vec![action_r?], &gi.last_irreversible_block_id, exp_time)?;
453        let rk = eos.get_required_keys(&ti, keys).await?;
454        assert!(rk.required_keys.len() > 0);
455        let k = &rk.required_keys[0];
456
457        // accidentally set one of chains to have 'owner' key instead of 'active'
458        if k == "EOS7ctUUZhtCGHnxUnh4Rg5eethj3qNS5S9fijyLMKgRsBLh8eMBB" {
459            ()
460        } else {
461            assert_eq!(k, "EOS8fdsPr1aKsmszNHeY4RrgupbabNQ5nmLgQWMEkTn2dENrPbRgP");
462        }
463        Ok(())
464    }
465
466    /// these two need some static thing which will exist over all test environments
467    /// the TicTacToe example deletes ALL of it's data on successful completion, so can't really be
468    /// used
469    ///
470    #[tokio::test]
471    async fn non_blocking_table_rows() -> Result<()> {
472        let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
473        let _r = eos
474            .get_table_rows(
475                TEST_ACCOUNT_NAME,
476                "tictactoe",
477                "games",
478                "",
479                "",
480                "",
481                10,
482                "",
483                "",
484                "dec",
485                false,
486                true,
487            )
488            .await;
489        Ok(())
490    }
491
492    /// these two need some static thing which will exist over all test environments
493    /// the TicTacToe example deletes ALL of it's data on successful completion, so can't really be
494    /// used
495    ///
496    #[tokio::test]
497    async fn non_blocking_table_by_scope() -> Result<()> {
498        let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
499        let _r = eos
500            .get_table_by_scope("eosio.token", "", TEST_ACCOUNT_NAME, "", 10, false)
501            .await;
502        Ok(())
503    }
504
505    #[tokio::test]
506    async fn blocking_push_txn() -> Result<()> {
507        let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
508        let wallet = Wallet::create_with_chain_id(
509            EOSRPC::non_blocking(String::from(TEST_KEOSD)).await?,
510            EOSIO_CHAIN_ID,
511        );
512        let wallet_pass = get_wallet_pass()?;
513        wallet.unlock(&TEST_WALLET_NAME, &wallet_pass).await?;
514        let wasm = WASM::read_file("test/good-2.wasm")?;
515        let wasm_abi = fs::read_to_string("test/good-2.abi")?;
516
517        let name = TEST_ACCOUNT_NAME;
518
519        let gi: GetInfo = eos.get_info().await?;
520        let exp_time = gi.set_exp_time(Duration::seconds(1800));
521        let abi_trio = AbiTrio::create("eosio", "eosio", &eos).await?;
522
523        let action_clear = create_setcode_clear_action(&abi_trio.acct_abi, &name).map_err(|e| {
524            abi_trio.destroy();
525            Error::with_chain(e, "blocking_push_txn/create_setcode_clear_action")
526        })?;
527
528        let _res_clear_int = eos
529            .push_transaction(
530                &abi_trio.txn_abi,
531                &wallet,
532                vec![action_clear],
533                &gi.head_block_id,
534                exp_time,
535            )
536            .await
537            .map_err(|e| {
538                //   abi_trio.destroy();
539                Error::with_chain(e, "blocking_push_txn/push_transaction(clear)")
540            });
541        if _res_clear_int.is_err() {
542            eprintln!(
543                "Ignoring error for clearing contract - {:#?}",
544                _res_clear_int.err().unwrap()
545            )
546        }
547
548        let action = create_setcode_action(&abi_trio.acct_abi, &name, &wasm).map_err(|e| {
549            abi_trio.destroy();
550            Error::with_chain(e, "blocking_push_txn/create_setcode_action")
551        })?;
552        let action_abi =
553            create_setabi_action(&abi_trio.sys_abi, &abi_trio.acct_abi, &name, &wasm_abi).map_err(
554                |e| {
555                    abi_trio.destroy();
556                    Error::with_chain(e, "blocking_push_txn/create_setabi_action")
557                },
558            )?;
559
560        let _res_int = eos
561            .push_transaction(
562                &abi_trio.txn_abi,
563                &wallet,
564                vec![action, action_abi],
565                &gi.head_block_id,
566                exp_time,
567            )
568            .await
569            .map_err(|e| {
570                abi_trio.destroy();
571                Error::with_chain(e, "blocking_push_txn/push_transaction(set-code/abi)")
572            })?;
573
574        abi_trio.destroy();
575        Ok(())
576    }
577
578    #[tokio::test]
579    async fn non_blocking_get_raw_abi() -> Result<()> {
580        let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
581        let _res = eos.get_raw_abi("eosio").await?;
582
583        Ok(())
584    }
585
586    #[tokio::test]
587    async fn non_blocking_getsetabi() -> Result<()> {
588        let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
589        let wasm_abi = fs::read_to_string("test/good-2.abi")?;
590        let wallet = Wallet::create_with_chain_id(
591            EOSRPC::non_blocking(String::from(TEST_KEOSD)).await?,
592            EOSIO_CHAIN_ID,
593        );
594        let wallet_pass = get_wallet_pass()?;
595        wallet.unlock(&TEST_WALLET_NAME, &wallet_pass).await?;
596        let gi = eos.get_info().await?;
597        let exp_time = gi.set_exp_time(Duration::seconds(1800));
598
599        let name = TEST_ACCOUNT_NAME;
600        let trio = AbiTrio::create("eosio", "eosio", &eos).await?;
601
602        let action_abi = create_setabi_action(&trio.sys_abi, &trio.acct_abi, &name, &wasm_abi)
603            .map_err(|e| {
604                &trio.destroy();
605                Error::with_chain(e, "create_setabi_action")
606            })?;
607
608        let _tr = eos
609            .push_transaction(
610                &trio.txn_abi,
611                &wallet,
612                vec![action_abi],
613                &gi.head_block_id,
614                exp_time,
615            )
616            .await
617            .map_err(|e| {
618                trio.destroy();
619                Error::with_chain(e, "push_transaction")
620            })?;
621        trio.destroy();
622
623        // if the abi is written incorrectly this will cause a server error
624        let _get_abi = eos.get_abi(name).await?;
625
626        Ok(())
627    }
628
629    #[tokio::test]
630    async fn non_block_getabi() -> Result<()> {
631        let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
632        let get_abi = eos.get_abi(TEST_ACCOUNT_NO_ABI).await?;
633        assert!(get_abi.abi.is_none());
634        let get_abi = eos.get_abi(TEST_ACCOUNT_NAME).await?;
635        assert!(get_abi.abi.is_some());
636        Ok(())
637    }
638    #[tokio::test]
639    async fn non_block_getblock() -> Result<()> {
640        let eos = EOSRPC::non_blocking(String::from(TEST_HOST)).await?;
641        let block = eos.get_block_num(1).await?;
642        let block2 = eos.get_block_id(&block.id).await?;
643        assert_eq!(block.block_num, block2.block_num);
644        Ok(())
645    }
646}