forked from lthn/blockchain
Merge branch 'develop' into secp256k1
# Conflicts: # src/gui/qt-daemon/layout # src/simplewallet/simplewallet.cpp # src/version.h.in # tests/core_tests/chaingen_main.cpp # tests/core_tests/multiassets_test.cpp # tests/core_tests/multiassets_test.h # tests/unit_tests/multiassets_test.cpp
This commit is contained in:
commit
08281059fb
18 changed files with 2427 additions and 69 deletions
|
|
@ -60,7 +60,7 @@ namespace currency
|
|||
return m_keys;
|
||||
}
|
||||
//-----------------------------------------------------------------
|
||||
void crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password)
|
||||
void account_base::crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password)
|
||||
{
|
||||
crypto::chacha8_key key = AUTO_VAL_INIT(key);
|
||||
crypto::generate_chacha8_key(password, key);
|
||||
|
|
@ -71,16 +71,23 @@ namespace currency
|
|||
crypto::chacha8(scr_data, src_length, key, iv, (char*)dst_data);
|
||||
}
|
||||
//-----------------------------------------------------------------
|
||||
std::string account_base::get_seed_phrase(const std::string& password) const
|
||||
std::string account_base::get_seed_phrase(const std::string& password) const
|
||||
{
|
||||
if (m_keys_seed_binary.empty())
|
||||
return "";
|
||||
return get_seed_phrase(password, m_keys_seed_binary);
|
||||
}
|
||||
//-----------------------------------------------------------------
|
||||
std::string account_base::get_seed_phrase(const std::string& password, const std::vector<unsigned char>& keys_seed_binary) const
|
||||
{
|
||||
if (keys_seed_binary.empty())
|
||||
return "";
|
||||
|
||||
std::vector<unsigned char> processed_seed_binary = m_keys_seed_binary;
|
||||
std::vector<unsigned char> processed_seed_binary = keys_seed_binary;
|
||||
if (!password.empty())
|
||||
{
|
||||
//encrypt seed phrase binary data
|
||||
crypt_with_pass(&m_keys_seed_binary[0], m_keys_seed_binary.size(), &processed_seed_binary[0], password);
|
||||
crypt_with_pass(&keys_seed_binary[0], keys_seed_binary.size(), &processed_seed_binary[0], password);
|
||||
}
|
||||
|
||||
std::string keys_seed_text = tools::mnemonic_encoding::binary2text(processed_seed_binary);
|
||||
|
|
@ -92,7 +99,7 @@ namespace currency
|
|||
CHECK_AND_ASSERT_THROW_MES(self_check_is_password_used == !password.empty(), "Account seed phrase internal error: password flag encoded wrong");
|
||||
|
||||
constexpr uint16_t checksum_max = tools::mnemonic_encoding::NUMWORDS >> 1; // maximum value of checksum
|
||||
std::string binary_for_check_sum((const char*)&m_keys_seed_binary[0], m_keys_seed_binary.size());
|
||||
std::string binary_for_check_sum((const char*)&keys_seed_binary[0], keys_seed_binary.size());
|
||||
binary_for_check_sum.append(password);
|
||||
crypto::hash h = crypto::cn_fast_hash(binary_for_check_sum.data(), binary_for_check_sum.size());
|
||||
*reinterpret_cast<uint64_t*>(&h) = creation_timestamp_rounded;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ namespace currency
|
|||
std::string get_public_address_str() const;
|
||||
|
||||
std::string get_seed_phrase(const std::string& seed_password) const;
|
||||
std::string get_seed_phrase(const std::string& password, const std::vector<unsigned char>& keys_seed_binary) const;
|
||||
std::string get_tracking_seed() const;
|
||||
bool restore_from_seed_phrase(const std::string& seed_phrase, const std::string& seed_password);
|
||||
bool restore_from_tracking_seed(const std::string& tracking_seed);
|
||||
|
|
@ -82,6 +83,8 @@ namespace currency
|
|||
static std::vector<unsigned char> string_to_vector_of_chars(const std::string& v) { return std::vector<unsigned char>(v.begin(), v.end()); }
|
||||
static bool is_seed_password_protected(const std::string& seed_phrase, bool& is_password_protected);
|
||||
static bool is_seed_tracking(const std::string& seed_phrase);
|
||||
static void crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password);
|
||||
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(m_keys)
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ namespace currency
|
|||
secret_index = ring.size() - 1;
|
||||
}
|
||||
|
||||
CHECK_AND_ASSERT_MES(secret_index != SIZE_MAX, false, "out #" << j << ": can't find a corresponding asset id in inputs, asset id: " << H);
|
||||
CHECK_AND_ASSERT_MES(secret_index != SIZE_MAX, false, "out #" << j << ": cannot find a corresponding asset id in inputs or asset operations; asset id: " << H);
|
||||
|
||||
result.bge_proofs.emplace_back(crypto::BGE_proof_s{});
|
||||
uint8_t err = 0;
|
||||
|
|
@ -322,7 +322,7 @@ namespace currency
|
|||
CHECK_AND_ASSERT_MES(ogc.asset_id_blinding_mask_x_amount_sum.is_zero(), false, "it's expected that all asset ids for this tx are obvious and thus explicit"); // because this tx has no ZC inputs => all outs clearly have native asset id
|
||||
CHECK_AND_ASSERT_MES(ogc.ao_amount_blinding_mask.is_zero(), false, "asset emmission is not allowed for txs without ZC inputs");
|
||||
|
||||
// (sum(bare inputs' amounts) - fee) * H + sum(pseudo out amount commitments) - sum(outputs' commitments) = lin(G)
|
||||
// (sum(bare inputs' amounts) - fee) * H - sum(outputs' commitments) = lin(G)
|
||||
|
||||
crypto::point_t commitment_to_zero = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * currency::native_coin_asset_id_pt - ogc.amount_commitments_sum;
|
||||
crypto::scalar_t secret_x = -ogc.amount_blinding_masks_sum;
|
||||
|
|
@ -336,6 +336,8 @@ namespace currency
|
|||
{
|
||||
// there're ZC inputs => in main balance equation we only need to cancel out X-component, because G-component cancelled out by choosing blinding mask for the last pseudo out amount commitment
|
||||
|
||||
// (sum(bare inputs' amounts) - fee) * H + sum(pseudo out amount commitments) + asset_op_commitment - sum(outputs' commitments) = lin(X)
|
||||
|
||||
crypto::point_t commitment_to_zero = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * currency::native_coin_asset_id_pt + ogc.pseudo_out_amount_commitments_sum + (ogc.ao_commitment_in_outputs ? -ogc.ao_amount_commitment : ogc.ao_amount_commitment) - ogc.amount_commitments_sum;
|
||||
crypto::scalar_t secret_x = ogc.real_in_asset_id_blinding_mask_x_amount_sum - ogc.asset_id_blinding_mask_x_amount_sum;
|
||||
|
||||
|
|
@ -472,7 +474,7 @@ namespace currency
|
|||
}
|
||||
|
||||
CHECK_AND_ASSERT_MES(destinations.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS || height == 0, false, "Too many outs (" << destinations.size() << ")! Miner tx can't be constructed.");
|
||||
tx = AUTO_VAL_INIT_T(transaction);
|
||||
// tx is not cleared intentionally to allow passing additional args in the extra/attachments
|
||||
tx.version = tx_version;
|
||||
|
||||
tx_generation_context tx_gen_context{};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2014-2018 Zano Project
|
||||
// Copyright (c) 2014-2024 Zano Project
|
||||
// Copyright (c) 2014-2018 The Louisdor Project
|
||||
// Copyright (c) 2012-2013 The Boolberry developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
|
|
@ -714,9 +714,12 @@ bool MainWindow::show_inital()
|
|||
{
|
||||
TRY_ENTRY();
|
||||
if (load_app_config())
|
||||
{
|
||||
restore_pos(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_PRINT_L1("set defaults values to config");
|
||||
m_config = AUTO_VAL_INIT(m_config);
|
||||
this->show();
|
||||
QSize sz = AUTO_VAL_INIT(sz);
|
||||
|
|
@ -976,6 +979,11 @@ QString MainWindow::start_backend(const QString& params)
|
|||
CATCH_ENTRY_FAIL_API_RESPONCE();
|
||||
}
|
||||
|
||||
void MainWindow::show_notification(const QString& title, const QString& message)
|
||||
{
|
||||
show_notification(title.toStdString(), message.toStdString());
|
||||
}
|
||||
|
||||
QString MainWindow::sync_call(const QString& func_name, const QString& params)
|
||||
{
|
||||
if (func_name == "test_call")
|
||||
|
|
@ -1107,6 +1115,7 @@ bool MainWindow::get_is_disabled_notifications(const QString& param)
|
|||
}
|
||||
bool MainWindow::set_is_disabled_notifications(const bool& param)
|
||||
{
|
||||
LOG_PRINT_L1("set_is_disabled_notifications: notifications were " << (m_config.disable_notifications ? "DISABLED" : "ENABLED") << " -> now " << (param ? "DISABLED" : "ENABLED"));
|
||||
m_config.disable_notifications = param;
|
||||
return m_config.disable_notifications;
|
||||
}
|
||||
|
|
@ -2468,6 +2477,10 @@ QString MainWindow::print_log(const QString& param)
|
|||
void MainWindow::show_notification(const std::string& title, const std::string& message)
|
||||
{
|
||||
TRY_ENTRY();
|
||||
|
||||
if (m_config.disable_notifications)
|
||||
return;
|
||||
|
||||
LOG_PRINT_L1("system notification: \"" << title << "\", \"" << message << "\"");
|
||||
|
||||
// it's expected that title and message are utf-8 encoded!
|
||||
|
|
@ -2482,5 +2495,3 @@ void MainWindow::show_notification(const std::string& title, const std::string&
|
|||
#endif
|
||||
CATCH_ENTRY2(void());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ public:
|
|||
void on_menu_show(const QString& param);
|
||||
QString is_remnotenode_mode_preconfigured(const QString& param);
|
||||
QString start_backend(const QString& params);
|
||||
void show_notification(const QString& title, const QString& message);
|
||||
|
||||
QString async_call(const QString& func_name, const QString& params);
|
||||
QString sync_call(const QString& func_name, const QString& params);
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ namespace
|
|||
{
|
||||
const command_line::arg_descriptor<std::string> arg_wallet_file ("wallet-file", "Use wallet <arg>", "");
|
||||
const command_line::arg_descriptor<std::string> arg_generate_new_wallet ("generate-new-wallet", "Generate new wallet and save it to <arg> or <address>.wallet by default", "");
|
||||
const command_line::arg_descriptor<bool> arg_derive_custom_seed("derive_custom_seed", "Derive seed phrase from custom 24-words secret(advanced option, do it on your own risk)", "");
|
||||
const command_line::arg_descriptor<std::string> arg_generate_new_auditable_wallet ("generate-new-auditable-wallet", "Generate new auditable wallet and store it to <arg>", "");
|
||||
const command_line::arg_descriptor<std::string> arg_daemon_address ("daemon-address", "Use daemon instance at <host>:<port>", "");
|
||||
const command_line::arg_descriptor<std::string> arg_daemon_host ("daemon-host", "Use daemon instance at host <arg> instead of localhost", "");
|
||||
|
|
@ -2870,6 +2871,266 @@ bool search_for_wallet_file(const std::wstring &search_here/*, const std::string
|
|||
return false;
|
||||
}
|
||||
|
||||
int custom_seed_builder()
|
||||
{
|
||||
success_msg_writer() <<
|
||||
"**********************************************************************\n" <<
|
||||
"This is an experimental tool that helps you create a custom seed phrase \n"
|
||||
"based on your own 24 words. It can be extremely unsafe, so only use it \n"
|
||||
"if you're confident in what you're doing.\n"
|
||||
"**********************************************************************";
|
||||
|
||||
success_msg_writer() << "Please enter 24 words that you want to use as base for the seed:";
|
||||
std::string seed_24;
|
||||
std::getline(std::cin, seed_24);
|
||||
|
||||
|
||||
std::list<std::string> words;
|
||||
std::string trimed_seed_24 = epee::string_tools::trim(seed_24);
|
||||
boost::split(words, trimed_seed_24, boost::is_space(), boost::token_compress_on);
|
||||
seed_24 = boost::algorithm::join(words, " ");
|
||||
|
||||
std::string passphrase;
|
||||
success_msg_writer() << "Please enter seed passphrase(it's highly recommended to use passphrase for custom seed):";
|
||||
std::getline(std::cin, passphrase);
|
||||
if (passphrase.empty())
|
||||
{
|
||||
success_msg_writer() << "Using unsecured seed(no passphrase)";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string passphrase_confirmation;
|
||||
success_msg_writer() << "Please confirm passphrase:";
|
||||
std::getline(std::cin, passphrase_confirmation);
|
||||
if (passphrase_confirmation != passphrase)
|
||||
{
|
||||
success_msg_writer() << "Passphrase mismatched, try again";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
account_base acc;
|
||||
acc.generate();
|
||||
std::string pass_protected_or_not = "";
|
||||
std::vector<unsigned char> binary_from_seed = tools::mnemonic_encoding::text2binary(seed_24);
|
||||
std::vector<unsigned char> processed_binary_from_seed = binary_from_seed;
|
||||
if (!passphrase.empty())
|
||||
{
|
||||
//encrypt seed phrase binary data
|
||||
account_base::crypt_with_pass(&binary_from_seed[0], binary_from_seed.size(), &processed_binary_from_seed[0], passphrase);
|
||||
pass_protected_or_not = "(secured with passphrase)";
|
||||
}
|
||||
{
|
||||
pass_protected_or_not = "(!without passphrase!)";
|
||||
}
|
||||
const std::string new_seed = acc.get_seed_phrase(passphrase, processed_binary_from_seed);
|
||||
|
||||
success_msg_writer() << "Here is your seed" << pass_protected_or_not << "\n " << new_seed;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int seed_doctor()
|
||||
{
|
||||
success_msg_writer() <<
|
||||
"**********************************************************************\n" <<
|
||||
"This is experimental tool that might help you to recover your wallet's corrupted seed phrase. \n" <<
|
||||
"It might help to sort out only trivial situations where one word was written wrong or \n" <<
|
||||
"two word was written in wrong order\n" <<
|
||||
"**********************************************************************";
|
||||
|
||||
|
||||
success_msg_writer() << "Please enter problematic seed phrase:";
|
||||
std::string seed;
|
||||
std::getline(std::cin, seed);
|
||||
|
||||
success_msg_writer() << "Please enter wallet address if you have it(press enter if you don't know it):";
|
||||
std::string address;
|
||||
std::getline(std::cin, address);
|
||||
address = epee::string_tools::trim(address);
|
||||
|
||||
std::string passphrase;
|
||||
bool check_summ_available = false;
|
||||
|
||||
//cut the last timestamp word from restore_dats
|
||||
std::vector<std::string> words;
|
||||
boost::split(words, seed, boost::is_space());
|
||||
|
||||
std::set<size_t> failed_words;
|
||||
size_t i = 0;
|
||||
//let's validate each word
|
||||
for (const auto& w : words)
|
||||
{
|
||||
if (!tools::mnemonic_encoding::valid_word(w))
|
||||
{
|
||||
success_msg_writer() << "Word " << i << " '" << w << "' is invalid, attempting to restore it";
|
||||
failed_words.insert(i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (words.size() == SEED_PHRASE_V1_WORDS_COUNT)
|
||||
{
|
||||
// 24 seed words + one timestamp word = 25 total
|
||||
success_msg_writer() << "SEED_PHRASE_V1_WORDS_COUNT, checksum is unavailable";
|
||||
if (address.empty())
|
||||
{
|
||||
success_msg_writer() << "With SEED_PHRASE_V1_WORDS_COUNT and address unknown it's not enough information to recover it, sorry";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else if (words.size() == SEED_PHRASE_V2_WORDS_COUNT)
|
||||
{
|
||||
// 24 seed words + one timestamp word + one flags & checksum = 26 total
|
||||
|
||||
success_msg_writer() << "SEED_PHRASE_V2_WORDS_COUNT, checksum is available";
|
||||
check_summ_available = true;
|
||||
}
|
||||
else if (words.size() == 24)
|
||||
{
|
||||
//seed only with no date, add date to it
|
||||
std::string zero_word = tools::mnemonic_encoding::word_by_num(0); //zero_word is likely to be "like"
|
||||
words.push_back(zero_word); //
|
||||
}
|
||||
else
|
||||
{
|
||||
success_msg_writer() << "Impossible to recover something with " << words.size() << " words only";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
bool pass_protected = false;
|
||||
bool success = account_base::is_seed_password_protected(seed, pass_protected);
|
||||
success_msg_writer() << "SECURED_SEED: " << (pass_protected ? "true" : "false");
|
||||
|
||||
if (pass_protected)
|
||||
{
|
||||
success_msg_writer() << "Please enter seed passphrase(if passphrase is wrong then chances to correct seed recovery is nearly zero):";
|
||||
std::getline(std::cin, passphrase);
|
||||
}
|
||||
|
||||
size_t global_candidates_count = 0;
|
||||
|
||||
auto brute_force_func = [&](size_t word_index) -> bool {
|
||||
const map<string, uint32_t>& all_words = tools::mnemonic_encoding::get_words_map();
|
||||
success_msg_writer() << "Brute forcing word " << word_index << " ....";
|
||||
size_t candidates_count = 0;
|
||||
std::vector<std::string> words_local = words;
|
||||
|
||||
for (auto it = all_words.begin(); it != all_words.end(); it++)
|
||||
{
|
||||
words_local[word_index] = it->first;
|
||||
|
||||
std::string result = boost::algorithm::join(words_local, " ");
|
||||
account_base acc;
|
||||
bool r = acc.restore_from_seed_phrase(result, passphrase);
|
||||
if (r)
|
||||
{
|
||||
if (!address.empty())
|
||||
{
|
||||
//check against address
|
||||
if (acc.get_public_address_str() == address)
|
||||
{
|
||||
success_msg_writer(true) << "!!!SUCCESS!!!";
|
||||
success_msg_writer() << "Seed recovered, please write down recovered seed and use it to restore the wallet:\n" << result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success_msg_writer() << "Potential seed candidate:\n" << result << "\nAddress: " << acc.get_public_address_str();
|
||||
candidates_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
global_candidates_count += candidates_count;
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
auto swap_func = [&](size_t word_index) -> bool {
|
||||
size_t candidates_count = 0;
|
||||
std::vector<std::string> words_local = words;
|
||||
|
||||
std::string tmp = words_local[word_index];
|
||||
words_local[word_index] = words_local[word_index + 1];
|
||||
words_local[word_index + 1] = tmp;
|
||||
std::string result = boost::algorithm::join(words_local, " ");
|
||||
account_base acc;
|
||||
bool r = acc.restore_from_seed_phrase(result, passphrase);
|
||||
if (r)
|
||||
{
|
||||
if (!address.empty())
|
||||
{
|
||||
//check against address
|
||||
if (acc.get_public_address_str() == address)
|
||||
{
|
||||
success_msg_writer(true) << "!!!SUCCESS!!!";
|
||||
success_msg_writer() << "Seed recovered, please write down recovered seed and use it to restore the wallet:\n" << result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success_msg_writer() << "Potential seed candidate:\n" << result << "\nAddress: " << acc.get_public_address_str();
|
||||
candidates_count++;
|
||||
}
|
||||
}
|
||||
global_candidates_count += candidates_count;
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
if (failed_words.size())
|
||||
{
|
||||
if (failed_words.size() > 1)
|
||||
{
|
||||
success_msg_writer() << "Restoring more then 1 broken words not implemented yet, sorry";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!check_summ_available && address.empty())
|
||||
{
|
||||
success_msg_writer() << "No address and no checksum, recovery is impossible, sorry";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
size_t broken_word_index = *failed_words.begin();
|
||||
bool r = brute_force_func(broken_word_index);
|
||||
success_msg_writer() << "Brute forcing finished, " << global_candidates_count << " potential candidates found";
|
||||
return r ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!check_summ_available && address.empty())
|
||||
{
|
||||
success_msg_writer() << "No address and no checksum, recovery is limited only to date reset";
|
||||
std::string result = boost::algorithm::join(words, " ");
|
||||
account_base acc;
|
||||
bool r = acc.restore_from_seed_phrase(result, passphrase);
|
||||
success_msg_writer() << "Potential seed candidate:\n" << result << "\nAddress: " << acc.get_public_address_str();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
success_msg_writer() << "Brute forcing all each word";
|
||||
for (size_t i = 0; i != words.size() && i != 24; i++)
|
||||
{
|
||||
bool r = brute_force_func(i);
|
||||
if(r)
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
success_msg_writer() << "Brute forcing finished, " << global_candidates_count << " potential candidates found";
|
||||
|
||||
success_msg_writer() << "Swap check...";
|
||||
|
||||
for (size_t i = 0; i != words.size() && i != 23; i++)
|
||||
{
|
||||
bool r = swap_func(i);
|
||||
if (r)
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
#ifdef WIN32
|
||||
int wmain( int argc, wchar_t* argv_w[ ], wchar_t* envp[ ] )
|
||||
|
|
@ -2934,7 +3195,10 @@ int main(int argc, char* argv[])
|
|||
command_line::add_arg(desc_params, arg_set_timeout);
|
||||
command_line::add_arg(desc_params, arg_voting_config_file);
|
||||
command_line::add_arg(desc_params, arg_no_password_confirmations);
|
||||
command_line::add_arg(desc_params, command_line::arg_generate_rpc_autodoc);
|
||||
command_line::add_arg(desc_params, command_line::arg_generate_rpc_autodoc);
|
||||
command_line::add_arg(desc_params, arg_seed_doctor);
|
||||
command_line::add_arg(desc_params, arg_derive_custom_seed);
|
||||
|
||||
|
||||
tools::wallet_rpc_server::init_options(desc_params);
|
||||
|
||||
|
|
@ -3012,6 +3276,16 @@ int main(int argc, char* argv[])
|
|||
|
||||
bool offline_mode = command_line::get_arg(vm, arg_offline_mode);
|
||||
|
||||
if (command_line::has_arg(vm, arg_seed_doctor))
|
||||
{
|
||||
return seed_doctor();
|
||||
}
|
||||
|
||||
if (command_line::has_arg(vm, arg_derive_custom_seed))
|
||||
{
|
||||
return custom_seed_builder();
|
||||
}
|
||||
|
||||
if (command_line::has_arg(vm, tools::wallet_rpc_server::arg_rpc_bind_port))
|
||||
{
|
||||
// runs wallet as RPC server
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@
|
|||
#define PROJECT_REVISION "0"
|
||||
#define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION
|
||||
|
||||
#define PROJECT_VERSION_BUILD_NO 336
|
||||
#define PROJECT_VERSION_BUILD_NO 341
|
||||
#define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO)
|
||||
#define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]"
|
||||
|
|
|
|||
|
|
@ -817,3 +817,241 @@ bool chain_switching_when_out_spent_in_alt_chain_ref_id::generate(std::vector<te
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
alt_chain_and_block_tx_fee_median::alt_chain_and_block_tx_fee_median()
|
||||
{
|
||||
REGISTER_CALLBACK_METHOD(alt_chain_and_block_tx_fee_median, check_after_hf4);
|
||||
REGISTER_CALLBACK_METHOD(alt_chain_and_block_tx_fee_median, check_before_hf4);
|
||||
}
|
||||
|
||||
bool alt_chain_and_block_tx_fee_median::generate(
|
||||
std::vector<test_event_entry>& events) const
|
||||
{
|
||||
/* Test idea: check chain switching rules.
|
||||
Rules before and after HF4 for PoW blocks are different. There're only PoW
|
||||
blocks in the test situation. If the last blocks contain transactions (non
|
||||
empty blocks), then the chain with the largest this_block_tx_fee_median on its
|
||||
head becomes the main.
|
||||
0 10 11 21 22
|
||||
(0 ) - ... - (0r) - (1 ) - ... - (1r) - (2 )
|
||||
| main | \
|
||||
| | [tx_0]
|
||||
| |
|
||||
| | main
|
||||
\ - (1a) \ - (2a)
|
||||
\
|
||||
[tx_1]
|
||||
Chain with head blk_1 versus chain with head blk_1a: chain with head blk_1
|
||||
is the main, because blocks 1, 1a are empty.
|
||||
Chain with head blk_2 versus chain with head blk_2a: chain with head blk_2a
|
||||
is the main, because blocks 2, 2a aren't empty and the fee of tx_1 is larger
|
||||
than the fee of tx_0.
|
||||
*/
|
||||
|
||||
bool success{};
|
||||
bool hf4_active{};
|
||||
std::vector<tx_source_entry> sources{};
|
||||
std::vector<tx_destination_entry> destinations{};
|
||||
transaction tx_0{}, tx_1{};
|
||||
uint64_t tx_version{};
|
||||
crypto::hash top_block{};
|
||||
|
||||
GENERATE_ACCOUNT(miner);
|
||||
|
||||
MAKE_GENESIS_BLOCK(events,
|
||||
blk_0,
|
||||
miner,
|
||||
test_core_time::get_time());
|
||||
|
||||
DO_CALLBACK(events, "configure_core");
|
||||
|
||||
REWIND_BLOCKS_N(events, blk_0r, blk_0, miner,
|
||||
CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
|
||||
|
||||
MAKE_NEXT_BLOCK(events, blk_1, blk_0r, miner);
|
||||
MAKE_NEXT_BLOCK(events, blk_1a, blk_0r, miner);
|
||||
|
||||
/* It is decided which chain will be the main: with the head blk_1 or with the
|
||||
head blk_1a.
|
||||
0 10 11
|
||||
/ - (1 )
|
||||
|
|
||||
(0 ) - ... - (0r)
|
||||
|
|
||||
\ - (1a)
|
||||
*/
|
||||
|
||||
CHECK_AND_ASSERT_EQ(is_pos_block(blk_1), false);
|
||||
CHECK_AND_ASSERT_EQ(is_pos_block(blk_1a), false);
|
||||
CHECK_AND_ASSERT_EQ(get_block_height(blk_1), 11);
|
||||
CHECK_AND_ASSERT_EQ(get_block_height(blk_1), get_block_height(blk_1a));
|
||||
|
||||
/* Blocks blk_1, blk_1a do not contain transactions (they are empty blocks).
|
||||
Switching to the alternative chain with head blk_1a will not occur. The main
|
||||
chain is the chain with the head blk_1. */
|
||||
|
||||
DO_CALLBACK_PARAMS(events,
|
||||
"check_top_block",
|
||||
params_top_block(get_block_height(blk_1),
|
||||
get_block_hash(blk_1)));
|
||||
|
||||
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner,
|
||||
CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
|
||||
|
||||
// Transaction tx_0 is constructed and placed in block blk_2.
|
||||
|
||||
success = fill_tx_sources_and_destinations(
|
||||
events,
|
||||
/* head = */ blk_1r,
|
||||
/* from = */ miner.get_keys(),
|
||||
/* to = */ miner.get_public_address(),
|
||||
/* amount = */ MK_TEST_COINS(10),
|
||||
/* fee = */ m_fee_tx_0_blk_2,
|
||||
/* nmix = */ 0,
|
||||
sources,
|
||||
destinations);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to fill sources, destinations");
|
||||
|
||||
tx_version = get_tx_version(get_block_height(blk_1r),
|
||||
m_hardforks);
|
||||
|
||||
success = construct_tx(miner.get_keys(),
|
||||
sources,
|
||||
destinations,
|
||||
empty_attachment,
|
||||
tx_0,
|
||||
tx_version,
|
||||
0);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to construct tx_0");
|
||||
|
||||
ADD_CUSTOM_EVENT(events, tx_0);
|
||||
MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner, tx_0);
|
||||
|
||||
sources.clear();
|
||||
destinations.clear();
|
||||
|
||||
// Transaction tx_1 is constructed and placed in block blk_2a.
|
||||
|
||||
tx_version = get_tx_version(get_block_height(blk_1r),
|
||||
m_hardforks);
|
||||
|
||||
success = fill_tx_sources_and_destinations(
|
||||
events,
|
||||
/* head = */ blk_1r,
|
||||
/* from = */ miner.get_keys(),
|
||||
/* to = */ miner.get_public_address(),
|
||||
/* amount = */ MK_TEST_COINS(10),
|
||||
/* fee = */ m_fee_tx_1_blk_2a,
|
||||
/* nmix = */ 0,
|
||||
sources,
|
||||
destinations);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to fill sources, destinations");
|
||||
|
||||
success = construct_tx(miner.get_keys(),
|
||||
sources,
|
||||
destinations,
|
||||
empty_attachment,
|
||||
tx_1,
|
||||
tx_version,
|
||||
0);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to construct tx_1");
|
||||
|
||||
ADD_CUSTOM_EVENT(events, tx_1);
|
||||
MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1r, miner, tx_1);
|
||||
|
||||
/* It is decided which chain will be the main: with the head blk_2 or with the
|
||||
head blk_2a.
|
||||
0 21 22
|
||||
/ - (2 )
|
||||
| \
|
||||
| [tx_0]
|
||||
|
|
||||
(0 ) - ... - (1r)
|
||||
|
|
||||
|
|
||||
|
|
||||
\ - (2a)
|
||||
\
|
||||
[tx_1]
|
||||
*/
|
||||
|
||||
CHECK_AND_ASSERT_EQ(is_pos_block(blk_2), false);
|
||||
CHECK_AND_ASSERT_EQ(is_pos_block(blk_2a), false);
|
||||
CHECK_AND_ASSERT_GREATER(m_fee_tx_1_blk_2a, m_fee_tx_0_blk_2);
|
||||
CHECK_AND_ASSERT_EQ(get_block_height(blk_2), 22);
|
||||
CHECK_AND_ASSERT_EQ(get_block_height(blk_2), get_block_height(blk_2a));
|
||||
|
||||
hf4_active =
|
||||
m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM,
|
||||
get_block_height(blk_2) + 1);
|
||||
|
||||
if (hf4_active)
|
||||
{
|
||||
/* With HF4 active, the chain with head blk_2a wins because transaction tx_1
|
||||
has a greater fee than transaction tx_0. The main chain is the chain with
|
||||
the head blk_2a. */
|
||||
|
||||
DO_CALLBACK(events, "check_after_hf4");
|
||||
top_block = get_block_hash(blk_2a);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
/* The chains have the same commulative difficulty. Therefore, with HF4
|
||||
inactive, switching to the chain with the blk_2a head will not occur. The
|
||||
main chain is the chain with the head blk_2. */
|
||||
|
||||
DO_CALLBACK(events, "check_before_hf4");
|
||||
top_block = get_block_hash(blk_2);
|
||||
}
|
||||
|
||||
DO_CALLBACK_PARAMS(events,
|
||||
"check_top_block",
|
||||
params_top_block(/* height = */ 22, top_block));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool alt_chain_and_block_tx_fee_median::check_after_hf4(
|
||||
currency::core& c,
|
||||
size_t ev_index,
|
||||
const std::vector<test_event_entry>& events)
|
||||
{
|
||||
block_extended_info bei{};
|
||||
const uint64_t height_block{22};
|
||||
|
||||
CHECK_AND_ASSERT_EQ(
|
||||
m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM,
|
||||
height_block),
|
||||
true);
|
||||
|
||||
c.get_blockchain_storage().get_block_extended_info_by_height(height_block,
|
||||
bei);
|
||||
CHECK_AND_ASSERT_EQ(bei.this_block_tx_fee_median, m_fee_tx_1_blk_2a);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool alt_chain_and_block_tx_fee_median::check_before_hf4(
|
||||
currency::core& c,
|
||||
size_t ev_index,
|
||||
const std::vector<test_event_entry>& events)
|
||||
{
|
||||
block_extended_info bei{};
|
||||
const uint64_t height_block{22};
|
||||
|
||||
CHECK_AND_ASSERT_EQ(
|
||||
m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM,
|
||||
height_block),
|
||||
false);
|
||||
|
||||
c.get_blockchain_storage().get_block_extended_info_by_height(height_block,
|
||||
bei);
|
||||
CHECK_AND_ASSERT_EQ(bei.this_block_tx_fee_median, m_fee_tx_0_blk_2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,3 +82,21 @@ struct chain_switching_when_out_spent_in_alt_chain_ref_id : public test_chain_un
|
|||
{
|
||||
bool generate(std::vector<test_event_entry>& events) const;
|
||||
};
|
||||
|
||||
struct alt_chain_and_block_tx_fee_median : public test_chain_unit_enchanced
|
||||
{
|
||||
alt_chain_and_block_tx_fee_median();
|
||||
|
||||
bool generate(std::vector<test_event_entry>& events) const;
|
||||
bool check_after_hf4(currency::core& c,
|
||||
size_t ev_index,
|
||||
const std::vector<test_event_entry>& events);
|
||||
|
||||
bool check_before_hf4(currency::core& c,
|
||||
size_t ev_index,
|
||||
const std::vector<test_event_entry>& events);
|
||||
|
||||
private:
|
||||
const uint64_t m_fee_tx_0_blk_2{TESTS_DEFAULT_FEE};
|
||||
const uint64_t m_fee_tx_1_blk_2a{2 * m_fee_tx_0_blk_2};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1814,6 +1814,48 @@ bool construct_tx_with_many_outputs(const currency::hard_forks_descriptor& hf, s
|
|||
return construct_tx(keys_from, sources, destinations, empty_attachment, tx, tx_version, 0);
|
||||
}
|
||||
|
||||
bool construct_tx(const account_keys& sender_account_keys,
|
||||
const std::vector<tx_source_entry>& sources,
|
||||
const std::vector<tx_destination_entry>& destinations,
|
||||
const std::vector<extra_v>& extra,
|
||||
const std::vector<attachment_v>& attachments,
|
||||
transaction& tx,
|
||||
uint64_t tx_version,
|
||||
crypto::secret_key& one_time_secret_key,
|
||||
uint64_t unlock_time,
|
||||
uint64_t expiration_time,
|
||||
uint8_t tx_outs_attr,
|
||||
bool shuffle,
|
||||
uint64_t flags,
|
||||
uint64_t explicit_consolidated_tx_fee,
|
||||
tx_generation_context& gen_context)
|
||||
{
|
||||
// extra copy operation, but creating transaction is not sensitive to this
|
||||
finalize_tx_param ftp {};
|
||||
ftp.tx_version = tx_version;
|
||||
ftp.sources = sources;
|
||||
ftp.prepared_destinations = destinations;
|
||||
ftp.extra = extra;
|
||||
ftp.attachments = attachments;
|
||||
ftp.unlock_time = unlock_time;
|
||||
// ftp.crypt_address = crypt_destination_addr;
|
||||
ftp.expiration_time = expiration_time;
|
||||
ftp.tx_outs_attr = tx_outs_attr;
|
||||
ftp.shuffle = shuffle;
|
||||
ftp.flags = flags;
|
||||
ftp.mode_separate_fee = explicit_consolidated_tx_fee;
|
||||
|
||||
finalized_tx ft = AUTO_VAL_INIT(ft);
|
||||
ft.tx = tx;
|
||||
ft.one_time_key = one_time_secret_key;
|
||||
ftp.gen_context = gen_context; // ftp, not ft here, this is UGLY -- sowle
|
||||
bool r = construct_tx(sender_account_keys, ftp, ft);
|
||||
tx = ft.tx;
|
||||
one_time_secret_key = ft.one_time_key;
|
||||
gen_context = ft.ftp.gen_context;
|
||||
return r;
|
||||
}
|
||||
|
||||
uint64_t get_balance(const currency::account_keys& addr, const std::vector<currency::block>& blockchain, const map_hash2tx_t& mtx, bool dbg_log)
|
||||
{
|
||||
uint64_t res = 0;
|
||||
|
|
|
|||
|
|
@ -673,6 +673,22 @@ bool construct_tx_with_many_outputs(const currency::hard_forks_descriptor& hf, s
|
|||
const currency::account_keys& keys_from, const currency::account_public_address& addr_to,
|
||||
uint64_t total_amount, size_t outputs_count, uint64_t fee, currency::transaction& tx, bool use_ref_by_id = false);
|
||||
|
||||
bool construct_tx(const currency::account_keys& sender_account_keys,
|
||||
const std::vector<currency::tx_source_entry>& sources,
|
||||
const std::vector<currency::tx_destination_entry>& destinations,
|
||||
const std::vector<currency::extra_v>& extra,
|
||||
const std::vector<currency::attachment_v>& attachments,
|
||||
currency::transaction& tx,
|
||||
uint64_t tx_version,
|
||||
crypto::secret_key& one_time_secret_key,
|
||||
uint64_t unlock_time,
|
||||
uint64_t expiration_time,
|
||||
uint8_t tx_outs_attr,
|
||||
bool shuffle,
|
||||
uint64_t flags,
|
||||
uint64_t explicit_consolidated_tx_fee,
|
||||
currency::tx_generation_context& gen_context);
|
||||
|
||||
void get_confirmed_txs(const std::vector<currency::block>& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs);
|
||||
bool find_block_chain(const std::vector<test_event_entry>& events, std::vector<currency::block>& blockchain, map_hash2tx_t& mtx, const crypto::hash& head);
|
||||
bool fill_tx_sources(std::vector<currency::tx_source_entry>& sources, const std::vector<test_event_entry>& events,
|
||||
|
|
|
|||
|
|
@ -1140,7 +1140,7 @@ int main(int argc, char* argv[])
|
|||
GENERATE_AND_PLAY_HF(alt_blocks_with_the_same_txs, "3-*");
|
||||
GENERATE_AND_PLAY_HF(chain_switching_when_out_spent_in_alt_chain_mixin, "3-*");
|
||||
GENERATE_AND_PLAY_HF(chain_switching_when_out_spent_in_alt_chain_ref_id, "3-*");
|
||||
|
||||
GENERATE_AND_PLAY_HF(alt_chain_and_block_tx_fee_median, "3-*");
|
||||
|
||||
// miscellaneous tests
|
||||
GENERATE_AND_PLAY(test_blockchain_vs_spent_keyimges);
|
||||
|
|
@ -1291,15 +1291,16 @@ int main(int argc, char* argv[])
|
|||
GENERATE_AND_PLAY(asset_depoyment_and_few_zc_utxos);
|
||||
GENERATE_AND_PLAY_HF(assets_and_pos_mining, "4-*");
|
||||
GENERATE_AND_PLAY_HF(asset_emission_and_unconfirmed_balance, "4-*");
|
||||
GENERATE_AND_PLAY_HF(asset_operation_in_consolidated_tx, "4-*");
|
||||
GENERATE_AND_PLAY_HF(asset_operation_and_hardfork_checks, "4-*");
|
||||
GENERATE_AND_PLAY_HF(eth_signed_asset_basics, "4-*"); // TODO
|
||||
GENERATE_AND_PLAY_HF(eth_signed_asset_via_rpc, "4-*");
|
||||
|
||||
|
||||
GENERATE_AND_PLAY_HF(pos_fuse_test, "4-*");
|
||||
|
||||
|
||||
|
||||
GENERATE_AND_PLAY_HF(attachment_isolation_test, "4-*");
|
||||
|
||||
|
||||
// GENERATE_AND_PLAY(gen_block_reward);
|
||||
// END OF TESTS */
|
||||
|
|
|
|||
|
|
@ -7,51 +7,7 @@
|
|||
|
||||
using namespace currency;
|
||||
|
||||
namespace currency
|
||||
{
|
||||
bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources,
|
||||
const std::vector<tx_destination_entry>& destinations,
|
||||
const std::vector<extra_v>& extra,
|
||||
const std::vector<attachment_v>& attachments,
|
||||
transaction& tx,
|
||||
uint64_t tx_version,
|
||||
crypto::secret_key& one_time_secret_key,
|
||||
uint64_t unlock_time,
|
||||
uint64_t expiration_time,
|
||||
uint8_t tx_outs_attr,
|
||||
bool shuffle,
|
||||
uint64_t flags,
|
||||
uint64_t explicit_consolidated_tx_fee,
|
||||
tx_generation_context& gen_context)
|
||||
{
|
||||
//extra copy operation, but creating transaction is not sensitive to this
|
||||
finalize_tx_param ftp{};
|
||||
ftp.tx_version = tx_version;
|
||||
ftp.sources = sources;
|
||||
ftp.prepared_destinations = destinations;
|
||||
ftp.extra = extra;
|
||||
ftp.attachments = attachments;
|
||||
ftp.unlock_time = unlock_time;
|
||||
// ftp.crypt_address = crypt_destination_addr;
|
||||
ftp.expiration_time = expiration_time;
|
||||
ftp.tx_outs_attr = tx_outs_attr;
|
||||
ftp.shuffle = shuffle;
|
||||
ftp.flags = flags;
|
||||
ftp.mode_separate_fee = explicit_consolidated_tx_fee;
|
||||
|
||||
finalized_tx ft = AUTO_VAL_INIT(ft);
|
||||
ft.tx = tx;
|
||||
ft.one_time_key = one_time_secret_key;
|
||||
ftp.gen_context = gen_context; // ftp, not ft here, this is UGLY -- sowle
|
||||
bool r = construct_tx(sender_account_keys, ftp, ft);
|
||||
tx = ft.tx;
|
||||
one_time_secret_key = ft.one_time_key;
|
||||
gen_context = ft.ftp.gen_context;
|
||||
return r;
|
||||
}
|
||||
} // namespace currency
|
||||
|
||||
void add_flags_to_all_destination_entries(const uint64_t flags, std::vector<currency::tx_destination_entry>& destinations)
|
||||
static void add_flags_to_all_destination_entries(const uint64_t flags, std::vector<currency::tx_destination_entry>& destinations)
|
||||
{
|
||||
for(auto& de : destinations)
|
||||
de.flags |= flags;
|
||||
|
|
|
|||
|
|
@ -892,6 +892,445 @@ bool asset_emission_and_unconfirmed_balance::c1(currency::core& c, size_t ev_ind
|
|||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
asset_operation_and_hardfork_checks::asset_operation_and_hardfork_checks()
|
||||
{
|
||||
m_adb_hello.total_max_supply = 1'000'000'000'000'000'000;
|
||||
m_adb_hello.current_supply = 1'000'000'000'000'000'000;
|
||||
m_adb_hello.ticker = "HLO";
|
||||
m_adb_hello.full_name = "HELLO_WORLD";
|
||||
m_adb_hello.meta_info = "Hello, world!";
|
||||
m_adb_hello.hidden_supply = false;
|
||||
|
||||
m_ado_hello.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER;
|
||||
m_ado_hello.opt_asset_id = currency::null_pkey;
|
||||
|
||||
m_adb_bye.total_max_supply = 1'000'000'000'000'000'000;
|
||||
m_adb_bye.current_supply = 1'000'000'000'000'000'000;
|
||||
m_adb_bye.ticker = "BYE";
|
||||
m_adb_bye.full_name = "BYE_WORLD";
|
||||
m_adb_bye.meta_info = "Bye, world!";
|
||||
m_adb_bye.hidden_supply = false;
|
||||
|
||||
m_ado_bye.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER;
|
||||
m_ado_hello.opt_asset_id = currency::null_pkey;
|
||||
|
||||
REGISTER_CALLBACK_METHOD(asset_operation_and_hardfork_checks, c1);
|
||||
REGISTER_CALLBACK_METHOD(asset_operation_and_hardfork_checks, c2);
|
||||
}
|
||||
|
||||
bool asset_operation_and_hardfork_checks::generate(
|
||||
std::vector<test_event_entry>& events) const
|
||||
{
|
||||
/*
|
||||
0 10 11 21 22
|
||||
( 0) - ... - (0r) - ( 1) - ... - (1r) - ( 2)
|
||||
\ \
|
||||
[tx_0] [tx_1]
|
||||
*/
|
||||
|
||||
bool success{false};
|
||||
std::vector<tx_source_entry> sources{};
|
||||
std::vector<tx_destination_entry> destinations{};
|
||||
transaction tx_0{}, tx_1{}, tx_2{}, tx_3{}, tx_4{};
|
||||
uint64_t tx_version{};
|
||||
crypto::secret_key stub{};
|
||||
|
||||
m_accounts.resize(2);
|
||||
account_base& miner{m_accounts[MINER_ACC_IDX]};
|
||||
miner.generate();
|
||||
account_base& alice{m_accounts[ALICE_ACC_IDX]};
|
||||
alice.generate();
|
||||
|
||||
m_adb_hello.owner = alice.get_public_address().spend_public_key;
|
||||
m_ado_hello.descriptor = m_adb_hello;
|
||||
|
||||
m_adb_bye.owner = alice.get_public_address().spend_public_key;
|
||||
m_ado_bye.descriptor = m_adb_bye;
|
||||
|
||||
MAKE_GENESIS_BLOCK(events,
|
||||
blk_0,
|
||||
miner,
|
||||
test_core_time::get_time());
|
||||
|
||||
DO_CALLBACK(events, "configure_core");
|
||||
|
||||
REWIND_BLOCKS_N_WITH_TIME(events,
|
||||
blk_0r,
|
||||
blk_0,
|
||||
miner,
|
||||
CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
|
||||
|
||||
success = fill_tx_sources_and_destinations(events,
|
||||
/* head = */ blk_0r,
|
||||
/* from = */ miner.get_keys(),
|
||||
/* to = */ alice.get_public_address(),
|
||||
/* amount = */ MK_TEST_COINS(12),
|
||||
/* fee = */ TESTS_DEFAULT_FEE,
|
||||
/* nmix = */ 0,
|
||||
sources,
|
||||
destinations);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to fill sources, destinations");
|
||||
|
||||
tx_version = get_tx_version(get_block_height(blk_0r),
|
||||
m_hardforks);
|
||||
|
||||
success = construct_tx(miner.get_keys(),
|
||||
sources,
|
||||
destinations,
|
||||
empty_attachment,
|
||||
tx_0,
|
||||
tx_version,
|
||||
0);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to construct tx_0");
|
||||
|
||||
ADD_CUSTOM_EVENT(events, tx_0);
|
||||
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner, tx_0);
|
||||
|
||||
REWIND_BLOCKS_N_WITH_TIME(events,
|
||||
blk_1r,
|
||||
blk_1,
|
||||
miner,
|
||||
CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
|
||||
|
||||
sources.clear();
|
||||
destinations.clear();
|
||||
|
||||
success = fill_tx_sources_and_destinations(events,
|
||||
/* head = */ blk_1r,
|
||||
/* from = */ alice,
|
||||
/* to = */ alice,
|
||||
/* amount = */ MK_TEST_COINS(5),
|
||||
/* fee = */ TESTS_DEFAULT_FEE,
|
||||
/* nmix = */ 0,
|
||||
sources,
|
||||
destinations);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to fill sources, destinations");
|
||||
|
||||
destinations.emplace_back(/* amount = */ 1'000'000'000'000'000'000,
|
||||
/* to = */ alice.get_public_address(),
|
||||
/* asset_id = */ currency::null_pkey);
|
||||
|
||||
tx_version = get_tx_version(get_block_height(blk_1r), m_hardforks);
|
||||
|
||||
success = construct_tx(alice.get_keys(),
|
||||
sources,
|
||||
destinations,
|
||||
/* extra = */ {m_ado_hello},
|
||||
empty_attachment,
|
||||
tx_1,
|
||||
tx_version,
|
||||
stub,
|
||||
0);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to construct tx_1");
|
||||
ADD_CUSTOM_EVENT(events, tx_1);
|
||||
|
||||
MAKE_NEXT_BLOCK_TX1(events,
|
||||
blk_2,
|
||||
blk_1r,
|
||||
alice,
|
||||
tx_1);
|
||||
|
||||
REWIND_BLOCKS_N_WITH_TIME(events,
|
||||
blk_2r,
|
||||
blk_2,
|
||||
miner,
|
||||
CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
|
||||
|
||||
/* A transaction that has at least 2 registration operation descriptors in its
|
||||
extra is rejected by the core. */
|
||||
|
||||
sources.clear();
|
||||
destinations.clear();
|
||||
|
||||
success = fill_tx_sources_and_destinations(events,
|
||||
/* head = */ blk_2r,
|
||||
/* from = */ alice,
|
||||
/* to = */ alice,
|
||||
/* amount = */ MK_TEST_COINS(2),
|
||||
/* fee = */ TESTS_DEFAULT_FEE,
|
||||
/* nmix = */ 0,
|
||||
sources,
|
||||
destinations);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to fill sources, destinations");
|
||||
|
||||
tx_version = get_tx_version(get_block_height(blk_2r),
|
||||
m_hardforks);
|
||||
|
||||
success = construct_tx(alice.get_keys(),
|
||||
sources,
|
||||
destinations,
|
||||
/* extra = */ {m_ado_hello, m_ado_hello},
|
||||
empty_attachment,
|
||||
tx_2,
|
||||
tx_version,
|
||||
stub,
|
||||
0);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to construct tx_2");
|
||||
|
||||
DO_CALLBACK(events, "mark_invalid_tx");
|
||||
ADD_CUSTOM_EVENT(events, tx_2);
|
||||
|
||||
sources.clear();
|
||||
destinations.clear();
|
||||
|
||||
/* A transaction that contains a registration operation descriptor in its
|
||||
attachement, but extra is empty, is valid, but doesn't register the asset. The
|
||||
fact that the asset is not registered is checked in the assertions in the
|
||||
callback с2. */
|
||||
|
||||
success = fill_tx_sources_and_destinations(events,
|
||||
/* head = */ blk_2r,
|
||||
/* from = */ alice,
|
||||
/* to = */ alice,
|
||||
/* amount = */ MK_TEST_COINS(2),
|
||||
/* fee = */ TESTS_DEFAULT_FEE,
|
||||
/* nmix = */ 0,
|
||||
sources,
|
||||
destinations);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to fill sources, destinations");
|
||||
|
||||
tx_version = get_tx_version(get_block_height(blk_2r),
|
||||
m_hardforks);
|
||||
|
||||
success = construct_tx(alice.get_keys(),
|
||||
sources,
|
||||
destinations,
|
||||
/* attachments = */ {m_ado_bye},
|
||||
tx_3,
|
||||
tx_version,
|
||||
0);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to construct tx_3");
|
||||
|
||||
ADD_CUSTOM_EVENT(events, tx_3);
|
||||
DO_CALLBACK(events, "c2");
|
||||
|
||||
sources.clear();
|
||||
destinations.clear();
|
||||
|
||||
/* A transaction that contains a registration operation descriptor in its
|
||||
attachement, but extra is empty, is valid, but doesn't register the asset. In
|
||||
this case a different definition of the function construct_tx is used. */
|
||||
|
||||
success = fill_tx_sources_and_destinations(events,
|
||||
/* head = */ blk_2r,
|
||||
/* from = */ alice,
|
||||
/* to = */ alice,
|
||||
/* amount = */ MK_TEST_COINS(2),
|
||||
/* fee = */ TESTS_DEFAULT_FEE,
|
||||
/* nmix = */ 0,
|
||||
sources,
|
||||
destinations);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to fill sources, destinations");
|
||||
|
||||
tx_version = get_tx_version(get_block_height(blk_2r),
|
||||
m_hardforks);
|
||||
|
||||
success = construct_tx(alice.get_keys(),
|
||||
sources,
|
||||
destinations,
|
||||
empty_extra,
|
||||
/* attachments = */ {m_ado_bye},
|
||||
tx_4,
|
||||
tx_version,
|
||||
stub,
|
||||
0);
|
||||
|
||||
CHECK_AND_ASSERT_MES(success, false, "fail to construct tx_4");
|
||||
|
||||
ADD_CUSTOM_EVENT(events, tx_4);
|
||||
DO_CALLBACK(events, "c2");
|
||||
DO_CALLBACK(events, "c1");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool asset_operation_and_hardfork_checks::c1(
|
||||
currency::core& c,
|
||||
size_t ev_index,
|
||||
const std::vector<test_event_entry>& events)
|
||||
{
|
||||
std::shared_ptr<tools::wallet2> wallet{
|
||||
init_playtime_test_wallet_t<tools::wallet2>(events, c, ALICE_ACC_IDX)
|
||||
};
|
||||
|
||||
wallet->refresh();
|
||||
|
||||
crypto::point_t asset_id_point{};
|
||||
crypto::public_key asset_id_public_key{};
|
||||
currency::get_or_calculate_asset_id(m_ado_hello,
|
||||
&asset_id_point,
|
||||
&asset_id_public_key);
|
||||
|
||||
CHECK_AND_ASSERT_MES(
|
||||
check_balance_via_wallet(*wallet,
|
||||
/* name = */ "Alice",
|
||||
/* expected_total = */ 1'000'000'000'000'000'000,
|
||||
/* expected_mined = */ 2 * COIN,
|
||||
/* expected_unlocked = */ 1'000'000'000'000'000'000,
|
||||
/* expected_awaiting_in = */ INVALID_BALANCE_VAL,
|
||||
/* expected_awaiting_out = */ INVALID_BALANCE_VAL,
|
||||
asset_id_public_key),
|
||||
false,
|
||||
"balance check failed");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool asset_operation_and_hardfork_checks::c2(
|
||||
currency::core& c,
|
||||
size_t ev_index,
|
||||
const std::vector<test_event_entry>& events)
|
||||
{
|
||||
crypto::point_t asset_id_pt{};
|
||||
crypto::public_key asset_id{};
|
||||
currency::asset_descriptor_base stub{};
|
||||
|
||||
CHECK_AND_ASSERT_MES(
|
||||
get_or_calculate_asset_id(m_ado_bye,
|
||||
&asset_id_pt,
|
||||
&asset_id),
|
||||
false,
|
||||
"fail to calculate asset id");
|
||||
|
||||
CHECK_AND_ASSERT_MES(
|
||||
!c.get_blockchain_storage().get_asset_info(asset_id,
|
||||
stub),
|
||||
false,
|
||||
"unregistered asset has info");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
asset_operation_in_consolidated_tx::asset_operation_in_consolidated_tx()
|
||||
{
|
||||
m_adb_alice_currency.total_max_supply = 1'000'000'000'000'000'000;
|
||||
m_adb_alice_currency.current_supply = 1'000'000'000'000'000'000;
|
||||
m_adb_alice_currency.ticker = "ALC";
|
||||
m_adb_alice_currency.full_name = "ALICE";
|
||||
m_adb_alice_currency.meta_info = "Currency created by Alice";
|
||||
m_adb_alice_currency.hidden_supply = false;
|
||||
|
||||
m_ado_alice_currency.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER;
|
||||
m_ado_alice_currency.opt_asset_id = currency::null_pkey;
|
||||
|
||||
REGISTER_CALLBACK_METHOD(asset_operation_in_consolidated_tx, assert_balances);
|
||||
REGISTER_CALLBACK_METHOD(asset_operation_in_consolidated_tx, assert_alice_currency_not_registered);
|
||||
}
|
||||
|
||||
bool asset_operation_in_consolidated_tx::generate(std::vector<test_event_entry>& events) const
|
||||
{
|
||||
// Test idea: make sure that the core rule prohibiting operations with assets in TX_FLAG_SIGNATURE_MODE_SEPARATE transactions works.
|
||||
bool success {};
|
||||
transaction tx_2 {};
|
||||
uint64_t tx_version {};
|
||||
crypto::secret_key one_time {};
|
||||
tx_generation_context context_tx_2 {};
|
||||
GENERATE_ACCOUNT(miner);
|
||||
GENERATE_ACCOUNT(alice);
|
||||
GENERATE_ACCOUNT(bob);
|
||||
|
||||
m_accounts.push_back(miner);
|
||||
m_accounts.push_back(alice);
|
||||
m_accounts.push_back(bob);
|
||||
m_adb_alice_currency.owner = m_accounts.at(ALICE_ACC_IDX).get_public_address().spend_public_key;
|
||||
m_ado_alice_currency.descriptor = m_adb_alice_currency;
|
||||
|
||||
MAKE_GENESIS_BLOCK(events, blk_0, miner, test_core_time::get_time());
|
||||
DO_CALLBACK(events, "configure_core");
|
||||
REWIND_BLOCKS_N(events, blk_0r, blk_0, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
|
||||
MAKE_TX(events, tx_0, miner, bob, MK_TEST_COINS(10), blk_0r);
|
||||
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner, tx_0);
|
||||
REWIND_BLOCKS_N(events, blk_1r, blk_1, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
|
||||
MAKE_TX(events, tx_1, miner, alice, MK_TEST_COINS(10), blk_1r);
|
||||
MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner, tx_1);
|
||||
REWIND_BLOCKS_N(events, blk_2r, blk_2, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
|
||||
// Miner sent 10 coins to Alice, 10 coins to Bob.
|
||||
DO_CALLBACK(events, "assert_balances");
|
||||
|
||||
{
|
||||
std::vector<tx_source_entry> sources {};
|
||||
std::vector<tx_destination_entry> destinations {};
|
||||
|
||||
success = fill_tx_sources(sources, events, blk_2r, alice.get_keys(), MK_TEST_COINS(5), 1);
|
||||
CHECK_AND_ASSERT_MES(success, false, "failed to fill transaction sources on step 1");
|
||||
destinations.emplace_back(MK_TEST_COINS(5), bob.get_public_address());
|
||||
destinations.emplace_back(MK_TEST_COINS(/* 10 - 5 - 1 = */ 4), alice.get_public_address());
|
||||
tx_version = get_tx_version(get_block_height(blk_2r), m_hardforks);
|
||||
success = construct_tx(alice.get_keys(), sources, destinations, empty_extra, empty_attachment, tx_2, tx_version, one_time, 0, 0, 0, true, TX_FLAG_SIGNATURE_MODE_SEPARATE, TESTS_DEFAULT_FEE,
|
||||
context_tx_2);
|
||||
CHECK_AND_ASSERT_MES(success, false, "failed to construct transaction tx_2 on step 1");
|
||||
}
|
||||
|
||||
// Transaction tx_2 hasn't been constructed completely yet. Core rejects tx_2.
|
||||
DO_CALLBACK(events, "mark_invalid_tx");
|
||||
ADD_CUSTOM_EVENT(events, tx_2);
|
||||
|
||||
{
|
||||
std::vector<tx_source_entry> sources {};
|
||||
std::vector<tx_destination_entry> destinations {};
|
||||
|
||||
success = fill_tx_sources(sources, events, blk_2r, bob.get_keys(), MK_TEST_COINS(5), 0);
|
||||
CHECK_AND_ASSERT_MES(success, false, "failed to fill transaction sources on step 2");
|
||||
for(tx_source_entry& source : sources)
|
||||
{
|
||||
source.separately_signed_tx_complete = true;
|
||||
}
|
||||
destinations.emplace_back(MK_TEST_COINS(5), alice.get_public_address());
|
||||
destinations.emplace_back(MK_TEST_COINS(/* 10 - 5 - 0 = */ 5), bob.get_public_address());
|
||||
destinations.emplace_back(m_adb_alice_currency.current_supply, alice.get_public_address(), null_pkey);
|
||||
tx_version = get_tx_version(get_block_height(blk_2r), m_hardforks);
|
||||
success = construct_tx(bob.get_keys(), sources, destinations, { m_ado_alice_currency }, empty_attachment, tx_2, tx_version, one_time, 0, 0, 0, true, TX_FLAG_SIGNATURE_MODE_SEPARATE,
|
||||
/* fee = */ 0, context_tx_2);
|
||||
CHECK_AND_ASSERT_MES(success, false, "failed to construct transaction tx_2 on step 2");
|
||||
}
|
||||
|
||||
DO_CALLBACK(events, "mark_invalid_tx");
|
||||
ADD_CUSTOM_EVENT(events, tx_2);
|
||||
// Core rejects transaction tx_2. The balances of Alice, Bob haven't changed: Alice has 10 coins, Bob has 10 coins.
|
||||
DO_CALLBACK(events, "assert_balances");
|
||||
// Alice's asset hasn't registered, because transaction tx_2 was rejected.
|
||||
DO_CALLBACK(events, "assert_alice_currency_not_registered");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool asset_operation_in_consolidated_tx::assert_balances(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
|
||||
{
|
||||
std::shared_ptr<tools::wallet2> alice_wallet{init_playtime_test_wallet(events, c, ALICE_ACC_IDX)};
|
||||
std::shared_ptr<tools::wallet2> bob_wallet{init_playtime_test_wallet(events, c, BOB_ACC_IDX)};
|
||||
|
||||
alice_wallet->refresh();
|
||||
bob_wallet->refresh();
|
||||
|
||||
CHECK_AND_ASSERT_EQ(alice_wallet->balance(currency::native_coin_asset_id), MK_TEST_COINS(10));
|
||||
CHECK_AND_ASSERT_EQ(bob_wallet->balance(currency::native_coin_asset_id), MK_TEST_COINS(10));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool asset_operation_in_consolidated_tx::assert_alice_currency_not_registered(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
|
||||
{
|
||||
crypto::point_t asset_id_point{};
|
||||
crypto::public_key asset_id{};
|
||||
currency::asset_descriptor_base stub{};
|
||||
|
||||
CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(m_ado_alice_currency, &asset_id_point, &asset_id), false, "fail to calculate asset id");
|
||||
CHECK_AND_ASSERT_MES(!c.get_blockchain_storage().get_asset_info(asset_id, stub), false, "unregistered asset has info");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
eth_signed_asset_basics::eth_signed_asset_basics()
|
||||
{
|
||||
REGISTER_CALLBACK_METHOD(eth_signed_asset_basics, c1);
|
||||
|
|
@ -938,7 +1377,7 @@ std::shared_ptr<eth_sign_cb_helper<T>> construct_eth_sign_cb_helper(T&& cb)
|
|||
bool eth_signed_asset_basics::c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
|
||||
{
|
||||
bool r = false;
|
||||
|
||||
|
||||
crypto::eth_secret_key eth_sk{};
|
||||
crypto::eth_public_key eth_pk{};
|
||||
r = crypto::generate_eth_key_pair(eth_sk, eth_pk);
|
||||
|
|
@ -1020,7 +1459,7 @@ bool eth_signed_asset_basics::c1(currency::core& c, size_t ev_index, const std::
|
|||
auto signer = construct_eth_sign_cb_helper([&](const crypto::hash& h, const crypto::eth_public_key& asset_owner, crypto::eth_signature& sig)->bool {
|
||||
CHECK_AND_ASSERT_EQ(asset_owner, eth_pk);
|
||||
return crypto::generate_eth_signature(h, eth_sk, sig);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO reconsider this ===>> ctp.p_eth_signer = signer.get();
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,39 @@ struct asset_emission_and_unconfirmed_balance : public wallet_test
|
|||
bool c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
|
||||
};
|
||||
|
||||
struct asset_operation_and_hardfork_checks : public wallet_test
|
||||
{
|
||||
public:
|
||||
asset_operation_and_hardfork_checks();
|
||||
|
||||
bool generate(std::vector<test_event_entry>& events) const;
|
||||
bool c1(currency::core& c,
|
||||
size_t ev_index,
|
||||
const std::vector<test_event_entry>& events);
|
||||
|
||||
bool c2(currency::core& c,
|
||||
size_t ev_index,
|
||||
const std::vector<test_event_entry>& events);
|
||||
|
||||
private:
|
||||
mutable currency::asset_descriptor_base m_adb_hello{};
|
||||
mutable currency::asset_descriptor_operation m_ado_hello{};
|
||||
mutable currency::asset_descriptor_base m_adb_bye{};
|
||||
mutable currency::asset_descriptor_operation m_ado_bye{};
|
||||
};
|
||||
|
||||
struct asset_operation_in_consolidated_tx : public wallet_test
|
||||
{
|
||||
public:
|
||||
asset_operation_in_consolidated_tx();
|
||||
bool generate(std::vector<test_event_entry>& events) const;
|
||||
bool assert_balances(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
|
||||
bool assert_alice_currency_not_registered(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
|
||||
|
||||
private:
|
||||
mutable currency::asset_descriptor_base m_adb_alice_currency{};
|
||||
mutable currency::asset_descriptor_operation m_ado_alice_currency{};
|
||||
};
|
||||
|
||||
struct eth_signed_asset_basics : public wallet_test
|
||||
{
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ void pos_block_builder::step4_generate_coinbase_tx(size_t median_size,
|
|||
uint64_t block_reward_without_fee = 0;
|
||||
m_block_reward = 0;
|
||||
size_t estimated_block_size = m_txs_total_size;
|
||||
m_block.miner_tx = transaction{};
|
||||
bool r = construct_miner_tx(m_height, median_size, already_generated_coins, estimated_block_size, m_total_fee,
|
||||
reward_receiver_address, stakeholder_address, m_block.miner_tx, block_reward_without_fee, m_block_reward, tx_version, extra_nonce, max_outs, true, pe, &m_miner_tx_tgc, tx_one_time_key_to_use);
|
||||
CHECK_AND_ASSERT_THROW_MES(r, "construct_miner_tx failed");
|
||||
|
|
@ -177,6 +178,7 @@ void pos_block_builder::step4_generate_coinbase_tx(size_t median_size,
|
|||
size_t cumulative_size = 0;
|
||||
for (size_t try_count = 0; try_count != 10; ++try_count)
|
||||
{
|
||||
m_block.miner_tx = transaction{};
|
||||
r = construct_miner_tx(m_height, median_size, already_generated_coins, estimated_block_size, m_total_fee,
|
||||
reward_receiver_address, stakeholder_address, m_block.miner_tx, block_reward_without_fee, m_block_reward, tx_version, extra_nonce, max_outs, true, pe, &m_miner_tx_tgc, tx_one_time_key_to_use);
|
||||
CHECK_AND_ASSERT_THROW_MES(r, "construct_homemade_pos_miner_tx failed");
|
||||
|
|
|
|||
1314
tests/unit_tests/multiassets_test.cpp
Normal file
1314
tests/unit_tests/multiassets_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -757,30 +757,30 @@ struct A
|
|||
struct A_v1 : public A
|
||||
{
|
||||
std::vector<std::string> vector_two;
|
||||
|
||||
uint8_t current_version = 1;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(one)
|
||||
FIELD(two)
|
||||
FIELD(vector_one)
|
||||
VERSION(1)
|
||||
if (s_version < 1) return true;
|
||||
VERSION_TO_MEMBER(1, current_version)
|
||||
FIELD(vector_two)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
|
||||
struct A_v2 : public A_v1
|
||||
{
|
||||
std::vector<std::string> vector_3;
|
||||
std::vector<std::string> vector_4;
|
||||
|
||||
uint8_t current_version = 2;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
//CURRENT_VERSION(2)
|
||||
FIELD(one)
|
||||
FIELD(two)
|
||||
FIELD(vector_one)
|
||||
VERSION(2)
|
||||
VERSION_TO_MEMBER(2, current_version)
|
||||
if (s_version < 1) return true;
|
||||
FIELD(vector_two)
|
||||
if (s_version < 2) return true;
|
||||
|
|
@ -792,13 +792,14 @@ struct A_v2 : public A_v1
|
|||
struct A_v3 : public A_v2
|
||||
{
|
||||
std::vector<std::string> vector_5;
|
||||
uint8_t current_version = 3;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
//CURRENT_VERSION(3)
|
||||
FIELD(one)
|
||||
FIELD(two)
|
||||
FIELD(vector_one)
|
||||
VERSION(3)
|
||||
VERSION_TO_MEMBER(3, current_version)
|
||||
if (s_version < 1) return true;
|
||||
FIELD(vector_two)
|
||||
if (s_version < 2) return true;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue