mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-22 01:28:32 +02:00
* Update rust edition to 2021 (#749) * build: update rust edition 2021 * cargo: format all * build: fix tests and warnings * Save last scanned block info for wallet scanning (#748) * scan: save last scanned block info for initial wallet scanning * scan: save start height at scanned block info on outputs collecting * scan: 10k blocks batches * fix: include last height into batch on scan * scan: save hash of last block * scan: add larger window (2880 blocks) to scan outputs when updating wallet state * add test batched scan and resume, add scan doc * rename corrupted restore test --------- Co-authored-by: Joerg <wiesche89@googlemail.com> * Bump openssl from 0.10.68 to 0.10.80 (#752) * Update lmdb (#755) * wallet: update lmdb from store crate, migrate single backend from trait to struct, update tests cargo deps and design doc * backend: do not panic on iter and directory creation * return iterator errors * build: update to last node revision * fix: height selection for scan, remove unused mutability for wallet * wallet: remove unused mutability conversion * tx: store slate state at database * build: cargo fmt * fix: do not return error when slate not found on tx slate state update * lmdb: do not collect all tx log records at iterator, show read errors at log * fix: save slate state, return error if slate not found * tx: specify amount of bad txs on slate state update error --------- Co-authored-by: Joerg <wiesche89@googlemail.com> * Node submodules (#758) * build: use node submodule as dependency * ci: checkout submodules * docs: add contributing and build instruction * ci: update release build * git: remove old submodule * git: grin staging submodule * git: add submodule dir * Integrated Tor arti Client (#750) * tor: integrated client for listener, update tokio to last version * tor: send transaction with integrated client * build: code format * tor: optimize bootstrap percent check * tor: handle config build error * tor: do not show slatepack address if tor not launched * tor: handle keystore errors * tor: keep service launched * tor: bootstrap timeout, clean cache before client launch * tor: post request timeout * fix: post timeout * build: update deps after merge * tor: store arti runtime * tor: do not send over tor if `manual` arg presents or `skip_send_attempt` set at config * fix: arg name * tor: handle arti runtime creation error * tor: handle connection error * tor: handle bootstrap error * tor: handle start service name error * tor: handle service config creation error * tor: service proxy errors * tor: handle service key creation emptiness * build: format code * build: update arti to 0.43, hyper to 0.10.1 * api: start foreign listener before tor connection * tor: handle json parse errors * tor: prevent to create several client runtimes, ability to recreate * tor: return bootstrap error * tor: handle host parse error * tor: restart proxy on error * tor: do not use integrated client when config value use_integrated is missing, add logging * build: update lock file * Global Tor client state (#762) * tor: global client state * tor: optimize init client thread-safety * tor: handle config change on client init * tor: add logging for client reuse or reinit * tor: do not reuse arti client for requests * Optimize tor sync flow (#763) * Connection timeout (#765) * Update deps (#768) * build: update grin submodule to last staging * build: update grin submodule * build: remove remove_dir_all dependency * build: update rpassword * build: update semver * build: remove unused deps * build: update ring * build: update lock file deps * build: update rustyline * build: update ed25519-dalek * build: update curve25519-dalek * build: update age * build: update uuid * fix: uuid test * build: update x25519-dalek * fix: x25519-dalek build * build: update arti * build: update grin submodule * build: update hyper * fix: initialize rustls provider for tests * build: update grin submodule * fix: rustls install provider * tor: share getting private key * fix: show an error on node version parse * cli: renamed prompt password method * fix: replace into_boxed_slice with try_from * fix: dalek tests for onion * config: comment expanded secret key * fix: import api body for controller * password: handle prompt error * wallet: parse node error message * tests: do not use random values for sign messages * build: make hook executable * build: cargo format all * rustls: do not install provider at tests and globally * build: update grin submodule * build: fix api channel from grin submodule * args: use read_password for piped prompt * build: remove unused dep * test: fixed signature * preserve piped pw input --------- Co-authored-by: wiesche <wiesche89@googlemail.com> * Bump lodash from 4.17.21 to 4.18.1 (#774) * build: update lodash to 4.18.1 * fix: readme * docs: add owner api authentication setup * build: update package-lock.json to version 3 * fix: token result, inline some vars --------- Co-authored-by: ardocrat <ardocrat@gri.mw> * Global config instance (#769) * config: global instance * fix: cli tests * fix: owner api tests * config: save tor config * fix: owner rpc tests * fix: set config file path on init and owner api call * tor: store launched services, get global config at command loop, remove unused listen args, check for empty global config to set default, do not save empty tor config at owner * api: update config in memory after successful file write * arti: restart proxy in loop * fix: comment for tor config save * config: set or init, update without error * config: scoped tor config * build: update lock file * config: ability to store multiple configurations at memory from config path * tor: restart service on config change * fix: owner api test * foreign: pass tor config from args, stop service, error handling * arti: move runtime wrapper to single file * config: read existing config from file at create_config * config: handle error on read * arti: single dir for cache * foreign: register config listener before launch * config: save temp * config: remove unused error type * config: make path and members not optional, return an error on config creation if it exists, pass default tor config to send if not set * arti: non public runtime wrapper * config: migrate comments * fix: rpc tests * config: return from cache if exists * config: save disabled tor config when null provided at owner and tor section was not set * config: update tor with args at listener * config: pass pathbuf directly on creation * config: unique tmp file name, setting permissions and providing config version * fix global config handling * fix config persistence * fix tor service restart * fix config synchronization * require wallet close before changing data directory * unify global config updates --------- Co-authored-by: wiesche <wiesche89@googlemail.com> * Wrong Slatepack address fix (#773) * Use native tls for client (#779) * client: use native tls and disable default features for reqwest * build: bring back reqwest default features except tls * client: remove tls selection * expose mwixnet onion apis (#781) * Update jayson and uuid (#777) * build: update jayson and uuid * npm: update lock file to v3 * npm: update lock file --------- Co-authored-by: ardocrat <ardocrat@gri.mw> * Optimize outputs scan and selection (#780) * Make updater APIs public (#782) * Add mwixnet wallet request support * mwixnet key conversion tests * validate and track mwixnet requests * fix Arti HTTP POST request formatting * add mwixnet wallet CLI submission * unlock mwixnet outputs when requests are not sent * support automatic mwixnet output selection * move mwixnet response parsing to libwallet * limit mwixnet hops * centralize grin workspace dependencies * fix tx filter * handle unavailable terminal colors * reject invalid invoice locking * clarify bip32 derivation paths * restore derivation path comments * tor: downgrade arti to 0.43 * owner: put active account on top of list * account table layout * fix: some typos, imports, unwraps and formatting * tx: lock outputs before sending with tor * accounts: display balances at CLI and from owner API when min confirmations are set * unpack plain slatepacks without password (#805) * unpack plain slatepacks without password * cli: better open wallet handling on wallet subcommand --------- Co-authored-by: ardocrat <ardocrat@gri.mw> * account: do not skip info from serialization, fix owner rpc test * reject zero send amounts (#799) * cli: fix short for min confirmations at accounts subcommand * accounts: optimize accounts selection * owner: document minimum_confirmations for accounts * accounts: optimize selection * preserve accounts api compatibi and clean up balances * avoid empty wallet seeds (#801) * fix empty wallet seeds * mnemonic: return actual error with entropy size on recovering * seed: extend mnemonic error description * seed: clarify mnemonic errors * seed: clarify mnemonic errors, fix missing phrase test * Clean up seed handling add test --------- Co-authored-by: ardocrat <ardocrat@gri.mw> * owner: use same wallet lock for selecting and locking outputs on send * fix: do not lock for late lock * cli: explain how to cancel * fix: error message for offline address and integrated tor * test: late lock for cli and owner api * wallet: persistence for active account * fix: owner_rpc test * fix: lock wallet till lock outputs on send * fix: update docs when to call tx_lock_outputs * build: update grin submodule * tests: get rid of owner_single_use to use correct config path * test: parallel send attempts * fix: cancel hint * fix: late lock test refresh balance * fix: set active account at single batch * owner: document persistence of set_active_account * cli: error when create and active args are present at account * fix: keep display account type as str * restore windows test fixes * fix locking docs cancel hint and tests * test cli send failure * fix current account flag * fix active account use * test account persistence * fix cli account selection * fix: cmd tests * fix: cmd test * fix account test * Show HTTP status when node requests fail (#820) * ci: cargo clippy * fix submodule checkout * update grin for clippy fixes * isolate tests * fix cli test * fix clippy warnings * fix mwixnet output lookup * build: update grin submodule * git: add clippy to hook * fix review nits * fix checks * mwixnet: use MwixnetServerPublicKey wrapper for hop * build: update grin submodule * build: clippy fixes * mwixnet: rename dalek public key * simplify mwixnet keys * clarify mwixnet docs * clarify arti usage * simplify mwixnet tests * tidy mwixnet code and docs * skip spent outputs * simplify mwixnet send * drop host clone * fix mwixnet selection and arti errors * make mwixnet fee optional * mwixnet: use fee at amount format, show min on error * fix: fee per hop test * fix: fee error text check * tor: increase circuit timeout to 60 * fix mwixnet refresh and help * build: update grin submodule and mwixnet tests * cli: do not update wallet state on cancellation of tx after failed mwixnet request * mwixnet: create separate owner api method to send request, do not cancel tx on failed mwixnet request * fix mwixnet cli args and error messages --------- Co-authored-by: ardocrat <ardocrat@gri.mw> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
325 lines
10 KiB
Rust
325 lines
10 KiB
Rust
// Copyright 2024 The Grin Developers
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! Test a wallet sending to self, then creation of comsig request
|
|
#[macro_use]
|
|
extern crate log;
|
|
extern crate grin_wallet_controller as wallet;
|
|
extern crate grin_wallet_impls as impls;
|
|
|
|
use grin_core as core;
|
|
use grin_core::core::FeeFields;
|
|
use grin_util as util;
|
|
use grin_util::secp::key::SecretKey;
|
|
use std::path::PathBuf;
|
|
|
|
use grin_wallet_libwallet as libwallet;
|
|
use impls::test_framework::{self, LocalWalletClient};
|
|
use libwallet::{
|
|
mwixnet::{MixnetReqCreationParams, MwixnetServerPublicKey, MAX_MWIXNET_HOPS},
|
|
InitTxArgs, OutputStatus, TxLogEntryType,
|
|
};
|
|
use std::sync::atomic::Ordering;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
#[macro_use]
|
|
mod common;
|
|
use common::{clean_output_dir, create_wallet_proxy, setup};
|
|
|
|
/// self send impl
|
|
fn mwixnet_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
|
// Create a new proxy to simulate server and wallet responses
|
|
let mut wallet_proxy = create_wallet_proxy(test_dir);
|
|
let chain = wallet_proxy.chain.clone();
|
|
let stopper = wallet_proxy.running.clone();
|
|
|
|
// Create a new wallet test client, and set its queues to communicate with the
|
|
// proxy
|
|
create_wallet_and_add!(
|
|
client1,
|
|
wallet1,
|
|
mask1_i,
|
|
test_dir,
|
|
"wallet1",
|
|
None,
|
|
&mut wallet_proxy,
|
|
true,
|
|
api1
|
|
);
|
|
let mask1 = mask1_i.as_ref();
|
|
|
|
// Set the wallet proxy listener running
|
|
thread::spawn(move || {
|
|
if let Err(e) = wallet_proxy.run() {
|
|
error!("Wallet Proxy error: {}", e);
|
|
}
|
|
});
|
|
|
|
// few values to keep things shorter
|
|
let reward = core::consensus::REWARD;
|
|
|
|
// add some accounts
|
|
api1.create_account_path(mask1, "mining")?;
|
|
api1.create_account_path(mask1, "listener")?;
|
|
|
|
// Get some mining done
|
|
{
|
|
wallet_inst!(wallet1, w);
|
|
w.set_account_by_name("mining")?;
|
|
}
|
|
let mut bh = 10u64;
|
|
let _ =
|
|
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
|
|
|
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
|
|
let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?;
|
|
assert!(wallet1_refreshed);
|
|
assert_eq!(wallet1_info.last_confirmed_height, bh);
|
|
assert_eq!(wallet1_info.total, bh * reward);
|
|
// send to send
|
|
let args = InitTxArgs {
|
|
src_acct_name: Some("mining".to_owned()),
|
|
amount: reward * 2,
|
|
minimum_confirmations: 2,
|
|
max_outputs: 500,
|
|
num_change_outputs: 1,
|
|
selection_strategy_is_use_all: true,
|
|
..Default::default()
|
|
};
|
|
let mut slate = api1.init_send_tx(mask1, args)?;
|
|
api1.tx_lock_outputs(mask1, &slate)?;
|
|
// Send directly to self
|
|
wallet::controller::foreign_single_use(
|
|
wallet1.clone(),
|
|
PathBuf::from(test_dir),
|
|
mask1_i.clone(),
|
|
|api| {
|
|
slate = api.receive_tx(&slate, Some("listener"), None)?;
|
|
Ok(())
|
|
},
|
|
)?;
|
|
slate = api1.finalize_tx(mask1, &slate)?;
|
|
api1.post_tx(mask1, &slate, false)?; // mines a block
|
|
bh += 1;
|
|
|
|
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
|
|
bh += 3;
|
|
|
|
// Check total in mining account
|
|
let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?;
|
|
assert!(wallet1_refreshed);
|
|
assert_eq!(wallet1_info.last_confirmed_height, bh);
|
|
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
|
|
|
|
// Check total in 'listener' account
|
|
{
|
|
wallet_inst!(wallet1, w);
|
|
w.set_account_by_name("listener")?;
|
|
}
|
|
let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?;
|
|
assert!(wallet1_refreshed);
|
|
assert_eq!(wallet1_info.last_confirmed_height, bh);
|
|
assert_eq!(wallet1_info.total, 2 * reward);
|
|
|
|
// Recipient wallet creates a mwixnet request from the last output
|
|
let secp_locked = util::static_secp_instance();
|
|
let secp = secp_locked.lock();
|
|
let server_pubkey_str_1 = "97444ae673bb92c713c1a2f7b8882ffbfc1c67401a280a775dce1a8651584332";
|
|
let server_pubkey_str_2 = "0c9414341f2140ed34a5a12a6479bf5a6404820d001ab81d9d3e8cc38f049b4e";
|
|
let server_pubkey_str_3 = "b58ece97d60e71bb7e53218400b0d67bfe6a3cb7d3b4a67a44f8fb7c525cbca5";
|
|
let server_key_1 =
|
|
SecretKey::from_slice(&secp, &grin_util::from_hex(server_pubkey_str_1).unwrap())?;
|
|
let server_key_2 =
|
|
SecretKey::from_slice(&secp, &grin_util::from_hex(server_pubkey_str_2).unwrap())?;
|
|
let server_key_3 =
|
|
SecretKey::from_slice(&secp, &grin_util::from_hex(server_pubkey_str_3).unwrap())?;
|
|
let params = MixnetReqCreationParams {
|
|
server_keys: vec![
|
|
MwixnetServerPublicKey::from_secret(&server_key_1),
|
|
MwixnetServerPublicKey::from_secret(&server_key_2),
|
|
MwixnetServerPublicKey::from_secret(&server_key_3),
|
|
],
|
|
fee_per_hop: None,
|
|
};
|
|
let outputs = api1.retrieve_outputs(mask1, false, false, None)?;
|
|
// get last output
|
|
let last_output = outputs.1[outputs.1.len() - 1].clone();
|
|
|
|
// Store the output with an MMR index, like a restored output
|
|
let mmr_index = Some(chain.get_output_pos(&last_output.commit).unwrap());
|
|
{
|
|
wallet_inst!(wallet1, w);
|
|
let mut batch = w.batch(mask1)?;
|
|
let mut output = last_output.output.clone();
|
|
batch.delete(&output.key_id, &output.mmr_index)?;
|
|
output.mmr_index = mmr_index;
|
|
batch.save(output)?;
|
|
batch.commit()?;
|
|
}
|
|
|
|
let empty_params = MixnetReqCreationParams {
|
|
server_keys: vec![],
|
|
fee_per_hop: params.fee_per_hop,
|
|
};
|
|
assert!(api1
|
|
.create_mwixnet_req(mask1, &empty_params, &last_output.commit, true)
|
|
.is_err());
|
|
|
|
let too_many_params = MixnetReqCreationParams {
|
|
server_keys: vec![params.server_keys[0]; MAX_MWIXNET_HOPS + 1],
|
|
fee_per_hop: params.fee_per_hop,
|
|
};
|
|
assert!(api1
|
|
.create_mwixnet_req(mask1, &too_many_params, &last_output.commit, true)
|
|
.is_err());
|
|
|
|
let oversized_fee_params = MixnetReqCreationParams {
|
|
server_keys: params.server_keys[..2].to_vec(),
|
|
fee_per_hop: Some(1 << 39),
|
|
};
|
|
assert_eq!(
|
|
api1.create_mwixnet_req(mask1, &oversized_fee_params, &last_output.commit, true)
|
|
.unwrap_err(),
|
|
libwallet::Error::Fee("mwixnet total fee exceeds FeeFields limit".to_string())
|
|
);
|
|
|
|
for fee in [0, 12_499_999] {
|
|
let invalid_params = MixnetReqCreationParams {
|
|
server_keys: params.server_keys.clone(),
|
|
fee_per_hop: Some(fee),
|
|
};
|
|
assert_eq!(
|
|
api1.create_mwixnet_req(mask1, &invalid_params, &last_output.commit, true)
|
|
.unwrap_err(),
|
|
libwallet::Error::Fee("mwixnet fee is below the swap minimum 0.0125".to_string())
|
|
);
|
|
}
|
|
let custom_params = MixnetReqCreationParams {
|
|
server_keys: params.server_keys.clone(),
|
|
fee_per_hop: Some(25_000_000),
|
|
};
|
|
let custom = api1.create_mwixnet_req(mask1, &custom_params, &last_output.commit, false)?;
|
|
let mut onion = custom.request.onion;
|
|
for server_key in [&server_key_1, &server_key_2, &server_key_3] {
|
|
let peeled = onion.peel_layer(server_key).unwrap();
|
|
assert_eq!(peeled.payload.fee, FeeFields::try_from(25_000_000u64)?);
|
|
onion = peeled.onion;
|
|
}
|
|
|
|
drop(secp);
|
|
|
|
// Failed refresh must not create or lock a swap
|
|
let tx_count = api1.retrieve_txs(mask1, false, None, None, None)?.1.len();
|
|
let (disconnected, receiver) = std::sync::mpsc::channel();
|
|
drop(receiver);
|
|
let original_proxy = std::mem::replace(&mut *client1.proxy_tx.lock(), disconnected);
|
|
let mut api1 = api1;
|
|
let result = wallet::command::mwixnet(
|
|
&mut api1,
|
|
mask1,
|
|
wallet::command::MwixnetArgs {
|
|
server: "mza3u6vkqodqc6kjjapfbcjrg7hgpkuw6nyq5xvfjqp5jm2lfeaocwqd.onion".try_into()?,
|
|
output: wallet::command::MwixnetOutput::Commitment(last_output.commit),
|
|
minimum_confirmations: 1,
|
|
params: MixnetReqCreationParams {
|
|
server_keys: params.server_keys.clone(),
|
|
fee_per_hop: None,
|
|
},
|
|
},
|
|
);
|
|
*client1.proxy_tx.lock() = original_proxy;
|
|
assert!(result
|
|
.unwrap_err()
|
|
.to_string()
|
|
.contains("Could not refresh wallet outputs"));
|
|
assert_eq!(
|
|
api1.retrieve_txs(mask1, false, None, None, None)?.1.len(),
|
|
tx_count
|
|
);
|
|
let outputs = api1.retrieve_outputs(mask1, false, false, None)?.1;
|
|
assert_eq!(
|
|
outputs
|
|
.iter()
|
|
.find(|o| o.commit == last_output.commit)
|
|
.unwrap()
|
|
.output
|
|
.status,
|
|
OutputStatus::Unspent
|
|
);
|
|
|
|
let creation = api1.create_mwixnet_req(mask1, ¶ms, &last_output.commit, true)?;
|
|
let creation_tx_id = creation.tx_id.unwrap();
|
|
let fee_per_hop = 12_500_000;
|
|
let mut onion = creation.request.onion.clone();
|
|
for server_key in [&server_key_1, &server_key_2, &server_key_3] {
|
|
let peeled = onion
|
|
.peel_layer(server_key)
|
|
.map_err(|e| libwallet::Error::GenericError(e.to_string()))?;
|
|
assert_eq!(peeled.payload.fee, FeeFields::try_from(fee_per_hop)?);
|
|
onion = peeled.onion;
|
|
}
|
|
|
|
println!("MWIXNET REQ: {:?}", creation.request);
|
|
|
|
// Check the input lock and expected output are tracked together.
|
|
let outputs = api1.retrieve_outputs(mask1, false, false, None)?;
|
|
let input = outputs
|
|
.1
|
|
.iter()
|
|
.find(|o| o.commit == last_output.commit)
|
|
.unwrap();
|
|
assert_eq!(input.output.status, OutputStatus::Locked);
|
|
assert_eq!(input.output.mmr_index, mmr_index);
|
|
let total_fee = fee_per_hop * params.server_keys.len() as u64;
|
|
let expected_amount = last_output.output.value - total_fee;
|
|
let expected_output = outputs
|
|
.1
|
|
.iter()
|
|
.find(|o| o.output.status == OutputStatus::Unconfirmed && o.output.value == expected_amount)
|
|
.unwrap();
|
|
assert_eq!(
|
|
input.output.tx_log_entry,
|
|
expected_output.output.tx_log_entry
|
|
);
|
|
|
|
let txs = api1.retrieve_txs(mask1, false, None, None, None)?.1;
|
|
let tx = txs.last().unwrap();
|
|
assert_eq!(creation_tx_id, tx.id);
|
|
assert_eq!(tx.tx_type, TxLogEntryType::TxSent);
|
|
assert_eq!(tx.amount_debited, last_output.output.value);
|
|
assert_eq!(tx.amount_credited, expected_amount);
|
|
assert_eq!(tx.num_inputs, 1);
|
|
assert_eq!(tx.num_outputs, 1);
|
|
assert_eq!(tx.fee, Some(FeeFields::try_from(total_fee)?));
|
|
|
|
assert!(api1
|
|
.create_mwixnet_req(mask1, ¶ms, &last_output.commit, false)
|
|
.is_err());
|
|
|
|
// let logging finish
|
|
stopper.store(false, Ordering::Relaxed);
|
|
thread::sleep(Duration::from_millis(1000));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn mwixnet_comsig_test() {
|
|
let test_dir = "test_output/mwixnet";
|
|
setup(test_dir);
|
|
if let Err(e) = mwixnet_test_impl(test_dir) {
|
|
panic!("Libwallet Error: {}", e);
|
|
}
|
|
clean_output_dir(test_dir);
|
|
}
|