redgold_keys/monero/
node_wrapper.rs

1use std::env::home_dir;
2use std::io::Write;
3use std::path::PathBuf;
4use log::info;
5use monero_rpc::BalanceData;
6use crate::monero::rpc_core::MoneroRpcWrapper;
7use crate::monero::rpc_multisig::{ExchangeMultisigKeysResult, MakeMultisigResult, TransferResult};
8use crate::word_pass_support::WordsPassNodeConfig;
9use redgold_common_no_wasm::ssh_like::{LocalSSHLike, SSHOrCommandLike, SSHProcessInvoke};
10use redgold_schema::conf::node_config::NodeConfig;
11use redgold_schema::errors::into_error::ToErrorInfo;
12use redgold_schema::observability::errors::Loggable;
13use redgold_schema::proto_serde::ProtoSerde;
14use redgold_schema::structs::{Address, CurrencyAmount, ErrorInfo, ExternalTransactionId, Hash, MoneroMultisigFormationRequest, MultipartyIdentifier, NetworkEnvironment, PublicKey, RoomId, SupportedCurrency, Weighting};
15use redgold_schema::message::Request;
16use redgold_schema::util::lang_util::{AnyPrinter};
17use redgold_schema::{ErrorInfoContext, RgResult, SafeOption, ShortString};
18use serde::{Deserialize, Serialize};
19use redgold_common::external_resources::PeerBroadcast;
20use redgold_schema::config_data::RpcUrl;
21use redgold_schema::helpers::easy_json::EasyJsonDeser;
22use crate::monero::to_address::ToMoneroAddress;
23use crate::TestConstants;
24use crate::util::mnemonic_support::MnemonicSupport;
25
26use super::rpc_multisig::{DescribeTransferResult, SignedMultisigTxset, TransferSplitResult};
27
28/// This is the main interface the internal node process uses to interact with the Monero network.
29/// It should primarily and only be used for multi-sig operations
30/// It is not thread-safe, interactions with the docker process running the wallet daemon
31/// should be done in a single-threaded manner.
32/// More importantly, each interaction updates this interface's internal state (representing the
33/// wallets external state)
34#[derive(Clone)]
35pub struct MoneroNodeRpcInterfaceWrapper<S: SSHOrCommandLike> {
36    pub wallet_rpc: MoneroRpcWrapper,
37    pub daemon_rpc: MoneroRpcWrapper,
38    pub state : MoneroWalletMultisigRpcState,
39    pub cmd: S,
40    pub wallet_dir: String,
41    pub wallet_expect_contents: String,
42    pub allow_deletes: bool,
43    pub create_states: Vec<MoneroWalletMultisigRpcState>,
44    pub history: Vec<StateHistoryItem>,
45}
46
47
48#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
49pub struct PartySecretInstanceData {
50    pub address: Address,
51    pub monero_history: Option<Vec<StateHistoryItem>>,
52}
53
54
55#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
56pub struct PartySecretData {
57    pub instances: Vec<PartySecretInstanceData>
58}
59
60
61#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
62pub struct StateHistoryItem {
63    pub input_state: MoneroWalletMultisigRpcState,
64    pub output_state: MoneroWalletMultisigRpcState,
65    pub input_peer_strings: Option<Vec<String>>,
66    pub input_threshold: Option<i64>,
67    pub restore_height: Option<i64>,
68}
69
70#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
71pub enum MoneroWalletMultisigRpcState {
72    #[default]
73    Unknown,
74    Prepared(String),
75    Made(MakeMultisigResult),
76    Exchanged(ExchangeMultisigKeysResult),
77    Finalized(String),
78    MultisigReadyToSend
79}
80
81impl MoneroWalletMultisigRpcState {
82
83    pub fn is_before_final_state(&self) -> bool {
84        match &self {
85            MoneroWalletMultisigRpcState::Made(_) => true,
86            _ => false
87        }
88    }
89    pub fn multisig_address_str(&self) -> Option<String> {
90        match self {
91            MoneroWalletMultisigRpcState::Unknown => None,
92            MoneroWalletMultisigRpcState::Prepared(_) => None,
93            MoneroWalletMultisigRpcState::Made(m) => Some(m.address.clone()),
94            MoneroWalletMultisigRpcState::Exchanged(e) => Some(e.address.clone()),
95            MoneroWalletMultisigRpcState::Finalized(f) => Some(f.clone()),
96            MoneroWalletMultisigRpcState::MultisigReadyToSend => {None}
97        }
98    }
99
100    pub fn multisig_typed_address_external(&self) -> Option<Address> {
101        // TODO: is this right address? internal or external?
102        self.multisig_address_str().map(|s| Address::from_monero_external(&s))
103    }
104
105    pub fn multisig_info_string(&self) -> Option<String> {
106        match self {
107            MoneroWalletMultisigRpcState::Unknown => {None}
108            MoneroWalletMultisigRpcState::Prepared(s) => {Some(s.clone())}
109            MoneroWalletMultisigRpcState::Made(m) => {Some(m.multisig_info.clone())}
110            MoneroWalletMultisigRpcState::Exchanged(e) => {Some(e.multisig_info.clone())}
111            MoneroWalletMultisigRpcState::Finalized(e) => { None }
112            _ => { None}
113        }
114    }
115
116}
117
118impl<S: SSHOrCommandLike> MoneroNodeRpcInterfaceWrapper<S> {
119
120
121    pub fn reset(&mut self) {
122        self.create_states = vec![];
123        self.history = vec![];
124        self.state = MoneroWalletMultisigRpcState::Unknown;
125    }
126
127    pub fn any_multisig_addr_creation(&self) -> Option<String> {
128        self.create_states.iter()
129            .filter_map(|s| s.multisig_address_str())
130            .filter(|s| !s.is_empty())
131            .next()
132    }
133
134    pub fn any_multisig_addr_history(&self) -> Option<String> {
135        self.history.iter()
136            .filter_map(|s| s.output_state.multisig_address_str())
137            .filter(|s| !s.is_empty())
138            .next()
139    }
140
141    pub fn from_config_local(
142        nc: &NodeConfig,
143        wallet_dir: impl Into<String>,
144        expect_path: impl Into<String>,
145        allow_deletes: Option<bool>,
146    ) -> Option<RgResult<MoneroNodeRpcInterfaceWrapper<LocalSSHLike>>> {
147        let local_ssh_like = LocalSSHLike::new(None);
148        MoneroNodeRpcInterfaceWrapper::<LocalSSHLike>::from_config(nc, local_ssh_like, wallet_dir, expect_path, allow_deletes)
149    }
150
151    pub fn from_config(
152        nc: &NodeConfig,
153        cmd: S,
154        wallet_dir: impl Into<String>,
155        expect_contents: impl Into<String>,
156        allow_deletes: Option<bool>,
157    ) -> Option<RgResult<MoneroNodeRpcInterfaceWrapper<S>>> {
158        let allow_deletes = allow_deletes.unwrap_or(false);
159        let wallet_dir = wallet_dir.into();
160        // daemon
161        let daemon_opt = MoneroRpcWrapper::from_config(nc);
162        // wallet
163        let wallet_opt = MoneroRpcWrapper::authed_from_config(nc);
164        let exp = expect_contents.into();
165
166        Self::from_daemons(cmd, allow_deletes, wallet_dir, exp, daemon_opt, wallet_opt)
167    }
168
169    fn from_daemons(
170        cmd: S,
171        allow_deletes: bool,
172        wallet_dir: String,
173        wallet_exp_path: String,
174        daemon_opt: Option<RgResult<MoneroRpcWrapper>>,
175        wallet_opt: Option<RgResult<MoneroRpcWrapper>>
176    ) -> Option<Result<MoneroNodeRpcInterfaceWrapper<S>, ErrorInfo>> {
177        daemon_opt.and_then(
178            |daemon_result|
179                wallet_opt.map(|wallet_result|
180                    wallet_result.and_then(|wallet| daemon_result.map(|daemon|
181                        MoneroNodeRpcInterfaceWrapper::<S> {
182                            wallet_rpc: wallet,
183                            daemon_rpc: daemon,
184                            state: MoneroWalletMultisigRpcState::Unknown,
185                            cmd,
186                            wallet_dir,
187                            wallet_expect_contents: wallet_exp_path,
188                            allow_deletes,
189                            create_states: vec![],
190                            history: vec![],
191                        }))
192                )
193        )
194    }
195
196    pub async fn set_as_multisig_self(&mut self, x: &String,
197                                      restore_height: Option<i64>,
198                                        restore_from_daemon_cur: bool
199    ) -> RgResult<()> {
200        let mut restore_height = restore_height;
201        if restore_height.is_none() && restore_from_daemon_cur {
202            restore_height = Some(self.daemon_rpc.daemon_height().await?);
203            println!("Setting restore height to {}", restore_height.clone().unwrap());
204        }
205        self.wallet_rpc.register_self_activate_ok(
206            Some(x.clone()),
207            restore_height,
208        ).await
209    }
210
211    fn multisig_filename_prefix() -> Option<String> {
212        Some("multisig".to_string())
213    }
214
215    //
216    // pub async fn restore(&mut self, inst: &PartySecretInstanceData) -> RgResult<()> {
217    //
218    // }
219
220    pub async fn restore_from_history(
221        &mut self,
222        h: Vec<StateHistoryItem>, wallet_filename: &String
223    ) -> RgResult<()> {
224        let restore = h.iter().filter_map(|h| h.restore_height).min().unwrap_or(0);
225        self.prepare_wallet_fnm_and_set_multisig(wallet_filename, Some(restore), false).await?;
226        let mut m = self.wallet_rpc.get_multisig()?;
227        let is_ms = m.is_multisig().await?;
228        // if is_ms.multisig {
229        //     return "Wallet is already multisig, did not reset properly".to_error();
230        // }
231        for item in h.iter() {
232            self.multisig_create_next(
233                item.input_peer_strings.clone(),
234                item.input_threshold.clone(),
235                wallet_filename,
236                true,
237                2
238            ).await?;
239        }
240        Ok(())
241    }
242
243    pub fn get_secret(&self) -> RgResult<PartySecretInstanceData> {
244        Ok(PartySecretInstanceData {
245            address: Address::from_monero_external(&self.any_multisig_addr_creation().ok_msg("No multisig address found")?),
246            monero_history: Some(self.history.clone()),
247        })
248    }
249
250    /*
251    The correct sequence for multisig wallet setup would now be:
252
253    prepare_multisig()
254    make_multisig()
255    exchange_multisig_keys()
256    finalize_multisig() (for N-1/N wallets)
257
258    Then for transactions:
259
260    export_multisig_info() and import_multisig_info() to sync wallet states
261    Create the transaction using regular wallet RPC methods
262    sign_multisig() to sign the transaction
263    submit_multisig() to broadcast it
264     */
265    pub async fn start_multisig_creation(
266        &mut self,
267        wallet_filename: &String,
268        skip_init: bool
269    ) -> RgResult<String> {
270
271        if !skip_init {
272            self.prepare_wallet_fnm_and_set_multisig(wallet_filename, None, true).await?;
273        }
274        let mut m = self.wallet_rpc.get_multisig()?;
275        let is_ms = m.is_multisig().await?;
276        println!("Is multisig: {:?}", is_ms);
277        // let prepare_result = if is_ms.multisig {
278        //     let ms_info = m.export_multisig_info().await?;
279        //     println!("Exported multisig info: {:?}", ms_info);
280        //     return "Wallet is already multisig".to_error();
281        // } else {
282        let prepare_result = m.prepare_multisig().await?;
283        // {
284        //     Ok(a) => {
285        //         a
286        //     }
287        //     Err(e) => {
288        //         println!("Error start multisig creation prepare multisig {}", e.message);
289        //         m.export_multisig_info().await?
290        //     }
291        // };
292        // };
293        self.state = MoneroWalletMultisigRpcState::Prepared(prepare_result.clone());
294
295        // let ret = rpc.multisig_create_next(Some(peer_strs.clone()), Some(2), &mpi).await.unwrap();
296        println!("Created wallet for peer {:?}", prepare_result.clone());
297
298        Ok(prepare_result)
299    }
300
301    pub async fn refresh_sync_check_daemon_against_wallet(&self) -> RgResult<i64> {
302        let wallet = self.wallet_rpc.clone();
303        self.daemon_rpc.refresh_sync_check_daemon_against_wallet(wallet).await
304    }
305
306    pub async fn get_balance_all_info(&self) -> RgResult<BalanceData> {
307        self.wallet_rpc.clone().client.wallet().get_balance(0, None).await
308            .map_err(|e| "Failed to get balance".to_error_info().enhance(e.to_string()))
309    }
310
311
312    pub async fn prepare_wallet_fnm_and_set_multisig(
313        &mut self,
314        wallet_filename_relative: &String,
315        restore_height: Option<i64>,
316        restore_from_daemon_cur: bool
317    ) -> Result<(), ErrorInfo> {
318        let wallet_directory = self.wallet_dir.clone();
319
320        println!("Wallet relative filename on prepare and open: {:?}", wallet_filename_relative.clone());
321        println!("Wallet directory on prepare and open: {:?}", wallet_directory.clone());
322        self.wallet_rpc.close_wallet().await.log_error().ok();
323        // if self.allow_deletes {
324        //     let string = format!("rm -rf {}/*", p);
325        //     println!("Deleting wallet {}", string.clone());
326        //     self.cmd.execute(string, None).await.unwrap().print();
327        //     println!("deleted wallet");
328        // }
329        self.state = MoneroWalletMultisigRpcState::Unknown;
330        self.set_as_multisig_self(
331            &wallet_filename_relative,
332            restore_height,
333            restore_from_daemon_cur
334        ).await?;
335
336        // Close wallet so we can reactivate it as multisig
337        self.wallet_rpc.close_wallet().await.log_error().ok();
338        // let execute_ls_command = format!("ls {}/{}* | grep -v \"\\.keys$\"", wallet_directory, wallet_filename_relative.clone());
339        // println!("Executing ls command: {:?}", execute_ls_command.clone());
340        // let f = self.cmd.execute(execute_ls_command, None).await?;
341        // let remote_filename = f.replace("\n", "");
342        // println!("filename found on remote for wallet set multisig: {:?}", remote_filename.clone());
343        let remote_wallet_filename = format!("{}/{}", wallet_directory, wallet_filename_relative.clone());
344        let exp = self.wallet_expect_contents.replace("{{WALLET_FILENAME}}", &*remote_wallet_filename);
345        let local_fnm = "wallet.exp";
346        std::fs::remove_file(local_fnm).ok();
347        std::fs::write(local_fnm, exp).error_info("Failed to write expect file")?;
348        let expect_remote_filename = format!("{}_wallet.exp", remote_wallet_filename);
349        let copied = self.cmd.scp(local_fnm, expect_remote_filename.clone(), true, None).await?;
350        println!("Copied expect file: {:?} to dest {}", copied.clone(), expect_remote_filename.clone());
351
352        let expect_cmd = format!("expect {}", expect_remote_filename.clone());
353        println!("expect command: {:?}", expect_cmd.clone());
354        let result = self.cmd.execute(expect_cmd, None).await?;
355        if result.find("enable-multisig-experimental = 0").is_some() {
356            println!("Failed to enable multisig: {}", result.clone());
357        }
358        else {
359            println!("Enabled multisig: {}", result.clone());
360        }
361        println!("Worked!");
362        self.wallet_rpc.open_wallet_filename_exact_no_prefix(wallet_filename_relative.clone()).await?;
363        Ok(())
364    }
365
366
367    pub async fn multisig_create_loop<B>(
368        &mut self,
369        all_pks: &Vec<PublicKey>,
370        threshold: i64,
371        peer_broadcast: &B
372    ) -> RgResult<PartySecretInstanceData> where B: PeerBroadcast {
373        let b = Self::get_wallet_filename_id(all_pks, threshold);
374        let wallet_filename = b;
375        let mut peer_strs = vec![];
376        loop {
377            let next = self.multisig_create_next(
378                Some(peer_strs.clone()), Some(threshold), &wallet_filename, false, 2).await?;
379            if next == MoneroWalletMultisigRpcState::MultisigReadyToSend {
380                break;
381            }
382            let info_str = next.multisig_info_string().ok_msg("No multisig info string from self")?;
383            let mut req = Request::default();
384            req.monero_multisig_formation_request = Some(MoneroMultisigFormationRequest {
385                public_keys: all_pks.clone(),
386                threshold: Some(Weighting::from_int_basis(threshold, all_pks.len() as i64)),
387                peer_strings: peer_strs.clone(),
388            });
389
390            let mut new_peer_strs = vec![info_str];
391            // TODO: fix this with retries and or elimination of a particular peer that is failing.
392            for r in peer_broadcast.broadcast(&all_pks.clone(), req).await? {
393                let r = r?;
394                let r = r.monero_multisig_formation_response.ok_msg("No response from peer")?;
395                new_peer_strs.push(r);
396            }
397            peer_strs = new_peer_strs;
398        }
399        self.get_secret()
400    }
401
402    pub fn get_wallet_filename_id(all_pks: &Vec<PublicKey>, threshold: i64) -> String {
403        let mut wallet_ident = vec![];
404        all_pks.iter().for_each(|pk| wallet_ident.extend(pk.vec()));
405        threshold.to_le_bytes().iter().for_each(|b| wallet_ident.push(*b));
406        let b = Hash::digest(wallet_ident).raw_bytes_hex().unwrap();
407        b
408    }
409
410    pub async fn multisig_create_next(
411        &mut self,
412        peer_strings: Option<Vec<String>>,
413        threshold: Option<i64>,
414        wallet_filename: &String,
415        skip_initial: bool,
416        max_num_rounds: i64
417    ) -> RgResult<MoneroWalletMultisigRpcState> {
418
419        let mut history_item = StateHistoryItem {
420            input_state: self.state.clone(),
421            output_state: MoneroWalletMultisigRpcState::Unknown,
422            input_peer_strings: peer_strings.clone(),
423            input_threshold: threshold.clone(),
424            restore_height: Some(self.daemon_rpc.daemon_height().await?),
425        };
426        match self.state.clone() {
427            MoneroWalletMultisigRpcState::Unknown => {
428                println!("starting");
429                self.start_multisig_creation(&wallet_filename, skip_initial).await?;
430                println!("Finished preparing")
431            }
432            MoneroWalletMultisigRpcState::Prepared(_) => {
433                println!("making");
434                let res = self.make_multisig(
435                    peer_strings.ok_msg("missing peer strings in make multisig")?,
436                    threshold.ok_msg("Missing threshold on make multisig")?
437                ).await?;
438                self.state = MoneroWalletMultisigRpcState::Made(res.clone());
439            }
440            MoneroWalletMultisigRpcState::Made(_) => {
441                // let mut m = self.wallet_rpc.get_multisig()?;
442                // let f = m.finalize_multisig(
443                //     peer_strings.ok_msg("missing peer strings in finalize multisig")?,
444                //     "".to_string(),
445                // ).await?;
446                // self.state = MoneroWalletMultisigRpcState::Finalized(f);
447                let mut res = self.exchange_multisig_keys(
448                    peer_strings.ok_msg("missing peer strings in exchange multisig")?
449                ).await?;
450                res.input_round = 0;
451                res.output_round = 1;
452                self.state = MoneroWalletMultisigRpcState::Exchanged(res.clone());
453            }
454            MoneroWalletMultisigRpcState::Exchanged(e) => {
455                if e.output_round == max_num_rounds {
456                    self.state = MoneroWalletMultisigRpcState::MultisigReadyToSend;
457                } else {
458                    let mut res = self.exchange_multisig_keys(
459                        peer_strings.ok_msg("missing peer strings in exchange multisig")?
460                    ).await?;
461                    res.input_round = e.output_round;
462                    res.output_round = e.output_round + 1;
463                    self.state = MoneroWalletMultisigRpcState::Exchanged(res.clone());
464                }
465                // let mut m = self.wallet_rpc.get_multisig()?;
466                // let f = m.finalize_multisig(
467                //     peer_strings.ok_msg("missing peer strings in finalize multisig")?,
468                //     "".to_string(),
469                // ).await?;
470                // self.state = MoneroWalletMultisigRpcState::Finalized(f);
471            }
472            MoneroWalletMultisigRpcState::Finalized(a) => {
473                self.state = MoneroWalletMultisigRpcState::MultisigReadyToSend;
474            }
475            MoneroWalletMultisigRpcState::MultisigReadyToSend => {
476                return "Multisig wallet is ready to send transactions, but create next called".to_error()
477            }
478        }
479        self.create_states.push(self.state.clone());
480        history_item.output_state = self.state.clone();
481        self.history.push(history_item);
482        Ok(self.state.clone())
483    }
484
485    fn _id_to_filename(id: &MultipartyIdentifier) -> String {
486        let string = id.proto_serialize_hex().last_n(12).unwrap();
487        format!("{}_{}", Self::multisig_filename_prefix().unwrap(), string)
488    }
489
490    pub async fn multisig_send_prepare_and_sign(
491        &mut self,
492        amounts: Vec<(Address, CurrencyAmount)>
493    ) -> RgResult<(TransferResult, SignedMultisigTxset)> {
494        let mut m: super::rpc_multisig::MoneroWalletRpcMultisigClient = self.wallet_rpc.get_multisig()?;
495
496        let mut vec = vec![];
497
498        for (addr, amount) in amounts.iter() {
499            vec.push((addr.render_string()?, amount.amount as u64));
500        }
501        // Create the transaction
502        let tx = m.transfer(vec, None, None).await?;
503        
504        // Sign our portion of the multisig transaction
505        let signed = m.sign_multisig(tx.multisig_txset.clone()).await?;
506        
507        Ok((tx, signed))
508    }
509
510    pub async fn make_multisig(&mut self, peer_strings: Vec<String>, threshold: i64) -> RgResult<MakeMultisigResult> {
511        let mut m = self.wallet_rpc.get_multisig()?;
512        let make_result = m.make_multisig(peer_strings, threshold as u32, "".to_string()).await?;
513        Ok(make_result)
514    }
515
516    pub async fn exchange_multisig_keys(&mut self, peer_strings: Vec<String>) -> RgResult<ExchangeMultisigKeysResult> {
517        let mut m = self.wallet_rpc.get_multisig()?;
518        let exchanged = m.exchange_multisig_keys(peer_strings, "".to_string(), None).await?;
519        Ok(exchanged)
520    }
521
522    // This seems to throw an error if the exchange has already proceeded with all ?
523    // pub async fn finalize_multisig(&mut self, infos: Vec<String>) -> RgResult<String> {
524    //     let mut m = self.wallet_rpc.get_multisig()?;
525    //     let finalized = m.finalize_multisig(infos, "".to_string()).await?;
526    //     self.state = MoneroWalletMultisigRpcState::Finalized(finalized.clone());
527    //     Ok(finalized)
528    // }
529
530    // Export multisig info for other participants
531    pub async fn export_multisig_info(&mut self) -> RgResult<String> {
532        let mut m = self.wallet_rpc.get_multisig()?;
533        Ok(m.export_multisig_info().await?)
534    }
535
536    // Import multisig info from other participants
537    pub async fn import_multisig_info(&mut self, info: Vec<String>) -> RgResult<u64> {
538        let mut m = self.wallet_rpc.get_multisig()?;
539        Ok(m.import_multisig_info(info).await?)
540    }
541
542    // Submit a fully signed multisig transaction
543    pub async fn submit_multisig_transaction(&mut self, tx_data_hex: String) -> RgResult<Vec<String>> {
544        let mut m = self.wallet_rpc.get_multisig()?;
545        Ok(m.submit_multisig(tx_data_hex).await?)
546    }
547
548    // Helper function to describe a transaction before signing
549    pub async fn describe_multisig_transaction(&mut self, unsigned_txset: String) -> RgResult<DescribeTransferResult> {
550        let mut m = self.wallet_rpc.get_multisig()?;
551        Ok(m.describe_transfer(unsigned_txset).await?)
552    }
553
554}
555
556
557pub fn rpcs(seed_id: i64) -> Vec<RpcUrl> {
558    let password = std::env::var("MONERO_TEST_RPC_PASSWORD").unwrap();
559    vec![
560        RpcUrl{
561            currency: SupportedCurrency::Monero,
562            url: format!("http://server:28{}88", seed_id),
563            network: NetworkEnvironment::Main.to_std_string(),
564            wallet_only: Some(true),
565            authentication: Some(format!("username:{}", password)),
566            file_path: None,
567            ws_only: None,
568            ssh_host: None,
569        },
570        RpcUrl{
571            currency: SupportedCurrency::Monero,
572            url: "http://server:18089".to_string(),
573            network: NetworkEnvironment::Main.to_std_string(),
574            wallet_only: Some(false),
575            authentication: None,
576            file_path: None,
577            ws_only: None,
578            ssh_host: Some("server".to_string()),
579        },
580    ]
581
582}
583
584// #[ignore]
585#[tokio::test]
586async fn local_three_node() {
587
588    if std::env::var("REDGOLD_DEBUG_DEVELOPER").is_err() {
589        return;
590    }
591
592    let ci = TestConstants::test_words_pass().unwrap();
593    let ci1 = ci.hash_derive_words("1").unwrap();
594    let ci2 = ci.hash_derive_words("2").unwrap();
595    let path = TestConstants::dev_ci_kp_path();
596    let pkh = ci.private_at(path.clone()).unwrap();
597    let pkh1 = ci1.private_at(path.clone()).unwrap();
598    let pkh2 = ci2.private_at(path.clone()).unwrap();
599    let net = NetworkEnvironment::Main;
600    // let addr = ci.public_at(path.clone()).unwrap().to_monero_address_from_monero_public_format(&net).unwrap();
601    // let addr1 = ci1.public_at(path.clone()).unwrap().to_monero_address_from_monero_public_format(&net).unwrap();
602    // let addr2 = ci2.public_at(path.clone()).unwrap().to_monero_address_from_monero_public_format(&net).unwrap();
603
604    // temp testing only
605    let mut s = SSHProcessInvoke::new("server", None);
606    let user = std::env::var("USER").unwrap();
607    s.user = Some(user.clone());
608
609    let mut one = NodeConfig::from_test_id(&(1 as u16));
610    let mut two = NodeConfig::from_test_id(&(2 as u16));
611    let mut three = NodeConfig::from_test_id(&(3 as u16));
612    let mut four = NodeConfig::from_test_id(&(4 as u16));
613
614    one.set_words(ci.words.clone());
615    two.set_words(ci1.words.clone());
616    three.set_words(ci2.words.clone());
617    // let words_25 = std::env::var("MONERO_HOT_SEED").unwrap();
618    // let words_24 = words_25.split(" ").take(24).collect::<Vec<&str>>().join(" ");
619    four.set_words(ci.words.clone());
620    // four.set_words(words_24);
621    one.network = NetworkEnvironment::Main;
622    two.network = NetworkEnvironment::Main;
623    three.network = NetworkEnvironment::Main;
624    four.network = NetworkEnvironment::Main;
625
626    one.set_rpcs(rpcs(1));
627    two.set_rpcs(rpcs(2));
628    three.set_rpcs(rpcs(3));
629    four.set_rpcs(rpcs(4));
630
631
632    let delete = false;
633    let mut one_rpc = MoneroNodeRpcInterfaceWrapper::from_config(
634        &one, s.clone(), "/disk/monerotw2","~/wallet.exp".to_string(), Some(delete),
635    ).unwrap().unwrap();
636
637    let mut two_rpc = MoneroNodeRpcInterfaceWrapper::from_config(
638        &two, s.clone(), "/disk/monerotw3","~/wallet.exp".to_string(), Some(delete)).unwrap().unwrap();
639    let mut three_rpc = MoneroNodeRpcInterfaceWrapper::from_config(
640        &three, s.clone(), "/disk/monerotw4","~/wallet.exp".to_string(), Some(delete)).unwrap().unwrap();
641    let mut four_rpc = MoneroNodeRpcInterfaceWrapper::from_config(
642        &four, s.clone(), "/disk/monerow","~/wallet.exp".to_string(), Some(false)).unwrap().unwrap();
643    
644    let pub_keys = vec![
645        one.public_key.clone(),
646        two.public_key.clone(),
647        three.public_key.clone()
648    ];
649    // let mut mpi = MultipartyIdentifier::default();
650    // mpi.party_keys = pub_keys.clone();
651    // mpi.threshold = Some(Weighting::from_int_basis(2, 3));
652    // mpi.room_id = Some(RoomId{
653    //     uuid: Some("test".to_string()),
654    // });
655    let fnm = "test_multisig".to_string();
656    // let fnm = mpi.proto_serialize_hex().first_n(12).unwrap();
657
658    println!("Starting test on fnm {}", fnm.clone());
659    //
660    let mut rpc_vecs = vec![one_rpc.clone(), two_rpc.clone(), three_rpc.clone()];
661
662
663    let mut peer_strs: Vec<String> = vec![];
664
665
666    let mut loop_vec = vec![];
667    loop {
668        let mut new_peer_strs = vec![];
669        let mut last_ret = MoneroWalletMultisigRpcState::Unknown;
670
671        for (idx, rpc) in rpc_vecs.iter_mut().enumerate() {
672            let this_peer_strs = peer_strs.iter().enumerate().filter(
673                |(i, _)| *i != idx
674            ).map(|(_, s)| s.clone()).collect::<Vec<String>>();
675            let ret = rpc.multisig_create_next(
676                Some(this_peer_strs.clone()),
677                Some(2),
678                &fnm,
679                true,
680                2
681            ).await.unwrap();
682
683            if idx == 0 {
684                loop_vec.push(ret.clone());
685            }
686            println!("DONE wallet for peer {:?}", ret);
687            ret.multisig_info_string().map(|ss| new_peer_strs.push(ss));
688            if let Some(a) = ret.multisig_address_str() {
689                if !a.is_empty() {
690                    println!("Multisig address: {}", a);
691                }
692            }
693           last_ret = ret;
694        }
695        peer_strs = new_peer_strs.clone();
696        if let MoneroWalletMultisigRpcState::MultisigReadyToSend = last_ret {
697            break;
698        }
699    }
700
701    //
702    let mut rpc_addrs_0 = vec![];
703    //
704    // for (i, h) in rpc_vecs.iter_mut().enumerate() {
705    //     let path = home_dir().unwrap().join("multisig_history_".to_string() + &i.to_string());
706    //     // delete if already exists:
707    //
708    //     let data = std::fs::read_to_string(path.clone()).ok().unwrap().json_from::<PartySecretInstanceData>().unwrap();
709    //     let history = data.monero_history.clone().unwrap();
710    //
711    //     println!("Restoring from history: {:?}", history.len());
712    //     for i in history.iter() {
713    //         println!("History item: {:?}", i);
714    //     }
715    //     println!("------------");
716    //     h.state = MoneroWalletMultisigRpcState::Unknown;
717    //     h.wallet_rpc.close_wallet().await.unwrap();
718    //     h.wallet_rpc.open_wallet_filename(fnm.clone()).await.unwrap();
719    //     // h.restore_from_history(data.monero_history.unwrap(), &fnm).await.unwrap();
720    //     for item in history {
721    //         println!("Restoring from history item: {:?}", item);
722    //         h.state = item.input_state.clone();
723    //         if let Err(e) = h.multisig_create_next(item.input_peer_strings.clone(), item.input_threshold.clone(), &fnm.clone()).await {
724    //             println!("Error: {:?}", e.message);
725    //         }
726    //     }
727    //
728    // }
729
730    let mut msig_strs = vec![];
731
732    for (i, h) in rpc_vecs.iter_mut().enumerate() {
733        let info = h.export_multisig_info().await.unwrap();
734        msig_strs.push(info);
735    }
736    for (i, h) in rpc_vecs.iter_mut().enumerate() {
737        let info = h.import_multisig_info(msig_strs.clone()).await.unwrap();
738        println!("Imported multisig info: {:?}", info);
739    }
740
741    for (i, h) in rpc_vecs.iter_mut().enumerate() {
742        println!("Multisig address: {:?}", h.any_multisig_addr_history());
743        println!("Multisig balance {:?}", h.wallet_rpc.get_balance().await.unwrap().to_fractional());
744        let a = h.wallet_rpc.client.clone().wallet().get_address(0, None).await
745            .unwrap().address.to_string();
746        rpc_addrs_0.push(a.clone());
747        println!("RPC Address: {}", a)
748
749        // std::fs::remove_file(path.clone()).ok();
750        // let mut file = std::fs::File::create(path).unwrap();
751        // let secret = h.get_secret().unwrap();
752        // let secret = serde_json::to_string(&secret).unwrap();
753        // file.write_all(secret.as_bytes()).unwrap();
754    }
755
756
757    //
758    // let mut one_rpc_replicated = MoneroNodeRpcInterfaceWrapper::from_config(
759    //     &one, s.clone(), "/disk/monerotw2","~/wallet.exp".to_string(), Some(true)
760    // ).unwrap().unwrap();
761    //
762    // one_rpc_replicated.restore_from_history(history, &"test_filename_differenet".to_string()).await.unwrap();
763    //
764    // let addr = one_rpc_replicated.any_multisig_addr_creation().unwrap();
765    // println!("Restored wallet address {}", addr);
766
767    //
768    // let msig = rpc_vecs[0].clone();
769    // let addr = msig.any_multisig_addr_creation().unwrap();
770    // let balance_of_multisig = msig.wallet_rpc.get_balance().await.unwrap();
771    // println!("Balance of multisig: {:?}", balance_of_multisig.to_fractional());
772    //
773
774
775    //
776    println!("Done");
777    four_rpc.wallet_rpc.register_self_activate_ok(Some("hot".to_string()), None).await.unwrap();
778    // four_rpc.wallet_rpc.sync_info()
779    let sync_info = four_rpc.wallet_rpc.refresh_sync_check_wallet().await.expect("refresh");
780    println!("sync info done: {:?}", sync_info);
781    // let refresh = rpc.client.clone().wallet().refresh(None).await.expect("refresh");
782    let b = four_rpc.wallet_rpc.get_balance().await.unwrap();
783    println!("Balance: {:?}", b);
784    println!("Balance: {:?}", b.to_fractional());
785    println!("Address {}", four_rpc.wallet_rpc.self_address_str().unwrap());
786
787    //
788    //
789    //
790    // MULTISIG SEND
791    let dest = four_rpc.wallet_rpc.self_address().unwrap();
792    let amt =  CurrencyAmount::from_fractional_cur(0.004f64, SupportedCurrency::Monero).unwrap();
793    let send = vec![(dest, amt)];
794    let mut one = rpc_vecs.get(0).cloned().unwrap();
795    let (prep, tx) = one.multisig_send_prepare_and_sign(send).await.unwrap();
796    println!("Prepared: {:?}", prep);
797    println!("Tx: {:?}", tx);
798    let two = rpc_vecs.get(1).cloned().unwrap();
799    let res = two.wallet_rpc.clone().get_multisig().unwrap()
800        .sign_multisig(prep.multisig_txset.clone()).await.unwrap();
801    println!("Sign: {:?}", res);
802    // let finalized = one.wallet_rpc.clone().get_multisig().unwrap()
803    //     .finalize_multisig(vec![tx.tx_data_hex, res.tx_data_hex], "".to_string()).await.unwrap();
804    // println!("Finalized: {:?}", finalized);
805    // let submit = one.wallet_rpc.clone().get_multisig().unwrap()
806    //     .submit_multisig(finalized).await.unwrap();
807    // println!("Submit: {:?}", submit);
808
809
810    //
811    // for (cat, tx) in four_rpc.wallet_rpc.debug_get_all_tx().await.unwrap() {
812    //     for t in tx {
813    //         println!("Tx: {:?} {:?}", cat, t);
814    //     }
815    // }
816    //
817    // let destinations = vec![
818    //     (Address::from_monero_external(&rpc_addrs_0.get(0).cloned().unwrap()),
819    //     CurrencyAmount::from_fractional_cur(0.011f64, SupportedCurrency::Monero).unwrap())
820    // ];
821    // let tx = four_rpc.wallet_rpc.send(destinations).await.unwrap();
822    // println!("Tx: {}", tx);
823
824    // let amt = b.to_fractional() / 10;
825    //
826    // let (tx, signed) = four_rpc.multisig_send_prepare_and_sign(vec![(Address::from_monero_external(&addr),
827    // CurrencyAmount::from_fractional_cur(100_000_000_000, SupportedCurrency::Monero))]).await.unwrap();
828    // println!("Tx: {}", tx);
829
830}
831
832/*
833~/monero-wallet-cli --help
834./monero-wallet-cli --daemon-address http://127.0.0.1:18089 --generate-new-wallet /disk/monerotw2/manual_test2
835./monero-wallet-cli --daemon-address http://127.0.0.1:18089 --generate-new-wallet /disk/monerotw3/manual_test2
836./monero-wallet-cli --daemon-address http://127.0.0.1:18089 --generate-new-wallet /disk/monerotw4/manual_test2
837
838./monero-wallet-cli --daemon-address http://127.0.0.1:18089 --wallet-file /disk/monerotw2/manual_test
839set enable-multisig-experimental 1
840./monero-wallet-cli --daemon-address http://127.0.0.1:18089 --wallet-file /disk/monerotw3/manual_test
841set enable-multisig-experimental 1
842./monero-wallet-cli --daemon-address http://127.0.0.1:18089 --wallet-file /disk/monerotw4/manual_test
843set enable-multisig-experimental 1
844
845prepare_multisig
846make_multisig strs thresh
847
848./monero-wallet-cli --daemon-address http://127.0.0.1:18089 --wallet-file /disk/monerotw2/manual_test --command prepare_multisig > prep1
849
850 */