1use std::collections::HashMap;
12use std::fs::File;
13use std::io::{BufRead, BufReader};
14use std::iter::FromIterator;
15use std::path::PathBuf;
16use std::{fmt, result};
17
18use crate::{groestlcoin, deserialize_hex};
19use groestlcoin::hex::DisplayHex;
20use jsonrpc;
21use serde;
22use serde_json;
23
24use crate::groestlcoin::address::{NetworkUnchecked, NetworkChecked};
25use crate::groestlcoin::hashes::hex::FromHex;
26use crate::groestlcoin::secp256k1::ecdsa::Signature;
27use crate::groestlcoin::{
28 Address, Amount, Block, OutPoint, PrivateKey, PublicKey, Script, Transaction,
29};
30use log::Level::{Debug, Trace, Warn};
31
32use crate::error::*;
33use crate::json;
34use crate::queryable;
35
36pub type Result<T> = result::Result<T, Error>;
39
40#[derive(Clone, Debug, Serialize, Deserialize)]
43pub struct JsonOutPoint {
44 pub txid: groestlcoin::Txid,
45 pub vout: u32,
46}
47
48impl From<OutPoint> for JsonOutPoint {
49 fn from(o: OutPoint) -> JsonOutPoint {
50 JsonOutPoint {
51 txid: o.txid,
52 vout: o.vout,
53 }
54 }
55}
56
57impl Into<OutPoint> for JsonOutPoint {
58 fn into(self) -> OutPoint {
59 OutPoint {
60 txid: self.txid,
61 vout: self.vout,
62 }
63 }
64}
65
66fn into_json<T>(val: T) -> Result<serde_json::Value>
68where
69 T: serde::ser::Serialize,
70{
71 Ok(serde_json::to_value(val)?)
72}
73
74fn opt_into_json<T>(opt: Option<T>) -> Result<serde_json::Value>
76where
77 T: serde::ser::Serialize,
78{
79 match opt {
80 Some(val) => Ok(into_json(val)?),
81 None => Ok(serde_json::Value::Null),
82 }
83}
84
85fn null() -> serde_json::Value {
87 serde_json::Value::Null
88}
89
90fn empty_arr() -> serde_json::Value {
92 serde_json::Value::Array(vec![])
93}
94
95fn empty_obj() -> serde_json::Value {
97 serde_json::Value::Object(Default::default())
98}
99
100fn handle_defaults<'a, 'b>(
116 args: &'a mut [serde_json::Value],
117 defaults: &'b [serde_json::Value],
118) -> &'a [serde_json::Value] {
119 assert!(args.len() >= defaults.len());
120
121 let mut first_non_null_optional_idx = None;
124 for i in 0..defaults.len() {
125 let args_i = args.len() - 1 - i;
126 let defaults_i = defaults.len() - 1 - i;
127 if args[args_i] == serde_json::Value::Null {
128 if first_non_null_optional_idx.is_some() {
129 if defaults[defaults_i] == serde_json::Value::Null {
130 panic!("Missing `default` for argument idx {}", args_i);
131 }
132 args[args_i] = defaults[defaults_i].clone();
133 }
134 } else if first_non_null_optional_idx.is_none() {
135 first_non_null_optional_idx = Some(args_i);
136 }
137 }
138
139 let required_num = args.len() - defaults.len();
140
141 if let Some(i) = first_non_null_optional_idx {
142 &args[..i + 1]
143 } else {
144 &args[..required_num]
145 }
146}
147
148fn opt_result<T: for<'a> serde::de::Deserialize<'a>>(
150 result: serde_json::Value,
151) -> Result<Option<T>> {
152 if result == serde_json::Value::Null {
153 Ok(None)
154 } else {
155 Ok(serde_json::from_value(result)?)
156 }
157}
158
159pub trait RawTx: Sized + Clone {
161 fn raw_hex(self) -> String;
162}
163
164impl<'a> RawTx for &'a Transaction {
165 fn raw_hex(self) -> String {
166 groestlcoin::consensus::encode::serialize_hex(self)
167 }
168}
169
170impl<'a> RawTx for &'a [u8] {
171 fn raw_hex(self) -> String {
172 self.to_lower_hex_string()
173 }
174}
175
176impl<'a> RawTx for &'a Vec<u8> {
177 fn raw_hex(self) -> String {
178 self.to_lower_hex_string()
179 }
180}
181
182impl<'a> RawTx for &'a str {
183 fn raw_hex(self) -> String {
184 self.to_owned()
185 }
186}
187
188impl RawTx for String {
189 fn raw_hex(self) -> String {
190 self
191 }
192}
193
194#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
196pub enum Auth {
197 None,
198 UserPass(String, String),
199 CookieFile(PathBuf),
200}
201
202impl Auth {
203 pub fn get_user_pass(self) -> Result<(Option<String>, Option<String>)> {
205 match self {
206 Auth::None => Ok((None, None)),
207 Auth::UserPass(u, p) => Ok((Some(u), Some(p))),
208 Auth::CookieFile(path) => {
209 let line = BufReader::new(File::open(path)?)
210 .lines()
211 .next()
212 .ok_or(Error::InvalidCookieFile)??;
213 let colon = line.find(':').ok_or(Error::InvalidCookieFile)?;
214 Ok((Some(line[..colon].into()), Some(line[colon + 1..].into())))
215 }
216 }
217 }
218}
219
220pub trait RpcApi: Sized {
221 fn call<T: for<'a> serde::de::Deserialize<'a>>(
223 &self,
224 cmd: &str,
225 args: &[serde_json::Value],
226 ) -> Result<T>;
227
228 fn get_by_id<T: queryable::Queryable<Self>>(
230 &self,
231 id: &<T as queryable::Queryable<Self>>::Id,
232 ) -> Result<T> {
233 T::query(&self, &id)
234 }
235
236 fn get_network_info(&self) -> Result<json::GetNetworkInfoResult> {
237 self.call("getnetworkinfo", &[])
238 }
239
240 fn get_index_info(&self) -> Result<json::GetIndexInfoResult> {
241 self.call("getindexinfo", &[])
242 }
243
244 fn version(&self) -> Result<usize> {
245 #[derive(Deserialize)]
246 struct Response {
247 pub version: usize,
248 }
249 let res: Response = self.call("getnetworkinfo", &[])?;
250 Ok(res.version)
251 }
252
253 fn add_multisig_address(
254 &self,
255 nrequired: usize,
256 keys: &[json::PubKeyOrAddress],
257 label: Option<&str>,
258 address_type: Option<json::AddressType>,
259 ) -> Result<json::AddMultiSigAddressResult> {
260 let mut args = [
261 into_json(nrequired)?,
262 into_json(keys)?,
263 opt_into_json(label)?,
264 opt_into_json(address_type)?,
265 ];
266 self.call("addmultisigaddress", handle_defaults(&mut args, &[into_json("")?, null()]))
267 }
268
269 fn load_wallet(&self, wallet: &str) -> Result<json::LoadWalletResult> {
270 self.call("loadwallet", &[wallet.into()])
271 }
272
273 fn unload_wallet(&self, wallet: Option<&str>) -> Result<Option<json::UnloadWalletResult>> {
274 let mut args = [opt_into_json(wallet)?];
275 self.call("unloadwallet", handle_defaults(&mut args, &[null()]))
276 }
277
278 fn create_wallet(
279 &self,
280 wallet: &str,
281 disable_private_keys: Option<bool>,
282 blank: Option<bool>,
283 passphrase: Option<&str>,
284 avoid_reuse: Option<bool>,
285 ) -> Result<json::LoadWalletResult> {
286 let mut args = [
287 wallet.into(),
288 opt_into_json(disable_private_keys)?,
289 opt_into_json(blank)?,
290 opt_into_json(passphrase)?,
291 opt_into_json(avoid_reuse)?,
292 ];
293 self.call(
294 "createwallet",
295 handle_defaults(&mut args, &[false.into(), false.into(), into_json("")?, false.into()]),
296 )
297 }
298
299 fn list_wallets(&self) -> Result<Vec<String>> {
300 self.call("listwallets", &[])
301 }
302
303 fn list_wallet_dir(&self) -> Result<Vec<String>> {
304 let result: json::ListWalletDirResult = self.call("listwalletdir", &[])?;
305 let names = result.wallets.into_iter().map(|x| x.name).collect();
306 Ok(names)
307 }
308
309 fn get_wallet_info(&self) -> Result<json::GetWalletInfoResult> {
310 self.call("getwalletinfo", &[])
311 }
312
313 fn backup_wallet(&self, destination: Option<&str>) -> Result<()> {
314 let mut args = [opt_into_json(destination)?];
315 self.call("backupwallet", handle_defaults(&mut args, &[null()]))
316 }
317
318 fn dump_private_key(&self, address: &Address) -> Result<PrivateKey> {
319 self.call("dumpprivkey", &[address.to_string().into()])
320 }
321
322 fn encrypt_wallet(&self, passphrase: &str) -> Result<()> {
323 self.call("encryptwallet", &[into_json(passphrase)?])
324 }
325
326 fn get_difficulty(&self) -> Result<f64> {
327 self.call("getdifficulty", &[])
328 }
329
330 fn get_connection_count(&self) -> Result<usize> {
331 self.call("getconnectioncount", &[])
332 }
333
334 fn get_block(&self, hash: &groestlcoin::BlockHash) -> Result<Block> {
335 let hex: String = self.call("getblock", &[into_json(hash)?, 0.into()])?;
336 deserialize_hex(&hex)
337 }
338
339 fn get_block_hex(&self, hash: &groestlcoin::BlockHash) -> Result<String> {
340 self.call("getblock", &[into_json(hash)?, 0.into()])
341 }
342
343 fn get_block_info(&self, hash: &groestlcoin::BlockHash) -> Result<json::GetBlockResult> {
344 self.call("getblock", &[into_json(hash)?, 1.into()])
345 }
346 fn get_block_header(&self, hash: &groestlcoin::BlockHash) -> Result<groestlcoin::block::Header> {
349 let hex: String = self.call("getblockheader", &[into_json(hash)?, false.into()])?;
350 deserialize_hex(&hex)
351 }
352
353 fn get_block_header_info(
354 &self,
355 hash: &groestlcoin::BlockHash,
356 ) -> Result<json::GetBlockHeaderResult> {
357 self.call("getblockheader", &[into_json(hash)?, true.into()])
358 }
359
360 fn get_mining_info(&self) -> Result<json::GetMiningInfoResult> {
361 self.call("getmininginfo", &[])
362 }
363
364 fn get_block_template(
365 &self,
366 mode: json::GetBlockTemplateModes,
367 rules: &[json::GetBlockTemplateRules],
368 capabilities: &[json::GetBlockTemplateCapabilities],
369 ) -> Result<json::GetBlockTemplateResult> {
370 #[derive(Serialize)]
371 struct Argument<'a> {
372 mode: json::GetBlockTemplateModes,
373 rules: &'a [json::GetBlockTemplateRules],
374 capabilities: &'a [json::GetBlockTemplateCapabilities],
375 }
376
377 self.call(
378 "getblocktemplate",
379 &[into_json(Argument {
380 mode: mode,
381 rules: rules,
382 capabilities: capabilities,
383 })?],
384 )
385 }
386
387 fn get_blockchain_info(&self) -> Result<json::GetBlockchainInfoResult> {
390 let mut raw: serde_json::Value = self.call("getblockchaininfo", &[])?;
391 Ok(if self.version()? < 190000 {
395 use crate::Error::UnexpectedStructure as err;
396
397 let (bip9_softforks, old_softforks) = {
400 let map = raw.as_object_mut().ok_or(err)?;
401 let bip9_softforks = map.remove("bip9_softforks").ok_or(err)?;
402 let old_softforks = map.remove("softforks").ok_or(err)?;
403 map.insert("softforks".into(), serde_json::Map::new().into());
405 (bip9_softforks, old_softforks)
406 };
407 let mut ret: json::GetBlockchainInfoResult = serde_json::from_value(raw)?;
408
409 for sf in old_softforks.as_array().ok_or(err)?.iter() {
411 let json = sf.as_object().ok_or(err)?;
412 let id = json.get("id").ok_or(err)?.as_str().ok_or(err)?;
413 let reject = json.get("reject").ok_or(err)?.as_object().ok_or(err)?;
414 let active = reject.get("status").ok_or(err)?.as_bool().ok_or(err)?;
415 ret.softforks.insert(
416 id.into(),
417 json::Softfork {
418 type_: json::SoftforkType::Buried,
419 bip9: None,
420 height: None,
421 active: active,
422 },
423 );
424 }
425 for (id, sf) in bip9_softforks.as_object().ok_or(err)?.iter() {
426 #[derive(Deserialize)]
427 struct OldBip9SoftFork {
428 pub status: json::Bip9SoftforkStatus,
429 pub bit: Option<u8>,
430 #[serde(rename = "startTime")]
431 pub start_time: i64,
432 pub timeout: u64,
433 pub since: u32,
434 pub statistics: Option<json::Bip9SoftforkStatistics>,
435 }
436 let sf: OldBip9SoftFork = serde_json::from_value(sf.clone())?;
437 ret.softforks.insert(
438 id.clone(),
439 json::Softfork {
440 type_: json::SoftforkType::Bip9,
441 bip9: Some(json::Bip9SoftforkInfo {
442 status: sf.status,
443 bit: sf.bit,
444 start_time: sf.start_time,
445 timeout: sf.timeout,
446 since: sf.since,
447 statistics: sf.statistics,
448 }),
449 height: None,
450 active: sf.status == json::Bip9SoftforkStatus::Active,
451 },
452 );
453 }
454 ret
455 } else {
456 serde_json::from_value(raw)?
457 })
458 }
459
460 fn get_block_count(&self) -> Result<u64> {
462 self.call("getblockcount", &[])
463 }
464
465 fn get_best_block_hash(&self) -> Result<groestlcoin::BlockHash> {
467 self.call("getbestblockhash", &[])
468 }
469
470 fn get_block_hash(&self, height: u64) -> Result<groestlcoin::BlockHash> {
472 self.call("getblockhash", &[height.into()])
473 }
474
475 fn get_block_stats(&self, height: u64) -> Result<json::GetBlockStatsResult> {
476 self.call("getblockstats", &[height.into()])
477 }
478
479 fn get_block_stats_fields(
480 &self,
481 height: u64,
482 fields: &[json::BlockStatsFields],
483 ) -> Result<json::GetBlockStatsResultPartial> {
484 self.call("getblockstats", &[height.into(), fields.into()])
485 }
486
487 fn get_raw_transaction(
488 &self,
489 txid: &groestlcoin::Txid,
490 block_hash: Option<&groestlcoin::BlockHash>,
491 ) -> Result<Transaction> {
492 let mut args = [into_json(txid)?, into_json(false)?, opt_into_json(block_hash)?];
493 let hex: String = self.call("getrawtransaction", handle_defaults(&mut args, &[null()]))?;
494 deserialize_hex(&hex)
495 }
496
497 fn get_raw_transaction_hex(
498 &self,
499 txid: &groestlcoin::Txid,
500 block_hash: Option<&groestlcoin::BlockHash>,
501 ) -> Result<String> {
502 let mut args = [into_json(txid)?, into_json(false)?, opt_into_json(block_hash)?];
503 self.call("getrawtransaction", handle_defaults(&mut args, &[null()]))
504 }
505
506 fn get_raw_transaction_info(
507 &self,
508 txid: &groestlcoin::Txid,
509 block_hash: Option<&groestlcoin::BlockHash>,
510 ) -> Result<json::GetRawTransactionResult> {
511 let mut args = [into_json(txid)?, into_json(true)?, opt_into_json(block_hash)?];
512 self.call("getrawtransaction", handle_defaults(&mut args, &[null()]))
513 }
514
515 fn get_block_filter(
516 &self,
517 block_hash: &groestlcoin::BlockHash,
518 ) -> Result<json::GetBlockFilterResult> {
519 self.call("getblockfilter", &[into_json(block_hash)?])
520 }
521
522 fn get_balance(
523 &self,
524 minconf: Option<usize>,
525 include_watchonly: Option<bool>,
526 ) -> Result<Amount> {
527 let mut args = ["*".into(), opt_into_json(minconf)?, opt_into_json(include_watchonly)?];
528 Ok(Amount::from_btc(
529 self.call("getbalance", handle_defaults(&mut args, &[0.into(), null()]))?,
530 )?)
531 }
532
533 fn get_balances(&self) -> Result<json::GetBalancesResult> {
534 Ok(self.call("getbalances", &[])?)
535 }
536
537 fn get_received_by_address(&self, address: &Address, minconf: Option<u32>) -> Result<Amount> {
538 let mut args = [address.to_string().into(), opt_into_json(minconf)?];
539 Ok(Amount::from_btc(
540 self.call("getreceivedbyaddress", handle_defaults(&mut args, &[null()]))?,
541 )?)
542 }
543
544 fn get_transaction(
545 &self,
546 txid: &groestlcoin::Txid,
547 include_watchonly: Option<bool>,
548 ) -> Result<json::GetTransactionResult> {
549 let mut args = [into_json(txid)?, opt_into_json(include_watchonly)?];
550 self.call("gettransaction", handle_defaults(&mut args, &[null()]))
551 }
552
553 fn list_transactions(
554 &self,
555 label: Option<&str>,
556 count: Option<usize>,
557 skip: Option<usize>,
558 include_watchonly: Option<bool>,
559 ) -> Result<Vec<json::ListTransactionResult>> {
560 let mut args = [
561 label.unwrap_or("*").into(),
562 opt_into_json(count)?,
563 opt_into_json(skip)?,
564 opt_into_json(include_watchonly)?,
565 ];
566 self.call("listtransactions", handle_defaults(&mut args, &[10.into(), 0.into(), null()]))
567 }
568
569 fn list_since_block(
570 &self,
571 blockhash: Option<&groestlcoin::BlockHash>,
572 target_confirmations: Option<usize>,
573 include_watchonly: Option<bool>,
574 include_removed: Option<bool>,
575 ) -> Result<json::ListSinceBlockResult> {
576 let mut args = [
577 opt_into_json(blockhash)?,
578 opt_into_json(target_confirmations)?,
579 opt_into_json(include_watchonly)?,
580 opt_into_json(include_removed)?,
581 ];
582 self.call("listsinceblock", handle_defaults(&mut args, &[null()]))
583 }
584
585 fn get_tx_out(
586 &self,
587 txid: &groestlcoin::Txid,
588 vout: u32,
589 include_mempool: Option<bool>,
590 ) -> Result<Option<json::GetTxOutResult>> {
591 let mut args = [into_json(txid)?, into_json(vout)?, opt_into_json(include_mempool)?];
592 opt_result(self.call("gettxout", handle_defaults(&mut args, &[null()]))?)
593 }
594
595 fn get_tx_out_proof(
596 &self,
597 txids: &[groestlcoin::Txid],
598 block_hash: Option<&groestlcoin::BlockHash>,
599 ) -> Result<Vec<u8>> {
600 let mut args = [into_json(txids)?, opt_into_json(block_hash)?];
601 let hex: String = self.call("gettxoutproof", handle_defaults(&mut args, &[null()]))?;
602 Ok(FromHex::from_hex(&hex)?)
603 }
604
605 fn import_public_key(
606 &self,
607 pubkey: &PublicKey,
608 label: Option<&str>,
609 rescan: Option<bool>,
610 ) -> Result<()> {
611 let mut args = [pubkey.to_string().into(), opt_into_json(label)?, opt_into_json(rescan)?];
612 self.call("importpubkey", handle_defaults(&mut args, &[into_json("")?, null()]))
613 }
614
615 fn import_private_key(
616 &self,
617 privkey: &PrivateKey,
618 label: Option<&str>,
619 rescan: Option<bool>,
620 ) -> Result<()> {
621 let mut args = [privkey.to_string().into(), opt_into_json(label)?, opt_into_json(rescan)?];
622 self.call("importprivkey", handle_defaults(&mut args, &[into_json("")?, null()]))
623 }
624
625 fn import_address(
626 &self,
627 address: &Address,
628 label: Option<&str>,
629 rescan: Option<bool>,
630 ) -> Result<()> {
631 let mut args = [address.to_string().into(), opt_into_json(label)?, opt_into_json(rescan)?];
632 self.call("importaddress", handle_defaults(&mut args, &[into_json("")?, null()]))
633 }
634
635 fn import_address_script(
636 &self,
637 script: &Script,
638 label: Option<&str>,
639 rescan: Option<bool>,
640 p2sh: Option<bool>,
641 ) -> Result<()> {
642 let mut args = [
643 script.to_hex_string().into(),
644 opt_into_json(label)?,
645 opt_into_json(rescan)?,
646 opt_into_json(p2sh)?,
647 ];
648 self.call(
649 "importaddress",
650 handle_defaults(&mut args, &[into_json("")?, true.into(), null()]),
651 )
652 }
653
654 fn import_multi(
655 &self,
656 requests: &[json::ImportMultiRequest],
657 options: Option<&json::ImportMultiOptions>,
658 ) -> Result<Vec<json::ImportMultiResult>> {
659 let mut json_requests = Vec::with_capacity(requests.len());
660 for req in requests {
661 json_requests.push(serde_json::to_value(req)?);
662 }
663 let mut args = [json_requests.into(), opt_into_json(options)?];
664 self.call("importmulti", handle_defaults(&mut args, &[null()]))
665 }
666
667 fn import_descriptors(
668 &self,
669 req: json::ImportDescriptors,
670 ) -> Result<Vec<json::ImportMultiResult>> {
671 let json_request = vec![serde_json::to_value(req)?];
672 self.call("importdescriptors", handle_defaults(&mut [json_request.into()], &[null()]))
673 }
674
675 fn set_label(&self, address: &Address, label: &str) -> Result<()> {
676 self.call("setlabel", &[address.to_string().into(), label.into()])
677 }
678
679 fn key_pool_refill(&self, new_size: Option<usize>) -> Result<()> {
680 let mut args = [opt_into_json(new_size)?];
681 self.call("keypoolrefill", handle_defaults(&mut args, &[null()]))
682 }
683
684 fn list_unspent(
685 &self,
686 minconf: Option<usize>,
687 maxconf: Option<usize>,
688 addresses: Option<&[&Address<NetworkChecked>]>,
689 include_unsafe: Option<bool>,
690 query_options: Option<json::ListUnspentQueryOptions>,
691 ) -> Result<Vec<json::ListUnspentResultEntry>> {
692 let mut args = [
693 opt_into_json(minconf)?,
694 opt_into_json(maxconf)?,
695 opt_into_json(addresses)?,
696 opt_into_json(include_unsafe)?,
697 opt_into_json(query_options)?,
698 ];
699 let defaults = [into_json(0)?, into_json(9999999)?, empty_arr(), into_json(true)?, null()];
700 self.call("listunspent", handle_defaults(&mut args, &defaults))
701 }
702
703 fn lock_unspent(&self, outputs: &[OutPoint]) -> Result<bool> {
705 let outputs: Vec<_> = outputs
706 .into_iter()
707 .map(|o| serde_json::to_value(JsonOutPoint::from(*o)).unwrap())
708 .collect();
709 self.call("lockunspent", &[false.into(), outputs.into()])
710 }
711
712 fn unlock_unspent(&self, outputs: &[OutPoint]) -> Result<bool> {
713 let outputs: Vec<_> = outputs
714 .into_iter()
715 .map(|o| serde_json::to_value(JsonOutPoint::from(*o)).unwrap())
716 .collect();
717 self.call("lockunspent", &[true.into(), outputs.into()])
718 }
719
720 fn unlock_unspent_all(&self) -> Result<bool> {
722 self.call("lockunspent", &[true.into()])
723 }
724
725 fn list_received_by_address(
726 &self,
727 address_filter: Option<&Address>,
728 minconf: Option<u32>,
729 include_empty: Option<bool>,
730 include_watchonly: Option<bool>,
731 ) -> Result<Vec<json::ListReceivedByAddressResult>> {
732 let mut args = [
733 opt_into_json(minconf)?,
734 opt_into_json(include_empty)?,
735 opt_into_json(include_watchonly)?,
736 opt_into_json(address_filter)?,
737 ];
738 let defaults = [1.into(), false.into(), false.into(), null()];
739 self.call("listreceivedbyaddress", handle_defaults(&mut args, &defaults))
740 }
741
742 fn create_psbt(
743 &self,
744 inputs: &[json::CreateRawTransactionInput],
745 outputs: &HashMap<String, Amount>,
746 locktime: Option<i64>,
747 replaceable: Option<bool>,
748 ) -> Result<String> {
749 let outs_converted = serde_json::Map::from_iter(
750 outputs.iter().map(|(k, v)| (k.clone(), serde_json::Value::from(v.to_btc()))),
751 );
752 self.call(
753 "createpsbt",
754 &[
755 into_json(inputs)?,
756 into_json(outs_converted)?,
757 into_json(locktime)?,
758 into_json(replaceable)?,
759 ],
760 )
761 }
762
763 fn create_raw_transaction_hex(
764 &self,
765 utxos: &[json::CreateRawTransactionInput],
766 outs: &HashMap<String, Amount>,
767 locktime: Option<i64>,
768 replaceable: Option<bool>,
769 ) -> Result<String> {
770 let outs_converted = serde_json::Map::from_iter(
771 outs.iter().map(|(k, v)| (k.clone(), serde_json::Value::from(v.to_btc()))),
772 );
773 let mut args = [
774 into_json(utxos)?,
775 into_json(outs_converted)?,
776 opt_into_json(locktime)?,
777 opt_into_json(replaceable)?,
778 ];
779 let defaults = [into_json(0i64)?, null()];
780 self.call("createrawtransaction", handle_defaults(&mut args, &defaults))
781 }
782
783 fn create_raw_transaction(
784 &self,
785 utxos: &[json::CreateRawTransactionInput],
786 outs: &HashMap<String, Amount>,
787 locktime: Option<i64>,
788 replaceable: Option<bool>,
789 ) -> Result<Transaction> {
790 let hex: String = self.create_raw_transaction_hex(utxos, outs, locktime, replaceable)?;
791 deserialize_hex(&hex)
792 }
793
794 fn decode_raw_transaction<R: RawTx>(
795 &self,
796 tx: R,
797 is_witness: Option<bool>,
798 ) -> Result<json::DecodeRawTransactionResult> {
799 let mut args = [tx.raw_hex().into(), opt_into_json(is_witness)?];
800 let defaults = [null()];
801 self.call("decoderawtransaction", handle_defaults(&mut args, &defaults))
802 }
803
804 fn fund_raw_transaction<R: RawTx>(
805 &self,
806 tx: R,
807 options: Option<&json::FundRawTransactionOptions>,
808 is_witness: Option<bool>,
809 ) -> Result<json::FundRawTransactionResult> {
810 let mut args = [tx.raw_hex().into(), opt_into_json(options)?, opt_into_json(is_witness)?];
811 let defaults = [empty_obj(), null()];
812 self.call("fundrawtransaction", handle_defaults(&mut args, &defaults))
813 }
814
815 #[deprecated]
816 fn sign_raw_transaction<R: RawTx>(
817 &self,
818 tx: R,
819 utxos: Option<&[json::SignRawTransactionInput]>,
820 private_keys: Option<&[PrivateKey]>,
821 sighash_type: Option<json::SigHashType>,
822 ) -> Result<json::SignRawTransactionResult> {
823 let mut args = [
824 tx.raw_hex().into(),
825 opt_into_json(utxos)?,
826 opt_into_json(private_keys)?,
827 opt_into_json(sighash_type)?,
828 ];
829 let defaults = [empty_arr(), empty_arr(), null()];
830 self.call("signrawtransaction", handle_defaults(&mut args, &defaults))
831 }
832
833 fn sign_raw_transaction_with_wallet<R: RawTx>(
834 &self,
835 tx: R,
836 utxos: Option<&[json::SignRawTransactionInput]>,
837 sighash_type: Option<json::SigHashType>,
838 ) -> Result<json::SignRawTransactionResult> {
839 let mut args = [tx.raw_hex().into(), opt_into_json(utxos)?, opt_into_json(sighash_type)?];
840 let defaults = [empty_arr(), null()];
841 self.call("signrawtransactionwithwallet", handle_defaults(&mut args, &defaults))
842 }
843
844 fn sign_raw_transaction_with_key<R: RawTx>(
845 &self,
846 tx: R,
847 privkeys: &[PrivateKey],
848 prevtxs: Option<&[json::SignRawTransactionInput]>,
849 sighash_type: Option<json::SigHashType>,
850 ) -> Result<json::SignRawTransactionResult> {
851 let mut args = [
852 tx.raw_hex().into(),
853 into_json(privkeys)?,
854 opt_into_json(prevtxs)?,
855 opt_into_json(sighash_type)?,
856 ];
857 let defaults = [empty_arr(), null()];
858 self.call("signrawtransactionwithkey", handle_defaults(&mut args, &defaults))
859 }
860
861 fn test_mempool_accept<R: RawTx>(
862 &self,
863 rawtxs: &[R],
864 ) -> Result<Vec<json::TestMempoolAcceptResult>> {
865 let hexes: Vec<serde_json::Value> =
866 rawtxs.to_vec().into_iter().map(|r| r.raw_hex().into()).collect();
867 self.call("testmempoolaccept", &[hexes.into()])
868 }
869
870 fn stop(&self) -> Result<String> {
871 self.call("stop", &[])
872 }
873
874 fn verify_message(
875 &self,
876 address: &Address,
877 signature: &Signature,
878 message: &str,
879 ) -> Result<bool> {
880 let args = [address.to_string().into(), signature.to_string().into(), into_json(message)?];
881 self.call("verifymessage", &args)
882 }
883
884 fn get_new_address(
886 &self,
887 label: Option<&str>,
888 address_type: Option<json::AddressType>,
889 ) -> Result<Address<NetworkUnchecked>> {
890 self.call("getnewaddress", &[opt_into_json(label)?, opt_into_json(address_type)?])
891 }
892
893 fn get_raw_change_address(&self, address_type: Option<json::AddressType>) -> Result<Address<NetworkUnchecked>> {
895 self.call("getrawchangeaddress", &[opt_into_json(address_type)?])
896 }
897
898 fn get_address_info(&self, address: &Address) -> Result<json::GetAddressInfoResult> {
899 self.call("getaddressinfo", &[address.to_string().into()])
900 }
901
902 fn generate_to_address(
906 &self,
907 block_num: u64,
908 address: &Address<NetworkChecked>,
909 ) -> Result<Vec<groestlcoin::BlockHash>> {
910 self.call("generatetoaddress", &[block_num.into(), address.to_string().into()])
911 }
912
913 fn generate(&self, block_num: u64, maxtries: Option<u64>) -> Result<Vec<groestlcoin::BlockHash>> {
916 self.call("generate", &[block_num.into(), opt_into_json(maxtries)?])
917 }
918
919 fn invalidate_block(&self, block_hash: &groestlcoin::BlockHash) -> Result<()> {
921 self.call("invalidateblock", &[into_json(block_hash)?])
922 }
923
924 fn reconsider_block(&self, block_hash: &groestlcoin::BlockHash) -> Result<()> {
926 self.call("reconsiderblock", &[into_json(block_hash)?])
927 }
928
929 fn get_mempool_info(&self) -> Result<json::GetMempoolInfoResult> {
931 self.call("getmempoolinfo", &[])
932 }
933
934 fn get_raw_mempool(&self) -> Result<Vec<groestlcoin::Txid>> {
936 self.call("getrawmempool", &[])
937 }
938
939 fn get_raw_mempool_verbose(
941 &self,
942 ) -> Result<HashMap<groestlcoin::Txid, json::GetMempoolEntryResult>> {
943 self.call("getrawmempool", &[into_json(true)?])
944 }
945
946 fn get_mempool_entry(&self, txid: &groestlcoin::Txid) -> Result<json::GetMempoolEntryResult> {
948 self.call("getmempoolentry", &[into_json(txid)?])
949 }
950
951 fn get_chain_tips(&self) -> Result<json::GetChainTipsResult> {
954 self.call("getchaintips", &[])
955 }
956
957 fn send_to_address(
958 &self,
959 address: &Address<NetworkChecked>,
960 amount: Amount,
961 comment: Option<&str>,
962 comment_to: Option<&str>,
963 subtract_fee: Option<bool>,
964 replaceable: Option<bool>,
965 confirmation_target: Option<u32>,
966 estimate_mode: Option<json::EstimateMode>,
967 ) -> Result<groestlcoin::Txid> {
968 let mut args = [
969 address.to_string().into(),
970 into_json(amount.to_btc())?,
971 opt_into_json(comment)?,
972 opt_into_json(comment_to)?,
973 opt_into_json(subtract_fee)?,
974 opt_into_json(replaceable)?,
975 opt_into_json(confirmation_target)?,
976 opt_into_json(estimate_mode)?,
977 ];
978 self.call(
979 "sendtoaddress",
980 handle_defaults(
981 &mut args,
982 &["".into(), "".into(), false.into(), false.into(), 6.into(), null()],
983 ),
984 )
985 }
986
987 fn add_node(&self, addr: &str) -> Result<()> {
990 self.call("addnode", &[into_json(&addr)?, into_json("add")?])
991 }
992
993 fn remove_node(&self, addr: &str) -> Result<()> {
995 self.call("addnode", &[into_json(&addr)?, into_json("remove")?])
996 }
997
998 fn onetry_node(&self, addr: &str) -> Result<()> {
1000 self.call("addnode", &[into_json(&addr)?, into_json("onetry")?])
1001 }
1002
1003 fn disconnect_node(&self, addr: &str) -> Result<()> {
1005 self.call("disconnectnode", &[into_json(&addr)?])
1006 }
1007
1008 fn disconnect_node_by_id(&self, node_id: u32) -> Result<()> {
1009 self.call("disconnectnode", &[into_json("")?, into_json(node_id)?])
1010 }
1011
1012 fn get_added_node_info(&self, node: Option<&str>) -> Result<Vec<json::GetAddedNodeInfoResult>> {
1014 if let Some(addr) = node {
1015 self.call("getaddednodeinfo", &[into_json(&addr)?])
1016 } else {
1017 self.call("getaddednodeinfo", &[])
1018 }
1019 }
1020
1021 fn get_node_addresses(
1023 &self,
1024 count: Option<usize>,
1025 ) -> Result<Vec<json::GetNodeAddressesResult>> {
1026 let cnt = count.unwrap_or(1);
1027 self.call("getnodeaddresses", &[into_json(&cnt)?])
1028 }
1029
1030 fn list_banned(&self) -> Result<Vec<json::ListBannedResult>> {
1032 self.call("listbanned", &[])
1033 }
1034
1035 fn clear_banned(&self) -> Result<()> {
1037 self.call("clearbanned", &[])
1038 }
1039
1040 fn add_ban(&self, subnet: &str, bantime: u64, absolute: bool) -> Result<()> {
1042 self.call(
1043 "setban",
1044 &[into_json(&subnet)?, into_json("add")?, into_json(&bantime)?, into_json(&absolute)?],
1045 )
1046 }
1047
1048 fn remove_ban(&self, subnet: &str) -> Result<()> {
1050 self.call("setban", &[into_json(&subnet)?, into_json("remove")?])
1051 }
1052
1053 fn set_network_active(&self, state: bool) -> Result<bool> {
1055 self.call("setnetworkactive", &[into_json(&state)?])
1056 }
1057
1058 fn get_peer_info(&self) -> Result<Vec<json::GetPeerInfoResult>> {
1063 self.call("getpeerinfo", &[])
1064 }
1065
1066 fn ping(&self) -> Result<()> {
1075 self.call("ping", &[])
1076 }
1077
1078 fn send_raw_transaction<R: RawTx>(&self, tx: R) -> Result<groestlcoin::Txid> {
1079 self.call("sendrawtransaction", &[tx.raw_hex().into()])
1080 }
1081
1082 fn estimate_smart_fee(
1083 &self,
1084 conf_target: u16,
1085 estimate_mode: Option<json::EstimateMode>,
1086 ) -> Result<json::EstimateSmartFeeResult> {
1087 let mut args = [into_json(conf_target)?, opt_into_json(estimate_mode)?];
1088 self.call("estimatesmartfee", handle_defaults(&mut args, &[null()]))
1089 }
1090
1091 fn wait_for_new_block(&self, timeout: u64) -> Result<json::BlockRef> {
1099 self.call("waitfornewblock", &[into_json(timeout)?])
1100 }
1101
1102 fn wait_for_block(
1111 &self,
1112 blockhash: &groestlcoin::BlockHash,
1113 timeout: u64,
1114 ) -> Result<json::BlockRef> {
1115 let args = [into_json(blockhash)?, into_json(timeout)?];
1116 self.call("waitforblock", &args)
1117 }
1118
1119 fn wallet_create_funded_psbt(
1120 &self,
1121 inputs: &[json::CreateRawTransactionInput],
1122 outputs: &HashMap<String, Amount>,
1123 locktime: Option<i64>,
1124 options: Option<json::WalletCreateFundedPsbtOptions>,
1125 bip32derivs: Option<bool>,
1126 ) -> Result<json::WalletCreateFundedPsbtResult> {
1127 let outputs_converted = serde_json::Map::from_iter(
1128 outputs.iter().map(|(k, v)| (k.clone(), serde_json::Value::from(v.to_btc()))),
1129 );
1130 let mut args = [
1131 into_json(inputs)?,
1132 into_json(outputs_converted)?,
1133 opt_into_json(locktime)?,
1134 opt_into_json(options)?,
1135 opt_into_json(bip32derivs)?,
1136 ];
1137 self.call(
1138 "walletcreatefundedpsbt",
1139 handle_defaults(&mut args, &[0.into(), serde_json::Map::new().into(), false.into()]),
1140 )
1141 }
1142
1143 fn wallet_process_psbt(
1144 &self,
1145 psbt: &str,
1146 sign: Option<bool>,
1147 sighash_type: Option<json::SigHashType>,
1148 bip32derivs: Option<bool>,
1149 ) -> Result<json::WalletProcessPsbtResult> {
1150 let mut args = [
1151 into_json(psbt)?,
1152 opt_into_json(sign)?,
1153 opt_into_json(sighash_type)?,
1154 opt_into_json(bip32derivs)?,
1155 ];
1156 let defaults = [
1157 true.into(),
1158 into_json(json::SigHashType::from(groestlcoin::sighash::EcdsaSighashType::All))?,
1159 true.into(),
1160 ];
1161 self.call("walletprocesspsbt", handle_defaults(&mut args, &defaults))
1162 }
1163
1164 fn get_descriptor_info(&self, desc: &str) -> Result<json::GetDescriptorInfoResult> {
1165 self.call("getdescriptorinfo", &[desc.to_string().into()])
1166 }
1167
1168 fn join_psbt(&self, psbts: &[String]) -> Result<String> {
1169 self.call("joinpsbts", &[into_json(psbts)?])
1170 }
1171
1172 fn combine_psbt(&self, psbts: &[String]) -> Result<String> {
1173 self.call("combinepsbt", &[into_json(psbts)?])
1174 }
1175
1176 fn combine_raw_transaction(&self, hex_strings: &[String]) -> Result<String> {
1177 self.call("combinerawtransaction", &[into_json(hex_strings)?])
1178 }
1179
1180 fn finalize_psbt(&self, psbt: &str, extract: Option<bool>) -> Result<json::FinalizePsbtResult> {
1181 let mut args = [into_json(psbt)?, opt_into_json(extract)?];
1182 self.call("finalizepsbt", handle_defaults(&mut args, &[true.into()]))
1183 }
1184
1185 fn derive_addresses(&self, descriptor: &str, range: Option<[u32; 2]>) -> Result<Vec<Address<NetworkUnchecked>>> {
1186 let mut args = [into_json(descriptor)?, opt_into_json(range)?];
1187 self.call("deriveaddresses", handle_defaults(&mut args, &[null()]))
1188 }
1189
1190 fn rescan_blockchain(
1191 &self,
1192 start_from: Option<usize>,
1193 stop_height: Option<usize>,
1194 ) -> Result<(usize, Option<usize>)> {
1195 let mut args = [opt_into_json(start_from)?, opt_into_json(stop_height)?];
1196
1197 #[derive(Deserialize)]
1198 struct Response {
1199 pub start_height: usize,
1200 pub stop_height: Option<usize>,
1201 }
1202 let res: Response =
1203 self.call("rescanblockchain", handle_defaults(&mut args, &[0.into(), null()]))?;
1204 Ok((res.start_height, res.stop_height))
1205 }
1206
1207 fn get_tx_out_set_info(
1210 &self,
1211 hash_type: Option<json::TxOutSetHashType>,
1212 hash_or_height: Option<json::HashOrHeight>,
1213 use_index: Option<bool>,
1214 ) -> Result<json::GetTxOutSetInfoResult> {
1215 let mut args =
1216 [opt_into_json(hash_type)?, opt_into_json(hash_or_height)?, opt_into_json(use_index)?];
1217 self.call("gettxoutsetinfo", handle_defaults(&mut args, &[null(), null(), null()]))
1218 }
1219
1220 fn get_net_totals(&self) -> Result<json::GetNetTotalsResult> {
1223 self.call("getnettotals", &[])
1224 }
1225
1226 fn get_network_hash_ps(&self, nblocks: Option<u64>, height: Option<u64>) -> Result<f64> {
1228 let mut args = [opt_into_json(nblocks)?, opt_into_json(height)?];
1229 self.call("getnetworkhashps", handle_defaults(&mut args, &[null(), null()]))
1230 }
1231
1232 fn uptime(&self) -> Result<u64> {
1234 self.call("uptime", &[])
1235 }
1236
1237 fn submit_block(&self, block: &groestlcoin::Block) -> Result<()> {
1239 let block_hex: String = groestlcoin::consensus::encode::serialize_hex(block);
1240 self.submit_block_hex(&block_hex)
1241 }
1242
1243 fn submit_block_bytes(&self, block_bytes: &[u8]) -> Result<()> {
1245 let block_hex: String = block_bytes.to_lower_hex_string();
1246 self.submit_block_hex(&block_hex)
1247 }
1248
1249 fn submit_block_hex(&self, block_hex: &str) -> Result<()> {
1251 match self.call("submitblock", &[into_json(&block_hex)?]) {
1252 Ok(serde_json::Value::Null) => Ok(()),
1253 Ok(res) => Err(Error::ReturnedError(res.to_string())),
1254 Err(err) => Err(err.into()),
1255 }
1256 }
1257
1258 fn scan_tx_out_set_blocking(
1259 &self,
1260 descriptors: &[json::ScanTxOutRequest],
1261 ) -> Result<json::ScanTxOutResult> {
1262 self.call("scantxoutset", &["start".into(), into_json(descriptors)?])
1263 }
1264}
1265
1266pub struct Client {
1268 client: jsonrpc::client::Client,
1269}
1270
1271impl fmt::Debug for Client {
1272 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1273 write!(f, "groestlcoincore_rpc::Client({:?})", self.client)
1274 }
1275}
1276
1277impl Client {
1278 pub fn new(url: &str, auth: Auth) -> Result<Self> {
1282 let (user, pass) = auth.get_user_pass()?;
1283 jsonrpc::client::Client::simple_http(url, user, pass)
1284 .map(|client| Client {
1285 client,
1286 })
1287 .map_err(|e| super::error::Error::JsonRpc(e.into()))
1288 }
1289
1290 pub fn from_jsonrpc(client: jsonrpc::client::Client) -> Client {
1292 Client {
1293 client,
1294 }
1295 }
1296
1297 pub fn get_jsonrpc_client(&self) -> &jsonrpc::client::Client {
1299 &self.client
1300 }
1301}
1302
1303impl RpcApi for Client {
1304 fn call<T: for<'a> serde::de::Deserialize<'a>>(
1306 &self,
1307 cmd: &str,
1308 args: &[serde_json::Value],
1309 ) -> Result<T> {
1310 let raw_args: Vec<_> = args
1311 .iter()
1312 .map(|a| {
1313 let json_string = serde_json::to_string(a)?;
1314 serde_json::value::RawValue::from_string(json_string) })
1316 .map(|a| a.map_err(|e| Error::Json(e)))
1317 .collect::<Result<Vec<_>>>()?;
1318 let req = self.client.build_request(&cmd, &raw_args);
1319 if log_enabled!(Debug) {
1320 debug!(target: "groestlcoincore_rpc", "JSON-RPC request: {} {}", cmd, serde_json::Value::from(args));
1321 }
1322
1323 let resp = self.client.send_request(req).map_err(Error::from);
1324 log_response(cmd, &resp);
1325 Ok(resp?.result()?)
1326 }
1327}
1328
1329fn log_response(cmd: &str, resp: &Result<jsonrpc::Response>) {
1330 if log_enabled!(Warn) || log_enabled!(Debug) || log_enabled!(Trace) {
1331 match resp {
1332 Err(ref e) => {
1333 if log_enabled!(Debug) {
1334 debug!(target: "groestlcoincore_rpc", "JSON-RPC failed parsing reply of {}: {:?}", cmd, e);
1335 }
1336 }
1337 Ok(ref resp) => {
1338 if let Some(ref e) = resp.error {
1339 if log_enabled!(Debug) {
1340 debug!(target: "groestlcoincore_rpc", "JSON-RPC error for {}: {:?}", cmd, e);
1341 }
1342 } else if log_enabled!(Trace) {
1343 let def = serde_json::value::RawValue::from_string(
1345 serde_json::Value::Null.to_string(),
1346 )
1347 .unwrap();
1348 let result = resp.result.as_ref().unwrap_or(&def);
1349 trace!(target: "groestlcoincore_rpc", "JSON-RPC response for {}: {}", cmd, result);
1350 }
1351 }
1352 }
1353 }
1354}
1355
1356#[cfg(test)]
1357mod tests {
1358 use super::*;
1359 use crate::groestlcoin;
1360 use serde_json;
1361
1362 #[test]
1363 fn test_raw_tx() {
1364 use crate::groestlcoin::consensus::encode;
1365 let client = Client::new("http://localhost/".into(), Auth::None).unwrap();
1366 let tx: groestlcoin::Transaction = encode::deserialize(&Vec::<u8>::from_hex("0200000001586bd02815cf5faabfec986a4e50d25dbee089bd2758621e61c5fab06c334af0000000006b483045022100e85425f6d7c589972ee061413bcf08dc8c8e589ce37b217535a42af924f0e4d602205c9ba9cb14ef15513c9d946fa1c4b797883e748e8c32171bdf6166583946e35c012103dae30a4d7870cd87b45dd53e6012f71318fdd059c1c2623b8cc73f8af287bb2dfeffffff021dc4260c010000001976a914f602e88b2b5901d8aab15ebe4a97cf92ec6e03b388ac00e1f505000000001976a914687ffeffe8cf4e4c038da46a9b1d37db385a472d88acfd211500").unwrap()).unwrap();
1367
1368 assert!(client.send_raw_transaction(&tx).is_err());
1369 assert!(client.send_raw_transaction(&encode::serialize(&tx)).is_err());
1370 assert!(client.send_raw_transaction("deadbeef").is_err());
1371 assert!(client.send_raw_transaction("deadbeef".to_owned()).is_err());
1372 }
1373
1374 fn test_handle_defaults_inner() -> Result<()> {
1375 {
1376 let mut args = [into_json(0)?, null(), null()];
1377 let defaults = [into_json(1)?, into_json(2)?];
1378 let res = [into_json(0)?];
1379 assert_eq!(handle_defaults(&mut args, &defaults), &res);
1380 }
1381 {
1382 let mut args = [into_json(0)?, into_json(1)?, null()];
1383 let defaults = [into_json(2)?];
1384 let res = [into_json(0)?, into_json(1)?];
1385 assert_eq!(handle_defaults(&mut args, &defaults), &res);
1386 }
1387 {
1388 let mut args = [into_json(0)?, null(), into_json(5)?];
1389 let defaults = [into_json(2)?, into_json(3)?];
1390 let res = [into_json(0)?, into_json(2)?, into_json(5)?];
1391 assert_eq!(handle_defaults(&mut args, &defaults), &res);
1392 }
1393 {
1394 let mut args = [into_json(0)?, null(), into_json(5)?, null()];
1395 let defaults = [into_json(2)?, into_json(3)?, into_json(4)?];
1396 let res = [into_json(0)?, into_json(2)?, into_json(5)?];
1397 assert_eq!(handle_defaults(&mut args, &defaults), &res);
1398 }
1399 {
1400 let mut args = [null(), null()];
1401 let defaults = [into_json(2)?, into_json(3)?];
1402 let res: [serde_json::Value; 0] = [];
1403 assert_eq!(handle_defaults(&mut args, &defaults), &res);
1404 }
1405 {
1406 let mut args = [null(), into_json(1)?];
1407 let defaults = [];
1408 let res = [null(), into_json(1)?];
1409 assert_eq!(handle_defaults(&mut args, &defaults), &res);
1410 }
1411 {
1412 let mut args = [];
1413 let defaults = [];
1414 let res: [serde_json::Value; 0] = [];
1415 assert_eq!(handle_defaults(&mut args, &defaults), &res);
1416 }
1417 {
1418 let mut args = [into_json(0)?];
1419 let defaults = [into_json(2)?];
1420 let res = [into_json(0)?];
1421 assert_eq!(handle_defaults(&mut args, &defaults), &res);
1422 }
1423 Ok(())
1424 }
1425
1426 #[test]
1427 fn test_handle_defaults() {
1428 test_handle_defaults_inner().unwrap();
1429 }
1430
1431 #[test]
1432 fn auth_cookie_file_ignores_newline() {
1433 let tempdir = tempfile::tempdir().unwrap();
1434 let path = tempdir.path().join("cookie");
1435 std::fs::write(&path, "foo:bar\n").unwrap();
1436 assert_eq!(
1437 Auth::CookieFile(path).get_user_pass().unwrap(),
1438 (Some("foo".into()), Some("bar".into())),
1439 );
1440 }
1441
1442 #[test]
1443 fn auth_cookie_file_ignores_additional_lines() {
1444 let tempdir = tempfile::tempdir().unwrap();
1445 let path = tempdir.path().join("cookie");
1446 std::fs::write(&path, "foo:bar\nbaz").unwrap();
1447 assert_eq!(
1448 Auth::CookieFile(path).get_user_pass().unwrap(),
1449 (Some("foo".into()), Some("bar".into())),
1450 );
1451 }
1452
1453 #[test]
1454 fn auth_cookie_file_fails_if_colon_isnt_present() {
1455 let tempdir = tempfile::tempdir().unwrap();
1456 let path = tempdir.path().join("cookie");
1457 std::fs::write(&path, "foobar").unwrap();
1458 assert!(matches!(Auth::CookieFile(path).get_user_pass(), Err(Error::InvalidCookieFile)));
1459 }
1460}