diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 676f994..d0fa1c6 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -86,6 +86,9 @@ #define PREMINE_AMOUNT_UPFRONT ((uint64_t)650000000000000ull) // 3.4% of MONEY_SUPPLY #define PREMINE_AMOUNT_MONTHLY ((uint64_t)65000000000000ull) // 8.6%/24 of MONEY_SUPPLY +#define TREASURY_SAL1_MINT_AMOUNT ((uint64_t)65000000000000ull) // 650K +#define TREASURY_SAL1_MINT_COUNT 16 // 16 times + #define DIFFICULTY_TARGET_V2 120 // seconds #define DIFFICULTY_TARGET_V1 60 // seconds - before first fork #define DIFFICULTY_WINDOW_V2 70 // blocks @@ -238,6 +241,7 @@ #define HF_VERSION_AUDIT1_PAUSE 7 #define HF_VERSION_AUDIT2 8 #define HF_VERSION_AUDIT2_PAUSE 9 +#define HF_VERSION_TREASURY_SAL1_MINT 10 #define HF_VERSION_REQUIRE_VIEW_TAGS 255 #define HF_VERSION_ENABLE_CONVERT 255 @@ -301,9 +305,30 @@ namespace config }; const uint64_t STAKE_LOCK_PERIOD = 30*24*30; + const uint64_t TREASURY_SAL1_MINT_PERIOD = 30*24*30; // 1 month of blocks std::string const TREASURY_ADDRESS = "SaLvdZR6w1A21sf2Wh6jYEh1wzY4GSbT7RX6FjyPsnLsffWLrzFQeXUXJcmBLRWDzZC2YXeYe5t7qKsnrg9FpmxmEcxPHsEYfqA"; + // treasury payout {tx-key, output-key} pairs + const std::vector> TREASURY_SAL1_MINT_OUTPUT_KEYS = { + {"a1bdd1da651fbbb845232816e1aa2d4ff29b790f10bbd4f574a012f1199e15a4", "b0733ab6f251b16458efa9ebb3fb99bd54d43173b5768fe9ffc42e0fe46ae3a8"}, + {"47996eccbcc078b06d0f6ece37bf3a700c2bd60adfdd898b22096f16a9ad315c", "fd6bcceb4799ee067d59b97a6f66a0f9a70f134220259d3b4d6a2278ba4aca4c"}, + {"a3e6754a849b80c21a77e6065fefdae29eeeabf17c407453356244a00545bdb8", "3d395454df1452d715d27190e022b20395871c99af578f7251c3f9752e0274a6"}, + {"0d5e97a910e0f9c606ad9c711b6595aaed142d857cde2efa519112b9a29240d5", "56c29e28bdcf4f20b4b45906b93ae7c4bf9ee82e18cd45543cb69a14ce5efb88"}, + {"495fa363de88915aa8b74818c4b80715a882a688b4f7127ab7cd3b6885f3567a", "d42dfe0da5579c82e8255eba8c0a17170023f14a6a5030da6abf9f10abb52cbb"}, + {"85ea10ec40390e4f406446fb519e974d89536154045c6df28bb3b538b254e20d", "0ce2b7dd3a8ce8b596889dac8081a62f98fd70f1f043944ab4ac592c3c59e77b"}, + {"40f201b38a319dda81e7201e57fea7924067a4a332ed71b8e51ec29ac2d67310", "8289aa6963b98d1034e94eae55d8be6b33d0a88f14f174ebcbaec70837986c7c"}, + {"c5a648cc7846341357b7b4653a58f9eb4800d88b5de587bceec7a5c28f98d05a", "3f308a203845d88e5e728fcebcdcea1f90e2f424d461617993c672a6138ad2d8"}, + {"4c51d6550b8eeb6cc8f0d395cc83a5f90ec2a4d86501b3f68da48d618ccf5711", "53f0bd8cebeefb3a88fffa5d7f6ad43d4712608ded561732467ca499df940454"}, + {"ce2f5d82118fed03d5e269e285fc16189a6cd34f38999e5c055a5dea5fce61bc", "f7fc6948b194d9bd6f2df6ecb83f04e6c8d1a2556a63fedb310a4631fe1bfc42"}, + {"6248028fd77fb02b5c6ea72dea10b417891a2da7aaf9565aed382e063b4981a3", "63986e1177499bdb23cd49afb519ec18f38cb1b0c386220b376d8ffdc2e37890"}, + {"6adcb695aa5d6d01133c68900f29e501e9549816e827ea0c164bbc78f3534dd6", "6a440ccb18f5e703e8000de3865ac40d4c18f081270d32eef377dc831f28d8d0"}, + {"b97a4d2259480f34f20e41c489ab5c2e5ae9ee84d8672a7eff8012f2260e121e", "e6eb9147ff40e22209d321d0f1bfbfe20acf5ceb6b9d0bfb13688ad28aa1232e"}, + {"4fd214602a36902f22d16927244c456e8cc5a406a9570131f138a028214ffdf0", "34060b8bd96009b9b298280ebd84fa9587fa8c9df6fb5ad0270fb6cd2098885c"}, + {"9d60178ec6d6599d7a31298f2559fb9c3111f2c70494b3a1638db877ea55b808", "7985ed03856a929663e954738d0938713407717835f760c7ca4d54844a128c91"}, + {"cd65718eab234bf419332e53bd2f48e2ade70af48c5e126ab5080321e1493dfc", "581cb4cca7a0a029ee2cac51dfc00a0c3a657d2eaf67ed3c6ae7bacc11b4f007"}, + }; + // Hash domain separators const char HASH_KEY_BULLETPROOF_EXPONENT[] = "bulletproof"; const char HASH_KEY_BULLETPROOF_PLUS_EXPONENT[] = "bulletproof_plus"; @@ -381,6 +406,7 @@ namespace config }; const uint64_t STAKE_LOCK_PERIOD = 20; + const uint64_t TREASURY_SAL1_MINT_PERIOD = 20; std::array const ORACLE_URLS = {{"oracle.salvium.io:8443", "oracle.salvium.io:8443", "oracle.salvium.io:8443"}}; @@ -390,6 +416,26 @@ namespace config "-----END PUBLIC KEY-----\n"; std::string const TREASURY_ADDRESS = "SaLvTyLFta9BiAXeUfFkKvViBkFt4ay5nEUBpWyDKewYggtsoxBbtCUVqaBjtcCDyY1euun8Giv7LLEgvztuurLo5a6Km1zskZn36"; + + // treasury payout {tx-key, output-key} pairs + const std::vector> TREASURY_SAL1_MINT_OUTPUT_KEYS = { + {"a1bdd1da651fbbb845232816e1aa2d4ff29b790f10bbd4f574a012f1199e15a4", "b0733ab6f251b16458efa9ebb3fb99bd54d43173b5768fe9ffc42e0fe46ae3a8"}, + {"47996eccbcc078b06d0f6ece37bf3a700c2bd60adfdd898b22096f16a9ad315c", "fd6bcceb4799ee067d59b97a6f66a0f9a70f134220259d3b4d6a2278ba4aca4c"}, + {"a3e6754a849b80c21a77e6065fefdae29eeeabf17c407453356244a00545bdb8", "3d395454df1452d715d27190e022b20395871c99af578f7251c3f9752e0274a6"}, + {"0d5e97a910e0f9c606ad9c711b6595aaed142d857cde2efa519112b9a29240d5", "56c29e28bdcf4f20b4b45906b93ae7c4bf9ee82e18cd45543cb69a14ce5efb88"}, + {"495fa363de88915aa8b74818c4b80715a882a688b4f7127ab7cd3b6885f3567a", "d42dfe0da5579c82e8255eba8c0a17170023f14a6a5030da6abf9f10abb52cbb"}, + {"85ea10ec40390e4f406446fb519e974d89536154045c6df28bb3b538b254e20d", "0ce2b7dd3a8ce8b596889dac8081a62f98fd70f1f043944ab4ac592c3c59e77b"}, + {"40f201b38a319dda81e7201e57fea7924067a4a332ed71b8e51ec29ac2d67310", "8289aa6963b98d1034e94eae55d8be6b33d0a88f14f174ebcbaec70837986c7c"}, + {"c5a648cc7846341357b7b4653a58f9eb4800d88b5de587bceec7a5c28f98d05a", "3f308a203845d88e5e728fcebcdcea1f90e2f424d461617993c672a6138ad2d8"}, + {"4c51d6550b8eeb6cc8f0d395cc83a5f90ec2a4d86501b3f68da48d618ccf5711", "53f0bd8cebeefb3a88fffa5d7f6ad43d4712608ded561732467ca499df940454"}, + {"ce2f5d82118fed03d5e269e285fc16189a6cd34f38999e5c055a5dea5fce61bc", "f7fc6948b194d9bd6f2df6ecb83f04e6c8d1a2556a63fedb310a4631fe1bfc42"}, + {"6248028fd77fb02b5c6ea72dea10b417891a2da7aaf9565aed382e063b4981a3", "63986e1177499bdb23cd49afb519ec18f38cb1b0c386220b376d8ffdc2e37890"}, + {"6adcb695aa5d6d01133c68900f29e501e9549816e827ea0c164bbc78f3534dd6", "6a440ccb18f5e703e8000de3865ac40d4c18f081270d32eef377dc831f28d8d0"}, + {"b97a4d2259480f34f20e41c489ab5c2e5ae9ee84d8672a7eff8012f2260e121e", "e6eb9147ff40e22209d321d0f1bfbfe20acf5ceb6b9d0bfb13688ad28aa1232e"}, + {"4fd214602a36902f22d16927244c456e8cc5a406a9570131f138a028214ffdf0", "34060b8bd96009b9b298280ebd84fa9587fa8c9df6fb5ad0270fb6cd2098885c"}, + {"9d60178ec6d6599d7a31298f2559fb9c3111f2c70494b3a1638db877ea55b808", "7985ed03856a929663e954738d0938713407717835f760c7ca4d54844a128c91"}, + {"cd65718eab234bf419332e53bd2f48e2ade70af48c5e126ab5080321e1493dfc", "581cb4cca7a0a029ee2cac51dfc00a0c3a657d2eaf67ed3c6ae7bacc11b4f007"}, + }; } namespace stagenet @@ -409,6 +455,7 @@ namespace config const std::map>> AUDIT_HARD_FORKS = { {HF_VERSION_AUDIT1, {30, {"SAL", "SAL1"}}} }; const uint64_t STAKE_LOCK_PERIOD = 20; + const uint64_t TREASURY_SAL1_MINT_PERIOD = 20; std::array const ORACLE_URLS = {{"oracle.salvium.io:8443", "oracle.salvium.io:8443", "oracle.salvium.io:8443"}}; @@ -418,6 +465,26 @@ namespace config "-----END PUBLIC KEY-----\n"; std::string const TREASURY_ADDRESS = "fuLMowH85abK8nz9BBMEem7MAfUbQu4aSHHUV9j5Z86o6Go9Lv2U5ZQiJCWPY9R9HA8p5idburazjAhCqDngLo7fYPCD9ciM9ee1A"; + + // treasury payout {tx-key, output-key} pairs + const std::vector> TREASURY_SAL1_MINT_OUTPUT_KEYS = { + {"a1bdd1da651fbbb845232816e1aa2d4ff29b790f10bbd4f574a012f1199e15a4", "b0733ab6f251b16458efa9ebb3fb99bd54d43173b5768fe9ffc42e0fe46ae3a8"}, + {"47996eccbcc078b06d0f6ece37bf3a700c2bd60adfdd898b22096f16a9ad315c", "fd6bcceb4799ee067d59b97a6f66a0f9a70f134220259d3b4d6a2278ba4aca4c"}, + {"a3e6754a849b80c21a77e6065fefdae29eeeabf17c407453356244a00545bdb8", "3d395454df1452d715d27190e022b20395871c99af578f7251c3f9752e0274a6"}, + {"0d5e97a910e0f9c606ad9c711b6595aaed142d857cde2efa519112b9a29240d5", "56c29e28bdcf4f20b4b45906b93ae7c4bf9ee82e18cd45543cb69a14ce5efb88"}, + {"495fa363de88915aa8b74818c4b80715a882a688b4f7127ab7cd3b6885f3567a", "d42dfe0da5579c82e8255eba8c0a17170023f14a6a5030da6abf9f10abb52cbb"}, + {"85ea10ec40390e4f406446fb519e974d89536154045c6df28bb3b538b254e20d", "0ce2b7dd3a8ce8b596889dac8081a62f98fd70f1f043944ab4ac592c3c59e77b"}, + {"40f201b38a319dda81e7201e57fea7924067a4a332ed71b8e51ec29ac2d67310", "8289aa6963b98d1034e94eae55d8be6b33d0a88f14f174ebcbaec70837986c7c"}, + {"c5a648cc7846341357b7b4653a58f9eb4800d88b5de587bceec7a5c28f98d05a", "3f308a203845d88e5e728fcebcdcea1f90e2f424d461617993c672a6138ad2d8"}, + {"4c51d6550b8eeb6cc8f0d395cc83a5f90ec2a4d86501b3f68da48d618ccf5711", "53f0bd8cebeefb3a88fffa5d7f6ad43d4712608ded561732467ca499df940454"}, + {"ce2f5d82118fed03d5e269e285fc16189a6cd34f38999e5c055a5dea5fce61bc", "f7fc6948b194d9bd6f2df6ecb83f04e6c8d1a2556a63fedb310a4631fe1bfc42"}, + {"6248028fd77fb02b5c6ea72dea10b417891a2da7aaf9565aed382e063b4981a3", "63986e1177499bdb23cd49afb519ec18f38cb1b0c386220b376d8ffdc2e37890"}, + {"6adcb695aa5d6d01133c68900f29e501e9549816e827ea0c164bbc78f3534dd6", "6a440ccb18f5e703e8000de3865ac40d4c18f081270d32eef377dc831f28d8d0"}, + {"b97a4d2259480f34f20e41c489ab5c2e5ae9ee84d8672a7eff8012f2260e121e", "e6eb9147ff40e22209d321d0f1bfbfe20acf5ceb6b9d0bfb13688ad28aa1232e"}, + {"4fd214602a36902f22d16927244c456e8cc5a406a9570131f138a028214ffdf0", "34060b8bd96009b9b298280ebd84fa9587fa8c9df6fb5ad0270fb6cd2098885c"}, + {"9d60178ec6d6599d7a31298f2559fb9c3111f2c70494b3a1638db877ea55b808", "7985ed03856a929663e954738d0938713407717835f760c7ca4d54844a128c91"}, + {"cd65718eab234bf419332e53bd2f48e2ade70af48c5e126ab5080321e1493dfc", "581cb4cca7a0a029ee2cac51dfc00a0c3a657d2eaf67ed3c6ae7bacc11b4f007"}, + }; } } @@ -445,8 +512,10 @@ namespace cryptonote std::array const ORACLE_URLS; std::string const ORACLE_PUBLIC_KEY; uint64_t const STAKE_LOCK_PERIOD; + uint64_t TREASURY_SAL1_MINT_PERIOD; std::map>> const AUDIT_HARD_FORKS; std::string TREASURY_ADDRESS; + std::vector> TREASURY_SAL1_MINT_OUTPUT_KEYS; }; inline const config_t& get_config(network_type nettype) { @@ -463,8 +532,10 @@ namespace cryptonote ::config::ORACLE_URLS, ::config::ORACLE_PUBLIC_KEY, ::config::STAKE_LOCK_PERIOD, + ::config::TREASURY_SAL1_MINT_PERIOD, ::config::AUDIT_HARD_FORKS, - ::config::TREASURY_ADDRESS + ::config::TREASURY_ADDRESS, + ::config::TREASURY_SAL1_MINT_OUTPUT_KEYS }; static const config_t testnet = { ::config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, @@ -479,8 +550,10 @@ namespace cryptonote ::config::testnet::ORACLE_URLS, ::config::testnet::ORACLE_PUBLIC_KEY, ::config::testnet::STAKE_LOCK_PERIOD, + ::config::testnet::TREASURY_SAL1_MINT_PERIOD, ::config::testnet::AUDIT_HARD_FORKS, - ::config::testnet::TREASURY_ADDRESS + ::config::testnet::TREASURY_ADDRESS, + ::config::testnet::TREASURY_SAL1_MINT_OUTPUT_KEYS }; static const config_t stagenet = { ::config::stagenet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, @@ -495,8 +568,10 @@ namespace cryptonote ::config::stagenet::ORACLE_URLS, ::config::stagenet::ORACLE_PUBLIC_KEY, ::config::stagenet::STAKE_LOCK_PERIOD, + ::config::stagenet::TREASURY_SAL1_MINT_PERIOD, ::config::stagenet::AUDIT_HARD_FORKS, - ::config::stagenet::TREASURY_ADDRESS + ::config::stagenet::TREASURY_ADDRESS, + ::config::stagenet::TREASURY_SAL1_MINT_OUTPUT_KEYS }; switch (nettype) { diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index bc5165c..bd5aa12 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1416,14 +1416,74 @@ bool Blockchain::prevalidate_protocol_transaction(const block& b, uint64_t heigh return true; } //------------------------------------------------------------------ +std::tuple Blockchain::validate_treasury_payout(const transaction& tx, const uint64_t payout_index, uint8_t hf_version) const { + // find the treasury output + const auto treasury_output_keys = get_config(m_nettype).TREASURY_SAL1_MINT_OUTPUT_KEYS; + const auto expected_output_key = treasury_output_keys[payout_index].second; + const auto &output = std::find_if(tx.vout.begin(), tx.vout.end(), [&expected_output_key](const tx_out &o) { + std::string output_key; + if (o.target.type() == typeid(txout_to_key)) { + output_key = epee::string_tools::pod_to_hex(boost::get(o.target).key); + } else if (o.target.type() == typeid(txout_to_tagged_key)) { + output_key = epee::string_tools::pod_to_hex(boost::get(o.target).key); + } else { + return false; + } + + return output_key == expected_output_key; + }); + + if (output == tx.vout.end()) { + MERROR_VER("Miner transaction does not contain treasury output"); + return {false, 0}; + } + + if (output->amount != TREASURY_SAL1_MINT_AMOUNT) { + MERROR_VER("Miner transaction contains treasury output with invalid amount"); + return {false, 0}; + } + + if (output->target.type() != typeid(txout_to_key)) { + MERROR_VER("Miner transaction contains treasury output with invalid target type"); + return {false, 0}; + } + + if (boost::get(output->target).unlock_time != CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW) { + MERROR_VER("Miner transaction contains treasury output with invalid target key"); + return {false, 0}; + } + + return {true, output - tx.vout.begin()}; +} +//------------------------------------------------------------------ // This function validates the miner transaction reward bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_block_weight, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward, uint8_t version) { LOG_PRINT_L3("Blockchain::" << __func__); + + // validate treasury payout + size_t treasury_index_in_tx_outputs; + auto [treasury_payout_exist, treasury_payout_index] = check_treasury_payout(m_nettype, boost::get(b.miner_tx.vin[0]).height, m_hardfork->get_hardforks(), version); + if (treasury_payout_exist) { + // check the treasury payout + auto [valid, index_in_tx_outputs] = validate_treasury_payout(b.miner_tx, treasury_payout_index, version); + if (!valid) { + MERROR_VER("Miner transaction treasury output was invalid"); + return false; + } + treasury_index_in_tx_outputs = index_in_tx_outputs; + } + //validate reward uint64_t money_in_use = 0; - for (auto& o: b.miner_tx.vout) - money_in_use += o.amount; + for(size_t i = 0; i < b.miner_tx.vout.size(); i++) + { + // skip the treasury output + if (treasury_payout_exist && (i == treasury_index_in_tx_outputs)) { + continue; + } + money_in_use += b.miner_tx.vout[i].amount; + } partial_block_reward = false; switch (version) { @@ -1436,6 +1496,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl case HF_VERSION_AUDIT1_PAUSE: case HF_VERSION_AUDIT2: case HF_VERSION_AUDIT2_PAUSE: + case HF_VERSION_TREASURY_SAL1_MINT: if (b.miner_tx.amount_burnt > 0) { CHECK_AND_ASSERT_MES(money_in_use + b.miner_tx.amount_burnt > money_in_use, false, "miner transaction is overflowed by amount_burnt"); money_in_use += b.miner_tx.amount_burnt; @@ -2002,7 +2063,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, //make blocks coin-base tx looks close to real coinbase tx to get truthful blob weight uint8_t hf_version = b.major_version; size_t max_outs = hf_version >= 4 ? 1 : 11; - bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); + bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, m_nettype, m_hardfork->get_hardforks(), ex_nonce, max_outs, hf_version); CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance"); size_t cumulative_weight = txs_weight + get_transaction_weight(b.miner_tx); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) @@ -2011,7 +2072,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, #endif for (size_t try_count = 0; try_count != 10; ++try_count) { - r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); + r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, m_nettype, m_hardfork->get_hardforks(), ex_nonce, max_outs, hf_version); CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, second chance"); size_t coinbase_weight = get_transaction_weight(b.miner_tx); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 7487463..0de7ee9 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1492,6 +1492,16 @@ namespace cryptonote */ difficulty_type get_next_difficulty_for_alternative_chain(const std::list& alt_chain, block_extended_info& bei) const; + /** + * @brief validates the treasury payout for a block + * @param tx transaction to validate + * @param payout_index the index of the payout to check + * @param hf_version the consensus rules to apply + * + * @return bool indicating payout valid, and the index of the output within miner transaction outputs. + */ + std::tuple validate_treasury_payout(const transaction& tx, const uint64_t payout_index, uint8_t hf_version) const; + /** * @brief sanity checks a miner transaction before validating an entire block * diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index a86498b..458ddbd 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -335,6 +335,34 @@ namespace cryptonote } */ //--------------------------------------------------------------- + std::tuple check_treasury_payout( + network_type nettype, + uint64_t height, + const std::vector& hardforks, + const uint8_t hf_version + ) { + if (hf_version >= HF_VERSION_TREASURY_SAL1_MINT) { + // find the hardfork height + const auto& hf = std::find_if(hardforks.begin(), hardforks.end(), [](const hardfork_t& hf) { + return hf.version == HF_VERSION_TREASURY_SAL1_MINT; + }); + + // since we are at least at the hard fork HF_VERSION_TREASURY_SAL1_MINT, we assume height >= hf->height + const auto diff = height - hf->height; + const auto mint_period = get_config(nettype).TREASURY_SAL1_MINT_PERIOD; + + // pay per period + if (diff % mint_period == 0) { + const auto payout_index = diff / mint_period; + if (payout_index < TREASURY_SAL1_MINT_COUNT) { + return {true, payout_index}; + } + } + } + + return {false, 0}; + } + //--------------------------------------------------------------- bool construct_protocol_tx( const size_t height, transaction& tx, @@ -389,12 +417,15 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) { + bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, network_type nettype, const std::vector& hardforks, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) { // Clear the TX contents tx.set_null(); tx.type = cryptonote::transaction_type::MINER; + // check for treasury payouts + auto[treasury_payout_exist, treasury_payout_index] = check_treasury_payout(nettype, height, hardforks, hard_fork_version); + keypair txkey = keypair::generate(hw::get_device("default")); add_tx_pub_key_to_extra(tx, txkey.pub); if(!extra_nonce.empty()) @@ -451,6 +482,7 @@ namespace cryptonote case HF_VERSION_AUDIT1_PAUSE: case HF_VERSION_AUDIT2: case HF_VERSION_AUDIT2_PAUSE: + case HF_VERSION_TREASURY_SAL1_MINT: // SRCG: subtract 20% that will be rewarded to staking users CHECK_AND_ASSERT_MES(tx.amount_burnt == 0, false, "while creating outs: amount_burnt is nonzero"); tx.amount_burnt = amount / 5; @@ -464,7 +496,7 @@ namespace cryptonote std::string asset_type = "SAL"; if (hard_fork_version >= HF_VERSION_SALVIUM_ONE_PROOFS) asset_type = "SAL1"; - cryptonote::set_tx_out(amount, asset_type, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, out_eph_public_key, use_view_tags, view_tag, out); + cryptonote::set_tx_out(amount, asset_type, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, out_eph_public_key, !treasury_payout_exist, view_tag, out); tx.vout.push_back(out); } else { @@ -477,10 +509,29 @@ namespace cryptonote CHECK_AND_ASSERT_MES(summary_amounts == block_reward, false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal block_reward = " << block_reward); + // add the treasury payout if needed + if (treasury_payout_exist) { + std::vector additional_tx_public_keys = {txkey.pub}; + const auto output_keys = get_config(nettype).TREASURY_SAL1_MINT_OUTPUT_KEYS; + const auto keys = output_keys[treasury_payout_index]; + + crypto::public_key tx_key; + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(keys.first, tx_key), false, "fail to deserialize treasury tx key"); + crypto::public_key output_key; + CHECK_AND_ASSERT_MES(epee::string_tools::hex_to_pod(keys.second, output_key), false, "fail to deserialize treasury output key"); + + // Create the TX output for this payout + tx_out out; + cryptonote::set_tx_out(TREASURY_SAL1_MINT_AMOUNT, "SAL1", CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, output_key, false, crypto::view_tag{}, out); + tx.vout.push_back(out); + + // add tx pub key to tx extra + additional_tx_public_keys.push_back(tx_key); + add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys); + } + tx.version = 2; - tx.vin.push_back(in); - tx.invalidate_hashes(); //LOG_PRINT("MINER_TX generated ok, block_reward=" << print_money(block_reward) << "(" << print_money(block_reward - fee) << "+" << print_money(fee) diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 02f162c..29689b3 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -37,6 +37,8 @@ #include "oracle/pricing_record.h" #include "cryptonote_protocol/enums.h" +#include "hardforks/hardforks.h" + namespace cryptonote { /* @@ -72,7 +74,9 @@ namespace cryptonote //--------------------------------------------------------------- bool construct_protocol_tx(const size_t height, transaction& tx, std::vector& protocol_data, const uint8_t hf_version); //--------------------------------------------------------------- - bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1); + bool construct_miner_tx(size_t height, size_t median_weight, uint64_t already_generated_coins, size_t current_block_weight, uint64_t fee, const account_public_address &miner_address, transaction& tx, network_type nettype = network_type::FAKECHAIN, const std::vector& hardforks = {}, const blobdata& extra_nonce = blobdata(), size_t max_outs = 999, uint8_t hard_fork_version = 1); + //--------------------------------------------------------------- + std::tuple check_treasury_payout(network_type nettype, uint64_t height, const std::vector& hardforks, const uint8_t hf_version); struct tx_source_entry { diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 68ebfd2..f3935a2 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -149,7 +149,7 @@ void print_genesis_tx_hex(const cryptonote::network_type nettype) { //Prepare genesis_tx cryptonote::transaction tx_genesis; - cryptonote::construct_miner_tx(0, 0, 0, 10, 0, miner_acc1.get_keys().m_account_address, tx_genesis, blobdata(), 999, 1); + cryptonote::construct_miner_tx(0, 0, 0, 10, 0, miner_acc1.get_keys().m_account_address, tx_genesis, (network_type)nettype, {}, blobdata(), 999, 1); std::cout << "Object:" << std::endl; std::cout << obj_to_json_str(tx_genesis) << std::endl << std::endl; diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 44260d2..6b9a578 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -89,6 +89,9 @@ const hardfork_t testnet_hard_forks[] = { // version 9 (audit 1 complete, whitelist controlling payouts) starts from block 1000 { 9, 1000, 0, 1739280000 }, + + // version 10 treasury mint starts from block 1100 + {10, 1100, 0, 1739780005 }, }; const size_t num_testnet_hard_forks = sizeof(testnet_hard_forks) / sizeof(testnet_hard_forks[0]); const uint64_t testnet_hard_fork_version_1_till = ((uint64_t)-1); diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 919ce2e..f70d992 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -272,7 +272,7 @@ bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, co size_t target_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); while (true) { - if (!construct_miner_tx(height, misc_utils::median(block_weights), already_generated_coins, target_block_weight, total_fee, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), 10, hf_ver ? hf_ver.get() : 1)) + if (!construct_miner_tx(height, misc_utils::median(block_weights), already_generated_coins, target_block_weight, total_fee, miner_acc.get_keys().m_account_address, blk.miner_tx, network_type::FAKECHAIN, {}, blobdata(), 10, hf_ver ? hf_ver.get() : 1)) return false; size_t actual_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); @@ -375,7 +375,7 @@ bool test_generator::construct_block_manually(block& blk, const block& prev_bloc { size_t current_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); // TODO: This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - if (!construct_miner_tx(height, misc_utils::median(block_weights), already_generated_coins, current_block_weight, fees, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), max_outs, hf_version)) + if (!construct_miner_tx(height, misc_utils::median(block_weights), already_generated_coins, current_block_weight, fees, miner_acc.get_keys().m_account_address, blk.miner_tx, cryptonote::network_type::FAKECHAIN, {}, blobdata(), max_outs, hf_version)) return false; } diff --git a/tests/core_tests/transaction_tests.cpp b/tests/core_tests/transaction_tests.cpp index fba282d..7d04b2f 100644 --- a/tests/core_tests/transaction_tests.cpp +++ b/tests/core_tests/transaction_tests.cpp @@ -192,7 +192,7 @@ bool test_block_creation() bool r = get_account_address_from_str(info, MAINNET, "0099be99c70ef10fd534c43c88e9d13d1c8853213df7e362afbec0e4ee6fec4948d0c190b58f4b356cd7feaf8d9d0a76e7c7e5a9a0a497a6b1faf7a765882dd08ac2"); CHECK_AND_ASSERT_MES(r, false, "failed to import"); block b; - r = construct_miner_tx(90, epee::misc_utils::median(szs), 3553616528562147, 33094, 10000000, info.address, b.miner_tx, blobdata(), 11); + r = construct_miner_tx(90, epee::misc_utils::median(szs), 3553616528562147, 33094, 10000000, info.address, b.miner_tx, cryptonote::network_type::FAKECHAIN, {}, blobdata(), 11); return r; } diff --git a/tests/unit_tests/account.cpp b/tests/unit_tests/account.cpp index 0ca16fe..cc0d0b4 100644 --- a/tests/unit_tests/account.cpp +++ b/tests/unit_tests/account.cpp @@ -30,6 +30,16 @@ #include "cryptonote_basic/account.h" + +#include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/subaddress_index.h" +#include "cryptonote_basic/cryptonote_format_utils.h" + +#include "string_tools.h" + +using namespace cryptonote; + TEST(account, encrypt_keys) { cryptonote::keypair recovery_key = cryptonote::keypair::generate(hw::get_device("default")); @@ -69,3 +79,48 @@ TEST(account, encrypt_keys) ASSERT_EQ(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); } + + +TEST(account, derive_output_key) +{ + const std::string secret_view = ""; + const std::string addr = ""; + address_parse_info address_info; + bool ok = get_account_address_from_str(address_info, network_type::MAINNET, addr); + ASSERT_TRUE(ok); + + std::unordered_map subaddresses = {{ + {address_info.address.m_spend_public_key, {0, 0}}, + }}; + + // derive keys + std::vector> txkeys; + for (size_t i = 0; i < 16; i++) { + // tx key + keypair txkey = keypair::generate(hw::get_device("default")); + + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); + ok = crypto::generate_key_derivation(address_info.address.m_view_public_key, txkey.sec, derivation); + ASSERT_TRUE(ok); + + ok = crypto::derive_public_key(derivation, 0, address_info.address.m_spend_public_key, out_eph_public_key); + ASSERT_TRUE(ok); + + txkeys.push_back({txkey.pub, out_eph_public_key}); + } + + // validate we can receive them + crypto::secret_key secret_view_key; + epee::string_tools::hex_to_pod(secret_view, secret_view_key); + size_t i = 0; + for (const auto& txkey : txkeys) { + crypto::key_derivation derivation; + hw::device *hw = &hw::get_device("default"); + + ASSERT_TRUE(hw->generate_key_derivation(txkey.first, secret_view_key, derivation)); + ASSERT_TRUE(is_out_to_acc_precomp(subaddresses, txkey.second, derivation, {}, 0, *hw, boost::none)); + i++; + } + +} diff --git a/tests/unit_tests/test_tx_utils.cpp b/tests/unit_tests/test_tx_utils.cpp index 42c82ca..604035a 100644 --- a/tests/unit_tests/test_tx_utils.cpp +++ b/tests/unit_tests/test_tx_utils.cpp @@ -141,7 +141,7 @@ TEST(parse_and_validate_tx_extra, is_valid_tx_extra_parsed) cryptonote::account_base acc; acc.generate(); cryptonote::blobdata b = "dsdsdfsdfsf"; - ASSERT_TRUE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, acc.get_keys().m_account_address, tx, b, 1)); + ASSERT_TRUE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, acc.get_keys().m_account_address, tx, cryptonote::network_type::FAKECHAIN, {}, b, 1)); crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(tx); ASSERT_NE(tx_pub_key, crypto::null_pkey); } @@ -151,7 +151,7 @@ TEST(parse_and_validate_tx_extra, fails_on_big_extra_nonce) cryptonote::account_base acc; acc.generate(); cryptonote::blobdata b(TX_EXTRA_NONCE_MAX_COUNT + 1, 0); - ASSERT_FALSE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, acc.get_keys().m_account_address, tx, b, 1)); + ASSERT_FALSE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, acc.get_keys().m_account_address, tx, cryptonote::network_type::FAKECHAIN, {}, b, 1)); } TEST(parse_and_validate_tx_extra, fails_on_wrong_size_in_extra_nonce) {