diff --git a/src/common/make_hashable.h b/src/common/make_hashable.h index af18f478..c22bfa45 100644 --- a/src/common/make_hashable.h +++ b/src/common/make_hashable.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2020 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Copyright (c) 2012-2013 The Boolberry developers @@ -37,3 +37,21 @@ namespace std { \ } \ }; \ } + +namespace std +{ + + // this allows using std::pair<> as a key in unordered std containers + template + struct hash> + { + size_t operator()(const pair& p) const + { + auto hash1 = hash{}(p.first); + auto hash2 = hash{}(p.second); + return hash1 ^ hash2; + } + }; + +} // namespace std + diff --git a/src/common/mnemonic-encoding.cpp b/src/common/mnemonic-encoding.cpp index e5f46641..b5a22c24 100644 --- a/src/common/mnemonic-encoding.cpp +++ b/src/common/mnemonic-encoding.cpp @@ -48,8 +48,6 @@ namespace tools { using namespace std; - const int NUMWORDS = 1626; - const map wordsMap = { {"like", 0}, {"just", 1}, @@ -3358,7 +3356,7 @@ namespace tools throw runtime_error("Invalid binary data size for mnemonic encoding"); // 4 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626 string res; - for (unsigned int i=0; i < binary.size() / 4; i++, res += ' ') + for (unsigned int i=0; i < binary.size() / 4; i++) { const uint32_t* val = reinterpret_cast(&binary[i * 4]); @@ -3369,8 +3367,9 @@ namespace tools res += wordsArray[w1] + " "; res += wordsArray[w2] + " "; - res += wordsArray[w3]; + res += wordsArray[w3] + " "; } + res.erase(--res.end()); // remove trailing space return res; } std::string word_by_num(uint32_t n) diff --git a/src/common/mnemonic-encoding.h b/src/common/mnemonic-encoding.h index 409a935b..2e92ffa4 100644 --- a/src/common/mnemonic-encoding.h +++ b/src/common/mnemonic-encoding.h @@ -40,6 +40,8 @@ namespace tools { namespace mnemonic_encoding { + constexpr int NUMWORDS = 1626; + std::vector text2binary(const std::string& text); std::string binary2text(const std::vector& binary); std::string word_by_num(uint32_t n); diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index eba65c8c..ec93366d 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -85,34 +85,31 @@ namespace crypto { memcpy(&res, tmp, 32); } - void crypto_ops::keys_from_default(unsigned char* a_part, public_key &pub, secret_key &sec, size_t brain_wallet_seed_size) - { - unsigned char tmp[64] = { 0 }; - - if (!(sizeof(tmp) >= brain_wallet_seed_size)) - { - throw std::runtime_error("size mismatch"); - } - - memcpy(tmp, a_part, brain_wallet_seed_size); - - cn_fast_hash(tmp, 32, (char*)&tmp[32]); - - sc_reduce(tmp); - memcpy(&sec, tmp, 32); - ge_p3 point; - ge_scalarmult_base(&point, &sec); - ge_p3_tobytes(&pub, &point); - } - - void crypto_ops::generate_brain_keys(public_key &pub, secret_key &sec, std::string& seed, size_t brain_wallet_seed_size) + void crypto_ops::keys_from_default(const unsigned char* a_part, public_key &pub, secret_key &sec, size_t keys_seed_binary_size) { - std::vector tmp_vector; - tmp_vector.resize(brain_wallet_seed_size, 0); - unsigned char *tmp = &tmp_vector[0]; - generate_random_bytes(brain_wallet_seed_size, tmp); - seed.assign((const char*)tmp, brain_wallet_seed_size); - keys_from_default(tmp, pub, sec, brain_wallet_seed_size); + unsigned char tmp[64] = { 0 }; + + if (!(sizeof(tmp) >= keys_seed_binary_size)) + { + throw std::runtime_error("size mismatch"); + } + + memcpy(tmp, a_part, keys_seed_binary_size); + + cn_fast_hash(tmp, 32, (char*)&tmp[32]); + + sc_reduce(tmp); + memcpy(&sec, tmp, 32); + ge_p3 point; + ge_scalarmult_base(&point, &sec); + ge_p3_tobytes(&pub, &point); + } + + void crypto_ops::generate_seed_keys(public_key &pub, secret_key &sec, std::vector& keys_seed_binary, size_t keys_seed_binary_size) + { + keys_seed_binary.resize(keys_seed_binary_size, 0); + generate_random_bytes(keys_seed_binary_size, keys_seed_binary.data()); + keys_from_default(keys_seed_binary.data(), pub, sec, keys_seed_binary_size); } static inline void hash_to_scalar(const void *data, size_t length, ec_scalar &res) diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index e7b2da6a..f6a3aebf 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -74,10 +74,10 @@ namespace crypto { static void generate_keys(public_key &, secret_key &); friend void generate_keys(public_key &, secret_key &); - static void generate_brain_keys(public_key &, secret_key &, std::string& seed, size_t brain_wallet_seed_size); - friend void generate_brain_keys(public_key &, secret_key &, std::string& seed, size_t brain_wallet_seed_size); - static void keys_from_default(unsigned char* a_part, public_key &pub, secret_key &sec, size_t brain_wallet_seed_size); - friend void keys_from_default(unsigned char* a_part, public_key &pub, secret_key &sec, size_t brain_wallet_seed_size); + static void generate_seed_keys(public_key &pub, secret_key &sec, std::vector& keys_seed_binary, size_t keys_seed_binary_size); + friend void generate_seed_keys(public_key &pub, secret_key &sec, std::vector& keys_seed_binary, size_t keys_seed_binary_size); + static void keys_from_default(const unsigned char* a_part, public_key &pub, secret_key &sec, size_t keys_seed_binary_size); + friend void keys_from_default(const unsigned char* a_part, public_key &pub, secret_key &sec, size_t keys_seed_binary_size); static void dependent_key(const secret_key& first, secret_key& second); friend void dependent_key(const secret_key& first, secret_key& second); static bool check_key(const public_key &); @@ -136,14 +136,14 @@ namespace crypto { crypto_ops::generate_keys(pub, sec); } - inline void generate_brain_keys(public_key &pub, secret_key &sec, std::string& seed, size_t brain_wallet_seed_size) { - crypto_ops::generate_brain_keys(pub, sec, seed, brain_wallet_seed_size); + inline void generate_seed_keys(public_key &pub, secret_key &sec, std::vector& keys_seed_binary, size_t keys_seed_binary_size) + { + crypto_ops::generate_seed_keys(pub, sec, keys_seed_binary, keys_seed_binary_size); } - - inline void keys_from_default(unsigned char* a_part, public_key &pub, secret_key &sec, size_t brain_wallet_seed_size) + inline void keys_from_default(const unsigned char* a_part, public_key &pub, secret_key &sec, size_t keys_seed_binary_size) { - crypto_ops::keys_from_default(a_part, pub, sec, brain_wallet_seed_size); + crypto_ops::keys_from_default(a_part, pub, sec, keys_seed_binary_size); } inline void dependent_key(const secret_key& first, secret_key& second){ @@ -290,8 +290,7 @@ namespace crypto { bool m_ready; }; -} - +} // namespace crypto POD_MAKE_HASHABLE(crypto, public_key) POD_MAKE_COMPARABLE(crypto, secret_key) diff --git a/src/currency_core/account.cpp b/src/currency_core/account.cpp index 543338ed..ac80a305 100644 --- a/src/currency_core/account.cpp +++ b/src/currency_core/account.cpp @@ -17,15 +17,10 @@ using namespace std; -DISABLE_VS_WARNINGS(4244 4345) - - +//DISABLE_VS_WARNINGS(4244 4345) namespace currency { - - - //----------------------------------------------------------------- account_base::account_base() { @@ -37,18 +32,22 @@ namespace currency // fill sensitive data with random bytes crypto::generate_random_bytes(sizeof m_keys.spend_secret_key, &m_keys.spend_secret_key); crypto::generate_random_bytes(sizeof m_keys.view_secret_key, &m_keys.view_secret_key); - crypto::generate_random_bytes(m_seed.size(), &m_seed[0]); + if (m_keys_seed_binary.size()) + crypto::generate_random_bytes(m_keys_seed_binary.size(), &m_keys_seed_binary[0]); // clear m_keys = account_keys(); m_creation_timestamp = 0; - m_seed.clear(); + m_keys_seed_binary.clear(); } //----------------------------------------------------------------- - void account_base::generate() + void account_base::generate(bool auditable /* = false */) { - generate_brain_keys(m_keys.account_address.spend_public_key, m_keys.spend_secret_key, m_seed, BRAINWALLET_DEFAULT_SEED_SIZE); - dependent_key(m_keys.spend_secret_key, m_keys.view_secret_key); + if (auditable) + m_keys.account_address.flags = ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE; + + crypto::generate_seed_keys(m_keys.account_address.spend_public_key, m_keys.spend_secret_key, m_keys_seed_binary, BRAINWALLET_DEFAULT_SEED_SIZE); + crypto::dependent_key(m_keys.spend_secret_key, m_keys.view_secret_key); if (!crypto::secret_key_to_public_key(m_keys.view_secret_key, m_keys.account_address.view_public_key)) throw std::runtime_error("Failed to create public view key"); @@ -61,68 +60,122 @@ namespace currency return m_keys; } //----------------------------------------------------------------- - std::string account_base::get_restore_data() const - { - return m_seed; - } - //----------------------------------------------------------------- - std::string account_base::get_restore_braindata() const { - std::string restore_buff = get_restore_data(); - if (restore_buff.empty()) + if (m_keys_seed_binary.empty()) return ""; - std::vector v; - v.assign((unsigned char*)restore_buff.data(), (unsigned char*)restore_buff.data() + restore_buff.size()); - std::string seed_brain_data = tools::mnemonic_encoding::binary2text(v); + std::string keys_seed_text = tools::mnemonic_encoding::binary2text(m_keys_seed_binary); std::string timestamp_word = currency::get_word_from_timstamp(m_creation_timestamp); - seed_brain_data = seed_brain_data + timestamp_word; - return seed_brain_data; + + // floor creation time to WALLET_BRAIN_DATE_QUANTUM to make checksum calculation stable + uint64_t creation_timestamp_rounded = get_timstamp_from_word(timestamp_word); + + constexpr uint16_t checksum_max = tools::mnemonic_encoding::NUMWORDS >> 1; // maximum value of checksum + crypto::hash h = crypto::cn_fast_hash(m_keys_seed_binary.data(), m_keys_seed_binary.size()); + *reinterpret_cast(&h) = creation_timestamp_rounded; + h = crypto::cn_fast_hash(&h, sizeof h); + uint64_t h_64 = *reinterpret_cast(&h); + uint16_t checksum = h_64 % (checksum_max + 1); + + uint8_t auditable_flag = 0; + if (m_keys.account_address.flags & ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE) + auditable_flag = 1; + + uint64_t auditable_flag_and_checksum = (auditable_flag & 1) | (checksum << 1); + std::string auditable_flag_and_checksum_word = tools::mnemonic_encoding::word_by_num(auditable_flag_and_checksum); + + return keys_seed_text + " " + timestamp_word + " " + auditable_flag_and_checksum_word; } //----------------------------------------------------------------- - bool account_base::restore_keys(const std::string& restore_data) + std::string account_base::get_awo_blob() const { - //CHECK_AND_ASSERT_MES(restore_data.size() == ACCOUNT_RESTORE_DATA_SIZE, false, "wrong restore data size"); - if (restore_data.size() == BRAINWALLET_DEFAULT_SEED_SIZE) - { - crypto::keys_from_default((unsigned char*)restore_data.data(), m_keys.account_address.spend_public_key, m_keys.spend_secret_key, BRAINWALLET_DEFAULT_SEED_SIZE); - } - else - { - LOG_ERROR("wrong restore data size=" << restore_data.size()); - return false; - } - m_seed = restore_data; + return get_public_address_str() + ":" + + epee::string_tools::pod_to_hex(m_keys.view_secret_key) + + (m_creation_timestamp ? ":" : "") + (m_creation_timestamp ? epee::string_tools::num_to_string_fast(m_creation_timestamp) : ""); + } + //----------------------------------------------------------------- + bool account_base::restore_keys(const std::vector& keys_seed_binary) + { + CHECK_AND_ASSERT_MES(keys_seed_binary.size() == BRAINWALLET_DEFAULT_SEED_SIZE, false, "wrong restore data size: " << keys_seed_binary.size()); + crypto::keys_from_default(keys_seed_binary.data(), m_keys.account_address.spend_public_key, m_keys.spend_secret_key, keys_seed_binary.size()); crypto::dependent_key(m_keys.spend_secret_key, m_keys.view_secret_key); bool r = crypto::secret_key_to_public_key(m_keys.view_secret_key, m_keys.account_address.view_public_key); CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_key for view key"); - set_createtime(0); return true; } //----------------------------------------------------------------- - bool account_base::restore_keys_from_braindata(const std::string& restore_data_) + bool account_base::restore_from_braindata(const std::string& seed_phrase) { //cut the last timestamp word from restore_dats std::list words; - boost::split(words, restore_data_, boost::is_space()); - CHECK_AND_ASSERT_MES(words.size() == BRAINWALLET_DEFAULT_WORDS_COUNT, false, "Words count missmatch: " << words.size()); - - std::string timestamp_word = words.back(); - words.erase(--words.end()); - - std::string restore_data_local = boost::algorithm::join(words, " "); + boost::split(words, seed_phrase, boost::is_space()); - std::vector bin = tools::mnemonic_encoding::text2binary(restore_data_local); - if (!bin.size()) + std::string keys_seed_text, timestamp_word, auditable_flag_and_checksum_word; + if (words.size() == SEED_PHRASE_V1_WORDS_COUNT) + { + // 24 seed words + one timestamp word = 25 total + timestamp_word = words.back(); + words.erase(--words.end()); + keys_seed_text = boost::algorithm::join(words, " "); + } + else if (words.size() == SEED_PHRASE_V2_WORDS_COUNT) + { + // 24 seed words + one timestamp word + one flags & checksum = 26 total + auditable_flag_and_checksum_word = words.back(); + words.erase(--words.end()); + timestamp_word = words.back(); + words.erase(--words.end()); + keys_seed_text = boost::algorithm::join(words, " "); + } + else + { + LOG_ERROR("Invalid seed words count: " << words.size()); return false; + } + + uint64_t auditable_flag_and_checksum = UINT64_MAX; + if (!auditable_flag_and_checksum_word.empty()) + auditable_flag_and_checksum = tools::mnemonic_encoding::num_by_word(auditable_flag_and_checksum_word); + + std::vector keys_seed_binary = tools::mnemonic_encoding::text2binary(keys_seed_text); + CHECK_AND_ASSERT_MES(keys_seed_binary.size(), false, "text2binary failed to convert the given text"); // don't prints event incorrect seed into the log for security - std::string restore_buff((const char*)&bin[0], bin.size()); - bool r = restore_keys(restore_buff); - CHECK_AND_ASSERT_MES(r, false, "restore_keys failed"); m_creation_timestamp = get_timstamp_from_word(timestamp_word); + + bool auditable_flag = false; + + // check the checksum if checksum word provided + if (auditable_flag_and_checksum != UINT64_MAX) + { + auditable_flag = (auditable_flag_and_checksum & 1) != 0; // auditable flag is the lower 1 bit + uint16_t checksum = auditable_flag_and_checksum >> 1; // checksum -- everything else + constexpr uint16_t checksum_max = tools::mnemonic_encoding::NUMWORDS >> 1; // maximum value of checksum + crypto::hash h = crypto::cn_fast_hash(keys_seed_binary.data(), keys_seed_binary.size()); + *reinterpret_cast(&h) = m_creation_timestamp; + h = crypto::cn_fast_hash(&h, sizeof h); + uint64_t h_64 = *reinterpret_cast(&h); + uint16_t checksum_calculated = h_64 % (checksum_max + 1); + CHECK_AND_ASSERT_MES(checksum == checksum_calculated, false, "seed phase has invalid checksum: " << checksum_calculated << ", while " << checksum << " is expected, check your words"); + } + + bool r = restore_keys(keys_seed_binary); + CHECK_AND_ASSERT_MES(r, false, "restore_keys failed"); + + m_keys_seed_binary = keys_seed_binary; + + if (auditable_flag) + m_keys.account_address.flags |= ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE; + return true; } //----------------------------------------------------------------- + bool account_base::restore_from_awo_blob(const std::string& awo_blob) + { + set_null(); + bool r = parse_awo_blob(awo_blob, m_keys.account_address, m_keys.view_secret_key, m_creation_timestamp); + return r; + } + //----------------------------------------------------------------- std::string account_base::get_public_address_str() const { //TODO: change this code into base 58 @@ -133,7 +186,7 @@ namespace currency { // keep only: // timestamp - // view pub & spend pub (public address) + // view pub & spend pub + flags (public address) // view sec // store to local tmp diff --git a/src/currency_core/account.h b/src/currency_core/account.h index 2c325728..52fec46a 100644 --- a/src/currency_core/account.h +++ b/src/currency_core/account.h @@ -12,7 +12,8 @@ #define BRAINWALLET_DEFAULT_SEED_SIZE 32 #define ACCOUNT_RESTORE_DATA_SIZE BRAINWALLET_DEFAULT_SEED_SIZE -#define BRAINWALLET_DEFAULT_WORDS_COUNT 25 +#define SEED_PHRASE_V1_WORDS_COUNT 25 +#define SEED_PHRASE_V2_WORDS_COUNT 26 @@ -47,15 +48,15 @@ namespace currency { public: account_base(); - void generate(); + void generate(bool auditable = false); const account_keys& get_keys() const; const account_public_address& get_public_address() const { return m_keys.account_address; }; std::string get_public_address_str() const; - std::string get_restore_data() const; + std::string get_restore_braindata() const; - - bool restore_keys(const std::string& restore_data); - bool restore_keys_from_braindata(const std::string& restore_data); + std::string get_awo_blob() const; + bool restore_from_braindata(const std::string& seed_phrase); + bool restore_from_awo_blob(const std::string& awo_blob); uint64_t get_createtime() const { return m_creation_timestamp; } void set_createtime(uint64_t val) { m_creation_timestamp = val; } @@ -70,20 +71,26 @@ namespace currency { a & m_keys; a & m_creation_timestamp; - a & m_seed; + a & m_keys_seed_binary; } + static std::string vector_of_chars_to_string(const std::vector& v) { return std::string(v.begin(), v.end()); } + static std::vector string_to_vector_of_chars(const std::string& v) { return std::vector(v.begin(), v.end()); } + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(m_keys) KV_SERIALIZE(m_creation_timestamp) - KV_SERIALIZE(m_seed) + KV_SERIALIZE_CUSTOM_N(m_keys_seed_binary, std::string, vector_of_chars_to_string, string_to_vector_of_chars, "m_seed") END_KV_SERIALIZE_MAP() private: void set_null(); + bool restore_keys(const std::vector& keys_seed_binary); + account_keys m_keys; uint64_t m_creation_timestamp; - std::string m_seed; + + std::vector m_keys_seed_binary; }; diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 49a9a5bf..f48d7478 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -4671,6 +4671,26 @@ bool blockchain_storage::validate_tx_for_hardfork_specific_terms(const transacti return true; } + if (block_height <= m_core_runtime_config.hard_fork_02_starts_after_height) + { + // before hardfork 2 + + auto check_lambda = [&](const std::vector& container) -> bool + { + for (const auto& el : container) + { + const auto& type = el.type(); + CHECK_AND_ASSERT_MES(type != typeid(tx_payer), false, "tx " << tx_id << " contains tx_payer which is not allowed on height " << block_height); + CHECK_AND_ASSERT_MES(type != typeid(tx_receiver), false, "tx " << tx_id << " contains tx_receiver which is not allowed on height " << block_height); + CHECK_AND_ASSERT_MES(type != typeid(extra_alias_entry), false, "tx " << tx_id << " contains extra_alias_entry which is not allowed on height " << block_height); + } + return true; + }; + + return check_lambda(tx.extra) && check_lambda(tx.attachment); + } + + return true; } //------------------------------------------------------------------ diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index ff0662f0..b51834b5 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -60,9 +60,9 @@ namespace currency /* */ /************************************************************************/ - //since structure used in blockchain as a key accessor, then be sure that there is no padding inside +//since structure used in blockchain as a key accessor, then be sure that there is no padding inside #pragma pack(push, 1) - struct account_public_address + struct account_public_address_old { crypto::public_key spend_public_key; crypto::public_key view_public_key; @@ -72,13 +72,63 @@ namespace currency FIELD(view_public_key) END_SERIALIZE() - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(spend_public_key, "m_spend_public_key") - KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(view_public_key, "m_view_public_key") - END_KV_SERIALIZE_MAP() + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(spend_public_key, "m_spend_public_key") + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(view_public_key, "m_view_public_key") + END_KV_SERIALIZE_MAP() }; #pragma pack(pop) + +#define ACCOUNT_PUBLIC_ADDRESS_SERIZALIZATION_VER 1 + +#define ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE 0x01 // auditable address + +//since structure used in blockchain as a key accessor, then be sure that there is no padding inside +#pragma pack(push, 1) + struct account_public_address + { + crypto::public_key spend_public_key; + crypto::public_key view_public_key; + uint8_t flags; + + DEFINE_SERIALIZATION_VERSION(ACCOUNT_PUBLIC_ADDRESS_SERIZALIZATION_VER) + BEGIN_SERIALIZE_OBJECT() + FIELD(spend_public_key) + FIELD(view_public_key) + FIELD(flags) + END_SERIALIZE() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(spend_public_key, "m_spend_public_key") + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(view_public_key, "m_view_public_key") + KV_SERIALIZE(flags) + END_KV_SERIALIZE_MAP() + + bool is_auditable() const + { + return (flags & ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE) != 0; + } + + static account_public_address from_old(const account_public_address_old& rhs) + { + account_public_address result = AUTO_VAL_INIT(result); + result.spend_public_key = rhs.spend_public_key; + result.view_public_key = rhs.view_public_key; + return result; + } + + account_public_address_old to_old() const + { + account_public_address_old result = AUTO_VAL_INIT(result); + result.spend_public_key = spend_public_key; + result.view_public_key = view_public_key; + return result; + } + }; +#pragma pack(pop) + + const static account_public_address null_pub_addr = AUTO_VAL_INIT(null_pub_addr); typedef std::vector ring_signature; @@ -226,9 +276,30 @@ namespace currency END_SERIALIZE() }; + struct tx_payer_old + { + account_public_address_old acc_addr; + + BEGIN_SERIALIZE() + FIELD(acc_addr) + END_SERIALIZE() + }; + struct tx_payer { - account_public_address acc_addr; + tx_payer() = default; + tx_payer(const tx_payer_old& old) : acc_addr(account_public_address::from_old(old.acc_addr)) {} + + account_public_address acc_addr{}; + + BEGIN_SERIALIZE() + FIELD(acc_addr) + END_SERIALIZE() + }; + + struct tx_receiver_old + { + account_public_address_old acc_addr; BEGIN_SERIALIZE() FIELD(acc_addr) @@ -237,7 +308,10 @@ namespace currency struct tx_receiver { - account_public_address acc_addr; + tx_receiver() = default; + tx_receiver(const tx_receiver_old& old) : acc_addr(account_public_address::from_old(old.acc_addr)) {} + + account_public_address acc_addr{}; BEGIN_SERIALIZE() FIELD(acc_addr) @@ -299,8 +373,42 @@ namespace currency }; + struct extra_alias_entry_base_old + { + account_public_address_old m_address; + std::string m_text_comment; + std::vector m_view_key; // only one or zero elments expected (std::vector is using as memory efficient container for such a case) + std::vector m_sign; // only one or zero elments expected (std::vector is using as memory efficient container for such a case) + + BEGIN_SERIALIZE() + FIELD(m_address) + FIELD(m_text_comment) + FIELD(m_view_key) + FIELD(m_sign) + END_SERIALIZE() + }; + + struct extra_alias_entry_old : public extra_alias_entry_base_old + { + std::string m_alias; + + BEGIN_SERIALIZE() + FIELD(m_alias) + FIELDS(*static_cast(this)) + END_SERIALIZE() + }; + struct extra_alias_entry_base { + extra_alias_entry_base() = default; + extra_alias_entry_base(const extra_alias_entry_base_old& old) + : m_address(account_public_address::from_old(old.m_address)) + , m_text_comment(old.m_text_comment) + , m_view_key(old.m_view_key) + , m_sign(old.m_sign) + { + } + account_public_address m_address; std::string m_text_comment; std::vector m_view_key; // only one or zero elments expected (std::vector is using as memory efficient container for such a case) @@ -314,14 +422,32 @@ namespace currency END_SERIALIZE() }; - struct extra_alias_entry: public extra_alias_entry_base + struct extra_alias_entry : public extra_alias_entry_base { + extra_alias_entry() = default; + extra_alias_entry(const extra_alias_entry_old& old) + : extra_alias_entry_base(old) + , m_alias(old.m_alias) + { + } + std::string m_alias; BEGIN_SERIALIZE() FIELD(m_alias) - FIELDS(*static_cast(this)) - END_SERIALIZE() + FIELDS(*static_cast(this)) + END_SERIALIZE() + + extra_alias_entry_old to_old() const + { + extra_alias_entry_old result = AUTO_VAL_INIT(result); + result.m_address = m_address.to_old(); + result.m_text_comment = m_text_comment; + result.m_view_key = m_view_key; + result.m_sign = m_sign; + result.m_alias = m_alias; + return result; + } }; @@ -390,9 +516,10 @@ namespace currency END_SERIALIZE() }; - typedef boost::mpl::vector< - tx_service_attachment, tx_comment, tx_payer, tx_receiver, tx_derivation_hint, std::string, tx_crypto_checksum, etc_tx_time, etc_tx_details_unlock_time, etc_tx_details_expiration_time, - etc_tx_details_flags, crypto::public_key, extra_attachment_info, extra_alias_entry, extra_user_data, extra_padding, etc_tx_uint16_t, etc_tx_details_unlock_time2 + typedef boost::mpl::vector21< + tx_service_attachment, tx_comment, tx_payer_old, tx_receiver_old, tx_derivation_hint, std::string, tx_crypto_checksum, etc_tx_time, etc_tx_details_unlock_time, etc_tx_details_expiration_time, + etc_tx_details_flags, crypto::public_key, extra_attachment_info, extra_alias_entry_old, extra_user_data, extra_padding, etc_tx_uint16_t, etc_tx_details_unlock_time2, + tx_payer, tx_receiver, extra_alias_entry > all_payload_types; typedef boost::make_variant_over::type payload_items_v; @@ -604,7 +731,7 @@ SET_VARIANT_TAGS(currency::transaction, 5, "tx"); SET_VARIANT_TAGS(currency::block, 6, "block"); //attachment_v definitions SET_VARIANT_TAGS(currency::tx_comment, 7, "comment"); -SET_VARIANT_TAGS(currency::tx_payer, 8, "payer"); +SET_VARIANT_TAGS(currency::tx_payer_old, 8, "payer"); SET_VARIANT_TAGS(std::string, 9, "string"); SET_VARIANT_TAGS(currency::tx_crypto_checksum, 10, "checksum"); SET_VARIANT_TAGS(currency::tx_derivation_hint, 11, "derivation_hint"); @@ -618,7 +745,7 @@ SET_VARIANT_TAGS(currency::signed_parts, 17, "signed_outs"); //extra_v definitions SET_VARIANT_TAGS(currency::extra_attachment_info, 18, "extra_attach_info"); SET_VARIANT_TAGS(currency::extra_user_data, 19, "user_data"); -SET_VARIANT_TAGS(currency::extra_alias_entry, 20, "alias_entry"); +SET_VARIANT_TAGS(currency::extra_alias_entry_old, 20, "alias_entry"); SET_VARIANT_TAGS(currency::extra_padding, 21, "extra_padding"); SET_VARIANT_TAGS(crypto::public_key, 22, "pub_key"); SET_VARIANT_TAGS(currency::etc_tx_uint16_t, 23, "etc_tx_uint16"); @@ -629,7 +756,16 @@ SET_VARIANT_TAGS(uint64_t, 26, "uint64_t"); //etc SET_VARIANT_TAGS(currency::etc_tx_time, 27, "etc_tx_time"); SET_VARIANT_TAGS(uint32_t, 28, "uint32_t"); -SET_VARIANT_TAGS(currency::tx_receiver, 29, "payer"); +SET_VARIANT_TAGS(currency::tx_receiver_old, 29, "payer"); // -- original +//SET_VARIANT_TAGS(currency::tx_receiver_old, 29, "receiver"); SET_VARIANT_TAGS(currency::etc_tx_details_unlock_time2, 30, "unlock_time2"); +SET_VARIANT_TAGS(currency::tx_payer, 31, "payer2"); +SET_VARIANT_TAGS(currency::tx_receiver, 32, "receiver2"); + +// @#@ TODO @#@ +SET_VARIANT_TAGS(currency::extra_alias_entry, 33, "alias_entry2"); + + + #undef SET_VARIANT_TAGS diff --git a/src/currency_core/currency_boost_serialization.h b/src/currency_core/currency_boost_serialization.h index 99928b7a..0b6622e3 100644 --- a/src/currency_core/currency_boost_serialization.h +++ b/src/currency_core/currency_boost_serialization.h @@ -29,10 +29,18 @@ namespace boost template inline void serialize(Archive &a, currency::account_public_address &x, const boost::serialization::version_type ver) { + //a & x.version; + a & x.flags; a & x.spend_public_key; a & x.view_public_key; } + template + inline void serialize(Archive &a, currency::account_public_address_old &x, const boost::serialization::version_type ver) + { + a & x.spend_public_key; + a & x.view_public_key; + } template inline void serialize(Archive &a, currency::txout_to_key &x, const boost::serialization::version_type ver) @@ -90,11 +98,24 @@ namespace boost a & x.comment; } + template + inline void serialize(Archive &a, currency::tx_payer_old &x, const boost::serialization::version_type ver) + { + a & x.acc_addr; + } + template inline void serialize(Archive &a, currency::tx_payer &x, const boost::serialization::version_type ver) { a & x.acc_addr; } + + template + inline void serialize(Archive &a, currency::tx_receiver_old &x, const boost::serialization::version_type ver) + { + a & x.acc_addr; + } + template inline void serialize(Archive &a, currency::tx_receiver &x, const boost::serialization::version_type ver) { @@ -136,14 +157,6 @@ namespace boost a & x.m_sign; } - - template - inline void serialize(Archive &a, currency::signed_parts &x, const boost::serialization::version_type ver) - { - a & x.n_outs; - a & x.n_extras; - } - template inline void serialize(Archive &a, currency::extra_alias_entry &x, const boost::serialization::version_type ver) { @@ -151,6 +164,29 @@ namespace boost a & static_cast(x); } + template + inline void serialize(Archive &a, currency::extra_alias_entry_base_old &x, const boost::serialization::version_type ver) + { + a & x.m_address; + a & x.m_text_comment; + a & x.m_view_key; + a & x.m_sign; + } + + template + inline void serialize(Archive &a, currency::extra_alias_entry_old &x, const boost::serialization::version_type ver) + { + a & x.m_alias; + a & static_cast(x); + } + + template + inline void serialize(Archive &a, currency::signed_parts &x, const boost::serialization::version_type ver) + { + a & x.n_outs; + a & x.n_extras; + } + template inline void serialize(Archive &a, currency::extra_padding &x, const boost::serialization::version_type ver) { diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index 82d59935..b599c604 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -20,8 +20,11 @@ #define CURRENCY_MAX_BLOCK_NUMBER 500000000 #define CURRENCY_MAX_BLOCK_SIZE 500000000 // block header blob limit, never used! #define CURRENCY_TX_MAX_ALLOWED_OUTS 2000 -#define CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX 197 // addresses start with 'Z' +#define CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX 0xc5 // addresses start with 'Zx' #define CURRENCY_PUBLIC_INTEG_ADDRESS_BASE58_PREFIX 0x3678 // integrated addresses start with 'iZ' +#define CURRENCY_PUBLIC_INTEG_ADDRESS_V2_BASE58_PREFIX 0x36f8 // integrated addresses start with 'iZ' (new format) +#define CURRENCY_PUBLIC_AUDITABLE_ADDRESS_BASE58_PREFIX 0x98c8 // auditable addresses start with 'aZx' +#define CURRENCY_PUBLIC_AUDITABLE_INTEG_ADDRESS_BASE58_PREFIX 0x8a49 // auditable integrated addresses start with 'aiZX' #define CURRENCY_MINED_MONEY_UNLOCK_WINDOW 10 #define CURRENT_TRANSACTION_VERSION 1 #define CURRENT_BLOCK_MAJOR_VERSION 1 @@ -141,7 +144,8 @@ #define POS_MINIMUM_COINSTAKE_AGE 10 // blocks count -#define WALLET_FILE_SIGNATURE 0x1111012101101011LL //Bender's nightmare +#define WALLET_FILE_SIGNATURE_OLD 0x1111012101101011LL // Bender's nightmare +#define WALLET_FILE_SIGNATURE_V2 0x1111011201101011LL // another Bender's nightmare #define WALLET_FILE_MAX_BODY_SIZE 0x88888888L //2GB #define WALLET_FILE_MAX_KEYS_SIZE 10000 // #define WALLET_BRAIN_DATE_OFFSET 1543622400 @@ -216,7 +220,7 @@ #define BC_OFFERS_CURRENCY_MARKET_FILENAME "market.bin" -#define WALLET_FILE_SERIALIZATION_VERSION (CURRENCY_FORMATION_VERSION+66) +#define WALLET_FILE_SERIALIZATION_VERSION (CURRENCY_FORMATION_VERSION+67) #define CURRENT_MEMPOOL_ARCHIVE_VER (CURRENCY_FORMATION_VERSION+31) diff --git a/src/currency_core/currency_core.cpp b/src/currency_core/currency_core.cpp index 004df00e..05f2eeb9 100644 --- a/src/currency_core/currency_core.cpp +++ b/src/currency_core/currency_core.cpp @@ -493,7 +493,17 @@ namespace currency //----------------------------------------------------------------------------------------------- bool core::add_new_block(const block& b, block_verification_context& bvc) { - return m_blockchain_storage.add_new_block(b, bvc); + bool r = m_blockchain_storage.add_new_block(b, bvc); + if (r && bvc.m_added_to_main_chain) + { + uint64_t h = get_block_height(b); + auto& crc = m_blockchain_storage.get_core_runtime_config(); + if (h == crc.hard_fork_01_starts_after_height + 1) + { LOG_PRINT_GREEN("Hardfork 1 activated at height " << h, LOG_LEVEL_0); } + else if (h == crc.hard_fork_02_starts_after_height + 1) + { LOG_PRINT_GREEN("Hardfork 2 activated at height " << h, LOG_LEVEL_0); } + } + return r; } //----------------------------------------------------------------------------------------------- bool core::parse_block(const blobdata& block_blob, block& b, block_verification_context& bvc) diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index feeab899..d51b7821 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -313,6 +313,46 @@ namespace currency return string_tools::get_xtype_from_string(amount, str_amount); } //-------------------------------------------------------------------------------- + bool parse_awo_blob(const std::string& awo_blob, account_public_address& address, crypto::secret_key& view_sec_key, uint64_t& creation_timestamp) + { + std::vector parts; + boost::split(parts, awo_blob, [](char x){ return x == ':'; } ); + if (parts.size() != 2 && parts.size() != 3) + return false; + + if (!get_account_address_from_str(address, parts[0])) + return false; + + if (!address.is_auditable()) + return false; + + if (!epee::string_tools::parse_tpod_from_hex_string(parts[1], view_sec_key)) + return false; + + crypto::public_key view_pub_key = AUTO_VAL_INIT(view_pub_key); + if (!crypto::secret_key_to_public_key(view_sec_key, view_pub_key)) + return false; + + if (view_pub_key != address.view_public_key) + return false; + + creation_timestamp = 0; + if (parts.size() == 3) + { + // parse timestamp + int64_t ts = 0; + if (!epee::string_tools::string_to_num_fast(parts[2], ts)) + return false; + + if (ts < WALLET_BRAIN_DATE_OFFSET) + return false; + + creation_timestamp = ts; + } + + return true; + } + //-------------------------------------------------------------------------------- std::string print_stake_kernel_info(const stake_kernel& sk) { std::stringstream ss; @@ -421,13 +461,16 @@ namespace currency rei.m_attachment_info = ai; return true; } - bool operator()(const extra_alias_entry& ae) const { ENSURE_ONETIME(was_alias, "alias"); rei.m_alias = ae; return true; } + bool operator()(const extra_alias_entry_old& ae) const + { + return operator()(static_cast(ae)); + } bool operator()(const extra_user_data& ud) const { ENSURE_ONETIME(was_userdata, "userdata"); @@ -589,7 +632,12 @@ namespace currency //out to key txout_to_key tk; tk.key = target_keys.back(); - tk.mix_attr = tx_outs_attr; + + if (de.addr.front().is_auditable()) // check only the first address because there's only one in this branch + tk.mix_attr = CURRENCY_TO_KEY_OUT_FORCED_NO_MIX; // override mix_attr to 1 for auditable target addresses + else + tk.mix_attr = tx_outs_attr; + out.target = tk; } else @@ -1106,7 +1154,7 @@ namespace currency //fill outputs size_t output_index = tx.vout.size(); // in case of append mode we need to start output indexing from the last one + 1 std::set deriv_cache; - BOOST_FOREACH(const tx_destination_entry& dst_entr, shuffled_dsts) + for(const tx_destination_entry& dst_entr : shuffled_dsts) { CHECK_AND_ASSERT_MES(dst_entr.amount > 0, false, "Destination with wrong amount: " << dst_entr.amount); bool r = construct_tx_out(dst_entr, txkey.sec, output_index, tx, deriv_cache, tx_outs_attr); @@ -1251,7 +1299,7 @@ namespace currency { uint64_t date_offset = timestamp > WALLET_BRAIN_DATE_OFFSET ? timestamp - WALLET_BRAIN_DATE_OFFSET : 0; uint64_t weeks_count = date_offset / WALLET_BRAIN_DATE_QUANTUM; - CHECK_AND_ASSERT_THROW_MES(weeks_count < std::numeric_limits::max(), "internal error: unable to converto to uint32, val = " << weeks_count); + CHECK_AND_ASSERT_THROW_MES(weeks_count < std::numeric_limits::max(), "internal error: unable to convert to uint32, val = " << weeks_count); uint32_t weeks_count_32 = static_cast(weeks_count); return tools::mnemonic_encoding::word_by_num(weeks_count_32); @@ -2026,7 +2074,7 @@ namespace currency //--------------------------------------------------------------- bool is_showing_sender_addres(const transaction& tx) { - return have_type_in_variant_container(tx.attachment); + return have_type_in_variant_container(tx.attachment) || have_type_in_variant_container(tx.attachment); } //--------------------------------------------------------------- bool is_mixin_tx(const transaction& tx) @@ -2181,6 +2229,10 @@ namespace currency return true; } + bool operator()(const extra_alias_entry_old& ee) + { + return operator()(static_cast(ee)); + } bool operator()(const extra_user_data& ee) { tv.type = "user_data"; @@ -2214,7 +2266,13 @@ namespace currency return true; } + bool operator()(const tx_payer_old&) + { + tv.type = "payer_old"; + tv.short_view = "(encrypted)"; + return true; + } bool operator()(const tx_receiver& ee) { //const tx_payer& ee = boost::get(extra); @@ -2223,6 +2281,13 @@ namespace currency return true; } + bool operator()(const tx_receiver_old& ee) + { + tv.type = "receiver_old"; + tv.short_view = "(encrypted)"; + + return true; + } bool operator()(const tx_derivation_hint& ee) { tv.type = "derivation_hint"; @@ -2511,12 +2576,24 @@ namespace currency //----------------------------------------------------------------------- std::string get_account_address_as_str(const account_public_address& addr) { - return tools::base58::encode_addr(CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(addr)); + if (addr.flags == 0) + return tools::base58::encode_addr(CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(addr.to_old())); // classic Zano address + + if (addr.flags & ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE) + return tools::base58::encode_addr(CURRENCY_PUBLIC_AUDITABLE_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(addr)); // new format Zano address (auditable) + + return tools::base58::encode_addr(CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(addr)); // new format Zano address (normal) } //----------------------------------------------------------------------- std::string get_account_address_and_payment_id_as_str(const account_public_address& addr, const payment_id_t& payment_id) { - return tools::base58::encode_addr(CURRENCY_PUBLIC_INTEG_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(addr) + payment_id); + if (addr.flags == 0) + return tools::base58::encode_addr(CURRENCY_PUBLIC_INTEG_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(addr.to_old()) + payment_id); // classic integrated Zano address + + if (addr.flags & ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE) + return tools::base58::encode_addr(CURRENCY_PUBLIC_AUDITABLE_INTEG_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(addr) + payment_id); // new format integrated Zano address (auditable) + + return tools::base58::encode_addr(CURRENCY_PUBLIC_INTEG_ADDRESS_V2_BASE58_PREFIX, t_serializable_object_to_blob(addr) + payment_id); // new format integrated Zano address (normal) } //----------------------------------------------------------------------- bool get_account_address_from_str(account_public_address& addr, const std::string& str) @@ -2527,7 +2604,7 @@ namespace currency //----------------------------------------------------------------------- bool get_account_address_and_payment_id_from_str(account_public_address& addr, payment_id_t& payment_id, const std::string& str) { - static const size_t addr_blob_size = sizeof(account_public_address); + payment_id.clear(); blobdata blob; uint64_t prefix; if (!tools::base58::decode_addr(str, prefix, blob)) @@ -2536,42 +2613,88 @@ namespace currency return false; } - if (blob.size() < addr_blob_size) + if (blob.size() < sizeof(account_public_address_old)) { - LOG_PRINT_L1("Address " << str << " has invalid format: blob size is " << blob.size() << " which is less, than expected " << addr_blob_size); + LOG_PRINT_L1("Address " << str << " has invalid format: blob size is " << blob.size() << " which is less, than expected " << sizeof(account_public_address_old)); return false; } - if (blob.size() > addr_blob_size + BC_PAYMENT_ID_SERVICE_SIZE_MAX) + if (blob.size() > sizeof(account_public_address) + BC_PAYMENT_ID_SERVICE_SIZE_MAX) { - LOG_PRINT_L1("Address " << str << " has invalid format: blob size is " << blob.size() << " which is more, than allowed " << addr_blob_size + BC_PAYMENT_ID_SERVICE_SIZE_MAX); + LOG_PRINT_L1("Address " << str << " has invalid format: blob size is " << blob.size() << " which is more, than allowed " << sizeof(account_public_address) + BC_PAYMENT_ID_SERVICE_SIZE_MAX); return false; } + bool parse_as_old_format = false; + if (prefix == CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX) { - // nothing + // normal address + if (blob.size() == sizeof(account_public_address_old)) + { + parse_as_old_format = true; + } + else if (blob.size() == sizeof(account_public_address)) + { + parse_as_old_format = false; + } + else + { + LOG_PRINT_L1("Account public address cannot be parsed from \"" << str << "\", incorrect size"); + return false; + } + } + else if (prefix == CURRENCY_PUBLIC_AUDITABLE_ADDRESS_BASE58_PREFIX) + { + // auditable, parse as new format + parse_as_old_format = false; } else if (prefix == CURRENCY_PUBLIC_INTEG_ADDRESS_BASE58_PREFIX) { - payment_id = blob.substr(addr_blob_size); - blob = blob.substr(0, addr_blob_size); + payment_id = blob.substr(sizeof(account_public_address_old)); + blob = blob.substr(0, sizeof(account_public_address_old)); + parse_as_old_format = true; + } + else if (prefix == CURRENCY_PUBLIC_AUDITABLE_INTEG_ADDRESS_BASE58_PREFIX || prefix == CURRENCY_PUBLIC_INTEG_ADDRESS_V2_BASE58_PREFIX) + { + payment_id = blob.substr(sizeof(account_public_address)); + blob = blob.substr(0, sizeof(account_public_address)); + parse_as_old_format = false; } else { - LOG_PRINT_L1("Address " << str << " has wrong prefix " << prefix << ", expected " << CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX << " or " << CURRENCY_PUBLIC_INTEG_ADDRESS_BASE58_PREFIX); + LOG_PRINT_L1("Address " << str << " has wrong prefix " << prefix); return false; } - if (!::serialization::parse_binary(blob, addr)) + if (parse_as_old_format) { - LOG_PRINT_L1("Account public address keys can't be parsed for address \"" << str << "\""); + account_public_address_old addr_old = AUTO_VAL_INIT(addr_old); + if (!::serialization::parse_binary(blob, addr_old)) + { + LOG_PRINT_L1("Account public address (old) cannot be parsed from \"" << str << "\""); + return false; + } + addr = account_public_address::from_old(addr_old); + } + else + { + if (!::serialization::parse_binary(blob, addr)) + { + LOG_PRINT_L1("Account public address cannot be parsed from \"" << str << "\""); + return false; + } + } + + if (payment_id.size() > BC_PAYMENT_ID_SERVICE_SIZE_MAX) + { + LOG_PRINT_L1("Failed to parse address from \"" << str << "\": payment id size exceeded: " << payment_id.size()); return false; } if (!crypto::check_key(addr.spend_public_key) || !crypto::check_key(addr.view_public_key)) { - LOG_PRINT_L1("Failed to validate address keys for address \"" << str << "\""); + LOG_PRINT_L1("Failed to validate address keys for public address \"" << str << "\""); return false; } diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 48441033..598b2cb3 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -27,6 +27,7 @@ #include "blockchain_storage_basic.h" #include "currency_format_utils_blocks.h" #include "currency_format_utils_transactions.h" +#include "core_runtime_config.h" // ------ get_tx_type_definition ------------- @@ -238,6 +239,7 @@ namespace currency bool check_inputs_types_supported(const transaction& tx); bool check_outs_valid(const transaction& tx); bool parse_amount(uint64_t& amount, const std::string& str_amount); + bool parse_awo_blob(const std::string& awo_blob, account_public_address& address, crypto::secret_key& view_sec_key, uint64_t& creation_timestamp); @@ -589,6 +591,51 @@ namespace currency return boost::apply_visitor(input_amount_getter(), v); } //--------------------------------------------------------------- + template + void create_and_add_tx_payer_to_container_from_address(container_t& container, const account_public_address& addr, uint64_t top_block_height, const core_runtime_config& crc) + { + if (top_block_height > crc.hard_fork_02_starts_after_height) + { + // after hardfork 2 + tx_payer result = AUTO_VAL_INIT(result); + result.acc_addr = addr; + container.push_back(result); + } + else + { + // before hardfork 2 -- add only if addr is not auditable + if (!addr.is_auditable()) + { + tx_payer_old result = AUTO_VAL_INIT(result); + result.acc_addr = addr.to_old(); + container.push_back(result); + } + } + } + //--------------------------------------------------------------- + template + void create_and_add_tx_receiver_to_container_from_address(container_t& container, const account_public_address& addr, uint64_t top_block_height, const core_runtime_config& crc) + { + if (top_block_height > crc.hard_fork_02_starts_after_height) + { + // after hardfork 2 + tx_receiver result = AUTO_VAL_INIT(result); + result.acc_addr = addr; + container.push_back(result); + } + else + { + // before hardfork 2 -- add only if addr is not auditable + if (!addr.is_auditable()) + { + tx_receiver_old result = AUTO_VAL_INIT(result); + result.acc_addr = addr.to_old(); + container.push_back(result); + } + } + } + //--------------------------------------------------------------- + //--------------------------------------------------------------- std::ostream& operator <<(std::ostream& o, const ref_by_id& r); //--------------------------------------------------------------- #ifndef ANDROID_BUILD diff --git a/src/currency_core/currency_format_utils_abstract.h b/src/currency_core/currency_format_utils_abstract.h index d6a48212..5f1c7cf6 100644 --- a/src/currency_core/currency_format_utils_abstract.h +++ b/src/currency_core/currency_format_utils_abstract.h @@ -107,8 +107,9 @@ namespace currency return false; } //--------------------------------------------------------------- + // callback should return true to continue iterating through the container template - bool handle_2_alternative_types_in_variant_container(const container_t& container, callback_t& cb) + bool handle_2_alternative_types_in_variant_container(const container_t& container, callback_t cb) { bool found = false; for (auto& item : container) @@ -122,7 +123,7 @@ namespace currency else if (item.type() == typeid(B)) { found = true; - if (!cb(boost::get(item))) + if (!cb(boost::get(item))) break; } } diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 03553604..8e4a5f33 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -1587,7 +1587,7 @@ QString MainWindow::restore_wallet(const QString& param) //return que_call2("restore_wallet", param, [this](const view::restore_wallet_request& owd, view::api_response& ar){ PREPARE_ARG_FROM_JSON(view::restore_wallet_request, owd); PREPARE_RESPONSE(view::open_wallet_response, ar); - ar.error_code = m_backend.restore_wallet(epee::string_encoding::utf8_to_wstring(owd.path), owd.pass, owd.restore_key, ar.response_data); + ar.error_code = m_backend.restore_wallet(epee::string_encoding::utf8_to_wstring(owd.path), owd.pass, owd.restore_key, owd.auditable_watch_only, ar.response_data); return MAKE_RESPONSE(ar); CATCH_ENTRY_FAIL_API_RESPONCE(); } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 9ff9fdb8..45c1799f 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -40,6 +40,7 @@ namespace { const command_line::arg_descriptor arg_wallet_file = {"wallet-file", "Use wallet ", ""}; const command_line::arg_descriptor arg_generate_new_wallet = {"generate-new-wallet", "Generate new wallet and save it to or
.wallet by default", ""}; + const command_line::arg_descriptor arg_generate_new_auditable_wallet = {"generate-new-auditable-wallet", "Generate new auditable wallet and store it to ", ""}; const command_line::arg_descriptor arg_daemon_address = {"daemon-address", "Use daemon instance at :", ""}; const command_line::arg_descriptor arg_daemon_host = {"daemon-host", "Use daemon instance at host instead of localhost", ""}; const command_line::arg_descriptor arg_password = {"password", "Wallet password", "", true}; @@ -51,6 +52,7 @@ namespace const command_line::arg_descriptor arg_do_pos_mining = { "do-pos-mining", "Do PoS mining", false, false }; const command_line::arg_descriptor arg_pos_mining_reward_address = { "pos-mining-reward-address", "Block reward will be sent to the giving address if specified", "" }; const command_line::arg_descriptor arg_restore_wallet = { "restore-wallet", "Restore wallet from the seed phrase and save it to ", "" }; + const command_line::arg_descriptor arg_restore_awo_wallet = { "restore-awo-wallet", "Restore auditable watch-only wallet from address and view key. Use \"address:viewkey\" as argument", "" }; const command_line::arg_descriptor arg_offline_mode = { "offline-mode", "Don't connect to daemon, work offline (for cold-signing process)", false, true }; const command_line::arg_descriptor< std::vector > arg_command = {"command", ""}; @@ -219,6 +221,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), "Get transaction one-time secret key (r) for a given "); + m_cmd_binder.set_handler("awo_blob", boost::bind(&simple_wallet::awo_blob, this, _1), "For auditable wallets: prints auditable watch-only blob for wallet's audit by a third party"); + m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), "Save wallet synchronized data"); m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), "save_watch_only - save as watch-only wallet file."); @@ -270,9 +274,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } - if (m_wallet_file.empty() && m_generate_new.empty() && m_restore_wallet.empty()) + if (m_wallet_file.empty() && m_generate_new.empty() && m_restore_wallet.empty() && m_restore_awo_wallet.empty() && m_generate_new_aw.empty()) { - fail_msg_writer() << "you must specify --wallet-file, --generate-new-wallet or --restore-wallet"; + fail_msg_writer() << "you must specify --wallet-file, --generate-new-wallet, --generate-new-auditable-wallet, --restore-wallet or --restore-awo-wallet"; return false; } @@ -311,9 +315,13 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (!m_generate_new.empty()) { - bool r = new_wallet(m_generate_new, pwd_container.password()); - CHECK_AND_ASSERT_MES(r, false, "account creation failed"); - + bool r = new_wallet(m_generate_new, pwd_container.password(), false); + CHECK_AND_ASSERT_MES(r, false, "failed to create new wallet"); + } + else if (!m_generate_new_aw.empty()) + { + bool r = new_wallet(m_generate_new_aw, pwd_container.password(), true); + CHECK_AND_ASSERT_MES(r, false, "failed to create new auditable wallet"); } else if (!m_restore_wallet.empty()) { @@ -330,7 +338,25 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } - bool r = restore_wallet(m_restore_wallet, restore_seed_container.password(), pwd_container.password()); + bool r = restore_wallet(m_restore_wallet, restore_seed_container.password(), pwd_container.password(), false); + CHECK_AND_ASSERT_MES(r, false, "wallet restoring failed"); + } + else if (!m_restore_awo_wallet.empty()) + { + if (boost::filesystem::exists(m_restore_awo_wallet)) + { + fail_msg_writer() << "file " << m_restore_awo_wallet << " already exists"; + return false; + } + + tools::password_container restore_addr_and_viewkey_container; + if (!restore_addr_and_viewkey_container.read_password("please, enter wallet auditable address, viewkey and timestamp, separated by a colon (\"address:viewkey:timestamp\"):\n")) + { + fail_msg_writer() << "failed to read seed phrase"; + return false; + } + + bool r = restore_wallet(m_restore_awo_wallet, restore_addr_and_viewkey_container.password(), pwd_container.password(), true); CHECK_AND_ASSERT_MES(r, false, "wallet restoring failed"); } else @@ -354,12 +380,14 @@ void simple_wallet::handle_command_line(const boost::program_options::variables_ { m_wallet_file = command_line::get_arg(vm, arg_wallet_file); m_generate_new = command_line::get_arg(vm, arg_generate_new_wallet); + m_generate_new_aw = command_line::get_arg(vm, arg_generate_new_auditable_wallet); m_daemon_address = command_line::get_arg(vm, arg_daemon_address); m_daemon_host = command_line::get_arg(vm, arg_daemon_host); m_daemon_port = command_line::get_arg(vm, arg_daemon_port); m_do_not_set_date = command_line::get_arg(vm, arg_dont_set_date); m_do_pos_mining = command_line::get_arg(vm, arg_do_pos_mining); m_restore_wallet = command_line::get_arg(vm, arg_restore_wallet); + m_restore_awo_wallet = command_line::get_arg(vm, arg_restore_awo_wallet); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::try_connect_to_daemon() @@ -374,7 +402,7 @@ bool simple_wallet::try_connect_to_daemon() return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::new_wallet(const string &wallet_file, const std::string& password) +bool simple_wallet::new_wallet(const string &wallet_file, const std::string& password, bool create_auditable_wallet) { m_wallet_file = wallet_file; @@ -383,10 +411,10 @@ bool simple_wallet::new_wallet(const string &wallet_file, const std::string& pas m_wallet->set_do_rise_transfer(false); try { - m_wallet->generate(epee::string_encoding::utf8_to_wstring(m_wallet_file), password); - message_writer(epee::log_space::console_color_white, true) << "Generated new wallet: " << m_wallet->get_account().get_public_address_str(); + m_wallet->generate(epee::string_encoding::utf8_to_wstring(m_wallet_file), password, create_auditable_wallet); + message_writer(epee::log_space::console_color_white, true) << "Generated new " << (create_auditable_wallet ? "AUDITABLE" : "") << " wallet: " << m_wallet->get_account().get_public_address_str(); std::cout << "view key: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().view_secret_key) << std::endl << std::flush; - if(m_do_not_set_date) + if (m_do_not_set_date) m_wallet->reset_creation_time(0); if (m_print_brain_wallet) @@ -407,16 +435,11 @@ bool simple_wallet::new_wallet(const string &wallet_file, const std::string& pas success_msg_writer() << "**********************************************************************\n" << "Your wallet has been generated.\n" << - "To start synchronizing with the daemon use \"refresh\" command.\n" << - "Use \"help\" command to see the list of available commands.\n" << - "Always use \"exit\" command when closing simplewallet to save\n" << - "current session's state. Otherwise, you will possibly need to synchronize \n" << - "your wallet again. Your wallet key is NOT under risk anyway.\n" << "**********************************************************************"; return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::restore_wallet(const std::string &wallet_file, const std::string &restore_seed, const std::string& password) +bool simple_wallet::restore_wallet(const std::string &wallet_file, const std::string &seed_or_awo_blob, const std::string& password, bool auditable_watch_only) { m_wallet_file = wallet_file; @@ -425,15 +448,24 @@ bool simple_wallet::restore_wallet(const std::string &wallet_file, const std::st m_wallet->set_do_rise_transfer(false); try { - m_wallet->restore(epee::string_encoding::utf8_to_wstring(wallet_file), password, restore_seed); - message_writer(epee::log_space::console_color_white, true) << "Wallet restored: " << m_wallet->get_account().get_public_address_str(); - std::cout << "view key: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().view_secret_key) << std::endl << std::flush; + if (auditable_watch_only) + { + m_wallet->restore(epee::string_encoding::utf8_to_wstring(wallet_file), password, seed_or_awo_blob, true); + message_writer(epee::log_space::console_color_white, true) << "Auditable watch-only wallet restored: " << m_wallet->get_account().get_public_address_str(); + } + else + { + // normal wallet + m_wallet->restore(epee::string_encoding::utf8_to_wstring(wallet_file), password, seed_or_awo_blob, false); + message_writer(epee::log_space::console_color_white, true) << "Wallet restored: " << m_wallet->get_account().get_public_address_str(); + std::cout << "view key: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().view_secret_key) << std::endl << std::flush; + } if (m_do_not_set_date) m_wallet->reset_creation_time(0); } catch (const std::exception& e) { - fail_msg_writer() << "failed to restore wallet: " << e.what(); + fail_msg_writer() << "failed to restore wallet, check your " << (auditable_watch_only ? "awo blob!" : "seed phrase!") << ENDL << e.what(); return false; } @@ -462,7 +494,7 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa try { m_wallet->load(epee::string_encoding::utf8_to_wstring(m_wallet_file), password); - message_writer(epee::log_space::console_color_white, true) << "Opened" << (m_wallet->is_watch_only() ? " watch-only" : "") << " wallet: " << m_wallet->get_account().get_public_address_str(); + message_writer(epee::log_space::console_color_white, true) << "Opened" << (m_wallet->is_auditable() ? " auditable" : "") << (m_wallet->is_watch_only() ? " watch-only" : "") << " wallet: " << m_wallet->get_account().get_public_address_str(); if (m_print_brain_wallet) std::cout << "Brain wallet: " << m_wallet->get_account().get_restore_braindata() << std::endl << std::flush; @@ -592,22 +624,40 @@ void simple_wallet::on_new_block(uint64_t height, const currency::block& block) m_refresh_progress_reporter.update(height, false); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_received(uint64_t height, const currency::transaction& tx, size_t out_index) +std::string print_money_trailing_zeros_replaced_with_spaces(uint64_t amount) { - message_writer(epee::log_space::console_color_green, false) << - "Height " << height << - ", transaction " << get_transaction_hash(tx) << - ", received " << print_money(tx.vout[out_index].amount); - m_refresh_progress_reporter.update(height, true); + std::string s = print_money(amount); + size_t p = s.find_last_not_of('0'); + if (p != std::string::npos) + { + if (s[p] == '.') + ++p; + size_t l = s.length() - p - 1; + return s.replace(p + 1, l, l, ' '); + } + return s; } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_spent(uint64_t height, const currency::transaction& in_tx, size_t out_index, const currency::transaction& spend_tx) +void simple_wallet::on_transfer2(const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) { - message_writer(epee::log_space::console_color_magenta, false) << - "Height " << height << - ", transaction " << get_transaction_hash(spend_tx) << - ", spent " << print_money(in_tx.vout[out_index].amount); - m_refresh_progress_reporter.update(height, true); + epee::log_space::console_colors color = wti.is_income ? epee::log_space::console_color_green : epee::log_space::console_color_magenta; + message_writer(color, false) << + "height " << wti.height << + ", tx " << wti.tx_hash << + " " << std::right << std::setw(18) << print_money_trailing_zeros_replaced_with_spaces(wti.amount) << (wti.is_income ? " received," : " spent, ") << + " balance: " << print_money_brief(balance); + m_refresh_progress_reporter.update(wti.height, true); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_message(i_wallet2_callback::message_severity severity, const std::string& m) +{ + epee::log_space::console_colors color = epee::log_space::console_color_white; + if (severity == i_wallet2_callback::ms_red) + color = epee::log_space::console_color_red; + else if (severity == i_wallet2_callback::ms_yellow) + color = epee::log_space::console_color_yellow; + + message_writer(color, true, std::string()) << m; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh(const std::vector& args) @@ -1322,7 +1372,7 @@ bool simple_wallet::print_address(const std::vector &args/* = std:: bool simple_wallet::show_seed(const std::vector &args) { success_msg_writer() << "Here's your wallet's seed phrase. Write it down and keep in a safe place."; - success_msg_writer(true) << "Anyone who knows the following 25 words can access your wallet:"; + success_msg_writer(true) << "Anyone who knows the following 26 words can access your wallet:"; std::cout << m_wallet->get_account().get_restore_braindata() << std::endl << std::flush; return true; } @@ -1452,6 +1502,19 @@ bool simple_wallet::get_tx_key(const std::vector &args_) } } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::awo_blob(const std::vector &args_) +{ + if (!m_wallet->is_auditable()) + { + fail_msg_writer() << "this command is allowed for auditable wallets only"; + return true; + } + + success_msg_writer() << "Auditable watch-only blob for this wallet is:"; + std::cout << m_wallet->get_account().get_awo_blob() << ENDL; + return true; +} +//---------------------------------------------------------------------------------------------------- void simple_wallet::set_offline_mode(bool offline_mode) { if (offline_mode && !m_offline_mode) @@ -1702,6 +1765,7 @@ int main(int argc, char* argv[]) po::options_description desc_params("Wallet options"); command_line::add_arg(desc_params, arg_wallet_file); command_line::add_arg(desc_params, arg_generate_new_wallet); + command_line::add_arg(desc_params, arg_generate_new_auditable_wallet); command_line::add_arg(desc_params, arg_password); command_line::add_arg(desc_params, arg_daemon_address); command_line::add_arg(desc_params, arg_daemon_host); @@ -1714,6 +1778,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_do_pos_mining); command_line::add_arg(desc_params, arg_pos_mining_reward_address); command_line::add_arg(desc_params, arg_restore_wallet); + command_line::add_arg(desc_params, arg_restore_awo_wallet); command_line::add_arg(desc_params, arg_offline_mode); command_line::add_arg(desc_params, command_line::arg_log_file); command_line::add_arg(desc_params, command_line::arg_log_level); @@ -1848,7 +1913,7 @@ int main(int argc, char* argv[]) { LOG_PRINT_L0("Initializing wallet..."); wal.init(daemon_address); - if (command_line::get_arg(vm, arg_generate_new_wallet).size()) + if (command_line::get_arg(vm, arg_generate_new_wallet).size() || command_line::get_arg(vm, arg_generate_new_auditable_wallet).size()) return EXIT_FAILURE; if (!offline_mode) @@ -1903,7 +1968,7 @@ int main(int argc, char* argv[]) sw->set_offline_mode(offline_mode); r = sw->init(vm); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet"); - if (command_line::get_arg(vm, arg_generate_new_wallet).size()) + if (command_line::get_arg(vm, arg_generate_new_wallet).size() || command_line::get_arg(vm, arg_generate_new_auditable_wallet).size()) return EXIT_FAILURE; diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 8c57697b..7808333b 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -44,9 +44,9 @@ namespace currency bool run_console_handler(); - bool new_wallet(const std::string &wallet_file, const std::string& password); + bool new_wallet(const std::string &wallet_file, const std::string& password, bool create_auditable_wallet); bool open_wallet(const std::string &wallet_file, const std::string& password); - bool restore_wallet(const std::string &wallet_file, const std::string &restore_seed, const std::string& password); + bool restore_wallet(const std::string &wallet_file, const std::string &seed_or_awo_blob, const std::string& password, bool auditable_watch_only); bool close_wallet(); bool help(const std::vector &args = std::vector()); @@ -81,6 +81,7 @@ namespace currency bool enable_console_logger(const std::vector &args); bool integrated_address(const std::vector &args); bool get_tx_key(const std::vector &args_); + bool awo_blob(const std::vector &args_); bool save_watch_only(const std::vector &args); bool sign_transfer(const std::vector &args); bool submit_transfer(const std::vector &args); @@ -93,9 +94,10 @@ namespace currency bool try_connect_to_daemon(); //----------------- i_wallet2_callback --------------------- - virtual void on_new_block(uint64_t height, const currency::block& block); - virtual void on_money_received(uint64_t height, const currency::transaction& tx, size_t out_index); - virtual void on_money_spent(uint64_t height, const currency::transaction& in_tx, size_t out_index, const currency::transaction& spend_tx); + virtual void on_new_block(uint64_t height, const currency::block& block) override; + virtual void on_transfer2(const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) override; + virtual void on_message(i_wallet2_callback::message_severity severity, const std::string& m) override; + //---------------------------------------------------------- friend class refresh_progress_reporter_t; @@ -153,6 +155,7 @@ namespace currency private: std::string m_wallet_file; std::string m_generate_new; + std::string m_generate_new_aw; std::string m_import_path; std::string m_daemon_address; @@ -164,6 +167,7 @@ namespace currency bool m_do_pos_mining; bool m_offline_mode; std::string m_restore_wallet; + std::string m_restore_awo_wallet; epee::console_handlers_binder m_cmd_binder; diff --git a/src/wallet/plain_wallet_api.cpp b/src/wallet/plain_wallet_api.cpp index 1f1b2b6d..ddadfe1c 100644 --- a/src/wallet/plain_wallet_api.cpp +++ b/src/wallet/plain_wallet_api.cpp @@ -365,7 +365,7 @@ namespace plain_wallet std::string full_path = get_wallets_folder() + path; epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); - std::string rsp = inst_ptr->gwm.restore_wallet(epee::string_encoding::convert_to_unicode(full_path), password, seed, ok_response.result); + std::string rsp = inst_ptr->gwm.restore_wallet(epee::string_encoding::convert_to_unicode(full_path), password, seed, false, ok_response.result); if (rsp == API_RETURN_CODE_OK || rsp == API_RETURN_CODE_FILE_RESTORED) { if (rsp == API_RETURN_CODE_FILE_RESTORED) diff --git a/src/wallet/view_iface.h b/src/wallet/view_iface.h index 35b20d4f..835bade9 100644 --- a/src/wallet/view_iface.h +++ b/src/wallet/view_iface.h @@ -213,16 +213,20 @@ public: uint64_t balance; uint64_t mined_total; std::string address; - std::string tracking_hey; + std::string view_sec_key; std::string path; + bool is_auditable; + bool is_watch_only; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(unlocked_balance) KV_SERIALIZE(balance) KV_SERIALIZE(mined_total) KV_SERIALIZE(address) - KV_SERIALIZE(tracking_hey) + KV_SERIALIZE(view_sec_key) KV_SERIALIZE(path) + KV_SERIALIZE(is_auditable); + KV_SERIALIZE(is_watch_only); END_KV_SERIALIZE_MAP() }; @@ -416,11 +420,13 @@ public: std::string pass; std::string path; std::string restore_key; + bool auditable_watch_only; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(pass) KV_SERIALIZE(path) KV_SERIALIZE(restore_key) + KV_SERIALIZE(auditable_watch_only) END_KV_SERIALIZE_MAP() }; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f9e2eab5..a211092c 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -263,11 +263,60 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t { if (in.type() == typeid(currency::txin_to_key)) { - auto it = m_key_images.find(boost::get(in).k_image); - if (it != m_key_images.end()) + const currency::txin_to_key& intk = boost::get(in); + + // check if this input spends our output + transfer_details* p_td = nullptr; + uint64_t tid = UINT64_MAX; + + if (is_auditable() && is_watch_only()) { - tx_money_spent_in_ins += boost::get(in).amount; - transfer_details& td = m_transfers[it->second]; + // auditable wallet + // try to find a reference among own UTXOs + std::vector abs_key_offsets = relative_output_offsets_to_absolute(intk.key_offsets); // potential speed-up: don't convert to abs offsets as we interested only in direct spends for auditable wallets. Now it's kind a bit paranoid. + for(auto v : abs_key_offsets) + { + if (v.type() != typeid(uint64_t)) + continue; + uint64_t gindex = boost::get(v); + auto it = m_amount_gindex_to_transfer_id.find(std::make_pair(intk.amount, gindex)); + if (it != m_amount_gindex_to_transfer_id.end()) + { + tid = it->second; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tid < m_transfers.size(), "invalid tid: " << tid << ", ref from input with amount: " << intk.amount << ", gindex: " << gindex); + auto& td = m_transfers[it->second]; + if (intk.key_offsets.size() != 1) + { + // own output was used in non-direct transaction + // the core should not allow this to happen, the only way it may happen - mixing in own output that was sent without mix_attr == 1 + // log strange situation + std::stringstream ss; + ss << "own transfer tid=" << tid << " tx=" << td.tx_hash() << " mix_attr=" << td.mix_attr() << ", is referenced by a transaction with mixins, ref from input with amount: " << intk.amount << ", gindex: " << gindex; + WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + continue; + } + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!td.is_spent(), "transfer is spent, tid: " << tid << ", ref from input with amount: " << intk.amount << ", gindex: " << gindex); + // own output is spent, handle it + break; + } + } + } + else + { + // wallet with spend secret key -- we can calculate own key images and then search by them + auto it = m_key_images.find(intk.k_image); + if (it != m_key_images.end()) + { + tid = it->second; + } + } + + if (tid != UINT64_MAX) + { + tx_money_spent_in_ins += intk.amount; + transfer_details& td = m_transfers[tid]; uint32_t flags_before = td.m_flags; td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; td.m_spent_height = height; @@ -275,10 +324,10 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t is_derived_from_coinbase = true; else is_derived_from_coinbase = false; - WLT_LOG_L0("Spent key out, transfer #" << it->second << ", amount: " << print_money(m_transfers[it->second].amount()) << ", with tx: " << get_transaction_hash(tx) << ", at height " << height << + WLT_LOG_L0("Spent key out, transfer #" << tid << ", amount: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx) << ", at height " << height << "; flags: " << flags_before << " -> " << td.m_flags); mtd.spent_indices.push_back(i); - remove_transfer_from_expiration_list(it->second); + remove_transfer_from_expiration_list(tid); } } else if (in.type() == typeid(currency::txin_multisig)) @@ -341,18 +390,21 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t crypto::key_image ki = currency::null_ki; if (m_watch_only) { - // don't have spend secret key, so we unable to calculate key image for an output - // look it up in special container instead - auto it = m_pending_key_images.find(otk.key); - if (it != m_pending_key_images.end()) + if (!is_auditable()) { - ki = it->second; - WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << otk.key); - } - else - { - ki = currency::null_ki; - WLT_LOG_L1("can't find pending key image by out pub key: " << otk.key << ", key image temporarily set to null"); + // don't have spend secret key, so we unable to calculate key image for an output + // look it up in special container instead + auto it = m_pending_key_images.find(otk.key); + if (it != m_pending_key_images.end()) + { + ki = it->second; + WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << otk.key); + } + else + { + ki = currency::null_ki; + WLT_LOG_L1("can't find pending key image by out pub key: " << otk.key << ", key image temporarily set to null"); + } } } else @@ -365,20 +417,37 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t if (ki != currency::null_ki) { + // make sure calculated key image for this own output has not been seen before auto it = m_key_images.find(ki); if (it != m_key_images.end()) { WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second < m_transfers.size(), "m_key_images entry has wrong m_transfers index, it->second: " << it->second << ", m_transfers.size(): " << m_transfers.size()); const transfer_details& local_td = m_transfers[it->second]; - WLT_LOG_YELLOW("tx " << get_transaction_hash(tx) << " @ block " << height << " has output #" << o << " with key image " << ki << " that has already been seen in output #" << + std::stringstream ss; + ss << "tx " << get_transaction_hash(tx) << " @ block " << height << " has output #" << o << " with key image " << ki << " that has already been seen in output #" << local_td.m_internal_output_index << " in tx " << get_transaction_hash(local_td.m_ptx_wallet_info->m_tx) << " @ block " << local_td.m_spent_height << - ". This output can't ever be spent and will be skipped.", LOG_LEVEL_0); + ". This output can't ever be spent and will be skipped."; + WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tx_money_got_in_outs >= tx.vout[o].amount, "tx_money_got_in_outs: " << tx_money_got_in_outs << ", tx.vout[o].amount:" << tx.vout[o].amount); tx_money_got_in_outs -= tx.vout[o].amount; continue; // skip the output } } + if (is_auditable() && otk.mix_attr != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) + { + std::stringstream ss; + ss << "output #" << o << " from tx " << get_transaction_hash(tx) << " with amount " << print_money_brief(tx.vout[o].amount) + << " is targeted to this auditable wallet and has INCORRECT mix_attr = " << (uint64_t)otk.mix_attr << ". Output IGNORED."; + WLT_LOG_RED(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_red, ss.str()); + tx_money_got_in_outs -= tx.vout[o].amount; + continue; // skip the output + } + mtd.receive_indices.push_back(o); m_transfers.push_back(boost::value_initialized()); @@ -399,6 +468,11 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t if (td.m_key_image != currency::null_ki) m_key_images[td.m_key_image] = transfer_index; add_transfer_to_transfers_cache(tx.vout[o].amount, transfer_index); + uint64_t amount = tx.vout[o].amount; + + auto amount_gindex_pair = std::make_pair(amount, td.m_global_output_index); + WLT_CHECK_AND_ASSERT_MES_NO_RET(m_amount_gindex_to_transfer_id.count(amount_gindex_pair) == 0, "update m_amount_gindex_to_transfer_id: amount " << amount << ", gindex " << td.m_global_output_index << " already exists"); + m_amount_gindex_to_transfer_id[amount_gindex_pair] = transfer_index; if (max_out_unlock_time < get_tx_unlock_time(tx, o)) max_out_unlock_time = get_tx_unlock_time(tx, o); @@ -469,20 +543,25 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t void wallet2::prepare_wti_decrypted_attachments(wallet_public::wallet_transfer_info& wti, const std::vector& decrypted_att) { PROFILE_FUNC("wallet2::prepare_wti_decrypted_attachments"); - tx_payer tp = AUTO_VAL_INIT(tp); - wti.show_sender = get_type_in_variant_container(decrypted_att, tp); if (wti.is_income) { - if(wti.show_sender) - wti.remote_addresses.push_back(currency::get_account_address_as_str(tp.acc_addr)); + account_public_address sender_address = AUTO_VAL_INIT(sender_address); + wti.show_sender = handle_2_alternative_types_in_variant_container(decrypted_att, [&](const tx_payer& p) { sender_address = p.acc_addr; return false; /* <- continue? */ } ); + if (wti.show_sender) + wti.remote_addresses.push_back(currency::get_account_address_as_str(sender_address)); } else { - //TODO: actually recipients could be more then one, handle it in future - tx_receiver tr = AUTO_VAL_INIT(tr); - if (!wti.remote_addresses.size() && get_type_in_variant_container(decrypted_att, tr)) - wti.remote_addresses.push_back(currency::get_account_address_as_str(tr.acc_addr)); + if (wti.remote_addresses.empty()) + { + handle_2_alternative_types_in_variant_container(decrypted_att, [&](const tx_receiver& p) { + std::string addr_str = currency::get_account_address_as_str(p.acc_addr); + wti.remote_addresses.push_back(addr_str); + LOG_PRINT_YELLOW("prepare_wti_decrypted_attachments, income=false, wti.amount = " << print_money_brief(wti.amount) << ", rem. addr = " << addr_str, LOG_LEVEL_0); + return true; // continue iterating through the container + }); + } } currency::tx_comment cm; @@ -1163,7 +1242,6 @@ void wallet2::pull_blocks(size_t& blocks_added, std::atomic& stop) bool r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DIRECT(req, res); if (!r) throw error::no_connection_to_daemon(LOCATION_STR, "getblocks.bin"); - WLT_LOG_L0("COMMAND_RPC_GET_BLOCKS_DIRECT: " << epee::serialization::store_t_to_json(req)); if (res.status == API_RETURN_CODE_GENESIS_MISMATCH) { WLT_LOG_MAGENTA("Reseting genesis block...", LOG_LEVEL_0); @@ -1799,6 +1877,17 @@ uint64_t wallet2::detach_from_block_ids(uint64_t including_height) return blocks_detached; } //---------------------------------------------------------------------------------------------------- +void wallet2::remove_transfer_from_amount_gindex_map(uint64_t tid) +{ + for (auto it = m_amount_gindex_to_transfer_id.begin(); it != m_amount_gindex_to_transfer_id.end(); ) + { + if (it->second == tid) + it = m_amount_gindex_to_transfer_id.erase(it); + else + ++it; + } +} +//---------------------------------------------------------------------------------------------------- void wallet2::detach_blockchain(uint64_t including_height) { WLT_LOG_L0("Detaching blockchain on height " << including_height); @@ -1817,6 +1906,7 @@ void wallet2::detach_blockchain(uint64_t including_height) WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it_ki != m_key_images.end(), "key image " << m_transfers[i].m_key_image << " not found"); WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_transfers[i].m_ptx_wallet_info->m_block_height >= including_height, "transfer #" << i << " block height is less than " << including_height); m_key_images.erase(it_ki); + remove_transfer_from_amount_gindex_map(i); ++transfers_detached; } m_transfers.erase(it, m_transfers.end()); @@ -1902,6 +1992,7 @@ bool wallet2::reset_all() //m_blockchain.clear(); m_chain.clear(); m_transfers.clear(); + m_amount_gindex_to_transfer_id.clear(); m_key_images.clear(); // m_pending_key_images is not cleared intentionally m_unconfirmed_in_transfers.clear(); @@ -2035,20 +2126,27 @@ bool wallet2::prepare_file_names(const std::wstring& file_path) return true; } //---------------------------------------------------------------------------------------------------- -void wallet2::load_keys(const std::string& buff, const std::string& password) +void wallet2::load_keys(const std::string& buff, const std::string& password, uint64_t file_signature) { - wallet2::keys_file_data keys_file_data; -// std::string buf; -// bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf); -// CHECK_AND_THROW_WALLET_EX(!r, error::file_read_error, keys_file_name); - bool r = ::serialization::parse_binary(buff, keys_file_data); + bool r = false; + wallet2::keys_file_data kf_data = AUTO_VAL_INIT(kf_data); + if (file_signature == WALLET_FILE_SIGNATURE_OLD) + { + wallet2::keys_file_data_old kf_data_old; + r = ::serialization::parse_binary(buff, kf_data_old); + kf_data = wallet2::keys_file_data::from_old(kf_data_old); + } + else if (file_signature == WALLET_FILE_SIGNATURE_V2) + { + r = ::serialization::parse_binary(buff, kf_data); + } THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "internal error: failed to deserialize"); crypto::chacha8_key key; crypto::generate_chacha8_key(password, key); std::string account_data; - account_data.resize(keys_file_data.account_data.size()); - crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + account_data.resize(kf_data.account_data.size()); + crypto::chacha8(kf_data.account_data.data(), kf_data.account_data.size(), key, kf_data.iv, &account_data[0]); const currency::account_keys& keys = m_account.get_keys(); r = epee::serialization::load_t_from_binary(m_account, account_data); @@ -2072,7 +2170,7 @@ void wallet2::assign_account(const currency::account_base& acc) init_log_prefix(); } //---------------------------------------------------------------------------------------------------- -void wallet2::generate(const std::wstring& path, const std::string& pass) +void wallet2::generate(const std::wstring& path, const std::string& pass, bool auditable_wallet) { WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(validate_password(pass), "new wallet generation failed: password contains forbidden characters") clear(); @@ -2081,11 +2179,11 @@ void wallet2::generate(const std::wstring& path, const std::string& pass) check_for_free_space_and_throw_if_it_lacks(m_wallet_file); m_password = pass; - m_account.generate(); + m_account.generate(auditable_wallet); init_log_prefix(); boost::system::error_code ignored_ec; THROW_IF_TRUE_WALLET_EX(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, epee::string_encoding::convert_to_ansii(m_wallet_file)); - if (m_watch_only) + if (m_watch_only && !auditable_wallet) { bool stub; load_keys2ki(true, stub); @@ -2093,14 +2191,27 @@ void wallet2::generate(const std::wstring& path, const std::string& pass) store(); } //---------------------------------------------------------------------------------------------------- -void wallet2::restore(const std::wstring& path, const std::string& pass, const std::string& restore_key) +void wallet2::restore(const std::wstring& path, const std::string& pass, const std::string& seed_phrase_or_awo_blob, bool auditable_watch_only) { + bool r = false; clear(); prepare_file_names(path); m_password = pass; - bool r = m_account.restore_keys_from_braindata(restore_key); - init_log_prefix(); - THROW_IF_TRUE_WALLET_EX(!r, error::wallet_wrong_seed_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + + if (auditable_watch_only) + { + r = m_account.restore_from_awo_blob(seed_phrase_or_awo_blob); + init_log_prefix(); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "Could not load auditable watch-only wallet from a given blob: invalid awo blob"); + m_watch_only = true; + } + else + { + r = m_account.restore_from_braindata(seed_phrase_or_awo_blob); + init_log_prefix(); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_wrong_seed_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + } + boost::system::error_code ignored_ec; THROW_IF_TRUE_WALLET_EX(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, epee::string_encoding::convert_to_ansii(m_wallet_file)); store(); @@ -2129,36 +2240,38 @@ void wallet2::load(const std::wstring& wallet_, const std::string& password) THROW_IF_TRUE_WALLET_EX(e || !exists, error::file_not_found, epee::string_encoding::convert_to_ansii(m_wallet_file)); boost::filesystem::ifstream data_file; data_file.open(m_wallet_file, std::ios_base::binary | std::ios_base::in); - THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_not_found, epee::string_encoding::convert_to_ansii(m_wallet_file)); + THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); data_file.read((char*)&wbh, sizeof(wbh)); - THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_not_found, epee::string_encoding::convert_to_ansii(m_wallet_file)); + THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - THROW_IF_TRUE_WALLET_EX(wbh.m_signature != WALLET_FILE_SIGNATURE, error::file_not_found, epee::string_encoding::convert_to_ansii(m_wallet_file)); + THROW_IF_TRUE_WALLET_EX(wbh.m_signature != WALLET_FILE_SIGNATURE_OLD && wbh.m_signature != WALLET_FILE_SIGNATURE_V2, error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); THROW_IF_TRUE_WALLET_EX(wbh.m_cb_body > WALLET_FILE_MAX_BODY_SIZE || - wbh.m_cb_keys > WALLET_FILE_MAX_KEYS_SIZE, error::file_not_found, epee::string_encoding::convert_to_ansii(m_wallet_file)); + wbh.m_cb_keys > WALLET_FILE_MAX_KEYS_SIZE, error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); keys_buff.resize(wbh.m_cb_keys); data_file.read((char*)keys_buff.data(), wbh.m_cb_keys); - load_keys(keys_buff, password); + load_keys(keys_buff, password, wbh.m_signature); bool need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, data_file); - if (m_watch_only) + if (m_watch_only && !is_auditable()) load_keys2ki(true, need_to_resync); + WLT_LOG_L0("Loaded wallet file" << (m_watch_only ? " (WATCH ONLY) " : " ") << string_encoding::convert_to_ansii(m_wallet_file) << " with public address: " << m_account.get_public_address_str()); + WLT_LOG_L0("(after loading: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); + if (need_to_resync) { reset_history(); + WLT_LOG_L0("Unable to load history data from wallet file, wallet will be resynced!"); } - THROW_IF_TRUE_WALLET_EX(need_to_resync, error::wallet_load_notice_wallet_restored, epee::string_encoding::convert_to_ansii(m_wallet_file)); - WLT_LOG_L0("Loaded wallet file" << (m_watch_only ? " (WATCH ONLY) " : " ") << string_encoding::convert_to_ansii(m_wallet_file) << " with public address: " << m_account.get_public_address_str()); - WLT_LOG_L0("(after loading: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); + THROW_IF_TRUE_WALLET_EX(need_to_resync, error::wallet_load_notice_wallet_restored, epee::string_encoding::convert_to_ansii(m_wallet_file)); } //---------------------------------------------------------------------------------------------------- void wallet2::store() @@ -2186,7 +2299,7 @@ void wallet2::store(const std::wstring& path_to_save, const std::string& passwor //store data wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); - wbh.m_signature = WALLET_FILE_SIGNATURE; + wbh.m_signature = WALLET_FILE_SIGNATURE_V2; wbh.m_cb_keys = keys_buff.size(); //@#@ change it to proper wbh.m_cb_body = 1000; @@ -2272,8 +2385,11 @@ void wallet2::store_watch_only(const std::wstring& path_to_save, const std::stri WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_pending_ki_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_pending_ki_file) << " already exists"); WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(wo.m_pending_key_images.empty(), "pending key images is expected to be empty"); - bool stub = false; - wo.load_keys2ki(true, stub); // to create outkey2ki file + if (!is_auditable()) + { + bool stub = false; + wo.load_keys2ki(true, stub); // to create outkey2ki file + } // populate pending key images for spent outputs (this will help to resync watch-only wallet) for (size_t ti = 0; ti < wo.m_transfers.size(); ++ti) @@ -3112,6 +3228,21 @@ void wallet2::update_offer_by_id(const crypto::hash& tx_id, uint64_t of_ind, con transfer(destinations, 0, 0, od.fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); } //---------------------------------------------------------------------------------------------------- +void wallet2::push_alias_info_to_extra_according_to_hf_status(const currency::extra_alias_entry& ai, std::vector& extra) +{ + if (get_top_block_height() > m_core_runtime_config.hard_fork_02_starts_after_height) + { + // after HF2 + extra.push_back(ai); + } + else + { + // before HF2 + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!ai.m_address.is_auditable(), "auditable addresses are not supported in aliases prior to HF2"); + extra.push_back(ai.to_old()); + } +} +//---------------------------------------------------------------------------------------------------- void wallet2::request_alias_registration(const currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee, uint64_t reward) { if (!validate_alias_name(ai.m_alias)) @@ -3122,7 +3253,8 @@ void wallet2::request_alias_registration(const currency::extra_alias_entry& ai, std::vector destinations; std::vector extra; std::vector attachments; - extra.push_back(ai); + + push_alias_info_to_extra_according_to_hf_status(ai, extra); currency::tx_destination_entry tx_dest_alias_reward; tx_dest_alias_reward.addr.resize(1); @@ -3152,7 +3284,9 @@ void wallet2::request_alias_update(currency::extra_alias_entry& ai, currency::tr std::vector destinations; std::vector extra; std::vector attachments; - extra.push_back(ai); + + push_alias_info_to_extra_according_to_hf_status(ai, extra); + transfer(destinations, 0, 0, fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); } //---------------------------------------------------------------------------------------------------- @@ -4465,6 +4599,8 @@ void wallet2::transfer(const construct_tx_param& ctp, bool send_to_network, std::string* p_signed_tx_blob_str) { + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!is_auditable() || !is_watch_only(), "You can't initiate coins transfer using an auditable watch-only wallet."); // btw, watch-only wallets can call transfer() within cold-signing process + check_and_throw_if_self_directed_tx_with_payment_id_requested(ctp); TIME_MEASURE_START(prepare_transaction_time); @@ -4578,9 +4714,9 @@ void wallet2::sweep_below(size_t fake_outs_count, const currency::account_public set_payment_id_to_tx(ftp.attachments, payment_id); // put encrypted payer info into the extra ftp.crypt_address = destination_addr; - currency::tx_payer txp = AUTO_VAL_INIT(txp); - txp.acc_addr = m_account.get_public_address(); - ftp.extra.push_back(txp); + + currency::create_and_add_tx_payer_to_container_from_address(ftp.extra, m_account.get_public_address(), get_top_block_height(), m_core_runtime_config); + ftp.flags = 0; // ftp.multisig_id -- not required // ftp.prepared_destinations -- will be filled by prepare_tx_destinations diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index d05749db..8a4a7b0f 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2020 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -24,6 +24,7 @@ #include "currency_core/account_boost_serialization.h" #include "currency_core/currency_format_utils.h" +#include "common/make_hashable.h" #include "wallet_public_structs_defs.h" #include "currency_core/currency_format_utils.h" #include "common/unordered_containers_boost_serialization.h" @@ -94,6 +95,8 @@ namespace tools class i_wallet2_callback { public: + enum message_severity { ms_red, ms_yellow, ms_normal }; + virtual ~i_wallet2_callback() = default; virtual void on_new_block(uint64_t /*height*/, const currency::block& /*block*/) {} @@ -101,6 +104,7 @@ namespace tools virtual void on_pos_block_found(const currency::block& /*block*/) {} virtual void on_sync_progress(const uint64_t& /*percents*/) {} virtual void on_transfer_canceled(const wallet_public::wallet_transfer_info& wti) {} + virtual void on_message(message_severity /*severity*/, const std::string& /*m*/) {} }; struct tx_dust_policy @@ -380,6 +384,9 @@ namespace tools uint32_t m_flags; uint64_t amount() const { return m_ptx_wallet_info->m_tx.vout[m_internal_output_index].amount; } + const currency::tx_out& output() const { return m_ptx_wallet_info->m_tx.vout[m_internal_output_index]; } + uint8_t mix_attr() const { return output().target.type() == typeid(currency::txout_to_key) ? boost::get(output().target).mix_attr : UINT8_MAX; } + crypto::hash tx_hash() const { return get_transaction_hash(m_ptx_wallet_info->m_tx); } bool is_spent() const { return m_flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT; } bool is_spendable() const { return (m_flags & (WALLET_TRANSFER_DETAIL_FLAG_SPENT | WALLET_TRANSFER_DETAIL_FLAG_BLOCKED | WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION | WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION)) == 0; } bool is_reserved_for_escrow() const { return ( (m_flags & WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) != 0 ); } @@ -443,9 +450,10 @@ namespace tools typedef std::unordered_map multisig_transfer_container; typedef std::unordered_map escrow_contracts_container; typedef std::map > free_amounts_cache_type; + typedef std::unordered_map, uint64_t> amount_gindex_to_transfer_id_container; // maps [amount; gindex] -> tid - struct keys_file_data + struct keys_file_data_old { crypto::chacha8_iv iv; std::string account_data; @@ -455,9 +463,32 @@ namespace tools FIELD(account_data) END_SERIALIZE() }; + + struct keys_file_data + { + uint8_t version; + crypto::chacha8_iv iv; + std::string account_data; + + static keys_file_data from_old(const keys_file_data_old& v) + { + keys_file_data result = AUTO_VAL_INIT(result); + result.iv = v.iv; + result.account_data = v.account_data; + return result; + } + + DEFINE_SERIALIZATION_VERSION(1) + BEGIN_SERIALIZE_OBJECT() + VERSION_ENTRY(version) + FIELD(iv) + FIELD(account_data) + END_SERIALIZE() + }; + void assign_account(const currency::account_base& acc); - void generate(const std::wstring& path, const std::string& password); - void restore(const std::wstring& path, const std::string& pass, const std::string& restore_key); + void generate(const std::wstring& path, const std::string& password, bool auditable_wallet); + void restore(const std::wstring& path, const std::string& pass, const std::string& seed_phrase_or_awo_blob, bool auditable_watch_only); void load(const std::wstring& path, const std::string& password); void store(); void store(const std::wstring& path); @@ -480,7 +511,7 @@ namespace tools //i_wallet2_callback* callback() const { return m_wcallback; } //void callback(i_wallet2_callback* callback) { m_callback = callback; } - void callback(std::shared_ptr callback) { m_wcallback = callback; m_do_rise_transfer = true; } + void callback(std::shared_ptr callback) { m_wcallback = callback; m_do_rise_transfer = (callback != nullptr); } void set_do_rise_transfer(bool do_rise) { m_do_rise_transfer = do_rise; } bool has_related_alias_entry_unconfirmed(const currency::transaction& tx); @@ -631,6 +662,7 @@ namespace tools void enumerate_unconfirmed_transfers(callback_t cb) const; bool is_watch_only() const { return m_watch_only; } + bool is_auditable() const { return m_account.get_public_address().is_auditable(); } void sign_transfer(const std::string& tx_sources_blob, std::string& signed_tx_blob, currency::transaction& tx); void sign_transfer_files(const std::string& tx_sources_file, const std::string& signed_tx_file, currency::transaction& tx); void submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx); @@ -697,6 +729,9 @@ namespace tools a & m_minimum_height; } + // v151: m_amount_gindex_to_transfer_id added + if (ver >= 151) + a & m_amount_gindex_to_transfer_id; a & m_transfers; a & m_multisig_transfers; @@ -788,7 +823,7 @@ private: void add_transfers_to_expiration_list(const std::vector& selected_transfers, uint64_t expiration, uint64_t change_amount, const crypto::hash& related_tx_id); void remove_transfer_from_expiration_list(uint64_t transfer_index); - void load_keys(const std::string& keys_file_name, const std::string& password); + void load_keys(const std::string& keys_file_name, const std::string& password, uint64_t file_signature); void process_new_transaction(const currency::transaction& tx, uint64_t height, const currency::block& b); void detach_blockchain(uint64_t including_height); bool extract_offers_from_transfer_entry(size_t i, std::unordered_map& offers_local); @@ -910,6 +945,9 @@ private: uint64_t detach_from_block_ids(uint64_t height); uint64_t get_wallet_minimum_height(); + void push_alias_info_to_extra_according_to_hf_status(const currency::extra_alias_entry& ai, std::vector& extra); + void remove_transfer_from_amount_gindex_map(uint64_t tid); + currency::account_base m_account; bool m_watch_only; std::string m_log_prefix; // part of pub address, prefix for logging functions @@ -924,6 +962,7 @@ private: transfer_container m_transfers; multisig_transfer_container m_multisig_transfers; + amount_gindex_to_transfer_id_container m_amount_gindex_to_transfer_id; payment_container m_payments; std::unordered_map m_key_images; std::unordered_map m_pending_key_images; // (out_pk -> ki) pairs of change outputs to be added in watch-only wallet without spend sec key diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 1430b35d..fe79adf4 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -68,6 +68,12 @@ namespace tools return ss.str(); } + virtual const char* what() const noexcept + { + m_what = to_string(); + return m_what.c_str(); + } + protected: wallet_error_base(std::string&& loc, const std::string& message) : Base(message) @@ -77,6 +83,7 @@ namespace tools private: std::string m_loc; + mutable std::string m_what; }; //---------------------------------------------------------------------------------------------------- const char* const failed_rpc_request_messages[] = { @@ -113,8 +120,9 @@ namespace tools std::string m_status; }; //---------------------------------------------------------------------------------------------------- - typedef wallet_error_base wallet_logic_error; - typedef wallet_error_base wallet_runtime_error; + typedef wallet_error_base wallet_error; + typedef wallet_error wallet_logic_error; + typedef wallet_error wallet_runtime_error; //---------------------------------------------------------------------------------------------------- struct wallet_internal_error : public wallet_runtime_error { diff --git a/src/wallet/wallet_helpers.h b/src/wallet/wallet_helpers.h index be227c62..9f415dfb 100644 --- a/src/wallet/wallet_helpers.h +++ b/src/wallet/wallet_helpers.h @@ -15,10 +15,12 @@ namespace tools { wi = AUTO_VAL_INIT_T(view::wallet_info); wi.address = w.get_account().get_public_address_str(); - wi.tracking_hey = epee::string_tools::pod_to_hex(w.get_account().get_keys().view_secret_key); + wi.view_sec_key = epee::string_tools::pod_to_hex(w.get_account().get_keys().view_secret_key); uint64_t fake = 0; wi.balance = w.balance(wi.unlocked_balance, fake, fake, wi.mined_total); wi.path = epee::string_encoding::wstring_to_utf8(w.get_wallet_path()); + wi.is_auditable = w.is_auditable(); + wi.is_watch_only = w.is_watch_only(); return true; } } \ No newline at end of file diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index c9fb111b..5b3fff59 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -292,20 +292,15 @@ namespace tools if (req.push_payer) { - currency::tx_payer txp = AUTO_VAL_INIT(txp); - txp.acc_addr = m_wallet.get_account().get_keys().account_address; - extra.push_back(txp); + currency::create_and_add_tx_payer_to_container_from_address(extra, m_wallet.get_account().get_keys().account_address, m_wallet.get_top_block_height(), m_wallet.get_core_runtime_config()); } + if (!req.hide_receiver) { for (auto& d : dsts) { for (auto& a : d.addr) - { - currency::tx_receiver txr = AUTO_VAL_INIT(txr); - txr.acc_addr = a; - extra.push_back(txr); - } + currency::create_and_add_tx_receiver_to_container_from_address(extra, a, m_wallet.get_top_block_height(), m_wallet.get_core_runtime_config()); } } diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index 91a50691..3dc30737 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -805,7 +805,7 @@ std::string wallets_manager::open_wallet(const std::wstring& path, const std::st try { w->load(path, password); - if (w->is_watch_only()) + if (w->is_watch_only() && !w->is_auditable()) return API_RETURN_CODE_WALLET_WATCH_ONLY_NOT_SUPPORTED; w->get_recent_transfers_history(owr.recent_history.history, 0, txs_to_return, owr.recent_history.total_history_items); //w->get_unconfirmed_transfers(owr.recent_history.unconfirmed); @@ -819,6 +819,10 @@ std::string wallets_manager::open_wallet(const std::wstring& path, const std::st { return API_RETURN_CODE_FILE_NOT_FOUND; } + catch (const tools::error::file_read_error&) + { + return API_RETURN_CODE_INVALID_FILE; + } catch (const tools::error::wallet_load_notice_wallet_restored& /**/) { return_code = API_RETURN_CODE_FILE_RESTORED; @@ -892,7 +896,7 @@ std::string wallets_manager::generate_wallet(const std::wstring& path, const std try { - w->generate(path, password); + w->generate(path, password, false); w->set_minimum_height(m_last_daemon_height); owr.seed = w->get_account().get_restore_braindata(); } @@ -939,7 +943,7 @@ std::string wallets_manager::is_pos_allowed() std::string wallets_manager::is_valid_brain_restore_data(const std::string& brain_text) { currency::account_base acc; - if (acc.restore_keys_from_braindata(brain_text)) + if (acc.restore_from_braindata(brain_text)) return API_RETURN_CODE_TRUE; else return API_RETURN_CODE_FALSE; @@ -957,7 +961,7 @@ void wallets_manager::get_gui_options(view::gui_options& opt) { opt = m_ui_opt; } -std::string wallets_manager::restore_wallet(const std::wstring& path, const std::string& password, const std::string& restore_key, view::open_wallet_response& owr) +std::string wallets_manager::restore_wallet(const std::wstring& path, const std::string& password, const std::string& restore_key, bool auditable_watch_only, view::open_wallet_response& owr) { std::shared_ptr w(new tools::wallet2()); owr.wallet_id = m_wallet_id_counter++; @@ -979,7 +983,7 @@ std::string wallets_manager::restore_wallet(const std::wstring& path, const std: currency::account_base acc; try { - w->restore(path, password, restore_key); + w->restore(path, password, restore_key, auditable_watch_only); owr.seed = w->get_account().get_restore_braindata(); } catch (const tools::error::file_exists&) @@ -1253,20 +1257,14 @@ std::string wallets_manager::transfer(size_t wallet_id, const view::transfer_par } if (tp.push_payer) { - currency::tx_payer txp = AUTO_VAL_INIT(txp); - txp.acc_addr = w->get()->get_account().get_keys().account_address; - extra.push_back(txp); + currency::create_and_add_tx_payer_to_container_from_address(extra, w->get()->get_account().get_keys().account_address, w->get()->get_top_block_height(), w->get()->get_core_runtime_config()); } if (!tp.hide_receiver) { for (auto& d : dsts) { for (auto& a : d.addr) - { - currency::tx_receiver txr = AUTO_VAL_INIT(txr); - txr.acc_addr = a; - extra.push_back(txr); - } + currency::create_and_add_tx_receiver_to_container_from_address(extra, a, w->get()->get_top_block_height(), w->get()->get_core_runtime_config()); } } w->get()->transfer(dsts, tp.mixin_count, unlock_time ? unlock_time + 1 : 0, fee, extra, attachments, res_tx); diff --git a/src/wallet/wallets_manager.h b/src/wallet/wallets_manager.h index 5b545f32..e992109b 100644 --- a/src/wallet/wallets_manager.h +++ b/src/wallet/wallets_manager.h @@ -94,7 +94,7 @@ public: bool send_stop_signal(); std::string open_wallet(const std::wstring& path, const std::string& password, uint64_t txs_to_return, view::open_wallet_response& owr); std::string generate_wallet(const std::wstring& path, const std::string& password, view::open_wallet_response& owr); - std::string restore_wallet(const std::wstring& path, const std::string& password, const std::string& restore_key, view::open_wallet_response& owr); + std::string restore_wallet(const std::wstring& path, const std::string& password, const std::string& restore_key, bool auditable_watch_only, view::open_wallet_response& owr); std::string invoke(uint64_t wallet_id, std::string params); std::string get_wallet_status(uint64_t wallet_id); std::string run_wallet(uint64_t wallet_id); diff --git a/tests/core_tests/alias_tests.cpp b/tests/core_tests/alias_tests.cpp index 81d9a219..97595c50 100644 --- a/tests/core_tests/alias_tests.cpp +++ b/tests/core_tests/alias_tests.cpp @@ -18,72 +18,20 @@ using namespace currency; #define FIFTH_NAME "fifth--01234567890" #define SIX_NAME "sixsix-double--01234567890" -std::string gen_random_alias(size_t len) -{ - const char allowed_chars[] = "abcdefghijklmnopqrstuvwxyz0123456789"; - char buffer[2048] = ""; - if (len >= sizeof buffer) - return ""; - - crypto::generate_random_bytes(len, buffer); - buffer[len] = 0; - for(size_t i = 0; i < len; ++i) - buffer[i] = allowed_chars[buffer[i] % (sizeof allowed_chars - 1)]; - return buffer; -} - bool put_alias_via_tx_to_list(std::vector& events, - std::list& tx_set, - const block& head_block, - const account_base& miner_acc, - currency::extra_alias_entry ai2, - test_generator& generator) - { - - std::vector ex; - ex.push_back(ai2); - account_base reward_acc; - account_keys& ak = const_cast(reward_acc.get_keys()); - currency::get_aliases_reward_account(ak.account_address, ak.view_secret_key); - - uint64_t fee_median = generator.get_last_n_blocks_fee_median(get_block_hash(head_block)); - uint64_t reward = currency::get_alias_coast_from_fee(ai2.m_alias, fee_median); - - MAKE_TX_MIX_LIST_EXTRA_MIX_ATTR(events, - tx_set, - miner_acc, - reward_acc, - reward, - 0, - head_block, - CURRENCY_TO_KEY_OUT_RELAXED, - ex, - std::vector()); - - - uint64_t found_alias_reward = get_amount_for_zero_pubkeys(tx_set.back()); - if (found_alias_reward != reward) - { - LOCAL_ASSERT(false); - CHECK_AND_ASSERT_MES(false, false, "wrong transaction constructed, first input value not match alias amount or account"); - return false; - } - return true; + std::list& tx_set, + const block& head_block, + const std::string& alias_name, + const account_base& miner_acc, + const account_base& alias_acc, + test_generator& generator) +{ + currency::extra_alias_entry ai2 = AUTO_VAL_INIT(ai2); + ai2.m_alias = alias_name; + ai2.m_address = alias_acc.get_keys().account_address; + ai2.m_text_comment = "ssdss"; + return put_alias_via_tx_to_list(events, tx_set, head_block, miner_acc, ai2, generator); } - bool put_alias_via_tx_to_list(std::vector& events, - std::list& tx_set, - const block& head_block, - const std::string& alias_name, - const account_base& miner_acc, - const account_base& alias_acc, - test_generator& generator) - { - currency::extra_alias_entry ai2 = AUTO_VAL_INIT(ai2); - ai2.m_alias = alias_name; - ai2.m_address = alias_acc.get_keys().account_address; - ai2.m_text_comment = "ssdss"; - return put_alias_via_tx_to_list(events, tx_set, head_block, miner_acc, ai2, generator); - } bool put_next_block_with_alias_in_tx(std::vector& events, block& b, @@ -92,13 +40,13 @@ bool put_next_block_with_alias_in_tx(std::vector& events, const currency::extra_alias_entry& ai, test_generator& generator) { - std::list txs_0; - if (!put_alias_via_tx_to_list(events, txs_0, head_block, miner_acc, ai, generator)) - return false; + std::list txs_0; + if (!put_alias_via_tx_to_list(events, txs_0, head_block, miner_acc, ai, generator)) + return false; - MAKE_NEXT_BLOCK_TX_LIST(events, blk, head_block, miner_acc, txs_0); - b = blk; - return true; + MAKE_NEXT_BLOCK_TX_LIST(events, blk, head_block, miner_acc, txs_0); + b = blk; + return true; } diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 8351521c..daf36a83 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -15,7 +15,6 @@ #include "currency_core/currency_core.h" #include "wallet/wallet2.h" #include "test_core_time.h" -#include "chaingen_helpers.h" #define TESTS_DEFAULT_FEE ((uint64_t)TX_DEFAULT_FEE) #define MK_TEST_COINS(amount) (static_cast(amount) * TX_DEFAULT_FEE) @@ -1205,3 +1204,4 @@ void append_vector_by_another_vector(U& dst, const V& src) // --- end of gentime wallet helpers ----------------------------------------------------------------------- +#include "chaingen_helpers.h" diff --git a/tests/core_tests/chaingen_helpers.h b/tests/core_tests/chaingen_helpers.h index 31835912..2e35652e 100644 --- a/tests/core_tests/chaingen_helpers.h +++ b/tests/core_tests/chaingen_helpers.h @@ -228,3 +228,57 @@ inline bool resign_tx(const currency::account_keys& sender_keys, const std::vect return true; } + +inline std::string gen_random_alias(size_t len) +{ + const char allowed_chars[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + char buffer[2048] = ""; + if (len >= sizeof buffer) + return ""; + + crypto::generate_random_bytes(len, buffer); + buffer[len] = 0; + for(size_t i = 0; i < len; ++i) + buffer[i] = allowed_chars[buffer[i] % (sizeof allowed_chars - 1)]; + return buffer; +} + +template +inline bool put_alias_via_tx_to_list(std::vector& events, + std::list& tx_set, + const currency::block& head_block, + const currency::account_base& miner_acc, + const alias_entry_t& ae, + test_generator& generator) +{ + std::vector ex; + ex.push_back(ae); + currency::account_base reward_acc; + currency::account_keys& ak = const_cast(reward_acc.get_keys()); + currency::get_aliases_reward_account(ak.account_address, ak.view_secret_key); + + uint64_t fee_median = generator.get_last_n_blocks_fee_median(get_block_hash(head_block)); + uint64_t reward = currency::get_alias_coast_from_fee(ae.m_alias, fee_median); + + MAKE_TX_MIX_LIST_EXTRA_MIX_ATTR(events, + tx_set, + miner_acc, + reward_acc, + reward, + 0, + head_block, + CURRENCY_TO_KEY_OUT_RELAXED, + ex, + std::vector()); + + + uint64_t found_alias_reward = get_amount_for_zero_pubkeys(tx_set.back()); + if (found_alias_reward != reward) + { + LOCAL_ASSERT(false); + CHECK_AND_ASSERT_MES(false, false, "wrong transaction constructed, first input value not match alias amount or account"); + return false; + } + + return true; +} diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 68ff8c68..e7a0b10f 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -987,7 +987,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_uint_overflow_2); - // Hardfok1 tests + // Hardfok 1 tests GENERATE_AND_PLAY(before_hard_fork_1_cumulative_difficulty); GENERATE_AND_PLAY(inthe_middle_hard_fork_1_cumulative_difficulty); GENERATE_AND_PLAY(after_hard_fork_1_cumulative_difficulty); @@ -998,10 +998,19 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(hard_fork_1_chain_switch_pow_only); GENERATE_AND_PLAY(hard_fork_1_checkpoint_basic_test); GENERATE_AND_PLAY(hard_fork_1_pos_locked_height_vs_time); - //GENERATE_AND_PLAY(gen_block_reward); */ - + + // Hardfork 2 tests + GENERATE_AND_PLAY(hard_fork_2_tx_payer_in_wallet); + GENERATE_AND_PLAY(hard_fork_2_tx_receiver_in_wallet); + GENERATE_AND_PLAY(hard_fork_2_tx_extra_alias_entry_in_wallet); + GENERATE_AND_PLAY(hard_fork_2_auditable_addresses_basics); + GENERATE_AND_PLAY(hard_fork_2_no_new_structures_before_hf); + GENERATE_AND_PLAY(hard_fork_2_awo_wallets_basic_test); + GENERATE_AND_PLAY(hard_fork_2_awo_wallets_basic_test); + // GENERATE_AND_PLAY(gen_block_reward); + // END OF TESTS */ size_t failed_postponed_tests_count = 0; uint64_t total_time = 0; diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h index 635e1cd7..bccf06ee 100644 --- a/tests/core_tests/chaingen_tests_list.h +++ b/tests/core_tests/chaingen_tests_list.h @@ -37,3 +37,4 @@ #include "hard_fork_1_consensus_test.h" #include "hard_fork_1_bad_pos_source.h" #include "hard_fork_1.h" +#include "hard_fork_2.h" diff --git a/tests/core_tests/checkpoints_tests.cpp b/tests/core_tests/checkpoints_tests.cpp index f4767acb..3a72115f 100644 --- a/tests/core_tests/checkpoints_tests.cpp +++ b/tests/core_tests/checkpoints_tests.cpp @@ -612,7 +612,7 @@ gen_no_attchments_in_coinbase::gen_no_attchments_in_coinbase() // 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) - bool r = m_miner_acc.restore_keys_from_braindata("battle harsh arrow gain best doubt nose raw protect salty apart tell distant final yeah stubborn true stop shoulder breathe throne problem everyone stranger only"); + bool r = m_miner_acc.restore_from_braindata("battle harsh arrow gain best doubt nose raw protect salty apart tell distant final yeah stubborn true stop shoulder breathe throne problem everyone stranger only"); CHECK_AND_ASSERT_THROW_MES(r, "gen_no_attchments_in_coinbase: Can't restore account from braindata"); REGISTER_CALLBACK_METHOD(gen_no_attchments_in_coinbase, c1); diff --git a/tests/core_tests/hard_fork_2.cpp b/tests/core_tests/hard_fork_2.cpp new file mode 100644 index 00000000..20aecd80 --- /dev/null +++ b/tests/core_tests/hard_fork_2.cpp @@ -0,0 +1,1116 @@ +// Copyright (c) 2020 Zano 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 "hard_fork_2.h" +#include "../../src/wallet/wallet_rpc_server.h" + +using namespace currency; + +//------------------------------------------------------------------------------ + +hard_fork_2_base_test::hard_fork_2_base_test(size_t hardfork_02_height) + : hard_fork_2_base_test(1, hardfork_02_height) +{ +} + +hard_fork_2_base_test::hard_fork_2_base_test(size_t hardfork_01_height, size_t hardfork_02_height) + : m_hardfork_01_height(hardfork_01_height) + , m_hardfork_02_height(hardfork_02_height) +{ + REGISTER_CALLBACK_METHOD(hard_fork_2_base_test, configure_core); +} + +bool hard_fork_2_base_test::configure_core(currency::core& c, size_t ev_index, const std::vector& events) +{ + currency::core_runtime_config pc = c.get_blockchain_storage().get_core_runtime_config(); + pc.min_coinstake_age = TESTS_POS_CONFIG_MIN_COINSTAKE_AGE; + pc.pos_minimum_heigh = TESTS_POS_CONFIG_POS_MINIMUM_HEIGH; + pc.hard_fork_01_starts_after_height = m_hardfork_01_height; + pc.hard_fork_02_starts_after_height = m_hardfork_02_height; + c.get_blockchain_storage().set_core_runtime_config(pc); + return true; +} + +void hard_fork_2_base_test::set_hard_fork_heights_to_generator(test_generator& generator) const +{ + generator.set_hardfork_height(1, m_hardfork_01_height); + generator.set_hardfork_height(2, m_hardfork_02_height); +} + +//------------------------------------------------------------------------------ + +hard_fork_2_tx_payer_in_wallet::hard_fork_2_tx_payer_in_wallet() + : hard_fork_2_base_test(24) +{ + REGISTER_CALLBACK_METHOD(hard_fork_2_tx_payer_in_wallet, c1); +} + +bool hard_fork_2_tx_payer_in_wallet::generate(std::vector& events) const +{ + // Test idea: make sure that wallet uses tx_payer_old only before HF2 and tx_payer after + + 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(true); // Bob has auditable address + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + set_hard_fork_heights_to_generator(generator); + DO_CALLBACK(events, "configure_core"); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + MAKE_TX(events, tx_0, miner_acc, bob_acc, MK_TEST_COINS(12), blk_0r); + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0); + + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool hard_fork_2_tx_payer_in_wallet::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + bool r = false, stub_bool = false; + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]); + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, m_accounts[BOB_ACC_IDX]); + + miner_wlt->refresh(); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, 0), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(12)), false, ""); + + // wallet RPC server + tools::wallet_rpc_server miner_wlt_rpc(*miner_wlt); + epee::json_rpc::error je; + tools::wallet_rpc_server::connection_context ctx; + + // Before HF2: Miner -> Alice (normal address) with payer info + tools::wallet_public::COMMAND_RPC_TRANSFER::request req_a = AUTO_VAL_INIT(req_a); + req_a.destinations.push_back(tools::wallet_public::transfer_destination{ MK_TEST_COINS(1), m_accounts[ALICE_ACC_IDX].get_public_address_str() }); + req_a.fee = TESTS_DEFAULT_FEE; + req_a.push_payer = true; + + tools::wallet_public::COMMAND_RPC_TRANSFER::response res = AUTO_VAL_INIT(res); + + r = miner_wlt_rpc.on_transfer(req_a, res, je, ctx); + CHECK_AND_ASSERT_MES(r, false, "on_transfer failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + crypto::hash tx_hash = null_hash; + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(res.tx_hash, tx_hash), false, ""); + + transaction tx = AUTO_VAL_INIT(tx); + CHECK_AND_ASSERT_MES(c.get_transaction(tx_hash, tx), false, ""); + + r = have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer_old is not found in extra"); + r = !have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer is found in extra"); + + + // Before HF2: Miner -> Bob (auditable address) with payer info + tools::wallet_public::COMMAND_RPC_TRANSFER::request req_b = AUTO_VAL_INIT(req_b); + req_b.destinations.push_back(tools::wallet_public::transfer_destination{ MK_TEST_COINS(1), m_accounts[BOB_ACC_IDX].get_public_address_str() }); + req_b.fee = TESTS_DEFAULT_FEE; + req_b.push_payer = true; + + res = AUTO_VAL_INIT(res); + + r = miner_wlt_rpc.on_transfer(req_b, res, je, ctx); + CHECK_AND_ASSERT_MES(r, false, "on_transfer failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 2, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + tx_hash = null_hash; + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(res.tx_hash, tx_hash), false, ""); + + tx = AUTO_VAL_INIT(tx); + CHECK_AND_ASSERT_MES(c.get_transaction(tx_hash, tx), false, ""); + + r = have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer_old is not found in extra"); + r = !have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer is found in extra"); + + + // mine a block and confirm both transactions + 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: " << c.get_pool_transactions_count()); + + size_t callback_counter = 0; + std::shared_ptr l(new wlt_lambda_on_transfer2_wrapper( + [&](const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) -> bool { + CHECK_AND_ASSERT_THROW_MES(wti.show_sender, "show_sender is false"); + CHECK_AND_ASSERT_THROW_MES(wti.remote_addresses.size() == 1, "incorrect wti.remote_addresses.size() = " << wti.remote_addresses.size()); + CHECK_AND_ASSERT_THROW_MES(wti.remote_addresses.front() == m_accounts[MINER_ACC_IDX].get_public_address_str(), "wti.remote_addresses.front is incorrect"); + ++callback_counter; + return true; + } + )); + alice_wlt->callback(l); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(1)), false, ""); + CHECK_AND_ASSERT_MES(callback_counter == 1, false, "callback_counter = " << callback_counter); + + bob_wlt->callback(l); // same callback -- same changes + callback_counter = 0; + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(13)), false, ""); + CHECK_AND_ASSERT_MES(callback_counter == 1, false, "callback_counter = " << callback_counter); + + alice_wlt->callback(std::make_shared()); // clear callback + bob_wlt->callback(std::make_shared()); // clear callback + + // Before HF2: Bob (auditable address) -> Alice with payer info requested (should NOT put tx_payer or tx_payer_old) + tools::wallet_rpc_server bob_wlt_rpc(*bob_wlt); + tools::wallet_public::COMMAND_RPC_TRANSFER::request req_c = AUTO_VAL_INIT(req_c); + req_c.destinations.push_back(tools::wallet_public::transfer_destination{ MK_TEST_COINS(1), m_accounts[ALICE_ACC_IDX].get_public_address_str() }); + req_c.fee = TESTS_DEFAULT_FEE; + req_c.push_payer = true; + res = AUTO_VAL_INIT(res); + r = bob_wlt_rpc.on_transfer(req_c, res, je, ctx); + CHECK_AND_ASSERT_MES(r, false, "on_transfer failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + tx_hash = null_hash; + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(res.tx_hash, tx_hash), false, ""); + tx = AUTO_VAL_INIT(tx); + CHECK_AND_ASSERT_MES(c.get_transaction(tx_hash, tx), false, ""); + r = !have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer_old is found in extra"); + r = !have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer is found in extra"); + + 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: " << c.get_pool_transactions_count()); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(11)), false, ""); + + + // + // mine blocks 24, 25, 26 to activate HF2 + // + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, 3); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + miner_wlt->refresh(); + alice_wlt->refresh(); + + // check again (Miner -> Alice), with different amount + req_a.destinations.front().amount = MK_TEST_COINS(2); + r = miner_wlt_rpc.on_transfer(req_a, res, je, ctx); + CHECK_AND_ASSERT_MES(r, false, "on_transfer failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + tx_hash = null_hash; + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(res.tx_hash, tx_hash), false, ""); + CHECK_AND_ASSERT_MES(c.get_transaction(tx_hash, tx), false, ""); + + r = have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer is not found in extra"); + r = !have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer_old is found in extra"); + + + // check again (Miner -> Bob), with different amount + req_b.destinations.front().amount = MK_TEST_COINS(2); + r = miner_wlt_rpc.on_transfer(req_b, res, je, ctx); + CHECK_AND_ASSERT_MES(r, false, "on_transfer failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 2, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + tx_hash = null_hash; + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(res.tx_hash, tx_hash), false, ""); + CHECK_AND_ASSERT_MES(c.get_transaction(tx_hash, tx), false, ""); + + r = have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer is not found in extra"); + r = !have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer_old is found in extra"); + + + 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: " << c.get_pool_transactions_count()); + + std::shared_ptr l2(new wlt_lambda_on_transfer2_wrapper( + [&](const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) -> bool { + CHECK_AND_ASSERT_THROW_MES(wti.amount == MK_TEST_COINS(2), "incorrect wti.amount = " << print_money_brief(wti.amount)); + CHECK_AND_ASSERT_THROW_MES(wti.show_sender, "show_sender is false"); + CHECK_AND_ASSERT_THROW_MES(wti.remote_addresses.size() == 1, "incorrect wti.remote_addresses.size() = " << wti.remote_addresses.size()); + CHECK_AND_ASSERT_THROW_MES(wti.remote_addresses.front() == m_accounts[MINER_ACC_IDX].get_public_address_str(), "wti.remote_addresses.front is incorrect"); + ++callback_counter; + return true; + } + )); + + alice_wlt->callback(l2); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(4)), false, ""); + + bob_wlt->callback(l2); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(13)), false, ""); + + alice_wlt->callback(std::make_shared()); // clear callback + bob_wlt->callback(std::make_shared()); // clear callback + + // After HF2: Bob (auditable address) -> Alice with payer info requested (should put tx_payer) + req_c = AUTO_VAL_INIT(req_c); + req_c.destinations.push_back(tools::wallet_public::transfer_destination{ MK_TEST_COINS(1), m_accounts[ALICE_ACC_IDX].get_public_address_str() }); + req_c.fee = TESTS_DEFAULT_FEE; + req_c.push_payer = true; + res = AUTO_VAL_INIT(res); + r = bob_wlt_rpc.on_transfer(req_c, res, je, ctx); + CHECK_AND_ASSERT_MES(r, false, "on_transfer failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + tx_hash = null_hash; + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(res.tx_hash, tx_hash), false, ""); + tx = AUTO_VAL_INIT(tx); + CHECK_AND_ASSERT_MES(c.get_transaction(tx_hash, tx), false, ""); + r = !have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer_old is found in extra"); + r = have_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(r, false, "tx_payer is NOT found in extra"); + + 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: " << c.get_pool_transactions_count()); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(5)), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(11)), false, ""); + + return true; +} + +//------------------------------------------------------------------------------ + +hard_fork_2_tx_receiver_in_wallet::hard_fork_2_tx_receiver_in_wallet() + : hard_fork_2_base_test(23) + , m_alice_start_balance(0) +{ + REGISTER_CALLBACK_METHOD(hard_fork_2_tx_receiver_in_wallet, c1); +} + +bool hard_fork_2_tx_receiver_in_wallet::generate(std::vector& events) const +{ + // Test idea: make sure that wallet uses tx_receiver_old only before HF2 and tx_receiver after + + 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(true); // Bob has auditable address + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + set_hard_fork_heights_to_generator(generator); + DO_CALLBACK(events, "configure_core"); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1); + + m_alice_start_balance = MK_TEST_COINS(111); + MAKE_TX(events, tx_0, miner_acc, alice_acc, m_alice_start_balance, blk_0r); + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0); + + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool hard_fork_2_tx_receiver_in_wallet::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + bool r = false, stub_bool = false; + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]); + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, m_accounts[BOB_ACC_IDX]); + + miner_wlt->refresh(); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, m_alice_start_balance), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, 0), false, ""); + + // wallet RPC server + tools::wallet_rpc_server alice_wlt_rpc(*alice_wlt); + epee::json_rpc::error je; + tools::wallet_rpc_server::connection_context ctx; + + tools::wallet_public::COMMAND_RPC_TRANSFER::request req = AUTO_VAL_INIT(req); + req.destinations.push_back(tools::wallet_public::transfer_destination { MK_TEST_COINS(1), m_accounts[MINER_ACC_IDX].get_public_address_str() }); + req.destinations.push_back(tools::wallet_public::transfer_destination { MK_TEST_COINS(1), m_accounts[BOB_ACC_IDX].get_public_address_str() }); // auditable address + req.fee = TESTS_DEFAULT_FEE; + req.hide_receiver = false; // just to emphasize, this is false by default + + LOG_PRINT_L0("Miner's address: " << m_accounts[MINER_ACC_IDX].get_public_address_str()); + LOG_PRINT_L0("Alice's address: " << m_accounts[ALICE_ACC_IDX].get_public_address_str()); + LOG_PRINT_L0("Bob's address: " << m_accounts[BOB_ACC_IDX].get_public_address_str()); + + tools::wallet_public::COMMAND_RPC_TRANSFER::response res = AUTO_VAL_INIT(res); + + r = alice_wlt_rpc.on_transfer(req, res, je, ctx); + CHECK_AND_ASSERT_MES(r, false, "on_transfer failed"); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + crypto::hash tx_hash = null_hash; + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(res.tx_hash, tx_hash), false, ""); + + transaction tx = AUTO_VAL_INIT(tx); + CHECK_AND_ASSERT_MES(c.get_transaction(tx_hash, tx), false, ""); + + // there should be one tx_receiver_old, as Bob's auditable address should not be supported for tx_receiver + size_t count = count_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(count == 1, false, "tx_receiver_old count: " << count); + + count = count_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(count == 0, false, "tx_receiver count: " << count); + + 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: " << c.get_pool_transactions_count()); + + std::shared_ptr l(new wlt_lambda_on_transfer2_wrapper( + [&](const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) -> bool { + CHECK_AND_ASSERT_THROW_MES(!wti.is_income, "wti.is_income is " << wti.is_income); + CHECK_AND_ASSERT_THROW_MES(wti.remote_addresses.size() == 2, "incorrect wti.remote_addresses.size() = " << wti.remote_addresses.size()); + CHECK_AND_ASSERT_THROW_MES(wti.remote_addresses.front() == m_accounts[MINER_ACC_IDX].get_public_address_str(), "wti.remote_addresses.front is incorrect"); + CHECK_AND_ASSERT_THROW_MES(wti.remote_addresses.back() == m_accounts[BOB_ACC_IDX].get_public_address_str(), "wti.remote_addresses.back is incorrect"); + return true; + } + )); + alice_wlt->callback(l); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, m_alice_start_balance - MK_TEST_COINS(2) - TESTS_DEFAULT_FEE), false, ""); + + // mine blocks 23, 24, 25 to activate HF2 + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, 3); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + miner_wlt->refresh(); + alice_wlt->refresh(); + + // check again + req.destinations.front().amount = MK_TEST_COINS(2); + req.destinations.back().amount = MK_TEST_COINS(2); + r = alice_wlt_rpc.on_transfer(req, res, je, ctx); + CHECK_AND_ASSERT_MES(r, false, "on_transfer failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + tx_hash = null_hash; + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(res.tx_hash, tx_hash), false, ""); + CHECK_AND_ASSERT_MES(c.get_transaction(tx_hash, tx), false, ""); + + // shouold be 2 tx_receiver as we passed HF2 and auditable addresses CAN be used with tx_receiver + count = count_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(count == 2, false, "tx_receiver count = " << count); + count = count_type_in_variant_container(tx.extra); + CHECK_AND_ASSERT_MES(count == 0, false, "tx_receiver_old count = " << count); + + 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: " << c.get_pool_transactions_count()); + + std::shared_ptr l2(new wlt_lambda_on_transfer2_wrapper( + [&](const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) -> bool { + CHECK_AND_ASSERT_THROW_MES(!wti.is_income, "wti.is_income is " << wti.is_income); + CHECK_AND_ASSERT_THROW_MES(wti.amount == MK_TEST_COINS(4), "incorrect wti.amount = " << print_money_brief(wti.amount)); + CHECK_AND_ASSERT_THROW_MES(wti.remote_addresses.size() == 2, "incorrect wti.remote_addresses.size() = " << wti.remote_addresses.size()); + CHECK_AND_ASSERT_THROW_MES(wti.remote_addresses.front() == m_accounts[MINER_ACC_IDX].get_public_address_str(), "wti.remote_addresses.front is incorrect"); + CHECK_AND_ASSERT_THROW_MES(wti.remote_addresses.back() == m_accounts[BOB_ACC_IDX].get_public_address_str(), "wti.remote_addresses.back is incorrect"); + return true; + } + )); + alice_wlt->callback(l2); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, m_alice_start_balance - MK_TEST_COINS(6) - TESTS_DEFAULT_FEE * 2), false, ""); + + return true; +} + +//------------------------------------------------------------------------------ + +hard_fork_2_tx_extra_alias_entry_in_wallet::hard_fork_2_tx_extra_alias_entry_in_wallet() + : hard_fork_2_base_test(23) +{ + REGISTER_CALLBACK_METHOD(hard_fork_2_tx_extra_alias_entry_in_wallet, c1); +} + +bool hard_fork_2_tx_extra_alias_entry_in_wallet::generate(std::vector& events) const +{ + 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(true); // auditable address + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + set_hard_fork_heights_to_generator(generator); + DO_CALLBACK(events, "configure_core"); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + uint64_t biggest_alias_reward = get_alias_coast_from_fee("a", TESTS_DEFAULT_FEE); + MAKE_TX(events, tx_0, miner_acc, alice_acc, biggest_alias_reward + TESTS_DEFAULT_FEE, blk_0r); + MAKE_TX(events, tx_1, miner_acc, alice_acc, biggest_alias_reward + TESTS_DEFAULT_FEE, blk_0r); + MAKE_TX(events, tx_2, miner_acc, alice_acc, biggest_alias_reward + TESTS_DEFAULT_FEE, blk_0r); + + MAKE_NEXT_BLOCK_TX_LIST(events, blk_1, blk_0r, miner_acc, std::list({ tx_0, tx_1, tx_2 })); + + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool hard_fork_2_tx_extra_alias_entry_in_wallet::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + bool r = false, stub_bool = false; + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); + std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_accounts[MINER_ACC_IDX]); + + size_t blocks_fetched = 0; + bool received_money; + std::atomic atomic_false = ATOMIC_VAR_INIT(false); + alice_wlt->refresh(blocks_fetched, received_money, atomic_false); + CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE, false, "Incorrect numbers of blocks fetched"); + + extra_alias_entry ai = AUTO_VAL_INIT(ai); + ai.m_alias = "alicealice"; + ai.m_address = m_accounts[ALICE_ACC_IDX].get_public_address(); + uint64_t alias_reward = get_alias_coast_from_fee(ai.m_alias, TESTS_DEFAULT_FEE); + transaction res_tx = AUTO_VAL_INIT(res_tx); + alice_wlt->request_alias_registration(ai, res_tx, TESTS_DEFAULT_FEE, alias_reward); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + // before the HF2 -- old structure should be present + r = have_type_in_variant_container(res_tx.extra); + CHECK_AND_ASSERT_MES(r, false, "extra_alias_entry_old is not found in extra"); + r = !have_type_in_variant_container(res_tx.extra); + CHECK_AND_ASSERT_MES(r, false, "extra_alias_entry is found in extra"); + + // before the HF2 an alias to an auditable address is not supported + extra_alias_entry ai_bob = AUTO_VAL_INIT(ai_bob); + ai_bob.m_alias = "bobbobbob"; + ai_bob.m_address = m_accounts[BOB_ACC_IDX].get_public_address(); + alias_reward = get_alias_coast_from_fee(ai_bob.m_alias, TESTS_DEFAULT_FEE); + res_tx = AUTO_VAL_INIT(res_tx); + r = false; + try + { + alice_wlt->request_alias_registration(ai_bob, res_tx, TESTS_DEFAULT_FEE, alias_reward); + } + catch (...) + { + r = true; + } + CHECK_AND_ASSERT_MES(r, false, "exception was not cought as expected"); + + // should be still one tx in the pool + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + 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: " << c.get_pool_transactions_count()); + + // try to update Alice's alias to an auditable address + extra_alias_entry ai_alice_update = ai; + ai_alice_update.m_text_comment = "Update to auditable"; + ai_alice_update.m_address = m_accounts[BOB_ACC_IDX].get_public_address(); // auditable + CHECK_AND_ASSERT_MES(ai_alice_update.m_address.is_auditable(), false, "address is not auditable"); + r = false; + try + { + alice_wlt->request_alias_update(ai_alice_update, res_tx, TESTS_DEFAULT_FEE, 0); + } + catch (...) + { + r = true; + } + CHECK_AND_ASSERT_MES(r, false, "exception was not cought as expected"); + + + // mine few blocks to activate HF2 + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, 2); + 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: " << c.get_pool_transactions_count()); + + CHECK_AND_ASSERT_MES(c.get_current_blockchain_size() == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE + 4, false, "Incorrect blockchain size"); + + alice_wlt->refresh(blocks_fetched, received_money, atomic_false); + CHECK_AND_ASSERT_MES(blocks_fetched == 3, false, "Incorrect numbers of blocks fetched"); + + // update alias, change comment and address + ai.m_text_comment = "Update to normal"; + ai.m_address = m_accounts[MINER_ACC_IDX].get_public_address(); + alice_wlt->request_alias_update(ai, res_tx, TESTS_DEFAULT_FEE, 0); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + // after HF2: extra_alias_entry should be here, not extra_alias_entry_old + r = have_type_in_variant_container(res_tx.extra); + CHECK_AND_ASSERT_MES(r, false, "extra_alias_entry is not found in extra"); + r = !have_type_in_variant_container(res_tx.extra); + CHECK_AND_ASSERT_MES(r, false, "extra_alias_entry_old is found in extra"); + + // make sure alias was updated indeed + extra_alias_entry ai_check = AUTO_VAL_INIT(ai_check); + r = c.get_blockchain_storage().get_alias_info(ai.m_alias, ai_check); + CHECK_AND_ASSERT_MES(r, false, "get_alias_info failed"); + CHECK_AND_ASSERT_MES(ai_check.m_text_comment == ai.m_text_comment && ai_check.m_address == m_accounts[MINER_ACC_IDX].get_public_address(), + false, "Incorrect alias info retunred by get_alias_info"); + + + // make sure an alias to auditable address can be registered now + alice_wlt->request_alias_registration(ai_bob, res_tx, TESTS_DEFAULT_FEE, alias_reward); + // after HF2: extra_alias_entry should be here, not extra_alias_entry_old + r = have_type_in_variant_container(res_tx.extra); + CHECK_AND_ASSERT_MES(r, false, "extra_alias_entry is not found in extra"); + r = !have_type_in_variant_container(res_tx.extra); + CHECK_AND_ASSERT_MES(r, false, "extra_alias_entry_old is found in extra"); + + // miner a block to confirm it + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + // make sure alias was updated to an auditable address indeed + ai_check = AUTO_VAL_INIT(ai_check); + r = c.get_blockchain_storage().get_alias_info(ai_bob.m_alias, ai_check); + CHECK_AND_ASSERT_MES(r, false, "get_alias_info failed"); + CHECK_AND_ASSERT_MES(ai_check.m_text_comment == ai_bob.m_text_comment && ai_check.m_address == m_accounts[BOB_ACC_IDX].get_public_address(), + false, "Incorrect alias info retunred by get_alias_info"); + + miner_wlt->refresh(); + + // update alias once again, change comment and address to auditable + // alias updated by miner, as he's the owner now + miner_wlt->request_alias_update(ai_alice_update, res_tx, TESTS_DEFAULT_FEE, 0); + + // after HF2: extra_alias_entry should be here, not extra_alias_entry_old + r = have_type_in_variant_container(res_tx.extra); + CHECK_AND_ASSERT_MES(r, false, "extra_alias_entry is not found in extra"); + r = !have_type_in_variant_container(res_tx.extra); + CHECK_AND_ASSERT_MES(r, false, "extra_alias_entry_old is found in extra"); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + // make sure alias was updated to an auditable address indeed + ai_check = AUTO_VAL_INIT(ai_check); + r = c.get_blockchain_storage().get_alias_info(ai_alice_update.m_alias, ai_check); + CHECK_AND_ASSERT_MES(r, false, "get_alias_info failed"); + CHECK_AND_ASSERT_MES(ai_check.m_text_comment == ai_alice_update.m_text_comment && ai_check.m_address == m_accounts[BOB_ACC_IDX].get_public_address(), + false, "Incorrect alias info retunred by get_alias_info"); + + return true; +} + +//------------------------------------------------------------------------------ + +hard_fork_2_auditable_addresses_basics::hard_fork_2_auditable_addresses_basics() + : hard_fork_2_base_test(23) +{ + REGISTER_CALLBACK_METHOD(hard_fork_2_auditable_addresses_basics, c1); +} + +bool hard_fork_2_auditable_addresses_basics::generate(std::vector& events) const +{ + /* + Test idea: make sure that: + (1) before HF2 txs with mix_attr == 1 can be sent and then they are recognized by wallet; + (2) before HF2 txs to an auditable address CAN be sent via wallet2::transfer() + (3) after HF2 txs to an auditable address CAN be sent via wallet2::transfer() + */ + + 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(true); // Bob has auditable address + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + set_hard_fork_heights_to_generator(generator); + DO_CALLBACK(events, "configure_core"); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + MAKE_TX(events, tx_0a, miner_acc, alice_acc, MK_TEST_COINS(11), blk_0r); + MAKE_TX(events, tx_0b, miner_acc, alice_acc, MK_TEST_COINS(11), blk_0r); + + // tx_1 has outputs to an auditable address, it's allowed before HF2 + MAKE_TX(events, tx_1, miner_acc, bob_acc, MK_TEST_COINS(5), blk_0r); + + // make sure all Bob's outputs has mix_attr = 1 + for (auto& out : tx_1.vout) + { + if (out.amount != MK_TEST_COINS(5)) + continue; // skip change + uint8_t mix_attr = boost::get(out.target).mix_attr; + CHECK_AND_ASSERT_MES(mix_attr == CURRENCY_TO_KEY_OUT_FORCED_NO_MIX, false, "Incorrect mix_attr in tx_1: " << mix_attr); + } + + MAKE_NEXT_BLOCK_TX_LIST(events, blk_1, blk_0r, miner_acc, std::list({ tx_0a, tx_0b, tx_1 })); + + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool hard_fork_2_auditable_addresses_basics::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + bool r = false, stub_bool = false; + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, m_accounts[ALICE_ACC_IDX]); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, m_accounts[BOB_ACC_IDX]); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(22), false, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(5), false, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE), false, ""); + + // sending coins to an auditable address should not be allowed until HF2 + std::vector destination{tx_destination_entry(MK_TEST_COINS(1), bob_wlt->get_account().get_public_address())}; + transaction tx = AUTO_VAL_INIT(tx); + alice_wlt->transfer(destination, 0, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment, tx); + + // make sure all Bob's outputs has mix_attr = 1 + for (auto& out : tx.vout) + { + if (out.amount != MK_TEST_COINS(1)) + continue; // skip change + uint8_t mix_attr = boost::get(out.target).mix_attr; + CHECK_AND_ASSERT_MES(mix_attr == CURRENCY_TO_KEY_OUT_FORCED_NO_MIX, false, "Incorrect mix_attr in tx: " << mix_attr); + } + + // mine a block to confirm the tx + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + // mine few block to activate HF2 + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, 2); + 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: " << c.get_pool_transactions_count()); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(20), false, 3), false, ""); + + // repeat the transfer after HF2 (using the same destinations) + alice_wlt->transfer(destination, 0, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment, tx); + + // make sure all Bob's outputs has mix_attr = 1 + for (auto& out : tx.vout) + { + if (out.amount != MK_TEST_COINS(1)) + continue; // skip change + uint8_t mix_attr = boost::get(out.target).mix_attr; + CHECK_AND_ASSERT_MES(mix_attr == CURRENCY_TO_KEY_OUT_FORCED_NO_MIX, false, "Incorrect mix_attr in tx: " << mix_attr); + } + + // mine a block to confirm the tx + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + // make sure the funds were received + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(5 + 1 + 1), false, 3 + 1), false, ""); + + return true; +} + + +//------------------------------------------------------------------------------ + +hard_fork_2_no_new_structures_before_hf::hard_fork_2_no_new_structures_before_hf() + : hard_fork_2_base_test(16) +{ + REGISTER_CALLBACK_METHOD(hard_fork_2_no_new_structures_before_hf, c1); +} + +bool hard_fork_2_no_new_structures_before_hf::generate(std::vector& events) const +{ + bool r = false; + 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(); + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + set_hard_fork_heights_to_generator(generator); + DO_CALLBACK(events, "configure_core"); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + std::vector extra; + + + // extra with tx_payer -- not allowed before HF2 + tx_payer payer = AUTO_VAL_INIT(payer); + payer.acc_addr = miner_acc.get_public_address(); + extra.push_back(payer); + MAKE_TX_MIX_ATTR_EXTRA(events, tx_0, miner_acc, alice_acc, MK_TEST_COINS(1), 0, blk_0r, 0, extra, true); + + // blk_1b_1 is invalid as containing tx_0 + DO_CALLBACK(events, "mark_invalid_block"); + MAKE_NEXT_BLOCK_TX1(events, blk_1b, blk_0r, miner_acc, tx_0); + + + // extra with tx_payer_old -- okay + extra.clear(); + tx_payer_old payer_old = AUTO_VAL_INIT(payer_old); + payer_old.acc_addr = miner_acc.get_public_address().to_old(); + extra.push_back(payer_old); + MAKE_TX_MIX_ATTR_EXTRA(events, tx_0_old, miner_acc, alice_acc, MK_TEST_COINS(1), 0, blk_0r, 0, extra, true); + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0_old); + + + // extra with tx_receiver -- not allowed before HF2 + extra.clear(); + tx_receiver receiver = AUTO_VAL_INIT(receiver); + receiver.acc_addr = miner_acc.get_public_address(); + extra.push_back(receiver); + MAKE_TX_MIX_ATTR_EXTRA(events, tx_1, miner_acc, alice_acc, MK_TEST_COINS(1), 0, blk_1, 0, extra, true); + + // blk_1b_2 is invalid as containing tx_1 + DO_CALLBACK(events, "mark_invalid_block"); + MAKE_NEXT_BLOCK_TX1(events, blk_2b, blk_1, miner_acc, tx_1); + + + // extra with tx_receiver_old -- okay + extra.clear(); + tx_receiver_old receiver_old = AUTO_VAL_INIT(receiver_old); + receiver_old.acc_addr = miner_acc.get_public_address().to_old(); + extra.push_back(receiver_old); + MAKE_TX_MIX_ATTR_EXTRA(events, tx_1_old, miner_acc, alice_acc, MK_TEST_COINS(1), 0, blk_1, 0, extra, true); + MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner_acc, tx_1_old); + + + // extra with extra_alias_entry -- not allowed before HF2 + extra_alias_entry alias_entry = AUTO_VAL_INIT(alias_entry); + alias_entry.m_address = miner_acc.get_public_address(); + alias_entry.m_alias = "minerminer"; + + std::list tx_set; + r = put_alias_via_tx_to_list(events, tx_set, blk_2, miner_acc, alias_entry, generator); + CHECK_AND_ASSERT_MES(r, false, "put_alias_via_tx_to_list failed"); + transaction tx_2 = tx_set.front(); + + // blk_1b_3 is invalid as containing tx_2 + DO_CALLBACK(events, "mark_invalid_block"); + MAKE_NEXT_BLOCK_TX1(events, blk_3b, blk_2, miner_acc, tx_2); + + + // extra with extra_alias_entry_old -- okay + extra_alias_entry_old alias_entry_old = AUTO_VAL_INIT(alias_entry_old); + alias_entry_old.m_address = alice_acc.get_public_address().to_old(); + alias_entry_old.m_alias = "alicealice"; + + tx_set.clear(); + r = put_alias_via_tx_to_list(events, tx_set, blk_2, miner_acc, alias_entry_old, generator); + CHECK_AND_ASSERT_MES(r, false, "put_alias_via_tx_to_list failed"); + transaction tx_2_old = tx_set.front(); + MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_2_old); + + + // activate HF2 + MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_acc); + MAKE_NEXT_BLOCK(events, blk_5, blk_4, miner_acc); + MAKE_NEXT_BLOCK(events, blk_6, blk_5, miner_acc); + MAKE_NEXT_BLOCK(events, blk_7, blk_6, miner_acc); + + + // tx_0 with tx_payer should be accepted after HF2 + MAKE_NEXT_BLOCK_TX1(events, blk_8, blk_7, miner_acc, tx_0); + + // tx_1 with tx_receiver should be accepted after HF2 + MAKE_NEXT_BLOCK_TX1(events, blk_9, blk_8, miner_acc, tx_1); + + // tx_2 with extra_alias_entry should be accepted after HF2 + MAKE_NEXT_BLOCK_TX1(events, blk_10, blk_9, miner_acc, tx_2); + + // check aliases + DO_CALLBACK(events, "c1"); + + return true; +} + +bool hard_fork_2_no_new_structures_before_hf::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + extra_alias_entry_base ai = AUTO_VAL_INIT(ai); + bool r = c.get_blockchain_storage().get_alias_info("alicealice", ai); + CHECK_AND_ASSERT_MES(r, false, "failed to get alias"); + CHECK_AND_ASSERT_MES(ai.m_address == m_accounts[ALICE_ACC_IDX].get_public_address(), false, "invalid address for alicealice alias"); + + ai = AUTO_VAL_INIT(ai); + r = c.get_blockchain_storage().get_alias_info("minerminer", ai); + CHECK_AND_ASSERT_MES(r, false, "failed to get alias minerminer"); + CHECK_AND_ASSERT_MES(ai.m_address == m_accounts[MINER_ACC_IDX].get_public_address(), false, "invalid address for minerminer alias"); + + return true; +} + +//------------------------------------------------------------------------------ + +template +hard_fork_2_awo_wallets_basic_test::hard_fork_2_awo_wallets_basic_test() + : hard_fork_2_base_test(before_hf_2 ? 100 : 3) +{ + REGISTER_CALLBACK_METHOD(hard_fork_2_awo_wallets_basic_test, c1); +} + +template +bool hard_fork_2_awo_wallets_basic_test::generate(std::vector& events) const +{ + bool r = false; + 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(true); // Bob has auditable address + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + set_hard_fork_heights_to_generator(generator); + DO_CALLBACK(events, "configure_core"); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + transaction tx_0 = AUTO_VAL_INIT(tx_0); + r = construct_tx_with_many_outputs(events, blk_0r, miner_acc.get_keys(), alice_acc.get_public_address(), MK_TEST_COINS(110), 10, TESTS_DEFAULT_FEE, tx_0); + CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs failed"); + events.push_back(tx_0); + + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0); + + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); + + DO_CALLBACK(events, "c1"); + + return true; +} + +template +bool hard_fork_2_awo_wallets_basic_test::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + static const std::wstring bob_wo_filename(L"bob_wo_wallet"); + static const std::wstring bob_wo_restored_filename(L"bob_wo_restored_wallet"); + static const std::wstring bob_non_auditable_filename(L"bob_non_auditable_wallet"); + + bool r = false, stub_bool = false; + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, BOB_ACC_IDX); + std::shared_ptr bob_wlt_awo = std::make_shared(); + + boost::system::error_code ec; + boost::filesystem::remove(bob_wo_filename, ec); + bob_wlt->store_watch_only(bob_wo_filename, ""); + + bob_wlt_awo->load(bob_wo_filename, ""); + bob_wlt_awo->set_core_runtime_config(c.get_blockchain_storage().get_core_runtime_config()); + bob_wlt_awo->set_core_proxy(m_core_proxy); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(110), false, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, 0, false, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo", bob_wlt_awo, 0, false, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE), false, ""); + + CHECK_AND_ASSERT_MES(bob_wlt->get_account().get_public_address() == bob_wlt_awo->get_account().get_public_address(), false, "Bob addresses do not match"); + + // + // Alice -> Bob, Bob_awo + // + std::vector destinations; + destinations.push_back(tx_destination_entry(MK_TEST_COINS(5), bob_wlt->get_account().get_public_address())); + destinations.push_back(tx_destination_entry(MK_TEST_COINS(5), bob_wlt_awo->get_account().get_public_address())); + alice_wlt->transfer(destinations, 2, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(99), false, 1), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(10), false, 1), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo", bob_wlt_awo, MK_TEST_COINS(10), false, 1), false, ""); + + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, WALLET_DEFAULT_TX_SPENDABLE_AGE); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); + + alice_wlt->refresh(); + bob_wlt->refresh(); + bob_wlt_awo->refresh(); + + // + // Bob -> miner + // + r = false; + try + { + // first, try with non-zero mixins first -- should fail + bob_wlt->transfer(std::vector{tx_destination_entry(MK_TEST_COINS(9), m_accounts[MINER_ACC_IDX].get_public_address())}, 1 /*mixins*/, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment); + } + catch (...) + { + r = true; + } + CHECK_AND_ASSERT_MES(r, false, "an exception was not caught as expected"); + + r = false; + try + { + // second, try from bob_wlt_awo -- should fail (watch-only wallet) + bob_wlt_awo->transfer(std::vector{tx_destination_entry(MK_TEST_COINS(9), m_accounts[MINER_ACC_IDX].get_public_address())}, 0 /*mixins*/, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment); + } + catch (...) + { + r = true; + } + CHECK_AND_ASSERT_MES(r, false, "an exception was not caught as expected"); + + + // third, try from bob_wlt with zero mixins first -- should pass + bob_wlt->transfer(std::vector{tx_destination_entry(MK_TEST_COINS(9), m_accounts[MINER_ACC_IDX].get_public_address())}, 0 /*mixins*/, 0, TESTS_DEFAULT_FEE, empty_extra, empty_attachment); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(99), false, 1), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(0), false, 1), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo", bob_wlt_awo, MK_TEST_COINS(0), false, 1), false, ""); + + // + // Alice -> Bob as non-auditable (mix_attr != 1) + // this transfer should not be taken into account for Bob and bob_wlt_awo + // + account_public_address bob_addr_non_aud = bob_wlt->get_account().get_public_address(); + bob_addr_non_aud.flags = 0; // clear auditable flag + + alice_wlt->transfer(MK_TEST_COINS(7), bob_addr_non_aud); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + bool callback_called = false; + std::shared_ptr l(new wlt_lambda_on_transfer2_wrapper( + [&callback_called](const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) -> bool { + callback_called = true; + return true; + } + )); + alice_wlt->callback(l); + bob_wlt->callback(l); + bob_wlt_awo->callback(l); + + callback_called = false; + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(91), false, 1), false, ""); + CHECK_AND_ASSERT_MES(callback_called, false, "callback was not called"); + callback_called = false; + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(0), false, 1), false, ""); + CHECK_AND_ASSERT_MES(!callback_called, false, "callback was called"); + callback_called = false; + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo", bob_wlt_awo, MK_TEST_COINS(0), false, 1), false, ""); + CHECK_AND_ASSERT_MES(!callback_called, false, "callback was called"); + + + // + // Alice -> Bob (normal) + // + alice_wlt->transfer(MK_TEST_COINS(3), m_accounts[BOB_ACC_IDX].get_public_address()); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(87), false, 1), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(3), false, 1), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo", bob_wlt_awo, MK_TEST_COINS(3), false, 1), false, ""); + + // + // Make sure a wallet, restored from awo blob will has the very same balance + // + account_base& bob_acc = m_accounts[BOB_ACC_IDX]; + std::string bob_awo_blob = bob_acc.get_awo_blob(); + + std::shared_ptr bob_wlt_awo_restored = std::make_shared(); + + boost::filesystem::remove(bob_wo_restored_filename, ec); + + bob_wlt_awo_restored->restore(bob_wo_restored_filename, "", bob_awo_blob, true); + bob_wlt_awo_restored->set_core_runtime_config(c.get_blockchain_storage().get_core_runtime_config()); + bob_wlt_awo_restored->set_core_proxy(m_core_proxy); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo_restored", bob_wlt_awo_restored, MK_TEST_COINS(3), false), false, ""); + + + // miner few blocks to unlock coins + r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, WALLET_DEFAULT_TX_SPENDABLE_AGE); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); + + bob_wlt->refresh(); + + // + // Bob -> miner, and check again all 3 wallets + // + bob_wlt->transfer(MK_TEST_COINS(1), m_accounts[MINER_ACC_IDX].get_public_address()); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(1), false, 1), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo", bob_wlt_awo, MK_TEST_COINS(1), false, WALLET_DEFAULT_TX_SPENDABLE_AGE + 1), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo_restored", bob_wlt_awo_restored, MK_TEST_COINS(1), false, WALLET_DEFAULT_TX_SPENDABLE_AGE + 1), false, ""); + + + // + // Restore Bob wallet as non-auditable and spend mix_attr!=1 output => make sure other auditable Bob's wallets remain intact + // + + std::string bob_seed = bob_wlt->get_account().get_restore_braindata(); + bob_seed.erase(bob_seed.find_last_of(" ")); // remove the last word (with flags and checksum) to make seed old-format 25-words non-auditable with the same keys + + std::shared_ptr bob_wlt_non_auditable = std::make_shared(); + + boost::filesystem::remove(bob_non_auditable_filename, ec); + + bob_wlt_non_auditable->restore(bob_non_auditable_filename, "", bob_seed, false); + bob_wlt_non_auditable->set_core_runtime_config(c.get_blockchain_storage().get_core_runtime_config()); + bob_wlt_non_auditable->set_core_proxy(m_core_proxy); + + // the balance for non-auditable wallet should be greather by mix_attr!=1 output (7 test coins + 1 left from prev step) + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_non_auditable", bob_wlt_non_auditable, MK_TEST_COINS(8), false), false, ""); + + // spend mix_attr!=1 7-coins output + bob_wlt_non_auditable->transfer(MK_TEST_COINS(6), m_accounts[ALICE_ACC_IDX].get_public_address()); + + // mine a block + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + 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: " << c.get_pool_transactions_count()); + + // all auditable wallets should keep the same balance value + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(1), false, 1), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo", bob_wlt_awo, MK_TEST_COINS(1), false, 1), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo_restored", bob_wlt_awo_restored, MK_TEST_COINS(1), false, 1), false, ""); + + // non-auditable should also show the same balance as we've just spent mix_attr!=1 output + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_non_auditable", bob_wlt_non_auditable, MK_TEST_COINS(1), false, 1), false, ""); + + // make sure Alice received coins + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(93), false, 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE + 1), false, ""); + + return true; +} + +template hard_fork_2_awo_wallets_basic_test::hard_fork_2_awo_wallets_basic_test(); +template bool hard_fork_2_awo_wallets_basic_test::generate(std::vector& events) const; +template hard_fork_2_awo_wallets_basic_test::hard_fork_2_awo_wallets_basic_test(); +template bool hard_fork_2_awo_wallets_basic_test::generate(std::vector& events) const; diff --git a/tests/core_tests/hard_fork_2.h b/tests/core_tests/hard_fork_2.h new file mode 100644 index 00000000..7b710603 --- /dev/null +++ b/tests/core_tests/hard_fork_2.h @@ -0,0 +1,68 @@ +// Copyright (c) 2020 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#pragma once + +#include "chaingen.h" +#include "wallet_tests_basic.h" + +struct hard_fork_2_base_test : virtual public test_chain_unit_enchanced +{ + hard_fork_2_base_test(size_t hardfork_02_height); + hard_fork_2_base_test(size_t hardfork_01_height, size_t hardfork_02_height); + bool configure_core(currency::core& c, size_t ev_index, const std::vector& events); + + void set_hard_fork_heights_to_generator(test_generator& generator) const; + + size_t m_hardfork_01_height; + size_t m_hardfork_02_height; +}; + +struct hard_fork_2_tx_payer_in_wallet : public wallet_test, public hard_fork_2_base_test +{ + hard_fork_2_tx_payer_in_wallet(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); +}; + +struct hard_fork_2_tx_receiver_in_wallet : public wallet_test, public hard_fork_2_base_test +{ + hard_fork_2_tx_receiver_in_wallet(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); + + mutable uint64_t m_alice_start_balance; +}; + +struct hard_fork_2_tx_extra_alias_entry_in_wallet : public wallet_test, public hard_fork_2_base_test +{ + hard_fork_2_tx_extra_alias_entry_in_wallet(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); +}; + +struct hard_fork_2_auditable_addresses_basics : public wallet_test, public hard_fork_2_base_test +{ + hard_fork_2_auditable_addresses_basics(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); +}; + +struct hard_fork_2_no_new_structures_before_hf : public wallet_test, public hard_fork_2_base_test +{ + using hard_fork_2_base_test::check_block_verification_context; // this is necessary for correct work of do_check_block_verification_context, consider rafactoring + + hard_fork_2_no_new_structures_before_hf(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); +}; + +template +struct hard_fork_2_awo_wallets_basic_test : public wallet_test, public hard_fork_2_base_test +{ + //using hard_fork_2_base_test::check_block_verification_context; // this is necessary for correct work of do_check_block_verification_context, consider rafactoring + + hard_fork_2_awo_wallets_basic_test(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); +}; diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index 1900c59e..3bee8148 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -22,18 +22,6 @@ const std::string g_wallet_password = "dofatibmzibeziyekigo"; const currency::account_base null_account = AUTO_VAL_INIT(null_account); -struct wlt_lambda_on_transfer2_wrapper : public tools::i_wallet2_callback -{ - typedef std::function Func; - wlt_lambda_on_transfer2_wrapper(Func callback) : m_result(false), m_callback(callback) {} - virtual void on_transfer2(const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) override - { - m_result = m_callback(wti, balance, unlocked_balance, total_mined); - } - bool m_result; - Func m_callback; -}; - POD_MAKE_COMPARABLE(currency, tx_out); // Determines which output is real and actually spent in tx inputs, when there are fake outputs. @@ -1434,7 +1422,7 @@ bool gen_wallet_decrypted_attachments::generate(std::vector& e CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0); REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_0r, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); - // these attachments will be use across all the transactions in this test + // these attachments will be used across all the transactions in this test currency::tx_payer a_tx_payer = AUTO_VAL_INIT(a_tx_payer); a_tx_payer.acc_addr = miner_acc.get_keys().account_address; currency::tx_comment a_tx_comment = AUTO_VAL_INIT(a_tx_comment); diff --git a/tests/core_tests/wallet_tests_basic.cpp b/tests/core_tests/wallet_tests_basic.cpp index 5c2744e2..fe4ec260 100644 --- a/tests/core_tests/wallet_tests_basic.cpp +++ b/tests/core_tests/wallet_tests_basic.cpp @@ -72,6 +72,7 @@ bool wallet_test::check_balance(currency::core& c, size_t ev_index, const std::v return true; } + std::shared_ptr wallet_test::init_playtime_test_wallet(const std::vector& events, currency::core& c, const account_base& acc) const { CHECK_AND_ASSERT_THROW_MES(events.size() > 0 && events[0].type() == typeid(currency::block), "Invalid events queue, can't find genesis block at the beginning"); @@ -84,6 +85,7 @@ std::shared_ptr wallet_test::init_playtime_test_wallet(const std w->set_core_proxy(m_core_proxy); return w; } + std::shared_ptr wallet_test::init_playtime_test_wallet(const std::vector& events, currency::core& c, size_t account_index) const { CHECK_AND_ASSERT_THROW_MES(account_index < m_accounts.size(), "Invalid account index"); diff --git a/tests/core_tests/wallet_tests_basic.h b/tests/core_tests/wallet_tests_basic.h index 4627284c..b23f19a5 100644 --- a/tests/core_tests/wallet_tests_basic.h +++ b/tests/core_tests/wallet_tests_basic.h @@ -6,7 +6,7 @@ #pragma once #include "chaingen.h" -struct wallet_test : public test_chain_unit_enchanced +struct wallet_test : virtual public test_chain_unit_enchanced { enum { MINER_ACC_IDX = 0, ALICE_ACC_IDX = 1, BOB_ACC_IDX = 2, CAROL_ACC_IDX = 3, DAN_ACC_IDX = 4, TOTAL_ACCS_COUNT = 5 }; // to be used as index for m_accounts @@ -87,3 +87,15 @@ struct wallet_callback_balance_checker : public tools::i_wallet2_callback uint64_t m_unlocked_balance; uint64_t m_total_mined; }; + +struct wlt_lambda_on_transfer2_wrapper : public tools::i_wallet2_callback +{ + typedef std::function Func; + wlt_lambda_on_transfer2_wrapper(Func callback) : m_result(false), m_callback(callback) {} + virtual void on_transfer2(const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) override + { + m_result = m_callback(wti, balance, unlocked_balance, total_mined); + } + bool m_result; + Func m_callback; +}; diff --git a/tests/unit_tests/base58.cpp b/tests/unit_tests/base58.cpp index d55c9a74..60b70ec2 100644 --- a/tests/unit_tests/base58.cpp +++ b/tests/unit_tests/base58.cpp @@ -439,7 +439,8 @@ namespace "\xf7\x24\xbc\x5c\x6c\xfb\xb9\xd9\x76\x02\xc3\x00\x42\x3a\x2f\x28" "\x64\x18\x74\x51\x3a\x03\x57\x78\xa0\xc1\x77\x8d\x83\x32\x01\xe9" "\x22\x09\x39\x68\x9e\xdf\x1a\xbd\x5b\xc1\xd0\x31\xf7\x3e\xcd\x6c" - "\x99\x3a\xdd\x66\xd6\x80\x88\x70\x45\x6a\xfe\xb8\xe7\xee\xb6\x8d"); + "\x99\x3a\xdd\x66\xd6\x80\x88\x70\x45\x6a\xfe\xb8\xe7\xee\xb6\x8d" + "\x00"); std::string test_keys_addr_str = "ZxDqHy6WnyYY5yQcdApjMb8tVPik5BC3LFdaevfbGq7X1KY5vdsWmUi5UQgse2GBZFbMsb47TFqBmPpdFHDDwDxR2ZuZ6zX4W"; // correct str address depends on CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX value } @@ -499,7 +500,7 @@ TEST(get_account_address_from_str, fails_on_invalid_address_spend_key) TEST(get_account_address_from_str, fails_on_invalid_address_view_key) { std::string serialized_keys_copy = test_serialized_keys; - serialized_keys_copy.back() = '\x01'; + serialized_keys_copy[serialized_keys_copy.size() - 2] = '\x01'; std::string addr_str = base58::encode_addr(CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); currency::account_public_address addr; @@ -551,3 +552,103 @@ TEST(integ_address, payment_id_sizes) ASSERT_NE(addr2, addr); ASSERT_NE(integrated_payment_id, payment_id); } + + +struct addr_entry_t +{ + std::string address; + std::string view_pub_key; + std::string spend_pub_key; + std::string payment_id_hex; + uint8_t flags; +}; + +addr_entry_t addr_entries[] = + { + { + // classic normal address + "ZxD5aoLDPTdcaRx4uCpyW4XiLfEXejepAVz8cSY2fwHNEiJNu6NmpBBDLGTJzCsUvn3acCVDVDPMV8yQXdPooAp338Se7AxeH", // address + "a3f208c8f9ba49bab28eed62b35b0f6be0a297bcd85c2faa1eb1820527bcf7e3", // view_pub_key + "9f5e1fa93630d4b281b18bb67a3db79e9622fc703cc3ad4a453a82e0a36d51fa", // spend_pub_key + "", // payment_id_hex + 0 // flags + }, + { + // classic integrated address + "iZ2Zi6RmTWwcaRx4uCpyW4XiLfEXejepAVz8cSY2fwHNEiJNu6NmpBBDLGTJzCsUvn3acCVDVDPMV8yQXdPooAp3iTqEsjvJoco1aLSZXS6T", // address + "a3f208c8f9ba49bab28eed62b35b0f6be0a297bcd85c2faa1eb1820527bcf7e3", // view_pub_key + "9f5e1fa93630d4b281b18bb67a3db79e9622fc703cc3ad4a453a82e0a36d51fa", // spend_pub_key + "87440d0b9acc42f1", // payment_id_hex + 0 // flags + }, + { + // new format normal address with custom flags + "ZxD5aoLDPTdcaRx4uCpyW4XiLfEXejepAVz8cSY2fwHNEiJNu6NmpBBDLGTJzCsUvn3acCVDVDPMV8yQXdPooAp3APrDvRoL5C", // address + "a3f208c8f9ba49bab28eed62b35b0f6be0a297bcd85c2faa1eb1820527bcf7e3", // view_pub_key + "9f5e1fa93630d4b281b18bb67a3db79e9622fc703cc3ad4a453a82e0a36d51fa", // spend_pub_key + "", // payment_id_hex + 0xfe // flags + }, + { + // new format integrated address with custom flags + "iZ4mBxubNfqcaRx4uCpyW4XiLfEXejepAVz8cSY2fwHNEiJNu6NmpBBDLGTJzCsUvn3acCVDVDPMV8yQXdPooAp3iTrG7nU5rRCWmcozLaMoY95sAbo6", // address + "a3f208c8f9ba49bab28eed62b35b0f6be0a297bcd85c2faa1eb1820527bcf7e3", // view_pub_key + "9f5e1fa93630d4b281b18bb67a3db79e9622fc703cc3ad4a453a82e0a36d51fa", // spend_pub_key + "3ba0527bcfb1fa93630d28eed6", // payment_id + 0xfe // flags + }, + { + // normal auditable address + "aZxb9Et6FhP9AinRwcPqSqBKjckre7PgoZjK3q5YG2fUKHYWFZMWjB6YAEAdw4yDDUGEQ7CGEgbqhGRKeadGV1jLYcEJMEmqQFn", // address + "a3f208c8f9ba49bab28eed62b35b0f6be0a297bcd85c2faa1eb1820527bcf7e3", // view_pub_key + "9f5e1fa93630d4b281b18bb67a3db79e9622fc703cc3ad4a453a82e0a36d51fa", // spend_pub_key + "", // payment_id + ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE // flags + }, + { + // auditable integrated address + "aiZXDondHWu9AinRwcPqSqBKjckre7PgoZjK3q5YG2fUKHYWFZMWjB6YAEAdw4yDDUGEQ7CGEgbqhGRKeadGV1jLYcEJM9xJH8EbjuRiMJgFmPRATsEV9", // address + "a3f208c8f9ba49bab28eed62b35b0f6be0a297bcd85c2faa1eb1820527bcf7e3", // view_pub_key + "9f5e1fa93630d4b281b18bb67a3db79e9622fc703cc3ad4a453a82e0a36d51fa", // spend_pub_key + "3ba0527bcfb1fa93630d28eed6", // payment_id + ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE // flags + } + }; + +void check_add_entry(const addr_entry_t& ae) +{ + std::string payment_id, payment_id_hex; + currency::account_public_address addr = AUTO_VAL_INIT(addr); + + ASSERT_TRUE(currency::get_account_address_and_payment_id_from_str(addr, payment_id, ae.address)); + payment_id_hex = epee::string_tools::buff_to_hex_nodelimer(payment_id); + + ASSERT_EQ(ae.flags, addr.flags); + ASSERT_EQ(ae.payment_id_hex, payment_id_hex); + ASSERT_EQ(ae.view_pub_key, epee::string_tools::pod_to_hex(addr.view_public_key)); + ASSERT_EQ(ae.spend_pub_key, epee::string_tools::pod_to_hex(addr.spend_public_key)); +} + +TEST(auditable_addresses, basic) +{ + /* + currency::account_keys keys = AUTO_VAL_INIT(keys); + epee::string_tools::parse_tpod_from_hex_string("248b019d145d485576ecb0367d92b5a12e8aa15084b59ef15014a7a22d1f3b0c", keys.spend_secret_key); + dependent_key(keys.spend_secret_key, keys.view_secret_key); + crypto::secret_key_to_public_key(keys.view_secret_key, keys.account_address.view_public_key); + crypto::secret_key_to_public_key(keys.spend_secret_key, keys.account_address.spend_public_key); + + keys.account_address.flags = 0xfe; + + std::string payment_id; + epee::string_tools::parse_hexstr_to_binbuff(std::string("3ba0527bcfb1fa93630d28eed6"), payment_id); + + std::cout << currency::get_account_address_as_str(keys.account_address) << " " << epee::string_tools::pod_to_hex(keys.account_address.view_public_key) << " " << epee::string_tools::pod_to_hex(keys.account_address.spend_public_key) << ENDL; + std::cout << currency::get_account_address_and_payment_id_as_str(keys.account_address, payment_id) << " " << epee::string_tools::pod_to_hex(keys.account_address.view_public_key) << " " << epee::string_tools::pod_to_hex(keys.account_address.spend_public_key) << ENDL; + */ + + + for (size_t i = 0; i < sizeof addr_entries / sizeof addr_entries[0]; ++i) + check_add_entry(addr_entries[i]); + +} diff --git a/tests/unit_tests/brain_wallet_test.cpp b/tests/unit_tests/brain_wallet_test.cpp index 70fc6e7a..1336f6dd 100644 --- a/tests/unit_tests/brain_wallet_test.cpp +++ b/tests/unit_tests/brain_wallet_test.cpp @@ -13,10 +13,10 @@ TEST(brain_wallet, store_restore_test) { currency::account_base acc; acc.generate(); - auto restore_data = acc.get_restore_data(); + auto seed_phrase = acc.get_restore_braindata(); currency::account_base acc2; - bool r = acc2.restore_keys(restore_data); + bool r = acc2.restore_from_braindata(seed_phrase); ASSERT_TRUE(r); if (memcmp(&acc2.get_keys(), &acc.get_keys(), sizeof(currency::account_keys))) @@ -29,10 +29,10 @@ TEST(brain_wallet, store_restore_test) { currency::account_base acc; acc.generate(); - auto restore_data = acc.get_restore_braindata(); + auto seed_phrase = acc.get_restore_braindata(); currency::account_base acc2; - bool r = acc2.restore_keys_from_braindata(restore_data); + bool r = acc2.restore_from_braindata(seed_phrase); ASSERT_TRUE(r); if (memcmp(&acc2.get_keys(), &acc.get_keys(), sizeof(currency::account_keys))) @@ -42,3 +42,136 @@ TEST(brain_wallet, store_restore_test) } } + +struct wallet_seed_entry +{ + std::string seed_phrase; + std::string spend_secret_key; + std::string view_secret_key; + uint64_t timestamp; + bool auditable; + bool valid; +}; + +wallet_seed_entry wallet_seed_entries[] = +{ + { + // legacy 24-word seed phrase -- invalid + "dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew", + "", + "", + 0, + false, + false + }, + { + // old-style 25-word seed phrase -- valid + "dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew", + "5e051454d7226b5734ebd64f754b57db4c655ecda00bd324f1b241d0b6381c0f", + "7dde5590fdf430568c00556ac2accf09da6cde9a29a4bc7d1cb6fd267130f006", + 0, + false, + true + }, + { + // old-style 25-word seed phrase -- valid + "conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation conversation", + "71162f207499bc16260957c36a6586bb931d54be33ff56b94d565dfedbb3c70e", + "8454372096986c457f4e7dceef2f39b6050c35d87b31d9c9eb8d37bf8f1f430f", + 0, + false, + true + }, + { + // old-style 25-word seed phrase -- invalid word + "dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew dew!", + "", + "", + 0, + false, + false + }, + { + // old-style 25-word seed phrase -- invalid word + "six six six six six six six six six sex six six six six six six six six six six six six six six six", + "", + "", + 0, + false, + false + }, + { + // new-style 26-word seed phrase -- invalid word + "six six six six six six six six six six six six six six six six six six six six six six six six six sex", + "", + "", + 0, + false, + false + }, + { + // new-style 26-word seed phrase -- invalid checksum + "six six six six six six six six six six six six six six six six six six six six six six six six six six", + "", + "", + 0, + false, + false + }, + { + // new-style 26-word seed phrase - valid + "six six six six six six six six six six six six six six six six six six six six six six six six six frown", + "F54F61E3B974AD86171AE4944205C7BD0395BD7845899CDA8B1FBC5C947BB402", + "A18715058BBD914959C3A735B2022E9AE1D04452BC1FAD9E63C53668B7F57907", + 1922832000, + false, + true + }, + { + // new-style 26-word seed phrase auditable - valid + "six six six six six six six six six six six six six six six six six six six six six six six six six grace", + "F54F61E3B974AD86171AE4944205C7BD0395BD7845899CDA8B1FBC5C947BB402", + "A18715058BBD914959C3A735B2022E9AE1D04452BC1FAD9E63C53668B7F57907", + 1922832000, + true, + true + }, + +}; + +TEST(wallet_seed, basic_test) +{ + for (size_t i = 0; i < sizeof wallet_seed_entries / sizeof wallet_seed_entries[0]; ++i) + { + const wallet_seed_entry& wse = wallet_seed_entries[i]; + currency::account_base acc; + bool r = false; + try + { + r = acc.restore_from_braindata(wse.seed_phrase); + } + catch (...) + { + r = false; + } + ASSERT_EQ(r, wse.valid); + + if (r) + { + if (wse.timestamp) + ASSERT_EQ(wse.timestamp, acc.get_createtime()); + + ASSERT_EQ(wse.auditable, acc.get_public_address().is_auditable()); + + // check keys + crypto::secret_key v, s; + ASSERT_TRUE(epee::string_tools::parse_tpod_from_hex_string(wse.spend_secret_key, s)); + ASSERT_EQ(s, acc.get_keys().spend_secret_key); + + ASSERT_TRUE(epee::string_tools::parse_tpod_from_hex_string(wse.view_secret_key, v)); + ASSERT_EQ(v, acc.get_keys().view_secret_key); + } + + } + +} diff --git a/tests/unit_tests/lmdb_tests.cpp b/tests/unit_tests/lmdb_tests.cpp index 8b179675..42bccb37 100644 --- a/tests/unit_tests/lmdb_tests.cpp +++ b/tests/unit_tests/lmdb_tests.cpp @@ -903,7 +903,7 @@ namespace lmdb_test static const uint64_t buffer_size = 64 * 1024; // 64 KB static const uint64_t db_total_size = static_cast(2.1 * 1024 * 1024 * 1024); // 2.1 GB -- a bit more than 2GB to test 2GB boundary - static const std::string db_file_path = std::string("2gb_") + typeid(db_backend_t).name() + "_test"; + static const std::string db_file_path = boost::algorithm::replace_all_copy(boost::algorithm::replace_all_copy(std::string("2gb_") + typeid(db_backend_t).name() + "_test", ":", "_"), " ", "_"); std::shared_ptr lmdb_ptr = std::make_shared(); db::basic_db_accessor bdba(lmdb_ptr, rw_lock);