blockchain/tests/core_tests/multisig_wallet_tests.cpp
2022-05-17 17:32:53 +02:00

2592 lines
129 KiB
C++

// 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
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<currency::account_keys>& owner_keys,
const crypto::hash& multisig_id,
const std::vector<currency::tx_destination_entry>& dsts,
uint64_t unlock_time,
uint64_t fee,
const std::vector<currency::extra_v>& extra,
const std::vector<currency::attachment_v>& 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;
w.prepare_transaction(ctp, ftp, tx);
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<test_event_entry>& 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<test_event_entry>& 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<tools::wallet2> miner_wlt = init_playtime_test_wallet(events, c, m_mining_accunt);
size_t blocks_fetched = 0;
bool received_money;
std::atomic<bool> 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<currency::extra_v> extra;
std::vector<currency::attachment_v> attachments;
std::vector<tx_destination_entry> 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 (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<tools::wallet2> wallet_a = init_playtime_test_wallet(events, c, m_accunt_a);
std::shared_ptr<tools::wallet2> wallet_b = init_playtime_test_wallet(events, c, m_accunt_b);
wallet_a->refresh();
wallet_b->refresh();
tools::wallet2::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<tx_destination_entry> 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<currency::account_keys> 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<tools::wallet2> 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<test_event_entry>& 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<test_event_entry>& 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<tools::wallet2> miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]);
size_t blocks_fetched = 0;
bool received_money;
std::atomic<bool> 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<currency::account_base> 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<tx_destination_entry>({ de }), 0, 0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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_bare& o) { return 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<tools::wallet2> w = init_playtime_test_wallet(events, c, addresses[0]);
w->refresh();
std::list<currency::account_keys> 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<tx_destination_entry>({ de2 }), 0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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<tools::wallet2> 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::wallet2::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<test_event_entry>& 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<test_event_entry>& events)
{
std::shared_ptr<tools::wallet2> 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<bool> 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<tx_destination_entry>({ de1, de2, de3, de4, de5, de6 }),
0, 0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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<tools::wallet2> 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::wallet2::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<tools::wallet2> 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::wallet2::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<tools::wallet2> 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::wallet2::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<tools::wallet2> 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::wallet2::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<account_keys>({ m_accounts[BOB_ACC_IDX].get_keys() }), ms1_hash, std::vector<tx_destination_entry>({ tx_destination_entry(amount1, receiver_acc.get_public_address()) }),
0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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<account_keys>({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms1_hash, std::vector<tx_destination_entry>({ tx_destination_entry(amount1, receiver_acc.get_public_address()) }),
0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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<account_keys>({ m_accounts[DAN_ACC_IDX].get_keys() }), ms2_hash, std::vector<tx_destination_entry>({ tx_destination_entry(amount2, receiver_acc.get_public_address()) }),
0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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<account_keys>({ m_accounts[CAROL_ACC_IDX].get_keys() }), ms2_hash, std::vector<tx_destination_entry>({ tx_destination_entry(amount2, receiver_acc.get_public_address()) }),
0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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<account_keys>({ m_accounts[CAROL_ACC_IDX].get_keys() }), ms2_hash, std::vector<tx_destination_entry>({ tx_destination_entry(amount2, receiver_acc.get_public_address()) }),
0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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<account_keys>({ m_accounts[DAN_ACC_IDX].get_keys() }), ms3_hash, std::vector<tx_destination_entry>({ tx_destination_entry(amount3, receiver_acc.get_public_address()) }),
0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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<account_keys>({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms3_hash, std::vector<tx_destination_entry>({ tx_destination_entry(amount3, receiver_acc.get_public_address()) }),
0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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<account_keys>({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms3_hash, std::vector<tx_destination_entry>({ tx_destination_entry(amount3, receiver_acc.get_public_address()) }),
0, TESTS_DEFAULT_FEE, std::vector<currency::extra_v>(), std::vector<currency::attachment_v>(), 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<test_event_entry>& 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<test_event_entry>& 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<bool> atomic_false(false);
static const uint64_t amount = TESTS_DEFAULT_FEE * 13;
std::shared_ptr<tools::wallet2> 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<tx_destination_entry>({ 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<tools::wallet2> 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<account_keys>({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms_hash,
std::vector<tx_destination_entry>({ 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<tools::wallet2> 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<test_event_entry>& 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<test_event_entry>& 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<bool> atomic_false(false);
static const uint64_t amount = TESTS_DEFAULT_FEE * 13;
std::shared_ptr<tools::wallet2> 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<tx_destination_entry>({ 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<tools::wallet2> 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<account_keys>({ m_accounts[BOB_ACC_IDX].get_keys() }), ms_hash,
std::vector<tx_destination_entry>({ 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<account_keys>({ m_accounts[BOB_ACC_IDX].get_keys() }), ms_hash2,
std::vector<tx_destination_entry>({ 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<tools::wallet2> 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<test_event_entry>& 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<account_public_address> 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<tx_source_entry> sources;
std::vector<tx_destination_entry> 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);
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);
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<txout_multisig>(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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ de }), empty_attachment, tx_2, get_tx_version_from_events(events), 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<txout_multisig>(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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, bob_acc.get_public_address()) }), empty_attachment, tx_3, get_tx_version_from_events(events), 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);
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<txout_multisig>(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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()) }), empty_attachment, tx_5, get_tx_version_from_events(events), 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);
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<txout_multisig>(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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()) }), empty_attachment, tx_7, get_tx_version_from_events(events), 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);
tx_7.signatures[0].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);
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<txout_multisig>(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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()) }), empty_attachment, tx_9, get_tx_version_from_events(events), 0);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
tx_9.signatures[0].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<test_event_entry>& 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<account_public_address> 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<tx_source_entry> sources;
std::vector<tx_destination_entry> 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<txout_multisig>(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<test_event_entry>& 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<account_public_address> ms_addr_list({ miner_acc.get_public_address(), alice_acc.get_public_address() });
// noramal input -> multisig output with unlock time
std::vector<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
r = fill_tx_sources_and_destinations(events, blk_0r, 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");
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), 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<txout_multisig>(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), 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");
MAKE_TX(events, tx_3, alice_acc, bob_acc, amount - TESTS_DEFAULT_FEE * 2, blk_2);
ADJUST_TEST_CORE_TIME(unlock_time_2 - CURRENCY_LOCKED_TX_ALLOWED_DELTA_SECONDS + 1);
events.push_back(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_v>({ extra_expiration_time }), empty_attachment, tx_4, get_tx_version_from_events(events), 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), 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<txout_multisig>(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_v>({ extra_expiration_time }), empty_attachment, tx_6, get_tx_version_from_events(events), 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<test_event_entry>& 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 = 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<txout_to_key>(stake.vout[stake_output_idx].target).key;
keypair tx_key = keypair::generate();
pos_block_builder pb;
pb.step1_init_header(height, prev_id);
pb.step2_set_txs(std::vector<transaction>());
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, extra_alias_entry(), 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<account_public_address> 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<transaction>(), 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<tx_source_entry> sources;
std::vector<tx_destination_entry> 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<txout_multisig>(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);
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<account_public_address> ms_addr_list({ miner_acc.get_public_address(), alice_acc.get_public_address() });
std::vector<tx_source_entry> sources;
std::vector<tx_destination_entry> 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), 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_bare& o) {return 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 = *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");
}
events.push_back(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<txout_multisig>(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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ tx_destination_entry(se.amount - TESTS_DEFAULT_FEE, alice_acc.get_public_address()) }),
empty_attachment, tx_2, get_tx_version_from_events(events), 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<test_event_entry>& 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<account_public_address> to_addrs({ alice_acc.get_public_address(), alice_acc.get_public_address() }); // multisig to same account, mentioned twice
std::vector<tx_source_entry> sources;
std::vector<tx_destination_entry> 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<tx_source_entry> sources2;
std::vector<tx_destination_entry> 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<txout_multisig>(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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ 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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ 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<test_event_entry>& events)
{
currency::checkpoints checkpoints;
checkpoints.add_checkpoint(15, "e2a1b0d51c4de81a79caafb28ba3c2f2ef3f53f7a728932b4d7e2a81a3fc2cc0");
c.set_checkpoints(std::move(checkpoints));
return true;
}
bool multisig_and_checkpoints::generate(std::vector<test_event_entry>& 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<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, ts);
// 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<account_public_address> 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, 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<txout_multisig>(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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ de }), 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, 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, 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<txout_multisig>(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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ de }), empty_attachment, tx_4, 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, 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<txout_multisig>(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<tx_source_entry>({ se }), std::vector<tx_destination_entry>({ de }), empty_attachment, tx_6, 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<test_event_entry>& 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<test_event_entry>& 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<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
uint64_t amount = TESTS_DEFAULT_FEE * 99;
std::list<account_public_address> 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(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<account_keys>({ 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<txin_multisig>(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<account_keys>({ 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<txin_multisig>(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<txout_multisig>(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<txout_multisig>(txb.m_tx.vout[0].target).keys[0];
boost::get<txout_multisig>(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<account_keys>({ 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<test_event_entry>& 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<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
uint64_t amount = TESTS_DEFAULT_FEE * 99;
std::list<account_public_address> 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<account_keys>({ 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<account_keys>({ 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<test_event_entry>& events)
{
params_tx_pool p = AUTO_VAL_INIT(p);
bool r = epee::string_tools::hex_to_pod(boost::get<callback_entry>(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<transaction> 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<test_event_entry>& 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<tx_source_entry> sources;
std::vector<tx_destination_entry> 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<txin_to_key>(tx_1.vin[0]), get_transaction_hash(tx_1), tx_1.signatures[0]);
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<test_event_entry>& 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<tx_source_entry> sources, sources2;
std::vector<tx_destination_entry> 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<test_event_entry>& 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<account_public_address> 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<tx_source_entry> sources;
std::vector<tx_destination_entry> 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);
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 = 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<txout_multisig>(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);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
// sign the transaction by (m_minimum_signs_to_spend) participants in random order
std::vector<account_base> 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<tools::wallet2> 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<test_event_entry>& events)
{
return true;
}
//------------------------------------------------------------------------------
bool multisig_out_make_and_spent_in_altchain::generate(std::vector<test_event_entry>& 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<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
uint64_t amount = TESTS_DEFAULT_FEE * 33;
std::list<account_public_address> 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<account_keys>({ 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<account_keys>({ 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<test_event_entry>& 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<tx_source_entry> sources;
std::vector<tx_destination_entry> destinations;
uint64_t amount = TESTS_DEFAULT_FEE * 22;
std::list<account_public_address> 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<account_keys>({ 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<test_event_entry>& 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<test_event_entry>& 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<tools::wallet2> miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]);
size_t blocks_fetched = 0;
bool received_money;
std::atomic<bool> 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<currency::extra_v> extra;
std::vector<currency::attachment_v> attachments;
std::vector<tx_destination_entry> 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<tools::wallet2> alice_wlt = init_playtime_test_wallet(events, c, alice_acc);
miner_wlt->refresh();
alice_wlt->refresh();
tools::wallet2::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<tx_destination_entry> 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<currency::account_keys> 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;
std::deque<tools::wallet2::transfer_details> transfers;
std::vector<tools::wallet_public::wallet_transfer_info> 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_trunsfers());
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_trunsfers());
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;
}