// Copyright (c) 2014-2018 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "chaingen.h" #include "multisig_wallet_tests.h" #include "pos_block_builder.h" #include "tx_builder.h" using namespace epee; using namespace crypto; using namespace currency; #define TMP_LOG_FLOOD_STOP // <- comment this when debugging a broken test to skip log filtering #if defined(TMP_LOG_FLOOD_STOP) #define TMP_LOG_SILENT log_level = log_space::get_set_log_detalisation_level(); \ log_space::get_set_log_detalisation_level(true, LOG_LEVEL_SILENT) #define TMP_LOG_RESTORE log_space::get_set_log_detalisation_level(true, log_level) #else #define TMP_LOG_SILENT #define TMP_LOG_RESTORE #endif static void exception_handler(){} //============================================================================================================================== // helper routine: creates multisig-spending tx using a wallet and keys of other ms-participants, then sends it to the core proxy void transfer_multisig(tools::wallet2& w, const std::list& owner_keys, const crypto::hash& multisig_id, const std::vector& dsts, uint64_t unlock_time, uint64_t fee, const std::vector& extra, const std::vector& attachments, tools::detail::split_strategy_id_t split_strategy_id, const tools::tx_dust_policy& dust_policy, currency::transaction &tx, uint64_t tx_version, uint8_t tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED, uint64_t flags = 0, bool shuffle = true, bool send_to_network = true) { currency::account_public_address crypt_address = get_crypt_address_from_destinations(w.get_account().get_keys(), dsts); // prepare transaction will sign ms input partially with wallet's keys - it needed to be signed fully with the others tools::construct_tx_param ctp = AUTO_VAL_INIT(ctp); currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); ctp.attachments = attachments; ctp.crypt_address = crypt_address; ctp.dsts = dsts; ctp.dust_policy = dust_policy; ctp.extra = extra; ctp.fake_outputs_count = 0; ctp.fee = fee; ctp.flags = flags; ctp.mark_tx_as_complete = false; ctp.multisig_id = multisig_id; ctp.shuffle = shuffle; ctp.split_strategy_id = split_strategy_id; ctp.tx_outs_attr = tx_outs_attr; ctp.unlock_time = unlock_time; ftp.tx_version = tx_version; tools::mode_separate_context emode_separate = AUTO_VAL_INIT(emode_separate); emode_separate.tx_for_mode_separate = tx; w.prepare_transaction(ctp, ftp, emode_separate); crypto::secret_key sk = AUTO_VAL_INIT(sk); w.finalize_transaction(ftp, tx, sk, false); // sign ms input with all other non-wallet keys auto it = w.get_multisig_transfers().find(multisig_id); THROW_IF_FALSE_WALLET_INT_ERR_EX(it != w.get_multisig_transfers().end(), "can't find multisig_id: " << multisig_id); const currency::transaction& ms_source_tx = it->second.m_ptx_wallet_info->m_tx; bool is_tx_input_fully_signed = false; bool r = false; for (const currency::account_keys& keys : owner_keys) { //if (keys == w.get_account().get_keys()) // continue; THROW_IF_FALSE_WALLET_INT_ERR_EX(!is_tx_input_fully_signed, "is_tx_input_fully_signed == true, expected: false"); r = sign_multisig_input_in_tx_custom(tx, 0, keys, ms_source_tx, &is_tx_input_fully_signed, true); THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_multisig_input_in_tx failed"); } THROW_IF_FALSE_WALLET_INT_ERR_EX(is_tx_input_fully_signed, "is_tx_input_fully_signed == false, expected: true"); // send to network if (send_to_network) { COMMAND_RPC_SEND_RAW_TX::request req; req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx)); COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; bool r = w.get_core_proxy()->call_COMMAND_RPC_SEND_RAW_TX(req, daemon_send_resp); THROW_IF_TRUE_WALLET_EX(!r, tools::error::no_connection_to_daemon, "sendrawtransaction"); THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_BUSY, tools::error::daemon_busy, "sendrawtransaction"); THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_DISCONNECTED, tools::error::wallet_internal_error, "Transfer attempt while daemon offline"); THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status != API_RETURN_CODE_OK, tools::error::tx_rejected, tx, daemon_send_resp.status); } } //============================================================================================================================== multisig_wallet_test::multisig_wallet_test() { REGISTER_CALLBACK_METHOD(multisig_wallet_test, c1); } bool multisig_wallet_test::generate(std::vector& events) const { currency::account_base genesis_acc; genesis_acc.generate(); m_mining_accunt.generate(); m_accunt_a.generate(); m_accunt_b.generate(); m_accunt_c.generate(); block blk_0 = AUTO_VAL_INIT(blk_0); generator.construct_genesis_block(blk_0, genesis_acc, test_core_time::get_time()); events.push_back(blk_0); REWIND_BLOCKS_N(events, blk_0r, blk_0, m_mining_accunt, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); DO_CALLBACK(events, "c1"); return true; } bool multisig_wallet_test::c1(currency::core& c, size_t ev_index, const std::vector& events) { #define AMOUNT_TO_TRANSFER_MULTISIG MK_TEST_COINS(11) // Test outline: // 1. Generate multisig and send it to blockchain // 2. Transfer from multisig std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_mining_accunt); size_t blocks_fetched = 0; bool received_money; std::atomic atomic_false = ATOMIC_VAR_INIT(false); miner_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW, false, "Incorrect numbers of blocks fetched"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool"); std::vector extra; std::vector attachments; std::vector dst; dst.resize(1); //multisig dst.back().addr.push_back(m_accunt_a.get_public_address()); dst.back().addr.push_back(m_accunt_b.get_public_address()); dst.back().amount = AMOUNT_TO_TRANSFER_MULTISIG + TESTS_DEFAULT_FEE; dst.back().minimum_sigs = dst.back().addr.size(); transaction result_tx = AUTO_VAL_INIT(result_tx); miner_wlt->transfer(dst, 0, 0, TESTS_DEFAULT_FEE, extra, attachments, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); bool r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); //findout multisig out intex size_t i = 0; for (; i != result_tx.vout.size(); i++) { if (boost::get(result_tx.vout[i]).target.type() == typeid(txout_multisig)) break; } CHECK_AND_ASSERT_MES(i != result_tx.vout.size(), false, "Incorrect txs outs"); crypto::hash multisig_id = get_multisig_out_id(result_tx, i); CHECK_AND_ASSERT_MES(multisig_id != null_hash, false, "Multisig failed: failed to get get_multisig_out_id"); r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); std::shared_ptr wallet_a = init_playtime_test_wallet(events, c, m_accunt_a); std::shared_ptr wallet_b = init_playtime_test_wallet(events, c, m_accunt_b); wallet_a->refresh(); wallet_b->refresh(); tools::multisig_transfer_container ms_a, ms_b; wallet_a->get_multisig_transfers(ms_a); wallet_b->get_multisig_transfers(ms_b); CHECK_AND_ASSERT_MES(ms_a.size() == 1 && ms_b.size() == 1, false, "Multisig failed: ms_a.size() == 1 && ms_b.size() == 1"); uint64_t tx_version = currency::get_tx_version(c.get_current_blockchain_size(), c.get_blockchain_storage().get_core_runtime_config().hard_forks) ; std::vector dst2(1); dst2.back().addr.resize(1); dst2.back().addr.back() = m_accunt_c.get_public_address(); dst2.back().amount = AMOUNT_TO_TRANSFER_MULTISIG; dst2.back().minimum_sigs = dst2.back().addr.size(); std::list acc_keys; //acc_keys.push_back(m_accunt_a.get_keys()); acc_keys.push_back(m_accunt_b.get_keys()); transfer_multisig(*wallet_a.get(), acc_keys, multisig_id, dst2, 0, TESTS_DEFAULT_FEE, extra, attachments, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx, tx_version); r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); wallet_a->refresh(); wallet_b->refresh(); ms_a.clear(); ms_b.clear(); wallet_b->get_multisig_transfers(ms_a); wallet_b->get_multisig_transfers(ms_b); CHECK_AND_ASSERT_MES(ms_a.size() == 1 && ms_b.size() == 1, false, "Multisig failed: ms_a.size() == 1 && ms_b.size() == 1"); CHECK_AND_ASSERT_MES(ms_a.begin()->second.is_spent() == true && ms_b.begin()->second.is_spent() == true, false, "ms_a.begin()->second.m_spent == false || ms_b.begin()->second.m_spent == false"); std::shared_ptr wallet_c = init_playtime_test_wallet(events, c, m_accunt_c); wallet_c->refresh(); uint64_t balance = wallet_c->balance(); CHECK_AND_ASSERT_MES(balance == AMOUNT_TO_TRANSFER_MULTISIG, false, "Multisig failed: balance missmatch"); return true; } //------------------------------------------------------------------------------ multisig_wallet_test_many_dst::multisig_wallet_test_many_dst() { REGISTER_CALLBACK_METHOD(multisig_wallet_test_many_dst, c1); } bool multisig_wallet_test_many_dst::generate(std::vector& events) const { GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); block blk_0 = AUTO_VAL_INIT(blk_0); generator.construct_genesis_block(blk_0, preminer_acc, test_core_time::get_time()); events.push_back(blk_0); REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); DO_CALLBACK(events, "c1"); return true; } bool multisig_wallet_test_many_dst::c1(currency::core& c, size_t ev_index, const std::vector& events) { // Test outline: // 1. Generate few PoW blocks. // 2. Transfer multisig tx to enormous amount of receivers (addresses_count) // 3. Receive the transfer, check it's ok. // 4. Send the money to Alice and make sure she successfully receives them. static const size_t addresses_count = 700; // !!! // NOTE: When debugging this test undefine TMP_LOG_FLOOD_STOP to skip log filtering! // !!! int log_level = 0; bool r = false; std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]); size_t blocks_fetched = 0; bool received_money; std::atomic atomic_false = ATOMIC_VAR_INIT(false); miner_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW, false, "Incorrect numbers of blocks fetched"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool"); uint64_t amount = 0; miner_wlt->balance(amount); CHECK_AND_ASSERT_MES(amount > TESTS_DEFAULT_FEE, false, "miner has unexpected balance"); amount -= TESTS_DEFAULT_FEE; std::vector addresses(addresses_count); tx_destination_entry de = AUTO_VAL_INIT(de); for (size_t i = 0; i < addresses_count; ++i) { addresses[i].generate(); de.addr.push_back(addresses[i].get_public_address()); } de.amount = amount; de.minimum_sigs = de.addr.size(); transaction result_tx = AUTO_VAL_INIT(result_tx); TMP_LOG_SILENT; miner_wlt->transfer(std::vector({ de }), 0, 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); TMP_LOG_RESTORE; auto it = std::find_if(result_tx.vout.begin(), result_tx.vout.end(), [](tx_out_v& o) { return boost::get(o).target.type() == typeid(txout_multisig); }); CHECK_AND_ASSERT_MES(it != result_tx.vout.end(), false, "Can't find output txout_multisig"); size_t multisig_index = it - result_tx.vout.begin(); r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); std::shared_ptr w = init_playtime_test_wallet(events, c, addresses[0]); w->refresh(); std::list owner_keys; for (auto a : addresses) owner_keys.push_back(a.get_keys()); owner_keys.pop_front(); // the first one was used to initialize the wallet 'w' tx_destination_entry de2(amount - TESTS_DEFAULT_FEE, m_accounts[ALICE_ACC_IDX].get_public_address()); transaction tx = AUTO_VAL_INIT(tx); TMP_LOG_SILENT; transfer_multisig(*w.get(), owner_keys, get_multisig_out_id(result_tx, multisig_index), std::vector({ de2 }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, c.get_current_tx_version()); TMP_LOG_RESTORE; r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); alice_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW * 2 + 1, false, "Incorrect numbers of blocks fetched"); r = check_balance_via_wallet(*alice_wlt.get(), "alice_wlt", amount - TESTS_DEFAULT_FEE); CHECK_AND_ASSERT_MES(r, false, "invalid balance"); tools::multisig_transfer_container mstc; alice_wlt->get_multisig_transfers(mstc); CHECK_AND_ASSERT_MES(mstc.empty(), false, "Got invalid multisig transfer"); return true; } //------------------------------------------------------------------------------ multisig_wallet_heterogenous_dst::multisig_wallet_heterogenous_dst() { REGISTER_CALLBACK_METHOD(multisig_wallet_heterogenous_dst, c1); } bool multisig_wallet_heterogenous_dst::generate(std::vector& events) const { GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); GENERATE_ACCOUNT(carol_acc); m_accounts.push_back(carol_acc); GENERATE_ACCOUNT(dan_acc); m_accounts.push_back(dan_acc); block blk_0 = AUTO_VAL_INIT(blk_0); generator.construct_genesis_block(blk_0, preminer_acc, test_core_time::get_time()); events.push_back(blk_0); REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); DO_CALLBACK(events, "c1"); return true; } bool multisig_wallet_heterogenous_dst::c1(currency::core& c, size_t ev_index, const std::vector& events) { std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]); size_t blocks_fetched = 0; bool received_money = false; bool r = false; std::atomic atomic_false(false); miner_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW, false, "Incorrect numbers of blocks fetched"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool"); uint64_t amount1 = TESTS_DEFAULT_FEE * 13 + 1; uint64_t amount2 = TESTS_DEFAULT_FEE * 11 + 2; uint64_t amount3 = TESTS_DEFAULT_FEE * 7 + 3; uint64_t amount4 = TESTS_DEFAULT_FEE * 5 + 4; uint64_t amount5 = TESTS_DEFAULT_FEE * 21; uint64_t amount6 = TESTS_DEFAULT_FEE * 23; tx_destination_entry de1 = AUTO_VAL_INIT(de1); de1.addr.push_back(m_accounts[ALICE_ACC_IDX].get_public_address()); de1.addr.push_back(m_accounts[BOB_ACC_IDX].get_public_address()); de1.amount = amount1 + TESTS_DEFAULT_FEE; de1.minimum_sigs = de1.addr.size(); tx_destination_entry de2 = AUTO_VAL_INIT(de2); de2.addr.push_back(m_accounts[CAROL_ACC_IDX].get_public_address()); de2.addr.push_back(m_accounts[DAN_ACC_IDX].get_public_address()); de2.amount = amount2 + TESTS_DEFAULT_FEE; de2.minimum_sigs = de2.addr.size(); tx_destination_entry de3 = AUTO_VAL_INIT(de3); de3.addr.push_back(m_accounts[ALICE_ACC_IDX].get_public_address()); de3.addr.push_back(m_accounts[DAN_ACC_IDX].get_public_address()); de3.amount = amount3 + TESTS_DEFAULT_FEE; de3.minimum_sigs = de3.addr.size(); tx_destination_entry de4 = AUTO_VAL_INIT(de4); de4.addr.push_back(m_accounts[CAROL_ACC_IDX].get_public_address()); de4.addr.push_back(m_accounts[BOB_ACC_IDX].get_public_address()); de4.amount = amount4 + TESTS_DEFAULT_FEE; de4.minimum_sigs = de4.addr.size(); tx_destination_entry de5 = AUTO_VAL_INIT(de5); de5.addr.push_back(m_accounts[ALICE_ACC_IDX].get_public_address()); de5.amount = amount5 + TESTS_DEFAULT_FEE; de5.minimum_sigs = de5.addr.size(); tx_destination_entry de6 = AUTO_VAL_INIT(de6); de6.addr.push_back(m_accounts[DAN_ACC_IDX].get_public_address()); de6.amount = amount6 + TESTS_DEFAULT_FEE; de6.minimum_sigs = de6.addr.size(); // Send multisig tx transaction ms_tx = AUTO_VAL_INIT(ms_tx); miner_wlt->transfer(std::vector({ de1, de2, de3, de4, de5, de6 }), 0, 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), ms_tx); // calculate multisig hashes for further usage crypto::hash ms1_hash = currency::get_multisig_out_id(ms_tx, get_tx_out_index_by_amount(ms_tx, de1.amount)); crypto::hash ms2_hash = currency::get_multisig_out_id(ms_tx, get_tx_out_index_by_amount(ms_tx, de2.amount)); crypto::hash ms3_hash = currency::get_multisig_out_id(ms_tx, get_tx_out_index_by_amount(ms_tx, de3.amount)); crypto::hash ms4_hash = currency::get_multisig_out_id(ms_tx, get_tx_out_index_by_amount(ms_tx, de4.amount)); CHECK_AND_ASSERT_MES(ms1_hash != null_hash && ms2_hash != null_hash && ms3_hash != null_hash && ms4_hash != null_hash, false, "Can't get multisig out id"); // Mine a block and make sure tx was put into it CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool"); r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool"); // Check Alice's wallet std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); alice_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1, false, "Incorrect numbers of blocks fetched"); r = check_balance_via_wallet(*alice_wlt.get(), "alice_wlt", amount5 + TESTS_DEFAULT_FEE); CHECK_AND_ASSERT_MES(r, false, "Invalid wallet balance"); tools::multisig_transfer_container alice_mstc; alice_wlt->get_multisig_transfers(alice_mstc); CHECK_AND_ASSERT_MES(alice_mstc.size() == 2 && alice_mstc.count(ms1_hash) == 1 && alice_mstc.count(ms3_hash) == 1, false, "Alice has incorrect multisig transfers"); // Check Bob's wallet std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, m_accounts[BOB_ACC_IDX]); bob_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1, false, "Incorrect numbers of blocks fetched"); r = check_balance_via_wallet(*bob_wlt.get(), "bob_wlt", 0); CHECK_AND_ASSERT_MES(r, false, "Invalid wallet balance"); tools::multisig_transfer_container bob_mstc; bob_wlt->get_multisig_transfers(bob_mstc); CHECK_AND_ASSERT_MES(bob_mstc.size() == 2 && bob_mstc.count(ms1_hash) == 1 && bob_mstc.count(ms4_hash) == 1, false, "Bob has incorrect multisig transfers"); // Check Carol's wallet std::shared_ptr carol_wlt = init_playtime_test_wallet(events, c, m_accounts[CAROL_ACC_IDX]); carol_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1, false, "Incorrect numbers of blocks fetched"); r = check_balance_via_wallet(*carol_wlt.get(), "carol_wlt", 0); CHECK_AND_ASSERT_MES(r, false, "Invalid wallet balance"); tools::multisig_transfer_container carol_mstc; carol_wlt->get_multisig_transfers(carol_mstc); CHECK_AND_ASSERT_MES(carol_mstc.size() == 2 && carol_mstc.count(ms2_hash) == 1 && carol_mstc.count(ms4_hash) == 1, false, "Carol has incorrect multisig transfers"); // Check Dan's wallet std::shared_ptr dan_wlt = init_playtime_test_wallet(events, c, m_accounts[DAN_ACC_IDX]); dan_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1, false, "Incorrect numbers of blocks fetched"); r = check_balance_via_wallet(*dan_wlt.get(), "dan_wlt", amount6 + TESTS_DEFAULT_FEE); CHECK_AND_ASSERT_MES(r, false, "Invalid wallet balance"); tools::multisig_transfer_container dan_mstc; dan_wlt->get_multisig_transfers(dan_mstc); CHECK_AND_ASSERT_MES(dan_mstc.size() == 2 && dan_mstc.count(ms2_hash) == 1 && dan_mstc.count(ms3_hash) == 1, false, "Dan has incorrect multisig transfers"); // Spending multisig 1: Alice & Bob // 1. Alice spends multisig out - send tx to the pool, the Miner add it to a block (so tx become confirmed) // 2. Bob refreshes his wallet ans tries to do the same // 3. Make sure Bob can't create multisig tx CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool"); GENERATE_ACCOUNT(receiver_acc); currency::transaction tx = AUTO_VAL_INIT(tx); transfer_multisig(*alice_wlt.get(), std::list({ m_accounts[BOB_ACC_IDX].get_keys() }), ms1_hash, std::vector({ tx_destination_entry(amount1, receiver_acc.get_public_address()) }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, c.get_current_tx_version()); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool"); r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); // Once Bob refreshes his wallet he should see that Alice has already spent they shared multisig bob_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == 1, false, "Incorrect numbers of blocks fetched"); bool caught = false; try { // as 'false' means don't send to network. This should fail during preparation, not during sending/processing transfer_multisig(*bob_wlt.get(), std::list({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms1_hash, std::vector({ tx_destination_entry(amount1, receiver_acc.get_public_address()) }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); } catch (tools::error::wallet_internal_error&) { caught = true; } CHECK_AND_ASSERT_MES(caught, false, "Bob was able to make multisig tx for alreadly spent output"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool"); // Spending multisig 2: Carol & Dan // 1. Carol spends multisig out - send tx to the pool (it's unconfirmed) // 2. Dan refreshes wallet and tries to do the same // 3. Make sure Dan can't do this // 4. Clear the pool // 5. Make sure Dan now is able to send tx after it refreshes his wallet tx = AUTO_VAL_INIT(tx); transfer_multisig(*carol_wlt.get(), std::list({ m_accounts[DAN_ACC_IDX].get_keys() }), ms2_hash, std::vector({ tx_destination_entry(amount2, receiver_acc.get_public_address()) }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, c.get_current_tx_version()); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool"); bool stub; dan_wlt->scan_tx_pool(stub); caught = false; try { // as 'false' means don't send to network. This should fail during preparation, not during sending/processing transfer_multisig(*dan_wlt.get(), std::list({ m_accounts[CAROL_ACC_IDX].get_keys() }), ms2_hash, std::vector({ tx_destination_entry(amount2, receiver_acc.get_public_address()) }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); } catch (tools::error::wallet_internal_error&) { caught = true; } CHECK_AND_ASSERT_MES(caught, false, "Dan was able to make multisig tx for alreadly spent output"); // Clear tx pool c.get_tx_pool().purge_transactions(); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are txs in the pool."); dan_wlt->scan_tx_pool(stub); // Re-try spending Carol-Dan multisig out on behalf of Dan. It should be OK now transfer_multisig(*dan_wlt.get(), std::list({ m_accounts[CAROL_ACC_IDX].get_keys() }), ms2_hash, std::vector({ tx_destination_entry(amount2, receiver_acc.get_public_address()) }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, c.get_current_tx_version()); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool"); r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are txs in the pool."); // Spending multisig 3: Alice & Dan // 1. Alice spends multisig out - send tx to the pool (it's unconfirmed) // 2. Dan refreshes wallet and tries to do the same // 3. Make sure Dan can't do this // 4. Miner mines a PoW block confirming Alice's tx. // 5. Make sure Dan's wallet correctly handle it. // 6. Make sure Dan is not able to spend multisig out. tx = AUTO_VAL_INIT(tx); transfer_multisig(*alice_wlt.get(), std::list({ m_accounts[DAN_ACC_IDX].get_keys() }), ms3_hash, std::vector({ tx_destination_entry(amount3, receiver_acc.get_public_address()) }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, c.get_current_tx_version()); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool"); dan_wlt->scan_tx_pool(stub); caught = false; try { // last 'false' means don't send to network. This should fail during preparation, not during sending/processing transfer_multisig(*dan_wlt.get(), std::list({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms3_hash, std::vector({ tx_destination_entry(amount3, receiver_acc.get_public_address()) }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); } catch (tools::error::wallet_internal_error&) { caught = true; } CHECK_AND_ASSERT_MES(caught, false, "Dan was able to make multisig tx for alreadly spent output"); // Miner mines the next PoW block, confirming Alice's transaction. r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are txs in the pool."); // Refresh Dan's wallet. Unconfirmed multisig out should be correctly processed in the wallet during refresh. dan_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == 3, false, "Incorrect numbers of blocks fetched"); // This should correctly removed the transfer from wallet's unconfirmed list. dan_wlt->scan_tx_pool(stub); // Both Dan's ms transfers should be marked as 'spent' now. dan_mstc.clear(); dan_wlt->get_multisig_transfers(dan_mstc); CHECK_AND_ASSERT_MES(dan_mstc.size() == 2 && dan_mstc.count(ms2_hash) == 1 && dan_mstc.count(ms3_hash) == 1 && dan_mstc[ms2_hash].is_spent() && dan_mstc[ms3_hash].is_spent(), false, "Dan has incorrect multisig transfers"); // Re-try spending Carol-Dan multisig out on behalf of Dan. It should fail caught = false; try { transfer_multisig(*dan_wlt.get(), std::list({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms3_hash, std::vector({ tx_destination_entry(amount3, receiver_acc.get_public_address()) }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); } catch (tools::error::wallet_internal_error&) { caught = true; } CHECK_AND_ASSERT_MES(caught, false, "Dan was able to make multisig tx for alreadly spent output"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are txs in the pool."); // Spending multisig 4: Carol & Bob // Reset Carol wallet and check it's correctness carol_mstc.clear(); carol_wlt->reset_history(); carol_wlt->get_multisig_transfers(carol_mstc); CHECK_AND_ASSERT_MES(carol_mstc.empty(), false, "Carol has multisig transfers"); carol_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 4, false, "Incorrect numbers of blocks fetched"); r = check_balance_via_wallet(*carol_wlt.get(), "carol_wlt", 0); CHECK_AND_ASSERT_MES(r, false, "Invalid wallet balance"); carol_wlt->get_multisig_transfers(carol_mstc); CHECK_AND_ASSERT_MES(carol_mstc.size() == 2 && carol_mstc.count(ms2_hash) == 1 && carol_mstc.count(ms4_hash) == 1 && carol_mstc[ms2_hash].is_spent() && !carol_mstc[ms4_hash].is_spent(), false, "Carol has incorrect multisig transfers"); return true; } //------------------------------------------------------------------------------ multisig_wallet_same_dst_addr::multisig_wallet_same_dst_addr() { REGISTER_CALLBACK_METHOD(multisig_wallet_same_dst_addr, c1); } bool multisig_wallet_same_dst_addr::generate(std::vector& events) const { GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); block blk_0 = AUTO_VAL_INIT(blk_0); generator.construct_genesis_block(blk_0, preminer_acc, test_core_time::get_time()); events.push_back(blk_0); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); DO_CALLBACK(events, "c1"); return true; } bool multisig_wallet_same_dst_addr::c1(currency::core& c, size_t ev_index, const std::vector& events) { // Test outline: // 1. Miner mines few blocks. // 2. Miner transfers 'amount' to Alice with multisig out (mentioning Alice's account _twice_). // 3. Alice refreshes her wallet and transfers to Bob the money received by multisig tx. // 4. Bob checks his wallet and balance. size_t blocks_fetched = 0; bool received_money = false, r = false; std::atomic atomic_false(false); static const uint64_t amount = TESTS_DEFAULT_FEE * 13; std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]); miner_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW, false, "Incorrect number of blocks fetched."); tx_destination_entry de = AUTO_VAL_INIT(de); de.addr.push_back(m_accounts[ALICE_ACC_IDX].get_public_address()); de.addr.push_back(m_accounts[ALICE_ACC_IDX].get_public_address()); // multisig to the same address de.amount = amount + TESTS_DEFAULT_FEE; de.minimum_sigs = de.addr.size(); // transfer multisig to Alice transaction ms_tx = AUTO_VAL_INIT(ms_tx); miner_wlt->transfer(std::vector({ de }), 0, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment, ms_tx); // mine the next PoW and make sure everythig is allright CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect number of tx in the pool"); r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are transactions in the pool"); crypto::hash ms_hash = currency::get_multisig_out_id(ms_tx, get_tx_out_index_by_amount(ms_tx, de.amount)); // refresh Alice wallet std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); alice_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1, false, "Incorrect number of blocks fetched."); // transfers money, Alice has just received with multitsig tx, to Bob transaction tx = AUTO_VAL_INIT(tx); transfer_multisig(*alice_wlt.get(), std::list({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms_hash, std::vector({ tx_destination_entry(amount, m_accounts[BOB_ACC_IDX].get_public_address()) }), 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, c.get_current_tx_version()); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect number of tx in the pool"); r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are transactions in the pool"); // make sure Bob gets them std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, m_accounts[BOB_ACC_IDX]); bob_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2, false, "Incorrect number of blocks fetched."); r = check_balance_via_wallet(*bob_wlt.get(), "bob_wlt", amount); CHECK_AND_ASSERT_MES(r, false, "check_balance_via_wallet failed"); return true; } //------------------------------------------------------------------------------ multisig_wallet_ms_to_ms::multisig_wallet_ms_to_ms() { REGISTER_CALLBACK_METHOD(multisig_wallet_ms_to_ms, c1); } bool multisig_wallet_ms_to_ms::generate(std::vector& events) const { GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); block blk_0 = AUTO_VAL_INIT(blk_0); generator.construct_genesis_block(blk_0, preminer_acc, test_core_time::get_time()); events.push_back(blk_0); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); DO_CALLBACK(events, "c1"); return true; } bool multisig_wallet_ms_to_ms::c1(currency::core& c, size_t ev_index, const std::vector& events) { // Test outline: // 1. Miner mines few blocks and sends multisig(Alice, Bob) with 'amount' of coins. // 2. Alice receives this multisig tx and spends it by sending the next multisig(Miner, Bob). This makes a tx, having one ms input and one ms output. // 3. Miner receives that multisig tx and spends it by sending all the money to Bob. // 4. Make sure Bob gets the money. size_t blocks_fetched = 0; bool received_money = false, r = false; std::atomic atomic_false(false); static const uint64_t amount = TESTS_DEFAULT_FEE * 13; std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]); miner_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW, false, "Incorrect number of blocks fetched."); tx_destination_entry de = AUTO_VAL_INIT(de); de.addr.push_back(m_accounts[ALICE_ACC_IDX].get_public_address()); de.addr.push_back(m_accounts[BOB_ACC_IDX].get_public_address()); de.amount = amount + TESTS_DEFAULT_FEE; de.minimum_sigs = de.addr.size(); // transfer multisig to Alice + Bob transaction ms_tx = AUTO_VAL_INIT(ms_tx); miner_wlt->transfer(std::vector({ de }), 0, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment, ms_tx); // mine the next PoW and make sure everythig is allright CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect number of tx in the pool"); r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are transactions in the pool"); crypto::hash ms_hash = currency::get_multisig_out_id(ms_tx, get_tx_out_index_by_amount(ms_tx, de.amount)); // refresh Alice wallet std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); alice_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1, false, "Incorrect number of blocks fetched."); // multisig(Alice, Bob) to multisig(Miner, Bob) transfer de = AUTO_VAL_INIT(de); de.addr.push_back(m_accounts[MINER_ACC_IDX].get_public_address()); de.addr.push_back(m_accounts[BOB_ACC_IDX].get_public_address()); de.amount = amount; de.minimum_sigs = de.addr.size(); transaction ms_tx2 = AUTO_VAL_INIT(ms_tx2); transfer_multisig(*alice_wlt.get(), std::list({ m_accounts[BOB_ACC_IDX].get_keys() }), ms_hash, std::vector({ de }), 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), ms_tx2, c.get_current_tx_version()); // check the pool and mine a block CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect number of tx in the pool"); r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are transactions in the pool"); crypto::hash ms_hash2 = currency::get_multisig_out_id(ms_tx2, get_tx_out_index_by_amount(ms_tx2, de.amount)); miner_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == 2, false, "Incorrect number of blocks fetched."); // spend the last multisig(Miner, Bob) and transfer to Bob transaction tx = AUTO_VAL_INIT(tx); transfer_multisig(*miner_wlt.get(), std::list({ m_accounts[BOB_ACC_IDX].get_keys() }), ms_hash2, std::vector({ tx_destination_entry(amount - TESTS_DEFAULT_FEE, m_accounts[BOB_ACC_IDX].get_public_address()) }), 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, c.get_current_tx_version()); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect number of tx in the pool"); r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "There are transactions in the pool"); // make sure Bob gets coins std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, m_accounts[BOB_ACC_IDX]); bob_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 3, false, "Incorrect number of blocks fetched."); r = check_balance_via_wallet(*bob_wlt.get(), "bob_wlt", amount - TESTS_DEFAULT_FEE); CHECK_AND_ASSERT_MES(r, false, "check_balance_via_wallet failed"); return true; } //------------------------------------------------------------------------------ multisig_minimum_sigs::multisig_minimum_sigs() { } bool multisig_minimum_sigs::generate(std::vector& events) const { // Checks minimum_sigs feature. Doesn't use wallet2. Gentime only. // Test outline: // Case 1. Create multisig for three participants (Miner, Alice, Bob) and specify minimum_sigs == 4 (should fail on construction) // Case 2. Create multisig for three participants (Miner, Alice, Bob) and specify minimum_sigs == 2 // 2.1. Then try to spend it using only 1 key (should fail) // 2.2. Spend using 2 keys (Alice, Bob) by sending money to Bob // Case 3. Try to spend already spent multisig output // Case 4. Use on spending multisig output more keys than required // Case 5. Spending multisig output using more keys than required and, moreover, add some fake keys // Case 6. Spending multisig using too many redundant keys (which is computationally expensive) GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time()); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); bool r = false; uint64_t amount = TESTS_DEFAULT_FEE * 17; std::list ms_addr_list({ miner_acc.get_public_address(), alice_acc.get_public_address(), bob_acc.get_public_address() }); // Case 1. Create multisig for three participants (Miner, Alice, Bob) and specify minimum_sigs == 4 (should fail on construction) std::vector sources; std::vector destinations; r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), ms_addr_list, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 4); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx = AUTO_VAL_INIT(tx); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r == false, false, "construct_tx was expected to fail, but successed"); // Case 2. Create multisig for three participants (Miner, Alice, Bob) and specify minimum_sigs == 2 // 2.1. Then try to spend it using only 1 key (should fail) // 2.2. Spend using 2 keys (Alice, Bob) by sending money to Bob sources.clear(); destinations.clear(); r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), ms_addr_list, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 2); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); events.push_back(tx_1); // store multisig output index and id for further reference size_t ms_out_idx = get_tx_out_index_by_amount(tx_1, amount); crypto::hash ms_id = get_multisig_out_id(tx_1, ms_out_idx); // Put tx_1 into a block MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); // Subcase 2.1 Try to spend multisig out using only one key tx_source_entry se = AUTO_VAL_INIT(se); se.amount = amount; se.multisig_id = ms_id; se.real_output_in_tx_index = ms_out_idx; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_1); se.ms_keys_count = boost::get(boost::get(tx_1.vout[se.real_output_in_tx_index]).target).keys.size(); se.ms_sigs_count = 2; tx_destination_entry de(se.amount - TESTS_DEFAULT_FEE, bob_acc.get_public_address()); // Transaction should be successfully created, but rejected by the core transaction tx_2 = AUTO_VAL_INIT(tx_2); r = construct_tx(miner_acc.get_keys(), std::vector({ se }), std::vector({ de }), empty_attachment, tx_2, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); bool tx_fully_signed = false; r = sign_multisig_input_in_tx(tx_2, 0, bob_acc.get_keys(), tx_1, &tx_fully_signed); CHECK_AND_ASSERT_MES(r && !tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_2); // Subcase 2.2 Add second participant and send. Should be okay. r = sign_multisig_input_in_tx(tx_2, 0, alice_acc.get_keys(), tx_1, &tx_fully_signed); CHECK_AND_ASSERT_MES(r && tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); // should be fully signed now events.push_back(tx_2); // Put the tx into a block MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner_acc, tx_2); // Make sure Bob's balance is correct DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, de.amount)); // Case 3. Try to spend already spent multisig output se = AUTO_VAL_INIT(se); se.amount = amount; se.multisig_id = ms_id; se.real_output_in_tx_index = ms_out_idx; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_1); se.ms_keys_count = boost::get(boost::get(tx_1.vout[se.real_output_in_tx_index]).target).keys.size(); se.ms_sigs_count = 2; transaction tx_3 = AUTO_VAL_INIT(tx_3); r = construct_tx(miner_acc.get_keys(), std::vector({ se }), std::vector({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, bob_acc.get_public_address()) }), empty_attachment, tx_3, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); r = sign_multisig_input_in_tx(tx_3, 0, bob_acc.get_keys(), tx_1, &tx_fully_signed); CHECK_AND_ASSERT_MES(r && !tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); r = sign_multisig_input_in_tx(tx_3, 0, alice_acc.get_keys(), tx_1, &tx_fully_signed); CHECK_AND_ASSERT_MES(r && tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); // should be fully signed now DO_CALLBACK(events, "mark_invalid_tx"); // already spent events.push_back(tx_3); // Case 4. Use on spending multisig output more keys than required // create source tx: tx_4 sources.clear(); destinations.clear(); r = fill_tx_sources_and_destinations(events, blk_2, miner_acc.get_keys(), ms_addr_list, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_4 = AUTO_VAL_INIT(tx_4); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_4, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); events.push_back(tx_4); MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_4); size_t ms_4_out_idx = get_tx_out_index_by_amount(tx_4, amount); crypto::hash ms_4_id = get_multisig_out_id(tx_4, ms_4_out_idx); se = AUTO_VAL_INIT(se); se.amount = amount; se.multisig_id = ms_4_id; se.real_output_in_tx_index = ms_4_out_idx; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_4); se.ms_keys_count = boost::get(boost::get(tx_4.vout[se.real_output_in_tx_index]).target).keys.size(); se.ms_sigs_count = 3; transaction tx_5 = AUTO_VAL_INIT(tx_5); r = construct_tx(miner_acc.get_keys(), std::vector({ se }), std::vector({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()) }), empty_attachment, tx_5, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); // use sign_multisig_input_in_tx_custom to create tx with more signatures (3) than minimum_sigs (1) r = sign_multisig_input_in_tx_custom(tx_5, ms_4_out_idx, bob_acc.get_keys(), tx_4, &tx_fully_signed, false); CHECK_AND_ASSERT_MES(r && !tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); r = sign_multisig_input_in_tx_custom(tx_5, ms_4_out_idx, alice_acc.get_keys(), tx_4, &tx_fully_signed, false); CHECK_AND_ASSERT_MES(r && !tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); r = sign_multisig_input_in_tx_custom(tx_5, ms_4_out_idx, miner_acc.get_keys(), tx_4, &tx_fully_signed, true); CHECK_AND_ASSERT_MES(r && tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); // should be fully signed now DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_5); MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_acc); //DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, amount - TX_POOL_MINIMUM_FEE)); // Case 5. Spending multisig output using more keys than required and, moreover, add some fake keys sources.clear(); destinations.clear(); r = fill_tx_sources_and_destinations(events, blk_4, miner_acc.get_keys(), ms_addr_list, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_6 = AUTO_VAL_INIT(tx_6); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_6, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); events.push_back(tx_6); MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4, miner_acc, tx_6); // store multisig output index and id for further reference size_t ms_6_out_idx = get_tx_out_index_by_amount(tx_6, amount); crypto::hash ms_6_id = get_multisig_out_id(tx_6, ms_6_out_idx); se = AUTO_VAL_INIT(se); se.amount = amount; se.multisig_id = ms_6_id; se.real_output_in_tx_index = ms_6_out_idx; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_6); se.ms_keys_count = boost::get(boost::get(tx_6.vout[se.real_output_in_tx_index]).target).keys.size(); se.ms_sigs_count = 4; transaction tx_7 = AUTO_VAL_INIT(tx_7); r = construct_tx(miner_acc.get_keys(), std::vector({ se }), std::vector({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()) }), empty_attachment, tx_7, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); // use sign_multisig_input_in_tx_custom to create tx with more signatures (4) than minimum_sigs (1) r = sign_multisig_input_in_tx_custom(tx_7, ms_6_out_idx, bob_acc.get_keys(), tx_6, &tx_fully_signed, false); CHECK_AND_ASSERT_MES(r && !tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); r = sign_multisig_input_in_tx_custom(tx_7, ms_6_out_idx, alice_acc.get_keys(), tx_6, &tx_fully_signed, false); CHECK_AND_ASSERT_MES(r && !tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); r = sign_multisig_input_in_tx_custom(tx_7, ms_6_out_idx, miner_acc.get_keys(), tx_6, &tx_fully_signed, false); CHECK_AND_ASSERT_MES(r && !tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); boost::get(tx_7.signatures[0]).s.push_back(invalid_signature); // instead of 4th sig just add invalid sig DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_7); // Case 6. Spending multisig using too many redundant keys sources.clear(); destinations.clear(); r = fill_tx_sources_and_destinations(events, blk_5, miner_acc.get_keys(), ms_addr_list, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_8 = AUTO_VAL_INIT(tx_8); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_8, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); events.push_back(tx_8); MAKE_NEXT_BLOCK_TX1(events, blk_6, blk_5, miner_acc, tx_8); // store multisig output index and id for further reference size_t ms_8_out_idx = get_tx_out_index_by_amount(tx_8, amount); crypto::hash ms_8_id = get_multisig_out_id(tx_8, ms_8_out_idx); se = AUTO_VAL_INIT(se); se.amount = amount; se.multisig_id = ms_8_id; se.real_output_in_tx_index = ms_8_out_idx; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_8); se.ms_keys_count = boost::get(boost::get(tx_8.vout[se.real_output_in_tx_index]).target).keys.size(); static const size_t redundant_keys_count = 7000; se.ms_sigs_count = redundant_keys_count; transaction tx_9 = AUTO_VAL_INIT(tx_9); r = construct_tx(miner_acc.get_keys(), std::vector({ se }), std::vector({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()) }), empty_attachment, tx_9, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); boost::get(tx_9.signatures[0]).s.resize(redundant_keys_count, invalid_signature); r = sign_multisig_input_in_tx_custom(tx_9, ms_8_out_idx, bob_acc.get_keys(), tx_8, &tx_fully_signed, false); CHECK_AND_ASSERT_MES(r && !tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed : " << tx_fully_signed); DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_9); //MAKE_NEXT_BLOCK_TX1(events, blk_7, blk_6, miner_acc, tx_9); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, 0)); return true; } //------------------------------------------------------------------------------ bool multisig_and_fake_outputs::generate(std::vector& events) const { // Test idea: multisig outs are incompartible with faked mix-ins. // Here we try to mix multisig and faked inputs/outputs in one transaction to see if smth goes wrong. // Setup accounts GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); LOCAL_ASSERT(m_accounts.size() - 1 == MINER_ACC_IDX); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); LOCAL_ASSERT(m_accounts.size() - 1 == ALICE_ACC_IDX); // Mine starter blocks MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time()); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 3); // Calculate amount that many outs already have to simplify debugging (such amount won't be splitted into digits) bool r = false; uint64_t amount = 0, stub; r = calculate_amounts_many_outs_have_and_no_outs_have(get_outs_money_amount(blk_0r.miner_tx), amount, stub); CHECK_AND_ASSERT_MES(r, false, "calculate_amounts_many_outs_have_and_no_outs_have failed"); // Use two account as multisig target std::list ms_addr_list({ miner_acc.get_public_address(), alice_acc.get_public_address() }); // tx_1: 1 real + 2 fake inputs => 1 multisig output (Miner + Alice, minimum_sigs = 1) std::vector sources; std::vector destinations; r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), ms_addr_list, amount, TESTS_DEFAULT_FEE, 2, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); // Make sure tx_1 is successfully created transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); events.push_back(tx_1); // Add it to blockchain MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); size_t tx_1_ms_out_idx = get_tx_out_index_by_amount(tx_1, amount); crypto::hash tx_1_ms_out_id = get_multisig_out_id(tx_1, tx_1_ms_out_idx); // Try to create tx_source_entry with a mix of multisig and to_key outputs size_t nmix = 2; // mix-in 1 fake output sources.clear(); // First, prepare as normal source entry with some fake inputs r = fill_tx_sources(sources, events, blk_1, miner_acc.get_keys(), amount, nmix, true, true); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed"); CHECK_AND_ASSERT_MES(sources.size() == 1 && sources[0].amount == amount && sources[0].outputs.size() == nmix + 1, false, "fill_tx_sources returned unexpected results"); // Second, correctly set up multisig part tx_source_entry& se = sources.back(); se.multisig_id = tx_1_ms_out_id; se.ms_keys_count = boost::get(boost::get(tx_1.vout[tx_1_ms_out_idx]).target).keys.size(); se.ms_sigs_count = 1; se.real_output_in_tx_index = tx_1_ms_out_idx; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_1); destinations.clear(); destinations.push_back(tx_destination_entry(amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address())); // Make sure tx is successfully created transaction tx_2 = AUTO_VAL_INIT(tx_2); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_2, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); bool tx_fully_signed = false; r = sign_multisig_input_in_tx(tx_2, 0, alice_acc.get_keys(), tx_1, &tx_fully_signed); CHECK_AND_ASSERT_MES(r && tx_fully_signed, false, "sign_multisig_input_in_tx failed, sign_multisig_input_in_tx: " << r); events.push_back(tx_2); // ...and put into a block MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner_acc, tx_2); // Finally, check the balance of Alice's wallet DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, amount - TESTS_DEFAULT_FEE)); return true; } //------------------------------------------------------------------------------ bool multisig_and_unlock_time::generate(std::vector& events) const { // Test idea: check how multisig-containing tx with unlock_time and expiration_time are processed GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); LOCAL_ASSERT(m_accounts.size() - 1 == MINER_ACC_IDX); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); LOCAL_ASSERT(m_accounts.size() - 1 == ALICE_ACC_IDX); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); LOCAL_ASSERT(m_accounts.size() - 1 == BOB_ACC_IDX); // Mine starter blocks MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time()); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 3); test_core_time::adjust(blk_0r.timestamp); // adjust gentime time to blockchain timestamps bool r = false; uint64_t amount = TESTS_DEFAULT_FEE * 9; std::list ms_addr_list({ miner_acc.get_public_address(), alice_acc.get_public_address() }); // noramal input -> multisig output with unlock time std::vector sources; std::vector destinations; r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), ms_addr_list, amount, TESTS_DEFAULT_FEE, 1 /*nmix*/, sources, destinations, true, true, 1 /* minimum sigs */); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); uint64_t unlock_time = blk_0r.timestamp + DIFFICULTY_TOTAL_TARGET * 3 + CURRENCY_LOCKED_TX_ALLOWED_DELTA_SECONDS; uint64_t unlock_time_2 = blk_0r.timestamp + DIFFICULTY_TOTAL_TARGET * 6 + CURRENCY_LOCKED_TX_ALLOWED_DELTA_SECONDS; transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, get_tx_version_from_events(events), 0, unlock_time, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); CHECK_AND_ASSERT_MES(get_tx_max_unlock_time(tx_1) == unlock_time, false, "Unlock time was not correctly set"); events.push_back(tx_1); MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); CHECK_AND_ASSERT_MES(blk_1.timestamp < unlock_time, false, "Block timestamp went far beyond expected limits, consider adjusting 'unlock_time'"); test_core_time::adjust(blk_1.timestamp); // adjust gentime time to blockchain timestamps events.push_back(event_core_time(blk_1.timestamp)); // adjust playtime time to blockchain timestamps // Then try to spend multisig out of tx_1 earlier than it's unlock_time size_t tx_1_ms_out_idx = get_tx_out_index_by_amount(tx_1, amount); crypto::hash tx_1_ms_out_id = get_multisig_out_id(tx_1, tx_1_ms_out_idx); tx_source_entry se = AUTO_VAL_INIT(se); se.amount = amount; se.multisig_id = tx_1_ms_out_id; se.real_output_in_tx_index = tx_1_ms_out_idx; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_1); se.ms_keys_count = boost::get(boost::get(tx_1.vout[tx_1_ms_out_idx]).target).keys.size(); se.ms_sigs_count = 1; sources.assign({ se }); destinations.assign({ tx_destination_entry(amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()) }); ADJUST_TEST_CORE_TIME(unlock_time - CURRENCY_LOCKED_TX_ALLOWED_DELTA_SECONDS - 1); // tx_2 should be created ok, but rejected by the core, as one of input refers to a locked tx // Note: tx_2 has unlock_time_2 specified transaction tx_2 = AUTO_VAL_INIT(tx_2); r = construct_tx(alice_acc.get_keys(), sources, destinations, empty_attachment, tx_2, get_tx_version_from_events(events), 0, unlock_time_2, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); bool tx_fully_signed = false; r = sign_multisig_input_in_tx(tx_2, tx_1_ms_out_idx, miner_acc.get_keys(), tx_1, &tx_fully_signed); CHECK_AND_ASSERT_MES(r && tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed: " << tx_fully_signed); DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_2); // rewind the time a little, now tx_2 should be accepted ADJUST_TEST_CORE_TIME(unlock_time - CURRENCY_LOCKED_TX_ALLOWED_DELTA_SECONDS + 1); events.push_back(tx_2); MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner_acc, tx_2); // Next, check tx_2 unlock time by spending all coins ADJUST_TEST_CORE_TIME(unlock_time_2 - CURRENCY_LOCKED_TX_ALLOWED_DELTA_SECONDS - 1); DO_CALLBACK(events, "mark_invalid_tx"); // instead of MAKE_TX use manual construction to set check_for_unlocktime = false, old: MAKE_TX(events, tx_3, alice_acc, bob_acc, amount - TESTS_DEFAULT_FEE * 2, blk_2); r = fill_tx_sources_and_destinations(events, blk_2, alice_acc, bob_acc, amount - TESTS_DEFAULT_FEE * 2, TESTS_DEFAULT_FEE, 0 /*nmix*/, sources, destinations, true, false /* check_for_unlocktime */); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_3{}; r = construct_tx(alice_acc.get_keys(), sources, destinations, empty_attachment, tx_3, get_tx_version_from_events(events), 0, 0 /* unlock time */, 0 /* mix attib */); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); ADD_CUSTOM_EVENT(events, tx_3); ADJUST_TEST_CORE_TIME(unlock_time_2 - CURRENCY_LOCKED_TX_ALLOWED_DELTA_SECONDS + 1); ADD_CUSTOM_EVENT(events, tx_3); MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_3); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, amount - TESTS_DEFAULT_FEE * 2)); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, 0)); // Expiration time // 1. txin_to_key -> multisig output with expiration time uint64_t expiration_time = generator.get_timestamps_median(get_block_hash(blk_3), TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW) - DIFFICULTY_TOTAL_TARGET * 3; sources.clear(); destinations.clear(); r = fill_tx_sources_and_destinations(events, blk_3, miner_acc.get_keys(), ms_addr_list, amount, TESTS_DEFAULT_FEE, 1, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_4 = AUTO_VAL_INIT(tx_4); crypto::secret_key stub_key = AUTO_VAL_INIT(stub_key); etc_tx_details_expiration_time extra_expiration_time = AUTO_VAL_INIT(extra_expiration_time); extra_expiration_time.v = expiration_time; r = construct_tx(miner_acc.get_keys(), sources, destinations, std::vector({ extra_expiration_time }), empty_attachment, tx_4, get_tx_version_from_events(events), 0, stub_key, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); CHECK_AND_ASSERT_MES(get_tx_expiration_time(tx_4) == expiration_time, false, "Expiration time was not correctly set"); DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_4); // add similar tx (same sources and destinations) with no expiration_time - should be accepted by the core transaction tx_5 = AUTO_VAL_INIT(tx_5); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_extra, empty_attachment, tx_5, get_tx_version_from_events(events), 0, stub_key, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); events.push_back(tx_5); MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3, miner_acc, tx_5); ADJUST_TEST_CORE_TIME(blk_4.timestamp); // 2. Multisig output -> txout_to_key with expiration time size_t tx_5_ms_out_idx = get_tx_out_index_by_amount(tx_5, amount); crypto::hash tx_5_ms_out_id = get_multisig_out_id(tx_5, tx_5_ms_out_idx); se = AUTO_VAL_INIT(se); se.amount = amount; se.multisig_id = tx_5_ms_out_id; se.real_output_in_tx_index = tx_5_ms_out_idx; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_5); se.ms_keys_count = boost::get(boost::get(tx_5.vout[tx_5_ms_out_idx]).target).keys.size(); se.ms_sigs_count = 1; sources.assign({ se }); destinations.assign({ tx_destination_entry(amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()) }); expiration_time = generator.get_timestamps_median(get_block_hash(blk_4), TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW) - DIFFICULTY_TOTAL_TARGET * 3; extra_expiration_time = AUTO_VAL_INIT(extra_expiration_time); extra_expiration_time.v = expiration_time; transaction tx_6 = AUTO_VAL_INIT(tx_6); r = construct_tx(miner_acc.get_keys(), sources, destinations, std::vector({ extra_expiration_time }), empty_attachment, tx_6, get_tx_version_from_events(events), 0, stub_key, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); CHECK_AND_ASSERT_MES(get_tx_expiration_time(tx_6) == expiration_time, false, "Expiration time was not correctly set"); r = sign_multisig_input_in_tx(tx_6, tx_5_ms_out_idx, miner_acc.get_keys(), tx_5, &tx_fully_signed); CHECK_AND_ASSERT_MES(r && tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed: " << tx_fully_signed); DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_6); return true; } //------------------------------------------------------------------------------ bool multisig_and_coinbase::generate(std::vector& events) const { // Test idea: creat a block with multisig output in its miner tx, then spend it to make sure everything is ok. GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); LOCAL_ASSERT(m_accounts.size() - 1 == MINER_ACC_IDX); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); LOCAL_ASSERT(m_accounts.size() - 1 == ALICE_ACC_IDX); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); LOCAL_ASSERT(m_accounts.size() - 1 == BOB_ACC_IDX); bool r = false; // Mine starter blocks MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); DO_CALLBACK(events, "configure_core"); // to lower PoS minimum height REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); // // Part 1. PoS block // const block& prev_block = blk_0r; crypto::hash prev_id = get_block_hash(prev_block); size_t height = get_block_height(prev_block) + 1; currency::wide_difficulty_type diff = generator.get_difficulty_for_next_block(prev_id, false); block blk_1 = AUTO_VAL_INIT(blk_1); { const transaction& stake = blk_0.miner_tx; crypto::public_key stake_tx_pub_key = get_tx_pub_key_from_extra(stake); size_t stake_output_idx = 0; size_t stake_output_gidx = 0; uint64_t stake_output_amount =boost::get( stake.vout[stake_output_idx]).amount; crypto::key_image stake_output_key_image; keypair kp; generate_key_image_helper(miner_acc.get_keys(), stake_tx_pub_key, stake_output_idx, kp, stake_output_key_image); crypto::public_key stake_output_pubkey = boost::get(boost::get(stake.vout[stake_output_idx]).target).key; keypair tx_key = keypair::generate(); pos_block_builder pb; pb.step1_init_header(generator.get_hardforks(), height, prev_id); pb.step2_set_txs(std::vector()); pb.step3_build_stake_kernel(stake_output_amount, stake_output_gidx, stake_output_key_image, diff, prev_id, null_hash, prev_block.timestamp); pb.step4_generate_coinbase_tx(generator.get_timestamps_median(prev_id), generator.get_already_generated_coins(prev_block), miner_acc.get_public_address(), blobdata(), CURRENCY_MINER_TX_MAX_OUTS, &tx_key); // The builder creates PoS miner tx with normal outputs. // Replace all miner_tx outputs with one multisig output and re-sign it. { std::vector participants({ alice_acc.get_public_address(), bob_acc.get_public_address() }); transaction& miner_tx = pb.m_block.miner_tx; txout_multisig ms_out_target = AUTO_VAL_INIT(ms_out_target); ms_out_target.minimum_sigs = 1; size_t multisig_out_idx = 0; // because it's the only output in vout for (auto& p : participants) { crypto::key_derivation der = AUTO_VAL_INIT(der); r = crypto::generate_key_derivation(p.view_public_key, tx_key.sec, der); CHECK_AND_ASSERT_MES(r, false, "generate_key_derivation failed"); crypto::public_key key = AUTO_VAL_INIT(key); r = crypto::derive_public_key(der, multisig_out_idx, p.spend_public_key, key); CHECK_AND_ASSERT_MES(r, false, "derive_public_key failed"); ms_out_target.keys.push_back(key); } tx_out_bare ms_out = AUTO_VAL_INIT(ms_out); ms_out.amount = get_outs_money_amount(miner_tx); // get amount from vout before clearing miner_tx.vout.clear(); ms_out.target = ms_out_target; miner_tx.vout.push_back(ms_out); } pb.step5_sign(stake_tx_pub_key, stake_output_idx, stake_output_pubkey, miner_acc); // add block info to generator only to be able to make the next block in gentime generator.add_block_info(test_generator::block_info(pb.m_block, generator.get_already_generated_coins(prev_block) + get_outs_money_amount(pb.m_block.miner_tx) - stake_output_amount, pb.m_txs_total_size + get_object_blobsize(pb.m_block), generator.get_cumul_difficulty_for_next_block(prev_id), std::list(), null_hash)); blk_1 = pb.m_block; } events.push_back(blk_1); // In order to spend coinbase tx we must wait for CURRENCY_MINED_MONEY_UNLOCK_WINDOW blocks being added to the blockchain REWIND_BLOCKS_N_WITH_TIME(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); // spend multisig output to make sure everything is okay std::vector sources; std::vector destinations; tx_source_entry se = AUTO_VAL_INIT(se); se.amount = get_outs_money_amount(blk_1.miner_tx); se.real_output_in_tx_index = 0; se.multisig_id = get_multisig_out_id(blk_1.miner_tx, se.real_output_in_tx_index); //se.participants.push_back(alice_acc.get_keys()); se.real_out_tx_key = get_tx_pub_key_from_extra(blk_1.miner_tx); se.ms_keys_count = boost::get(boost::get(blk_1.miner_tx.vout[se.real_output_in_tx_index]).target).keys.size(); se.ms_sigs_count = 1; sources.assign({ se }); destinations.assign({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, bob_acc.get_public_address()) }); transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(alice_acc.get_keys(), sources, destinations, empty_attachment, tx_1, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); bool fully_signed_tx = false; r = sign_multisig_input_in_tx(tx_1, 0, alice_acc.get_keys(), blk_1.miner_tx, &fully_signed_tx); CHECK_AND_ASSERT_MES(r && fully_signed_tx, false, "sign_multisig_input_in_tx failed, fully_signed_tx: " << fully_signed_tx); events.push_back(tx_1); MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner_acc, tx_1); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, 0)); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, se.amount - TESTS_DEFAULT_FEE)); // // Part 2. The same for PoW // uint64_t blk_2_reward = generator.get_base_reward_for_next_block(get_block_hash(blk_1), true); block blk_3 = AUTO_VAL_INIT(blk_3); { block& b = blk_3; const block& prev_block = blk_2; size_t height = get_block_height(prev_block) + 1; // manually create a transaction with multisig out std::list ms_addr_list({ miner_acc.get_public_address(), alice_acc.get_public_address() }); std::vector sources; std::vector destinations; r = fill_tx_sources_and_destinations(events, prev_block, miner_acc.get_keys(), ms_addr_list, blk_2_reward, TESTS_DEFAULT_FEE, 0, sources, destinations, false, false, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction miner_tx = AUTO_VAL_INIT(miner_tx); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, miner_tx, get_tx_version_from_events(events), 0, height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); // replace vin with coinbase input txin_gen in_gen = AUTO_VAL_INIT(in_gen); in_gen.height = height; miner_tx.vin.assign({ in_gen }); // remove all outputs except the multisig auto it = std::find_if(miner_tx.vout.begin(), miner_tx.vout.end(), [](const tx_out_v& o) {return boost::get(o).target.type() == typeid(txout_multisig); }); CHECK_AND_ASSERT_MES(it != miner_tx.vout.end(), false, "construct_tx didn't create multisig output as expected"); tx_out_bare ms_out = boost::get(*it); miner_tx.vout.assign({ ms_out }); CHECK_AND_ASSERT_MES(ms_out.amount == blk_2_reward, false, "unexpected amount for found ms output"); r = generator.construct_block_manually(b, prev_block, miner_acc, test_generator::bf_miner_tx, 0, 0, 0, null_hash, 1, miner_tx); CHECK_AND_ASSERT_MES(r, false, "construct_block_manually failed"); } ADD_CUSTOM_EVENT(events, blk_3); // rewind blocks to be able to spend mined money REWIND_BLOCKS_N(events, blk_3r, blk_3, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); // spend coinbase in blk_3 to make sure everything is okay se = AUTO_VAL_INIT(se); se.amount = blk_2_reward; se.multisig_id = get_multisig_out_id(blk_3.miner_tx, 0); se.real_output_in_tx_index = 0; se.real_out_tx_key = get_tx_pub_key_from_extra(blk_3.miner_tx); se.ms_keys_count= boost::get(boost::get(blk_3.miner_tx.vout[se.real_output_in_tx_index]).target).keys.size(); se.ms_sigs_count = 1; transaction tx_2 = AUTO_VAL_INIT(tx_2); r = construct_tx(alice_acc.get_keys(), std::vector({ se }), std::vector({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()) }), empty_attachment, tx_2, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); r = sign_multisig_input_in_tx(tx_2, 0, alice_acc.get_keys(), blk_3.miner_tx, &fully_signed_tx); CHECK_AND_ASSERT_MES(r && fully_signed_tx, false, "sign_multisig_input_in_tx failed, fully_signed_tx: " << fully_signed_tx); events.push_back(tx_2); MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3r, miner_acc, tx_2); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, se.amount - TESTS_DEFAULT_FEE)); return true; } //------------------------------------------------------------------------------ bool multisig_with_same_id_in_pool::generate(std::vector& events) const { // Test idea: create two txs having the same multisig id in their outputs and see how the core handles it. // Then put two txs referring to the ms out id in their inputs. GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); LOCAL_ASSERT(m_accounts.size() - 1 == MINER_ACC_IDX); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); LOCAL_ASSERT(m_accounts.size() - 1 == ALICE_ACC_IDX); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); LOCAL_ASSERT(m_accounts.size() - 1 == BOB_ACC_IDX); MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time()); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2); bool r = false; uint64_t amount_many_out_have = 0, stub = 0; r = calculate_amounts_many_outs_have_and_no_outs_have(generator.get_base_reward_for_next_block(get_block_hash(blk_0r)), amount_many_out_have, stub); // this amount corresponds to many outs, so no changeback will be generated CHECK_AND_ASSERT_MES(r, false, "calculate_amounts_many_outs_have_and_no_outs_have failed"); uint64_t amount = amount_many_out_have - TESTS_DEFAULT_FEE; std::list to_addrs({ alice_acc.get_public_address(), alice_acc.get_public_address() }); // multisig to same account, mentioned twice std::vector sources; std::vector destinations; // tx_1: normal input -> multisig output r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), to_addrs, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true); CHECK_AND_ASSERT_MES(r && sources.size() == 1 && destinations.size() == 1, false, "fill_tx_sources_and_destinations failed"); // we selected such good amount so sources.size() and destinations.size() should be eq 1 // create tx_1 manually in order to have access to its one-time key tx_builder txb_1; txb_1.step1_init(); txb_1.step2_fill_inputs(miner_acc.get_keys(), sources); txb_1.step3_fill_outputs(destinations, 0, 1); txb_1.step4_calc_hash(); txb_1.step5_sign(sources); const transaction& tx_1 = txb_1.m_tx; // tx_1 should be accepted, no surprize here events.push_back(tx_1); // prepare sources and destinations for tx_2, using the same amount std::vector sources2; std::vector destinations2; r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), to_addrs, amount, TESTS_DEFAULT_FEE, 0, sources2, destinations2, true, true); CHECK_AND_ASSERT_MES(r && sources2.size() == 1 && destinations2.size() == 1, false, "fill_tx_sources_and_destinations failed"); // we selected such good amount so sources.size() and destinations.size() should be eq 1 // tx_2 differs in inputs but should have the same outputs tx_builder txb_2; txb_2.step1_init(); txb_2.m_tx_key = txb_1.m_tx_key; // use the same one-time tx key as tx_1 // clear extra and put corresponding pub key to the extra txb_2.m_tx.extra.clear(); add_tx_pub_key_to_extra(txb_2.m_tx, txb_2.m_tx_key.pub); txb_2.step2_fill_inputs(miner_acc.get_keys(), sources2); // use other sources txb_2.step3_fill_outputs(destinations, 0, 1); // ...but the same destinations, thus vout should be the same (while one-time tx key is the same) txb_2.step4_calc_hash(); txb_2.step5_sign(sources2); const transaction& tx_2 = txb_2.m_tx; // Well done! Now multisig output id in both txs should be the same. crypto::hash tx_1_ms_id = get_multisig_out_id(tx_1, get_tx_out_index_by_amount(tx_1, amount)); crypto::hash tx_2_ms_id = get_multisig_out_id(tx_2, get_tx_out_index_by_amount(tx_2, amount)); CHECK_AND_ASSERT_MES(tx_1_ms_id == tx_2_ms_id, false, "Multisig ids didn't match as expected"); // tx_2 should be rejected from being added to the pool as it contains the same multisig id as tx_1 which is still in the poll DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_2); MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); // now tx_2 should still be rejected by the pool as the same multisig id is in then blockchain DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_2); // Create and add two txs referring to the same ms out id in their inputs tx_source_entry se = AUTO_VAL_INIT(se); se.amount = amount; se.multisig_id = tx_1_ms_id; se.real_output_in_tx_index = get_tx_out_index_by_amount(tx_1, amount); se.real_out_tx_key = get_tx_pub_key_from_extra(tx_1); se.ms_keys_count = boost::get(boost::get(tx_1.vout[se.real_output_in_tx_index]).target).keys.size(); se.ms_sigs_count = 1; tx_destination_entry de(amount - TESTS_DEFAULT_FEE, bob_acc.get_public_address()); transaction tx_3 = AUTO_VAL_INIT(tx_3); r = construct_tx(alice_acc.get_keys(), std::vector({ se }), std::vector({ de }), empty_attachment, tx_3, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); bool fully_signed_tx = false; r = sign_multisig_input_in_tx_custom(tx_3, 0, alice_acc.get_keys(), tx_1, &fully_signed_tx); CHECK_AND_ASSERT_MES(r && fully_signed_tx, false, "sign_multisig_input_in_tx failed, fully_signed_tx: " << fully_signed_tx); events.push_back(tx_3); transaction tx_4 = AUTO_VAL_INIT(tx_4); r = construct_tx(alice_acc.get_keys(), std::vector({ se }), std::vector({ de }), empty_attachment, tx_4, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); // tx_4 should be rejected as containing multisig input already mentioned in the pool (by tx_3) DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_4); MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_1, miner_acc, tx_3); // tx_4 should still be rejected as spending already spent (by tx_3) multisig output DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_4); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, de.amount)); return true; } //------------------------------------------------------------------------------ multisig_and_checkpoints::multisig_and_checkpoints() { // NOTE: This test is made deterministic to be able to correctly set up checkpoint. random_state_test_restorer::reset_random(); // random generator's state was previously stored, will be restore on dtor (see also m_random_state_test_restorer) REGISTER_CALLBACK_METHOD(multisig_and_checkpoints, set_cp); } bool multisig_and_checkpoints::set_cp(currency::core& c, size_t ev_index, const std::vector& events) { currency::checkpoints checkpoints; checkpoints.add_checkpoint(15, "a78fa870608991aa773d5d5aaf684ac77fea30c3e103b00d3c05c15912215c31"); c.set_checkpoints(std::move(checkpoints)); return true; } bool multisig_and_checkpoints::generate(std::vector& events) const { // Test outline: // 1. Set checkpoint (on height 0 for height 15). // 2. Make few block to unlock mined money. // 3. tx_1: normal input -> multisig output // 4. tx_2: multisig input -> normal output (spends tx_1 ms out) // 5. tx_3: normal input -> multisig output // 6. (here blockchain passes checkpoint) // 7. tx_4 multisig input -> normal output (spends tx_3 ms out before CP) // 8. tx_5: normal input -> multisig output // 9. tx_6 multisig input -> normal output (spends tx_5 ms out) // 10. Check balances. uint64_t ts = 1450000000; test_core_time::adjust(ts); GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); miner_acc.set_createtime(ts); m_accounts.push_back(miner_acc); LOCAL_ASSERT(m_accounts.size() - 1 == MINER_ACC_IDX); GENERATE_ACCOUNT(alice_acc); alice_acc.set_createtime(ts); m_accounts.push_back(alice_acc); LOCAL_ASSERT(m_accounts.size() - 1 == ALICE_ACC_IDX); GENERATE_ACCOUNT(bob_acc); bob_acc.set_createtime(ts); m_accounts.push_back(bob_acc); LOCAL_ASSERT(m_accounts.size() - 1 == BOB_ACC_IDX); bool r = false; std::vector sources; std::vector destinations; MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, ts); DO_CALLBACK(events, "configure_core"); // set checkpoint DO_CALLBACK(events, "set_cp"); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2); uint64_t amount = TESTS_DEFAULT_FEE * 99; std::list to_addrs({ alice_acc.get_public_address(), alice_acc.get_public_address() }); // multisig to same account, mentioned twice // tx_1: normal input -> multisig output r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), to_addrs, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, currency::get_tx_version(get_block_height(blk_0r), m_hardforks), 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx"); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); // tx_1 goes with the block blk_1 events.push_back(tx_1); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); // tx_2: multisig input -> normal output (spends tx_1 ms out) tx_source_entry se = AUTO_VAL_INIT(se); se.amount = amount; se.real_output_in_tx_index = get_tx_out_index_by_amount(tx_1, amount); se.multisig_id = get_multisig_out_id(tx_1, se.real_output_in_tx_index); se.real_out_tx_key = get_tx_pub_key_from_extra(tx_1); se.ms_keys_count = boost::get(boost::get(tx_1.vout[se.real_output_in_tx_index]).target).keys.size(); se.ms_sigs_count = 1; tx_destination_entry de(amount - TESTS_DEFAULT_FEE, bob_acc.get_public_address()); transaction tx_2 = AUTO_VAL_INIT(tx_2); r = construct_tx(alice_acc.get_keys(), std::vector({ se }), std::vector({ de }), empty_attachment, tx_2, currency::get_tx_version(get_block_height(blk_1), m_hardforks), 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); bool tx_fully_signed = false; r = sign_multisig_input_in_tx(tx_2, 0, alice_acc.get_keys(), tx_1, &tx_fully_signed); CHECK_AND_ASSERT_MES(r & tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed: " << tx_fully_signed); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); // tx_2 goes with the block blk_2 events.push_back(tx_2); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner_acc, tx_2); ADJUST_TEST_CORE_TIME(blk_2.timestamp); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, de.amount)); // tx_3: normal input -> multisig output r = fill_tx_sources_and_destinations(events, blk_2, miner_acc.get_keys(), to_addrs, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_3 = AUTO_VAL_INIT(tx_3); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_3, currency::get_tx_version(get_block_height(blk_2), m_hardforks), 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx"); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); // tx_3 goes with the block blk_3 events.push_back(tx_3); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_3); // <-- CP // // test multisig after CP // // tx_4 multisig input -> normal output (spends tx_3 ms out) se = AUTO_VAL_INIT(se); se.amount = amount; se.real_output_in_tx_index = get_tx_out_index_by_amount(tx_3, amount); se.multisig_id = get_multisig_out_id(tx_3, se.real_output_in_tx_index); se.real_out_tx_key = get_tx_pub_key_from_extra(tx_3); se.ms_keys_count = boost::get(boost::get(tx_3.vout[se.real_output_in_tx_index]).target).keys.size(); se.ms_sigs_count = 1; de = tx_destination_entry(amount - TESTS_DEFAULT_FEE, bob_acc.get_public_address()); transaction tx_4 = AUTO_VAL_INIT(tx_4); r = construct_tx(alice_acc.get_keys(), std::vector({ se }), std::vector({ de }), empty_attachment, tx_4, currency::get_tx_version(get_block_height(blk_3), m_hardforks), 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); r = sign_multisig_input_in_tx(tx_4, 0, alice_acc.get_keys(), tx_3, &tx_fully_signed); CHECK_AND_ASSERT_MES(r & tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed: " << tx_fully_signed); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); // tx_4 goes with the block blk_4 events.push_back(tx_4); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3, miner_acc, tx_4); // tx_5: normal input -> multisig output r = fill_tx_sources_and_destinations(events, blk_4, miner_acc.get_keys(), to_addrs, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_5 = AUTO_VAL_INIT(tx_5); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_5, currency::get_tx_version(get_block_height(blk_4), m_hardforks), 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx"); events.push_back(tx_5); MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4, miner_acc, tx_5); // tx_6 multisig input -> normal output (spends tx_5 ms out) se = AUTO_VAL_INIT(se); se.amount = amount; se.real_output_in_tx_index = get_tx_out_index_by_amount(tx_5, amount); se.multisig_id = get_multisig_out_id(tx_5, se.real_output_in_tx_index); se.real_out_tx_key = get_tx_pub_key_from_extra(tx_5); se.ms_keys_count = boost::get(boost::get(tx_5.vout[se.real_output_in_tx_index]).target).keys.size(); se.ms_sigs_count = 1; de = tx_destination_entry(amount - TESTS_DEFAULT_FEE, bob_acc.get_public_address()); transaction tx_6 = AUTO_VAL_INIT(tx_6); r = construct_tx(alice_acc.get_keys(), std::vector({ se }), std::vector({ de }), empty_attachment, tx_6, currency::get_tx_version(get_block_height(blk_5), m_hardforks), 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); r = sign_multisig_input_in_tx(tx_6, 0, alice_acc.get_keys(), tx_5, &tx_fully_signed); CHECK_AND_ASSERT_MES(r & tx_fully_signed, false, "sign_multisig_input_in_tx failed, tx_fully_signed: " << tx_fully_signed); events.push_back(tx_6); MAKE_NEXT_BLOCK_TX1(events, blk_6, blk_5, miner_acc, tx_6); ADJUST_TEST_CORE_TIME(blk_6.timestamp); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, de.amount * 3)); return true; } //------------------------------------------------------------------------------ multisig_and_checkpoints_bad_txs::multisig_and_checkpoints_bad_txs() { REGISTER_CALLBACK_METHOD(multisig_and_checkpoints_bad_txs, set_cp); } bool multisig_and_checkpoints_bad_txs::set_cp(currency::core& c, size_t ev_index, const std::vector& events) { // set checkpoint far enough - it won't be reached in the test currency::checkpoints checkpoints; checkpoints.add_checkpoint(100, "0000000000000000000000000000000000000000000000000000000000000001"); c.set_checkpoints(std::move(checkpoints)); return true; } bool multisig_and_checkpoints_bad_txs::generate(std::vector& events) const { // Test idea: check few hypotheses about handling incorrect multisig inputs and outputs within checkpoint zone. GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); LOCAL_ASSERT(m_accounts.size() - 1 == MINER_ACC_IDX); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); LOCAL_ASSERT(m_accounts.size() - 1 == ALICE_ACC_IDX); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); LOCAL_ASSERT(m_accounts.size() - 1 == BOB_ACC_IDX); MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time()); DO_CALLBACK(events, "set_cp"); // set checkpoint REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2); bool r = false; std::vector sources; std::vector destinations; uint64_t amount = TESTS_DEFAULT_FEE * 99; std::list to_addrs({ alice_acc.get_public_address(), alice_acc.get_public_address() }); // multisig to same account, mentioned twice // tx_1: normal key to multisig transaction r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), to_addrs, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, TRANSACTION_VERSION_PRE_HF4, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx"); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); // tx_1 goes with the block blk_1 events.push_back(tx_1); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, 0, 0, 0, 0, 0)); // tx_2: invalid ms input sigs_count (should fail even in CP zone) transaction tx_2 = AUTO_VAL_INIT(tx_2); r = make_tx_multisig_to_key(tx_1, get_tx_out_index_by_amount(tx_1, amount), std::list({ alice_acc.get_keys() }), bob_acc.get_public_address(), tx_2); CHECK_AND_ASSERT_MES(r, false, "make_tx_multisig_to_key failed"); tx_2.signatures.resize(10); boost::get(tx_2.vin[0]).sigs_count = 10; DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); events.push_back(tx_2); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_acc); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, 0, 0, 0, 0, 0)); // tx_3: no signatures, zero sigs_count in ms input (should pass under CP zone) transaction tx_3 = AUTO_VAL_INIT(tx_3); r = make_tx_multisig_to_key(tx_1, get_tx_out_index_by_amount(tx_1, amount), std::list({ alice_acc.get_keys() }), bob_acc.get_public_address(), tx_3); CHECK_AND_ASSERT_MES(r, false, "make_tx_multisig_to_key failed"); tx_3.signatures.clear(); boost::get(tx_3.vin[0]).sigs_count = 0; events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); events.push_back(tx_3); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); // tx_4: zero ms out keys (should FAIL) r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), to_addrs, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); tx_builder txb = AUTO_VAL_INIT(txb); txb.step1_init(); txb.step2_fill_inputs(miner_acc.get_keys(), sources); txb.step3_fill_outputs(destinations, 0, 1); boost::get(boost::get(txb.m_tx.vout[0]).target).keys.clear(); // zero keys txb.step4_calc_hash(); txb.step5_sign(sources); transaction tx_4 = txb.m_tx; DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); events.push_back(tx_4); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); // tx_6: many ms out keys + no sigs (should pass due to CP zone) txb = AUTO_VAL_INIT(txb); txb.step1_init(); txb.step2_fill_inputs(miner_acc.get_keys(), sources); txb.step3_fill_outputs(destinations, 0, 1); crypto::public_key k = boost::get(boost::get(txb.m_tx.vout[0]).target).keys[0]; boost::get(boost::get(txb.m_tx.vout[0]).target).keys.resize(1500, k); txb.step4_calc_hash(); txb.step5_sign(sources); txb.m_tx.signatures.clear(); transaction tx_6 = txb.m_tx; events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); events.push_back(tx_6); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_6); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, 0, 0, 0, 0, 0)); // Alice got the money within ms-input in tx_6, but they don't show up in the balance // tx_7: just spending tx_6 ms out, sending to key (Alice -> Bob) transaction tx_7 = AUTO_VAL_INIT(tx_7); r = make_tx_multisig_to_key(tx_6, get_tx_out_index_by_amount(tx_6, amount), std::list({ alice_acc.get_keys() }), bob_acc.get_public_address(), tx_7); CHECK_AND_ASSERT_MES(r, false, "make_tx_multisig_to_key failed"); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); events.push_back(tx_7); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3, miner_acc, tx_7); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, amount - TESTS_DEFAULT_FEE)); // Bob finally got the money return true; } //------------------------------------------------------------------------------ multisig_and_altchains::multisig_and_altchains() { REGISTER_CALLBACK_METHOD(multisig_and_altchains, check_tx_pool); } bool multisig_and_altchains::generate(std::vector& events) const { // Test idea: check multisig output handling in case of multiple altchain switching GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); LOCAL_ASSERT(m_accounts.size() - 1 == MINER_ACC_IDX); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); LOCAL_ASSERT(m_accounts.size() - 1 == ALICE_ACC_IDX); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); LOCAL_ASSERT(m_accounts.size() - 1 == BOB_ACC_IDX); GENERATE_ACCOUNT(carol_acc); m_accounts.push_back(carol_acc); LOCAL_ASSERT(m_accounts.size() - 1 == CAROL_ACC_IDX); MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time()); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); bool r = false; std::vector sources; std::vector destinations; uint64_t amount = TESTS_DEFAULT_FEE * 99; std::list to_addrs({ alice_acc.get_public_address(), alice_acc.get_public_address() }); // multisig to same account, mentioned twice // tx_1: normal key to multisig transaction r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), to_addrs, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, 1); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx"); events.push_back(tx_1); MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); MAKE_NEXT_BLOCK(events, blk_1b, blk_0r, miner_acc); // 0 .. 10 11 12 13 14 15 <- height // (0 ).. (0r)- (1 )- <- main chain // | tx_1 // \- (1b)- // tx_2 and tx_3 both spend the SAME multisig output in tx_1, transferring coins to Bob (tx_2) or Carol (tx_3) transaction tx_2 = AUTO_VAL_INIT(tx_2); r = make_tx_multisig_to_key(tx_1, get_tx_out_index_by_amount(tx_1, amount), std::list({ alice_acc.get_keys() }), bob_acc.get_public_address(), tx_2); CHECK_AND_ASSERT_MES(r, false, "make_tx_multisig_to_key failed"); transaction tx_3 = AUTO_VAL_INIT(tx_3); r = make_tx_multisig_to_key(tx_1, get_tx_out_index_by_amount(tx_1, amount), std::list({ alice_acc.get_keys() }), carol_acc.get_public_address(), tx_3); CHECK_AND_ASSERT_MES(r, false, "make_tx_multisig_to_key failed"); events.push_back(tx_2); DO_CALLBACK(events, "mark_invalid_block"); MAKE_NEXT_BLOCK_TX1(events, blk_2b, blk_1b, miner_acc, tx_2); // 0 .. 10 11 12 13 14 15 <- height // (0 ).. (0r)- (1 )- <- main chain // | tx_1 // \- (1b)- #2b# <- 2b is rejected as tx_2 refers to non-existing multisig out id (in tx_1) // | tx_2 // make sure tx_2 is still in the pool DO_CALLBACK_PARAMS(events, "check_tx_pool", params_tx_pool(1)); DO_CALLBACK_PARAMS(events, "check_tx_pool", params_tx_pool(tx_2)); MAKE_NEXT_BLOCK_TX1(events, blk_1c, blk_0r, miner_acc, tx_1); MAKE_NEXT_BLOCK(events, blk_2c, blk_1c, miner_acc); DO_CALLBACK(events, "mark_invalid_tx"); // tx_3 should be rejected here by the pool, as it's tx_2 in the pool which is referring to the same ms id in tx_1 events.push_back(tx_3); MAKE_NEXT_BLOCK_TX1(events, blk_3c, blk_2c, miner_acc, tx_2); // 0 .. 10 11 12 13 14 15 <- height // (0 ).. (0r)- (1 )- // | tx_1 // \- (1b)- #2b# // | tx_2 // |- (1c)- (2c)- (3c)- <- becomes main chain // tx_1 tx_2 DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, amount - TESTS_DEFAULT_FEE)); DO_CALLBACK(events, "mark_invalid_tx"); // tx_3 should be rejected by the pool, as spending already spent ms output in tx_2 events.push_back(tx_3); DO_CALLBACK_PARAMS(events, "check_tx_pool", params_tx_pool(0)); // add blocks to the first chain and trigger chain switching MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_acc); MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc); MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3, miner_acc, tx_2); // 0 .. 10 11 12 13 14 15 <- height // (0 ).. (0r)- (1 )- (2 )- (3 )- (4 )- <- becomes main chain // | tx_1 tx_2 // \- (1b)- #2b# // | tx_2 // |- (1c)- (2c)- (3c)- // tx_1 tx_2 DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(CAROL_ACC_IDX, 0)); // tx_3 was rejected, thus zero DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, amount - TESTS_DEFAULT_FEE)); DO_CALLBACK_PARAMS(events, "check_tx_pool", params_tx_pool(0)); // add blocks to new subchain and trigger chain switching MAKE_NEXT_BLOCK(events, blk_3d, blk_2, miner_acc); MAKE_NEXT_BLOCK(events, blk_4d, blk_3d, miner_acc); MAKE_NEXT_BLOCK(events, blk_5d, blk_4d, miner_acc); // make sure tx_2 was moved out to the pool DO_CALLBACK_PARAMS(events, "check_tx_pool", params_tx_pool(1)); DO_CALLBACK_PARAMS(events, "check_tx_pool", params_tx_pool(tx_2)); // 0 .. 10 11 12 13 14 15 <- height // (0 ).. (0r)- (1 )- (2 )- (3 )- (4 )- <- becomes main chain // | tx_1 tx_2 // \- (3d)- (4d)- (5d)- DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, amount - TESTS_DEFAULT_FEE, 0, 0, amount - TESTS_DEFAULT_FEE)); MAKE_NEXT_BLOCK_TX1(events, blk_6d, blk_5d, miner_acc, tx_2); DO_CALLBACK_PARAMS(events, "check_tx_pool", params_tx_pool(0)); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, amount - TESTS_DEFAULT_FEE, 0, 0, 0, 0)); return true; } bool multisig_and_altchains::check_tx_pool(currency::core& c, size_t ev_index, const std::vector& events) { params_tx_pool p = AUTO_VAL_INIT(p); bool r = epee::string_tools::hex_to_pod(boost::get(events[ev_index]).callback_params, p); CHECK_AND_ASSERT_MES(r, false, "Can't obtain event params. Forgot to pass them?"); if (p.counter != SIZE_MAX) { CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == p.counter, false, "Tx pool has " << c.get_pool_transactions_count() << " transactions, NOT " << p.counter << " as expected"); } else { std::list txs; bool r = c.get_pool_transactions(txs); CHECK_AND_ASSERT_MES(r, false, "get_pool_transactions failed"); r = false; for (const auto& t : txs) { if (get_transaction_hash(t) == p.tx_hash) { r = true; break; } } CHECK_AND_ASSERT_MES(r, false, "tx " << p.tx_hash << " is NOT in the pool as expected"); } return true; } //------------------------------------------------------------------------------ bool ref_by_id_basics::generate(std::vector& events) const { // Test idea: transfer some coins (Miner->Alicee->Bob) using only ref_by_id inputs to make sure they are handled okay. GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); LOCAL_ASSERT(m_accounts.size() - 1 == MINER_ACC_IDX); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); LOCAL_ASSERT(m_accounts.size() - 1 == ALICE_ACC_IDX); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); LOCAL_ASSERT(m_accounts.size() - 1 == BOB_ACC_IDX); MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time()); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 4); bool r = false; std::vector sources; std::vector destinations; uint64_t amount = TESTS_DEFAULT_FEE * 90; r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc, alice_acc, amount, TESTS_DEFAULT_FEE, 4, sources, destinations, true, true, true); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx"); r = check_ring_signature_at_gen_time(events, get_block_hash(blk_0r), boost::get(tx_1.vin[0]), get_transaction_hash(tx_1), boost::get(tx_1.signatures[0]).s); CHECK_AND_ASSERT_MES(r, false, "check_ring_signature_at_gen_time failed"); events.push_back(tx_1); MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, amount, 0, 0, 0, 0)); r = fill_tx_sources_and_destinations(events, blk_1, alice_acc, bob_acc, amount - TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, true); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_2 = AUTO_VAL_INIT(tx_2); r = construct_tx(alice_acc.get_keys(), sources, destinations, empty_attachment, tx_2, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx"); events.push_back(tx_2); MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner_acc, tx_2); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, 0, 0, 0, 0, 0)); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, amount - TESTS_DEFAULT_FEE, 0, 0, 0, 0)); return true; } //------------------------------------------------------------------------------ bool ref_by_id_mixed_inputs_types::generate(std::vector& events) const { GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); LOCAL_ASSERT(m_accounts.size() - 1 == MINER_ACC_IDX); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); LOCAL_ASSERT(m_accounts.size() - 1 == ALICE_ACC_IDX); GENERATE_ACCOUNT(bob_acc); m_accounts.push_back(bob_acc); LOCAL_ASSERT(m_accounts.size() - 1 == BOB_ACC_IDX); MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time()); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2); bool r = false; std::vector sources, sources2; std::vector destinations, destinations2; // obtain such amount no output ever has uint64_t amount = 0, stub = 0; r = calculate_amounts_many_outs_have_and_no_outs_have(get_outs_money_amount(blk_0r.miner_tx), stub, amount); CHECK_AND_ASSERT_MES(r, false, "calculate_amounts_many_outs_have_and_no_outs_have failed"); MAKE_TX_LIST_START(events, tx_list_1, miner_acc, alice_acc, amount, blk_0r); MAKE_TX_LIST(events, tx_list_1, miner_acc, alice_acc, amount, blk_0r); MAKE_TX_LIST(events, tx_list_1, miner_acc, alice_acc, amount, blk_0r); MAKE_NEXT_BLOCK_TX_LIST(events, blk_1, blk_0r, miner_acc, tx_list_1); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, amount * 3, 0, 0, 0, 0)); r = fill_tx_sources_and_destinations(events, blk_1, alice_acc, bob_acc, amount * 3 - TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, false); // normal inputs CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); r = fill_tx_sources_and_destinations(events, blk_1, alice_acc, bob_acc, amount * 3 - TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, 0, sources2, destinations2, true, true, true); // ref_by_id CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); // mix sources to achive mixed normal and ref_by_id inputs sources[1] = sources2[1]; transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(alice_acc.get_keys(), sources, destinations, empty_attachment, tx_1, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); // make sure we created correct transaction size_t normal_outs_count = 0, ref_by_id_outs_count = 0; for (auto& in : tx_1.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, in2key, false); CHECK_AND_ASSERT_MES(in2key.amount == amount, false, "Invalid amount in source entries"); for (auto& ko : in2key.key_offsets) { if (ko.type().hash_code() == typeid(ref_by_id).hash_code()) ++ref_by_id_outs_count; else if (ko.type().hash_code() == typeid(uint64_t).hash_code()) ++normal_outs_count; } } CHECK_AND_ASSERT_MES(normal_outs_count == 2 && ref_by_id_outs_count == 1, false, "Incorrect tx created"); events.push_back(tx_1); MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner_acc, tx_1); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, 0, 0, 0, 0, 0)); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, amount * 3 - TESTS_DEFAULT_FEE, 0, 0, 0, 0)); return true; } //------------------------------------------------------------------------------ multisig_n_participants_seq_signing::multisig_n_participants_seq_signing() : m_participants_count(10) , m_minimum_signs_to_spend(8) { m_participants.resize(m_participants_count); for(auto& p : m_participants) p.generate(); } bool multisig_n_participants_seq_signing::generate(std::vector& events) const { // test idea: tx with ms input is being signed by N participants _sequentially_ and _separately_ (real-life approximation: they don't know privates keys of each other) bool r = false; GENERATE_ACCOUNT(preminer_acc); GENERATE_ACCOUNT(miner_acc); m_accounts.push_back(miner_acc); GENERATE_ACCOUNT(alice_acc); m_accounts.push_back(alice_acc); block blk_0 = AUTO_VAL_INIT(blk_0); generator.construct_genesis_block(blk_0, preminer_acc, test_core_time::get_time()); events.push_back(blk_0); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); // prepare ms data: amount and list of participants' adresses (belived to be publicly available) uint64_t ms_amount = TESTS_DEFAULT_FEE * 17; std::list ms_addr_list; for(auto &p : m_participants) ms_addr_list.push_back(p.get_public_address()); // Miner makes a tx with ms output to all the participants (with minimum_sigs == m_minimum_signs_to_spend) std::vector sources; std::vector destinations; r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), ms_addr_list, ms_amount, TESTS_DEFAULT_FEE, 0, sources, destinations, true, true, m_minimum_signs_to_spend); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, get_tx_version_from_events(events), 0, 0); events.push_back(tx_1); MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); //REWIND_BLOCKS_N_WITH_TIME(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); // Alice (a very third person) make a tx, spending ms output // THEN all the participants one-by-one sign it sources.clear(); destinations.clear(); size_t ms_out_index = get_multisig_out_index(tx_1.vout); CHECK_AND_ASSERT_MES(ms_out_index != tx_1.vout.size(), false, "Can't find ms out index in tx_1"); tx_source_entry se = AUTO_VAL_INIT(se); se.amount =boost::get( tx_1.vout[ms_out_index]).amount; se.multisig_id = get_multisig_out_id(tx_1, ms_out_index); se.ms_sigs_count = m_minimum_signs_to_spend; // se.outputs -- not used for ms-outs // se.real_output -- not used for ms-outs se.real_output_in_tx_index = ms_out_index; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_1); // se.separately_signed_tx_complete -- not a separately-signed tx se.ms_keys_count = boost::get(boost::get(tx_1.vout[ms_out_index]).target).keys.size(); sources.push_back(se); tx_destination_entry de(ms_amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()); destinations.push_back(de); // construct a transaction (no participants keys are provided, thus no signs for ms input are created) transaction tx = AUTO_VAL_INIT(tx); r = construct_tx(alice_acc.get_keys(), sources, destinations, empty_attachment, tx, get_tx_version_from_events(events), 0, 0); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); // sign the transaction by (m_minimum_signs_to_spend) participants in random order std::vector shuffled_participants(m_participants); std::shuffle(shuffled_participants.begin(), shuffled_participants.end(), crypto::uniform_random_bit_generator()); for (size_t i = 0; i < m_minimum_signs_to_spend; ++i) { r = sign_multisig_input_in_tx(tx, 0, shuffled_participants[i].get_keys(), tx_1); CHECK_AND_ASSERT_MES(r, false, "sign_ms_input_by_a_participant failed"); } events.push_back(tx); MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner_acc, tx); std::shared_ptr alice_wlt; r = generator.init_test_wallet(alice_acc, get_block_hash(blk_0), alice_wlt); CHECK_AND_ASSERT_MES(r, false, ""); r = generator.refresh_test_wallet(events, alice_wlt.get(), get_block_hash(blk_2), CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2); CHECK_AND_ASSERT_MES(r, false, "refresh_test_wallet failed"); r = check_balance_via_wallet(*alice_wlt.get(), "alice", ms_amount - TESTS_DEFAULT_FEE, 0, 0, 0, 0); CHECK_AND_ASSERT_MES(r, false, "invalid balance"); return true; } bool multisig_n_participants_seq_signing::c1(currency::core& c, size_t ev_index, const std::vector& events) { return true; } //------------------------------------------------------------------------------ bool multisig_out_make_and_spent_in_altchain::generate(std::vector& events) const { // test idea: one tx has a multisig out in an altchain, and the second tx in the same altchain spents it m_accounts.resize(TOTAL_ACCS_COUNT); account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); account_base& bob_acc = m_accounts[BOB_ACC_IDX]; bob_acc.generate(); MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); // make a few blocks, this is main chain MAKE_NEXT_BLOCK(events, blk_1, blk_0r, miner_acc); MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_acc); MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc); MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_acc); bool r = false; std::vector sources; std::vector destinations; uint64_t amount = TESTS_DEFAULT_FEE * 33; std::list to_addrs({ alice_acc.get_public_address(), alice_acc.get_public_address() }); // multisig to same account, mentioned twice // tx_1: normal key to multisig transaction r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), to_addrs, amount, TESTS_DEFAULT_FEE * 7, 0, sources, destinations, true, true, 2); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); events.push_back(tx_1); MAKE_NEXT_BLOCK_TX1(events, blk_1a, blk_0r, miner_acc, tx_1); MAKE_NEXT_BLOCK(events, blk_2a, blk_1a, miner_acc); // 0 .. 10 11 12 13 14 15 <- height // (0 ).. (0r)- (1 )- (2 )- (3 )- (4 )- <- main chain // | // \- (1a)- (2a)- (3a)- (4a)- // tx_1 tx_2 tx_3 <- tx_2 spents multisig out in tx_1 // ^ | | // +<----<----<---+ <----+ // atm tx_2 can't be added to the pool, as it contains reference to an ms out (in it's inputs) that is already referenced to tx_1 (in it's outs) still waiting in the pool // to workaround this we simulate alt block tx pushing, when txs are pushed to the core right before the block and with "kept_by_block" flag // tx_2 transaction tx_2 = AUTO_VAL_INIT(tx_2); r = make_tx_multisig_to_key(tx_1, get_multisig_out_index(tx_1.vout), std::list({ alice_acc.get_keys(), alice_acc.get_keys() }), bob_acc.get_public_address(), tx_2, TESTS_DEFAULT_FEE * 13); CHECK_AND_ASSERT_MES(r, false, "make_tx_multisig_to_key failed"); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); // simulate events.push_back(tx_2); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2a, miner_acc, tx_2); // tx_3 spends the same ms out transaction tx_3 = AUTO_VAL_INIT(tx_3); r = make_tx_multisig_to_key(tx_1, get_multisig_out_index(tx_1.vout), std::list({ alice_acc.get_keys(), alice_acc.get_keys() }), bob_acc.get_public_address(), tx_3, TESTS_DEFAULT_FEE * 13); CHECK_AND_ASSERT_MES(r, false, "make_tx_multisig_to_key failed"); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); // simulate events.push_back(tx_3); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); DO_CALLBACK(events, "mark_invalid_block"); // alt block 4a should be rejected as it has invalid tx_3 which spends already spent ms output of tx_1 MAKE_NEXT_BLOCK_TX1(events, blk_4a, blk_3a, miner_acc, tx_3); MAKE_NEXT_BLOCK(events, blk_4b, blk_3a, miner_acc); MAKE_NEXT_BLOCK(events, blk_5b, blk_4b, miner_acc); DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_5b), get_block_hash(blk_5b))); size_t tx_count = 0; DO_CALLBACK_PARAMS(events, "check_tx_pool_count", tx_count); DO_CALLBACK(events, "clear_tx_pool"); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(ALICE_ACC_IDX, 0)); DO_CALLBACK_PARAMS(events, "check_balance", params_check_balance(BOB_ACC_IDX, amount - get_tx_fee(tx_2))); return true; } //------------------------------------------------------------------------------ bool multisig_out_spent_in_altchain_case_b4::generate(std::vector& events) const { // test idea: multisig output is spent in altchain, while output's source tx is in the main chain atfer split height (case b4) m_accounts.resize(TOTAL_ACCS_COUNT); account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); account_base& bob_acc = m_accounts[BOB_ACC_IDX]; bob_acc.generate(); MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); bool r = false; std::vector sources; std::vector destinations; uint64_t amount = TESTS_DEFAULT_FEE * 22; std::list to_addrs({ alice_acc.get_public_address(), alice_acc.get_public_address() }); // multisig to same account, mentioned twice // tx_1: normal key to multisig transaction r = fill_tx_sources_and_destinations(events, blk_0r, miner_acc.get_keys(), to_addrs, amount, TESTS_DEFAULT_FEE * 5, 0, sources, destinations, true, true, 2); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources_and_destinations failed"); transaction tx_1 = AUTO_VAL_INIT(tx_1); r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, 0, CURRENCY_TO_KEY_OUT_RELAXED, true); CHECK_AND_ASSERT_MES(r, false, "construct_tx failed"); events.push_back(tx_1); MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_acc); // 0 .. 10 11 12 13 14 15 <- height // (0 ).. (0r)- (1 )- (2 )- (3 )- (4 )- <- main chain // | tx_1 tx_2 <- tx_2 spents multisig out in tx_1 // | ^ | // | +<----<----<---+ // | // \- (1a)- (2a)- (3a)- (4a)- <- alt chain // tx_2 <- tx_2 refers to unknown ms out (tx_1 won't be in this chain event if switching occurs) // tx_2 transaction tx_2 = AUTO_VAL_INIT(tx_2); r = make_tx_multisig_to_key(tx_1, get_multisig_out_index(tx_1.vout), std::list({ alice_acc.get_keys(), alice_acc.get_keys() }), bob_acc.get_public_address(), tx_2, TESTS_DEFAULT_FEE * 13); CHECK_AND_ASSERT_MES(r, false, "make_tx_multisig_to_key failed"); events.push_back(tx_2); MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_2); MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_acc); // alt chain // block blk_1a is invalid as it contains tx_2 which refers to unknown ms output DO_CALLBACK(events, "mark_invalid_block"); MAKE_NEXT_BLOCK_TX1(events, blk_1a, blk_0r, miner_acc, tx_2); DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4), get_block_hash(blk_4))); size_t tx_count = 0; DO_CALLBACK_PARAMS(events, "check_tx_pool_count", tx_count); return true; } //------------------------------------------------------------------------------ multisig_unconfirmed_transfer_and_multiple_scan_pool_calls::multisig_unconfirmed_transfer_and_multiple_scan_pool_calls() { REGISTER_CALLBACK_METHOD(multisig_unconfirmed_transfer_and_multiple_scan_pool_calls, c1); } bool multisig_unconfirmed_transfer_and_multiple_scan_pool_calls::generate(std::vector& events) const { m_accounts.resize(TOTAL_ACCS_COUNT); auto& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); auto& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); auto& bob_acc = m_accounts[BOB_ACC_IDX]; bob_acc.generate(); MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); DO_CALLBACK(events, "c1"); return true; } bool multisig_unconfirmed_transfer_and_multiple_scan_pool_calls::c1(currency::core& c, size_t ev_index, const std::vector& events) { auto& miner_acc = m_accounts[MINER_ACC_IDX]; auto& alice_acc = m_accounts[ALICE_ACC_IDX]; auto& bob_acc = m_accounts[BOB_ACC_IDX]; uint64_t ms_amount = TESTS_DEFAULT_FEE * 13; std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]); size_t blocks_fetched = 0; bool received_money; std::atomic atomic_false = ATOMIC_VAR_INIT(false); miner_wlt->refresh(blocks_fetched, received_money, atomic_false); CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW, false, "Incorrect numbers of blocks fetched"); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool"); std::vector extra; std::vector attachments; std::vector dst; dst.resize(1); // to_key => multisig dst.back().addr.push_back(miner_acc.get_public_address()); dst.back().addr.push_back(alice_acc.get_public_address()); dst.back().amount = ms_amount + TESTS_DEFAULT_FEE; dst.back().minimum_sigs = dst.back().addr.size(); transaction key_to_ms_tx = AUTO_VAL_INIT(key_to_ms_tx); miner_wlt->transfer(dst, 0, 0, TESTS_DEFAULT_FEE, extra, attachments, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), key_to_ms_tx); bool r = mine_next_pow_blocks_in_playtime(miner_acc.get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); size_t ms_out_idx = get_multisig_out_index(key_to_ms_tx.vout); crypto::hash multisig_id = get_multisig_out_id(key_to_ms_tx, ms_out_idx); CHECK_AND_ASSERT_MES(multisig_id != null_hash, false, "Multisig failed: failed to get get_multisig_out_id"); //r = mine_next_pow_blocks_in_playtime(miner_acc.get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); //CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, alice_acc); miner_wlt->refresh(); alice_wlt->refresh(); tools::multisig_transfer_container ms_m, ms_a; miner_wlt->get_multisig_transfers(ms_m); alice_wlt->get_multisig_transfers(ms_a); CHECK_AND_ASSERT_MES(ms_m.size() == 1 && ms_a.size() == 1, false, "Multisig failed: ms_m.size() == 1 && ms_a.size() == 1"); LOG_PRINT_YELLOW("%%%%% generating multisig spending tx...", LOG_LEVEL_0); std::vector dst2(1); dst2.back().addr.resize(1); dst2.back().addr.back() = bob_acc.get_public_address(); dst2.back().amount = ms_amount; dst2.back().minimum_sigs = dst2.back().addr.size(); std::list acc_keys; //acc_keys.push_back(miner_acc.get_keys()); will be signed as we pass miner_wlt acc_keys.push_back(alice_acc.get_keys()); transaction tx = AUTO_VAL_INIT(tx); transfer_multisig(*miner_wlt.get(), acc_keys, multisig_id, dst2, 0, TESTS_DEFAULT_FEE, extra, attachments, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, c.get_current_tx_version()); LOG_PRINT_YELLOW("%%%%% tx " << get_transaction_hash(tx) << " is spending multisig output " << multisig_id, LOG_LEVEL_0); bool stub; tools::transfer_container transfers; std::vector unconfirmed_transfers; alice_wlt->scan_tx_pool(stub); alice_wlt->get_transfers(transfers); CHECK_AND_ASSERT_MES(transfers.size() == 0, false, "incorrect transfers size for Alice: " << transfers.size() << "\n" << alice_wlt->dump_transfers()); alice_wlt->get_unconfirmed_transfers(unconfirmed_transfers); CHECK_AND_ASSERT_MES(unconfirmed_transfers.size() == 1, false, "incorrect unconfirmed transfers size for Alice: " << unconfirmed_transfers.size()); CHECK_AND_ASSERT_MES(alice_wlt->get_multisig_transfers().size() == 1, false, "incorrect multisig transfers size for Alice: " << alice_wlt->get_multisig_transfers().size()); uint32_t flags = alice_wlt->get_multisig_transfers().begin()->second.m_flags; CHECK_AND_ASSERT_MES(flags == WALLET_TRANSFER_DETAIL_FLAG_SPENT, false, "incorrect ms transfer flags for Alice: " << flags); // scan the pool second time (nothing happened neither in the pool, nor in blockchain) alice_wlt->scan_tx_pool(stub); transfers.clear(); unconfirmed_transfers.clear(); alice_wlt->get_transfers(transfers); CHECK_AND_ASSERT_MES(transfers.size() == 0, false, "incorrect transfers size for Alice: " << transfers.size() << "\n" << alice_wlt->dump_transfers()); alice_wlt->get_unconfirmed_transfers(unconfirmed_transfers); CHECK_AND_ASSERT_MES(unconfirmed_transfers.size() == 1, false, "incorrect unconfirmed transfers size for Alice: " << unconfirmed_transfers.size()); CHECK_AND_ASSERT_MES(alice_wlt->get_multisig_transfers().size() == 1, false, "incorrect multisig transfers size for Alice: " << alice_wlt->get_multisig_transfers().size()); // flags should not change flags = alice_wlt->get_multisig_transfers().begin()->second.m_flags; CHECK_AND_ASSERT_MES(flags == WALLET_TRANSFER_DETAIL_FLAG_SPENT, false, "incorrect ms transfer flags for Alice: " << flags); return true; }