You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
445 lines
18 KiB
445 lines
18 KiB
#include <stdio.h>
|
|
#include <iostream>
|
|
#include "wallet2.h"
|
|
#include "wallet/wownero_wallet_full.h"
|
|
#include "utils/gen_utils.h"
|
|
#include "utils/wownero_utils.h"
|
|
//#include "wownero_wallet_model.h"
|
|
|
|
using namespace std;
|
|
|
|
// ----------------------------- TEST CONFIG ----------------------------------
|
|
|
|
// constants
|
|
const int LOG_LEVEL = 2;
|
|
const string DAEMON_URI = "http://localhost:28081";
|
|
const wownero_network_type NETWORK_TYPE = wownero_network_type::TESTNET;
|
|
|
|
// ------------------------ MULTISIG STRESS TEST ------------------------------
|
|
|
|
void synchronize_multisig_participants(vector<wownero_wallet*> wallets) {
|
|
|
|
// collect multisig hex of all participants to synchronize
|
|
vector<string> multisig_hexes;
|
|
for (int i = 0; i < wallets.size(); i++) {
|
|
wownero_wallet* wallet = wallets[i];
|
|
wallet->sync();
|
|
multisig_hexes.push_back(wallet->export_multisig_hex()); // TODO: does wallet need saved?
|
|
}
|
|
|
|
// import each wallet's peer multisig hex
|
|
for (int i = 0; i < wallets.size(); i++) {
|
|
vector<string> peer_multisig_hexes;
|
|
for (int j = 0; j < wallets.size(); j++) if (j != i) peer_multisig_hexes.push_back(multisig_hexes[j]);
|
|
wownero_wallet* wallet = wallets[i];
|
|
wallet->sync(); // TODO wownero-project: creating multisig tx fails if wallet not explicitly synced before import_multisig_hex: https://github.com/wownero-project/wownero/issues/6850
|
|
wallet->import_multisig_hex(peer_multisig_hexes);
|
|
}
|
|
}
|
|
|
|
void init_multisig_wallets(wownero_wallet* funding_wallet, vector<wownero_wallet*> participants, int M, int N, int account_idx, int num_addresses_to_fund) {
|
|
|
|
std::cout << "init_multisig_wallets(" << M << ", " << N << ")" << std::endl;
|
|
if (participants.size() != N) throw std::runtime_error("participants != N");
|
|
|
|
// prepare multisig hexes
|
|
vector<string> prepared_multisig_hexes;
|
|
for (int i = 0; i < N; i++) {
|
|
wownero_wallet* participant = participants[i];
|
|
prepared_multisig_hexes.push_back(participant->prepare_multisig());
|
|
}
|
|
|
|
// make wallets multisig
|
|
vector<string> made_multisig_hexes;
|
|
for (int i = 0; i < participants.size(); i++) {
|
|
wownero_wallet* participant = participants[i];
|
|
|
|
// // test bad input
|
|
// try {
|
|
// participant.make_multisig(Arrays.asList("asd", "dsa"), M, TestUtils.WALLET_PASSWORD);
|
|
// throw new RuntimeException("Should have thrown error making wallet multisig with incorrect values");
|
|
// } catch (MoneroError e) {
|
|
// assertEquals("basic_string", e.getMessage()); // TODO (wownero-project): improve error message https://github.com/wownero-project/wownero/issues/8493
|
|
// }
|
|
|
|
// collect prepared multisig hexes from wallet's peers
|
|
vector<string> peer_multisig_hexes;
|
|
for (int j = 0; j < participants.size(); j++) if (j != i) peer_multisig_hexes.push_back(prepared_multisig_hexes[j]);
|
|
|
|
// make the wallet multisig
|
|
string multisig_hex = participant->make_multisig(peer_multisig_hexes, M, "");
|
|
made_multisig_hexes.push_back(multisig_hex);
|
|
}
|
|
|
|
// try to get seed before wallet initialized
|
|
try {
|
|
participants[0]->get_seed();
|
|
throw std::runtime_error("Expected error getting seed before multisig wallet initialized");
|
|
} catch (exception& e) {
|
|
string msg = string(e.what());
|
|
if (msg != "This wallet is multisig, but not yet finalized") throw std::runtime_error("unexpected error: " + msg);
|
|
}
|
|
|
|
// exchange keys N - M + 1 times
|
|
string address;
|
|
if (N != made_multisig_hexes.size()) throw std::runtime_error("N != made_multisig_hexes.size()");
|
|
vector<string> prev_multisig_hexes = made_multisig_hexes;
|
|
for (int i = 0; i < N - M + 1; i++) {
|
|
//System.out.println("Exchanging multisig keys round " + (i + 1) + " / " + (N - M + 1));
|
|
|
|
// exchange multisig keys with each wallet and collect results
|
|
vector<string> exchange_multisig_hexes;
|
|
for (int j = 0; j < participants.size(); j++) {
|
|
wownero_wallet* participant = participants[j];
|
|
|
|
// // test bad input
|
|
// try {
|
|
// participant->exchange_multisig_keys(Arrays.asList("asd", "dsa"), TestUtils.WALLET_PASSWORD);
|
|
// throw new RuntimeException("Should have thrown error exchanging multisig keys with bad input");
|
|
// } catch (MoneroError e) {
|
|
// assertTrue(e.getMessage().length() > 0);
|
|
// }
|
|
|
|
// collect the multisig hexes of the wallet's peers from last round
|
|
vector<string> peer_multisig_hexes;
|
|
for (int k = 0; k < participants.size(); k++) if (k != j) peer_multisig_hexes.push_back(prev_multisig_hexes[k]);
|
|
|
|
// import the multisig hexes of the wallet's peers
|
|
wownero_multisig_init_result result = participant->exchange_multisig_keys(peer_multisig_hexes, "");
|
|
|
|
// test result
|
|
if (result.m_multisig_hex->empty()) throw runtime_error("m_multisig_hex is empty");
|
|
if (i == N - M) { // result on last round has address
|
|
if (result.m_address->empty()) throw runtime_error("address is empty");
|
|
if (address.empty()) address = result.m_address.get();
|
|
else if (address != result.m_address.get()) throw runtime_error("address != result address");
|
|
} else {
|
|
if (result.m_address) throw runtime_error("address should be empty");
|
|
exchange_multisig_hexes.push_back(result.m_multisig_hex.get());
|
|
}
|
|
}
|
|
|
|
// use results for next round of exchange
|
|
prev_multisig_hexes = exchange_multisig_hexes;
|
|
}
|
|
|
|
// validate final multisig
|
|
wownero_wallet* participant = participants[0];
|
|
wownero_utils::validate_address(participant->get_primary_address(), NETWORK_TYPE);
|
|
//test_multisig_info(participant->get_multisig_info(), M, N); // TODO
|
|
string seed = participant->get_seed();
|
|
if (seed.empty()) throw std::runtime_error("seed should not be empty");
|
|
|
|
// restore participant from multisig seed
|
|
// participant->close();
|
|
// delete participant;
|
|
// participant = createWallet(new MoneroWalletConfig().setSeed(seed).setIsMultisig(true));
|
|
// MoneroUtils.validateAddress(participant.getPrimaryAddress(), TestUtils.NETWORK_TYPE);
|
|
// assertEquals(address, participant.getPrimaryAddress());
|
|
// testMultisigInfo(participant.getMultisigInfo(), M, N);
|
|
// assertEquals(seed, participant.getSeed());
|
|
// participants.set(0, participant);
|
|
|
|
// test sending a multisig transaction if configured
|
|
if (num_addresses_to_fund == 0) return;
|
|
|
|
// create accounts in the first multisig wallet to receive funds
|
|
for (int i = 0; i < account_idx; i++) participant->create_account();
|
|
|
|
// get destinations to subaddresses within the account of the multisig wallet
|
|
vector<shared_ptr<wownero_destination>> destinations;
|
|
for (int i = 0; i < num_addresses_to_fund; i++) {
|
|
shared_ptr<wownero_destination> destination = make_shared<wownero_destination>();
|
|
destination->m_address = participant->get_address(account_idx, i);
|
|
destination->m_amount = 200000000000; // TODO: add a 0 for 2 XMR
|
|
destinations.push_back(destination);
|
|
if (i + 1 < num_addresses_to_fund) participant->create_subaddress(account_idx);
|
|
}
|
|
|
|
// // wait for txs to confirm and for sufficient unlocked balance
|
|
// TestUtils.WALLET_TX_TRACKER.waitForWalletTxsToClearPool(wallet);
|
|
// TestUtils.WALLET_TX_TRACKER.waitForUnlockedBalance(wallet, 0, null, TestUtils.MAX_FEE.multiply(new BigInteger("20")));
|
|
|
|
// send funds from the main test wallet to destinations in the first multisig wallet
|
|
std::cout << "Sending funds from main wallet" << std::endl;
|
|
wownero_tx_config tx_config;
|
|
tx_config.m_account_index = 0;
|
|
tx_config.m_destinations = destinations;
|
|
tx_config.m_relay = true;
|
|
shared_ptr<wownero_tx_wallet> tx = funding_wallet->create_tx(tx_config);
|
|
wownero_utils::free(tx);
|
|
|
|
// attempt to start mining
|
|
std::cout << "Starting mining" << std::endl;
|
|
try { funding_wallet->start_mining(1, false, false); }
|
|
catch (const exception& e) { if (string("BUSY") == string(e.what())) throw e; }
|
|
|
|
uint64_t last_num_confirmations = 0;
|
|
while (true) {
|
|
|
|
// wait a moment
|
|
gen_utils::wait_for(5000);
|
|
|
|
// fetch and test outputs
|
|
wownero_output_query query;
|
|
vector<shared_ptr<wownero_output_wallet>> outputs = participant->get_outputs(query);
|
|
if (outputs.size() == 0) std::cout << "No output reported yet" << endl;
|
|
else {
|
|
|
|
// print num confirmations
|
|
uint64_t height = funding_wallet->get_height();
|
|
uint64_t num_confirmations = height - *outputs[0]->m_tx->get_height();
|
|
if (last_num_confirmations != num_confirmations) std::cout << "Output has " << (height - *outputs[0]->m_tx->get_height()) << " confirmations" << endl;
|
|
|
|
// outputs are not spent
|
|
|
|
// break if output is unlocked
|
|
shared_ptr<wownero_tx_wallet> tx = std::static_pointer_cast<wownero_tx_wallet>(outputs[0]->m_tx);
|
|
if (!tx->m_is_locked.get()) {
|
|
wownero_utils::free(outputs);
|
|
break;
|
|
}
|
|
}
|
|
wownero_utils::free(outputs);
|
|
}
|
|
|
|
// stop mining
|
|
funding_wallet->stop_mining();
|
|
|
|
// multisig wallet should have unlocked balance in subaddresses
|
|
for (int i = 0; i < num_addresses_to_fund; i++) {
|
|
if (participant->get_unlocked_balance(account_idx, i) == 0) throw std::runtime_error("balance expected to be > 0");
|
|
}
|
|
wownero_output_query output_query;
|
|
output_query.m_account_index = account_idx;
|
|
vector<shared_ptr<wownero_output_wallet>> outputs = participant->get_outputs(output_query);
|
|
if (outputs.size() == 0) throw runtime_error("no outputs returned");
|
|
if (outputs.size() < 3) std::cout << "WARNING: not one output per subaddress?";
|
|
wownero_utils::free(outputs);
|
|
|
|
// wallet requires importing multisig to be reliable
|
|
if (!participant->is_multisig_import_needed()) throw std::runtime_error("multisig import should be needed");
|
|
|
|
// // attempt creating and relaying transaction without synchronizing with participants
|
|
// String returnAddress = wallet.getPrimaryAddress(); // funds will be returned to this address from the multisig wallet
|
|
// try {
|
|
// participant.createTxs(new MoneroTxConfig().setAccountIndex(accountIdx).setAddress(returnAddress).setAmount(TestUtils.MAX_FEE.multiply(BigInteger.valueOf(3))));
|
|
// throw new RuntimeException("Should have failed sending funds without synchronizing with peers");
|
|
// } catch (MoneroError e) {
|
|
// assertEquals("No transaction created", e.getMessage());
|
|
// }
|
|
|
|
// synchronize the multisig participants since receiving outputs
|
|
std::cout << "Synchronizing participants" << std::endl;
|
|
synchronize_multisig_participants(participants);
|
|
|
|
// // expect error exporting key images
|
|
// try {
|
|
// participant.exportKeyImages(true);
|
|
// } catch (Exception e) {
|
|
// assertTrue(e.getMessage().contains("key_image generated not matched with cached key image"), "Unexpected error: " + e.getMessage());
|
|
// }
|
|
|
|
// // attempt relaying created transactions without co-signing
|
|
// try {
|
|
// participant.createTx(new MoneroTxConfig().setAddress(returnAddress).setAmount(TestUtils.MAX_FEE).setAccountIndex(accountIdx).setSubaddressIndex(0).setRelay(true));
|
|
// throw new RuntimeException("Should have failed");
|
|
// } catch (Exception e) {
|
|
// assertTrue(e instanceof MoneroError);
|
|
// assertEquals("Cannot relay multisig transaction until co-signed", e.getMessage());
|
|
// }
|
|
}
|
|
|
|
void test_multisig_stress(wownero_wallet* funding_wallet, string wallet_name = "") {
|
|
|
|
// test config
|
|
int M = 2;
|
|
int N = 2;
|
|
int account_idx = 0;
|
|
int num_addresses_to_fund = 5;
|
|
int max_txs = 200;
|
|
//wallet_name = "multisig_stress_0d7c76d1-69b9-46b7-b0e0-5128bd5c725a";
|
|
if (wallet_name.empty()) wallet_name = "multisig_stress_" + gen_utils::get_uuid();
|
|
std::string wallet_path = wallet_name;
|
|
std::cout << "Stress testing multisig wallets: " << wallet_path << std::endl;
|
|
|
|
// open or create multisig wallets
|
|
vector<wownero_wallet*> participants;
|
|
try {
|
|
for (int i = 0; i < N; i++) {
|
|
participants.push_back(wownero_wallet_full::open_wallet(wallet_path + string("_") + std::to_string(i), "", NETWORK_TYPE));
|
|
}
|
|
for (int i = 0; i < participants.size(); i++) {
|
|
participants[i]->set_daemon_connection(DAEMON_URI);
|
|
participants[i]->sync();
|
|
participants[i]->start_syncing(5000);
|
|
}
|
|
} catch (const exception& e) {
|
|
for (int i = 0; i < N; i++) {
|
|
wownero_wallet_config wallet_config;
|
|
wallet_config.m_path = wallet_path + string("_") + std::to_string(i);
|
|
wallet_config.m_network_type = wownero_network_type::TESTNET;
|
|
wallet_config.m_server = wownero_rpc_connection(DAEMON_URI);
|
|
participants.push_back(wownero_wallet_full::create_wallet(wallet_config));
|
|
}
|
|
for (int i = 0; i < participants.size(); i++) {
|
|
participants[i]->sync();
|
|
participants[i]->start_syncing(5000);
|
|
}
|
|
init_multisig_wallets(funding_wallet, participants, M, N, account_idx, num_addresses_to_fund);
|
|
}
|
|
wownero_wallet* participant = participants[0];
|
|
|
|
// start mining
|
|
funding_wallet->start_mining(1, false, false);
|
|
|
|
// receive notifications when the wallet receives funds
|
|
struct : wownero_wallet_listener {
|
|
void on_output_received(const wownero_output_wallet& output) {
|
|
cout << "Wallet received funds!" << endl;
|
|
uint64_t amount = output.m_amount.get();
|
|
string tx_hash = output.m_tx->m_hash.get();
|
|
bool is_confirmed = output.m_tx->m_is_confirmed.get();
|
|
bool is_locked = static_pointer_cast<wownero_tx_wallet>(output.m_tx)->m_is_locked.get();
|
|
int account_index = output.m_account_index.get();
|
|
int subaddress_index = output.m_subaddress_index.get();
|
|
}
|
|
} my_listener;
|
|
participant->add_listener(my_listener);
|
|
|
|
// cycle funds
|
|
int num_txs = 0;
|
|
while (num_txs < max_txs) {
|
|
try {
|
|
uint64_t unlocked_balance = participant->get_unlocked_balance(0);
|
|
while (unlocked_balance > 0) {
|
|
|
|
// synchronize the multisig participants since spending outputs
|
|
std::cout << "Synchronizing participants" << std::endl;
|
|
synchronize_multisig_participants(participants);
|
|
|
|
// create txs to cycle funds
|
|
wownero_tx_config tx_config;
|
|
tx_config.m_account_index = account_idx;
|
|
for (int i = 0; i < num_addresses_to_fund; i++) {
|
|
shared_ptr<wownero_destination> destination = make_shared<wownero_destination>();
|
|
destination->m_address = participant->get_address(0, i);
|
|
destination->m_amount = unlocked_balance / (num_addresses_to_fund * 3);
|
|
tx_config.m_destinations.push_back(destination);
|
|
}
|
|
vector<shared_ptr<wownero_tx_wallet>> txs = participant->create_txs(tx_config);
|
|
if (txs.empty()) throw runtime_error("txs is empty");
|
|
if ((*txs[0]->m_tx_set.get()->m_multisig_tx_hex).empty()) throw runtime_error("multisig_tx_hex is empty");
|
|
//assertNull(txSet.getSignedTxHex());
|
|
//assertNull(txSet.getUnsignedTxHex());
|
|
|
|
// describe multisig tx hex and test
|
|
wownero_tx_set tx_set = participant->describe_tx_set(*txs[0]->m_tx_set.get());
|
|
wownero_utils::free(tx_set.m_txs);
|
|
|
|
// sign the tx with participants 1 through m - 1 to meet threshold
|
|
string multisig_tx_hex = txs[0]->m_tx_set.get()->m_multisig_tx_hex.get();
|
|
wownero_utils::free(txs);
|
|
std::cout << "Signing" << std::endl;
|
|
for (int j = 1; j < M; j++) {
|
|
wownero_multisig_sign_result result = participants[j]->sign_multisig_tx_hex(multisig_tx_hex);
|
|
multisig_tx_hex = result.m_signed_multisig_tx_hex.get();
|
|
}
|
|
|
|
// submit the signed multisig tx hex to the network
|
|
std::cout << "Submitting: " << multisig_tx_hex.length() << std::endl;
|
|
vector<string> tx_hashes = participant->submit_multisig_tx_hex(multisig_tx_hex);
|
|
if (tx_hashes.empty()) throw runtime_error("tx hashes is empty");
|
|
std::cout << "Tx submitted successfully!" << std::endl;
|
|
|
|
// fetch the wallet's multisig txs
|
|
wownero_tx_query tx_query;
|
|
tx_query.m_hashes = tx_hashes;
|
|
vector<shared_ptr<wownero_tx_wallet>> multisig_txs = participant->get_txs(tx_query);
|
|
if (multisig_txs.size() != tx_hashes.size()) throw runtime_error("multisigTxs.size() != txHashes.size()");
|
|
vector<shared_ptr<wownero_tx_wallet>> all_txs = participant->get_txs();
|
|
num_txs = all_txs.size();
|
|
std::cout << "All txs size: " << all_txs.size() << std::endl;
|
|
wownero_utils::free(multisig_txs);
|
|
wownero_utils::free(all_txs);
|
|
if (num_txs >= max_txs) break;
|
|
}
|
|
} catch (const exception& e) {
|
|
std::cout << "There was an error: " << string(e.what()) << std::endl;
|
|
}
|
|
|
|
// save wallets
|
|
for (int i = 0; i < participants.size(); i++) {
|
|
participants[i]->save();
|
|
}
|
|
|
|
// loop
|
|
std::cout << "Starting another loop" << std::endl;
|
|
gen_utils::wait_for(5000);
|
|
}
|
|
|
|
// save and close wallets
|
|
for (int i = 0; i < participants.size(); i++) {
|
|
participants[i]->close(true);
|
|
delete participants[i];
|
|
}
|
|
participant = 0;
|
|
|
|
// stop mining
|
|
funding_wallet->stop_mining();
|
|
}
|
|
|
|
// ----------------------------------- MAIN -----------------------------------
|
|
|
|
/**
|
|
* Main entry point for tests.
|
|
*/
|
|
int main(int argc, const char* argv[]) {
|
|
|
|
// configure logging
|
|
//mlog_configure("log_cpp_scratchpad.txt", true);
|
|
//mlog_set_log_level(LOG_LEVEL);
|
|
//MINFO("logging info!!!");
|
|
//MWARNING("logging a warning!!!");
|
|
//MERROR("logging an error!!!");
|
|
|
|
// print header
|
|
MINFO("===== Tests =====");
|
|
for (int i = 0; i < argc; i++) {
|
|
MINFO("Argument" << i << ": " << argv[i]);
|
|
}
|
|
|
|
string path = "test_wallet_1";
|
|
string password = "supersecretpassword123";
|
|
string language = "English";
|
|
int network_type = 2;
|
|
|
|
// load test wallet
|
|
wownero_wallet* wallet = wownero_wallet_full::open_wallet("../../test_wallets/test_wallet_1", "supersecretpassword123", wownero_network_type::TESTNET);
|
|
wallet->set_daemon_connection(DAEMON_URI);
|
|
wallet->sync();
|
|
wallet->start_syncing(5000);
|
|
|
|
// get txs
|
|
vector<shared_ptr<wownero_tx_wallet>> txs = wallet->get_txs();
|
|
MINFO("Wallet has " << txs.size() << " txs");
|
|
for (int i = 0; i < txs.size() && i < 10; i++) MINFO(txs[i]->serialize());
|
|
wownero_utils::free(txs);
|
|
|
|
// get transfers
|
|
vector<shared_ptr<wownero_transfer>> transfers = wallet->get_transfers(wownero_transfer_query());
|
|
MINFO("Wallet has " << transfers.size() << " transfers");
|
|
wownero_utils::free(transfers);
|
|
|
|
// get outputs
|
|
vector<shared_ptr<wownero_output_wallet>> outputs = wallet->get_outputs(wownero_output_query());
|
|
MINFO("Wallet has " << outputs.size() << " outputs");
|
|
wownero_utils::free(outputs);
|
|
|
|
// multisig stress test
|
|
test_multisig_stress(wallet);
|
|
|
|
// close wallet and free pointer
|
|
wallet->close(true);
|
|
delete wallet;
|
|
} |