Rotating Governance Schedule (#303)

* Add multiple governance keys

* Batch governance payments to every interval blocks

* Batching of gov rewards and fix breakages in tests

* Add changes to api for working with core tests

* Clean up left over debug stuff

* more cleanup

* Fix uninitialised batched governance value

* Remove dependency on blockchain in tx utils

* Remove redundant vector reserve
This commit is contained in:
Doyle 2018-11-12 14:02:21 +11:00 committed by GitHub
parent 36b2d6b2de
commit fad67eee46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 835 additions and 473 deletions

View File

@ -89,7 +89,7 @@ namespace cryptonote {
return CRYPTONOTE_MAX_TX_SIZE;
}
//-----------------------------------------------------------------------------------------------
bool get_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint8_t version, uint64_t height) {
bool get_base_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint8_t version, uint64_t height) {
//premine reward
if (already_generated_coins == 0)

View File

@ -89,7 +89,7 @@ namespace cryptonote {
size_t get_min_block_weight(uint8_t version);
size_t get_max_block_size();
size_t get_max_tx_size();
bool get_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint8_t version, uint64_t height);
bool get_base_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint8_t version, uint64_t height);
uint8_t get_account_address_checksum(const public_address_outer_blob& bl);
uint8_t get_account_integrated_address_checksum(const public_integrated_address_outer_blob& bl);

View File

@ -189,8 +189,12 @@ namespace config
std::string const GENESIS_TX = "021e01ff000380808d93f5d771027c4fd4553bc9886f1f49e3f76d945bf71e8632a94e6c177b19cbc780e7e6bdb48080b4ccd4dfc60302c8b9f6461f58ef3f2107e577c7425d06af584a1c7482bf19060e84059c98b4c3808088fccdbcc32302732b53b0b0db706fcc3087074fb4b786da5ab72b2065699f9453448b0db27f892101ed71f2ce3fc70d7b2036f8a4e4b3fb75c66c12184b55a908e7d1a1d6995566cf00";
uint32_t const GENESIS_NONCE = 1022201;
std::string const GOVERNANCE_WALLET_ADDRESS = "LCFxT37LAogDn1jLQKf4y7aAqfi21DjovX9qyijaLYQSdrxY1U5VGcnMJMjWrD9RhjeK5Lym67wZ73uh9AujXLQ1RKmXEyL";
uint64_t const GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS = ((60 * 60 * 24 * 7) / DIFFICULTY_TARGET_V2);
std::string const GOVERNANCE_WALLET_ADDRESS[] =
{
"LCFxT37LAogDn1jLQKf4y7aAqfi21DjovX9qyijaLYQSdrxY1U5VGcnMJMjWrD9RhjeK5Lym67wZ73uh9AujXLQ1RKmXEyL", // hardfork v7-10
};
namespace testnet
{
uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 156;
@ -205,7 +209,13 @@ namespace config
std::string const GENESIS_TX = "03011e001e01ff00018080c9db97f4fb270259b546996f69aa71abe4238995f41d780ab1abebcac9f00e808f147bdb9e3228420112573af8c309b69a1a646f41b5212ba7d9c4590bf86e04f36c486467cfef9d3d72000000000000000000000000000000000000000000000000000000000000000000";
uint32_t const GENESIS_NONCE = 10001;
std::string const GOVERNANCE_WALLET_ADDRESS = "T6SUprTYE5rQpep9iQFxyPcKVd91DFR1fQ1Qsyqp5eYLiFc8XuYd3reRE71qDL8c3DXioUbDEpDFdaUpetnL37NS1R3rzoKxi";
uint64_t const GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS = 1000;
std::string const GOVERNANCE_WALLET_ADDRESS[] =
{
"T6SUprTYE5rQpep9iQFxyPcKVd91DFR1fQ1Qsyqp5eYLiFc8XuYd3reRE71qDL8c3DXioUbDEpDFdaUpetnL37NS1R3rzoKxi", // hardfork v7-9
"T6TzkJb5EiASaCkcH7idBEi1HSrpSQJE1Zq3aL65ojBMPZvqHNYPTL56i3dncGVNEYCG5QG5zrBmRiVwcg6b1cRM1SRNqbp44", // hardfork v10
};
}
namespace stagenet
@ -222,12 +232,26 @@ namespace config
std::string const GENESIS_TX = "021e01ff000380808d93f5d771027e4490431900c66a6532917ad9e6a1de634a209b708f653097e7b48efc1238c68080b4ccd4dfc60302ba19a224e6474371f9161b2e6271a36d060cbdc2e479ad78f1be64c56576fa07808088fccdbcc32302bccf9c13ba1b5bb02638de6e557acdd46bf48953e42cf98a12d2ad2900cc316121018fc6728d9e3c062d3afae3b2317998d2abee1e12f51271ba1c0d3cdd236b81d200";
uint32_t const GENESIS_NONCE = 10002;
std::string const GOVERNANCE_WALLET_ADDRESS = "59f7FCwYMiwMnFr8HwsnfJ2hK3DYB1tryhjsfmXqEBJojKyqKeNWoaDaZaauoZPiZHUYp2wJuy5s9H96qy4q9xUVCXXHmTU";
uint64_t const GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS = ((60 * 60 * 24 * 7) / DIFFICULTY_TARGET_V2);
std::string const GOVERNANCE_WALLET_ADDRESS[] =
{
"59f7FCwYMiwMnFr8HwsnfJ2hK3DYB1tryhjsfmXqEBJojKyqKeNWoaDaZaauoZPiZHUYp2wJuy5s9H96qy4q9xUVCXXHmTU", // hardfork v7-9
"59f7FCwYMiwMnFr8HwsnfJ2hK3DYB1tryhjsfmXqEBJojKyqKeNWoaDaZaauoZPiZHUYp2wJuy5s9H96qy4q9xUVCXXHmTU", // hardfork v10
};
}
}
namespace cryptonote
{
enum network_version
{
network_version_7 = 7,
network_version_8,
network_version_9_service_nodes, // Proof Of Stake w/ Service Nodes
network_version_10_bulletproofs, // Bulletproofs, Service Node Grace Registration Period, Batched Governance
network_version_11_swarms,
};
enum network_type : uint8_t
{
MAINNET = 0,
@ -238,19 +262,21 @@ namespace cryptonote
};
struct config_t
{
uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX;
uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX;
uint16_t const P2P_DEFAULT_PORT;
uint16_t const RPC_DEFAULT_PORT;
uint16_t const ZMQ_RPC_DEFAULT_PORT;
boost::uuids::uuid const NETWORK_ID;
std::string const GENESIS_TX;
uint32_t const GENESIS_NONCE;
uint64_t CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX;
uint64_t CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
uint64_t CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX;
uint16_t P2P_DEFAULT_PORT;
uint16_t RPC_DEFAULT_PORT;
uint16_t ZMQ_RPC_DEFAULT_PORT;
boost::uuids::uuid NETWORK_ID;
std::string GENESIS_TX;
uint32_t GENESIS_NONCE;
uint64_t GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS;
std::string const *GOVERNANCE_WALLET_ADDRESS;
};
inline const config_t& get_config(network_type nettype)
inline const config_t& get_config(network_type nettype, int hard_fork_version = 7)
{
static const config_t mainnet = {
static config_t mainnet = {
::config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX,
::config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX,
::config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX,
@ -259,9 +285,12 @@ namespace cryptonote
::config::ZMQ_RPC_DEFAULT_PORT,
::config::NETWORK_ID,
::config::GENESIS_TX,
::config::GENESIS_NONCE
::config::GENESIS_NONCE,
::config::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS,
&::config::GOVERNANCE_WALLET_ADDRESS[0],
};
static const config_t testnet = {
static config_t testnet = {
::config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX,
::config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX,
::config::testnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX,
@ -270,9 +299,12 @@ namespace cryptonote
::config::testnet::ZMQ_RPC_DEFAULT_PORT,
::config::testnet::NETWORK_ID,
::config::testnet::GENESIS_TX,
::config::testnet::GENESIS_NONCE
::config::testnet::GENESIS_NONCE,
::config::testnet::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS,
&::config::testnet::GOVERNANCE_WALLET_ADDRESS[0],
};
static const config_t stagenet = {
static config_t stagenet = {
::config::stagenet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX,
::config::stagenet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX,
::config::stagenet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX,
@ -281,14 +313,41 @@ namespace cryptonote
::config::stagenet::ZMQ_RPC_DEFAULT_PORT,
::config::stagenet::NETWORK_ID,
::config::stagenet::GENESIS_TX,
::config::stagenet::GENESIS_NONCE
::config::stagenet::GENESIS_NONCE,
::config::stagenet::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS,
&::config::stagenet::GOVERNANCE_WALLET_ADDRESS[0],
};
switch (nettype)
{
case MAINNET: return mainnet;
case TESTNET: return testnet;
case STAGENET: return stagenet;
case FAKECHAIN: return mainnet;
case MAINNET: case FAKECHAIN:
{
if (nettype == FAKECHAIN)
mainnet.GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS = 100;
return mainnet;
}
case TESTNET:
{
if (hard_fork_version <= network_version_9_service_nodes)
testnet.GOVERNANCE_WALLET_ADDRESS = &::config::testnet::GOVERNANCE_WALLET_ADDRESS[0];
else
testnet.GOVERNANCE_WALLET_ADDRESS = &::config::testnet::GOVERNANCE_WALLET_ADDRESS[1];
return testnet;
}
case STAGENET:
{
if (hard_fork_version <= network_version_9_service_nodes)
stagenet.GOVERNANCE_WALLET_ADDRESS = &::config::stagenet::GOVERNANCE_WALLET_ADDRESS[0];
else
stagenet.GOVERNANCE_WALLET_ADDRESS = &::config::stagenet::GOVERNANCE_WALLET_ADDRESS[1];
return stagenet;
}
default: throw std::runtime_error("Invalid network type");
}
};

View File

@ -94,9 +94,9 @@ static const struct {
time_t time;
} mainnet_hard_forks[] = {
// version 7 from the start of the blockchain, inhereted from Monero mainnet
{ 7, 1, 0, 1503046577 },
{ 8, 64324, 0, 1533006000 },
{ 9, 101250, 0, 1537444800 },
{ network_version_7, 1, 0, 1503046577 },
{ network_version_8, 64324, 0, 1533006000 },
{ network_version_9_service_nodes, 101250, 0, 1537444800 },
};
static const struct {
@ -106,9 +106,9 @@ static const struct {
time_t time;
} testnet_hard_forks[] = {
// version 7 from the start of the blockchain, inhereted from Monero testnet
{ 7, 1, 0, 1533631121 },
{ 8, 2, 0, 1533631122 },
{ 9, 3, 0, 1533631123 },
{ network_version_7, 1, 0, 1533631121 },
{ network_version_8, 2, 0, 1533631122 },
{ network_version_9_service_nodes, 3, 0, 1533631123 },
};
static const struct {
@ -118,9 +118,9 @@ static const struct {
time_t time;
} stagenet_hard_forks[] = {
// version 7 from the start of the blockchain, inhereted from Monero testnet
{ 7, 1, 0, 1341378000 },
{ 8, 64324, 0, 1533006000 },
{ 9, 96210, 0, 1536840000 },
{ network_version_7, 1, 0, 1341378000 },
{ network_version_8, 64324, 0, 1533006000 },
{ network_version_9_service_nodes, 96210, 0, 1536840000 },
};
//------------------------------------------------------------------
@ -1125,84 +1125,68 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
return false;
}
if (version == 3) {
for (auto &o: b.miner_tx.vout) {
if (!is_valid_decomposed_amount(o.amount)) {
MERROR_VER("miner tx output " << print_money(o.amount) << " is not a valid decomposed amount");
return false;
}
}
}
uint64_t height = cryptonote::get_block_height(b);
std::vector<size_t> last_blocks_weights;
get_last_n_blocks_weights(last_blocks_weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW);
if (!get_block_reward(epee::misc_utils::median(last_blocks_weights), cumulative_block_weight, already_generated_coins, base_reward, version, cryptonote::get_block_height(b)))
loki_block_reward_context block_reward_context = {};
block_reward_context.fee = fee;
block_reward_context.height = height;
if (!calc_batched_governance_reward(height, block_reward_context.batched_governance))
{
MERROR_VER("Failed to calculate batched governance reward");
return false;
}
block_reward_parts reward_parts;
if (!get_loki_block_reward(epee::misc_utils::median(last_blocks_weights), cumulative_block_weight, already_generated_coins, version, reward_parts, block_reward_context))
{
MERROR_VER("block weight " << cumulative_block_weight << " is bigger than allowed for this blockchain");
return false;
}
for (ValidateMinerTxHook* hook : m_validate_miner_tx_hooks)
if (!hook->validate_miner_tx(b.prev_id, b.miner_tx, m_db->height(), get_current_hard_fork_version(), base_reward))
return false;
if (already_generated_coins != 0)
{
uint64_t governance_reward = get_governance_reward(m_db->height(), base_reward);
if (!hook->validate_miner_tx(b.prev_id, b.miner_tx, m_db->height(), version, reward_parts))
return false;
}
if (b.miner_tx.vout.back().amount != governance_reward)
if (already_generated_coins != 0 && block_has_governance_output(nettype(), b))
{
if (version >= network_version_10_bulletproofs && reward_parts.governance == 0)
{
MERROR("Governance reward amount incorrect. Should be: " << print_money(governance_reward) << ", is: " << print_money(b.miner_tx.vout.back().amount));
MERROR("Governance reward should not be 0 after hardfork v10 if this height has a governance output because it is the batched payout height");
return false;
}
std::string governance_wallet_address_str;
switch (m_nettype)
if (b.miner_tx.vout.back().amount != reward_parts.governance)
{
case STAGENET:
governance_wallet_address_str = ::config::stagenet::GOVERNANCE_WALLET_ADDRESS;
break;
case TESTNET:
governance_wallet_address_str = ::config::testnet::GOVERNANCE_WALLET_ADDRESS;
break;
case FAKECHAIN: case MAINNET:
governance_wallet_address_str = ::config::GOVERNANCE_WALLET_ADDRESS;
break;
default:
return false;
MERROR("Governance reward amount incorrect. Should be: " << print_money(reward_parts.governance) << ", is: " << print_money(b.miner_tx.vout.back().amount));
return false;
}
if (!validate_governance_reward_key(m_db->height(), governance_wallet_address_str, b.miner_tx.vout.size() - 1, boost::get<txout_to_key>(b.miner_tx.vout.back().target).key, m_nettype))
if (!validate_governance_reward_key(m_db->height(), *cryptonote::get_config(m_nettype, version).GOVERNANCE_WALLET_ADDRESS, b.miner_tx.vout.size() - 1, boost::get<txout_to_key>(b.miner_tx.vout.back().target).key, m_nettype))
{
MERROR("Governance reward public key incorrect.");
return false;
}
}
base_reward = reward_parts.adjusted_base_reward;
if(base_reward + fee < money_in_use)
{
MERROR_VER("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")");
MERROR_VER("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")");
return false;
}
// From hard fork 2, we allow a miner to claim less block reward than is allowed, in case a miner wants less dust
if (m_hardfork->get_current_version() < 2)
{
if(base_reward + fee != money_in_use)
{
MDEBUG("coinbase transaction doesn't use full amount of block reward: spent: " << money_in_use << ", block reward " << base_reward + fee << "(" << base_reward << "+" << fee << ")");
return false;
}
}
else
{
// from hard fork 2, since a miner can claim less than the full block reward, we update the base_reward
// to show the amount of coins that were actually generated, the remainder will be pushed back for later
// emission. This modifies the emission curve very slightly.
CHECK_AND_ASSERT_MES(money_in_use - fee <= base_reward, false, "base reward calculation bug");
if(base_reward + fee != money_in_use)
partial_block_reward = true;
base_reward = money_in_use - fee;
}
// since a miner can claim less than the full block reward, we update the base_reward
// to show the amount of coins that were actually generated, the remainder will be pushed back for later
// emission. This modifies the emission curve very slightly.
CHECK_AND_ASSERT_MES(money_in_use - fee <= base_reward, false, "base reward calculation bug");
if(base_reward != money_in_use)
partial_block_reward = true;
base_reward = money_in_use - fee;
return true;
}
//------------------------------------------------------------------
@ -1365,10 +1349,18 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m
//make blocks coin-base tx looks close to real coinbase tx to get truthful blob weight
uint8_t hf_version = m_hardfork->get_current_version();
crypto::public_key winner = m_service_node_list.select_winner(b.prev_id);
std::vector<std::pair<account_public_address, uint64_t>> service_node_addresses = m_service_node_list.get_winner_addresses_and_portions(b.prev_id);
loki_miner_tx_context miner_tx_context(m_nettype,
m_service_node_list.select_winner(b.prev_id),
m_service_node_list.get_winner_addresses_and_portions(b.prev_id));
if (!calc_batched_governance_reward(height, miner_tx_context.batched_governance))
{
LOG_ERROR("Failed to calculate batched governance reward");
return false;
}
bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, hf_version, miner_tx_context);
bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, hf_version, m_nettype, winner, service_node_addresses);
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)
@ -1377,7 +1369,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m
#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, hf_version, m_nettype, winner, service_node_addresses);
r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, hf_version, miner_tx_context);
CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, second chance");
size_t coinbase_weight = get_transaction_weight(b.miner_tx);
@ -3080,7 +3072,8 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const
{
median = m_current_block_cumul_weight_limit / 2;
already_generated_coins = m_db->height() ? m_db->get_block_already_generated_coins(m_db->height() - 1) : 0;
if (!get_block_reward(median, 1, already_generated_coins, base_reward, version, m_db->height()))
if (!get_base_block_reward(median, 1, already_generated_coins, base_reward, version, m_db->height()))
return false;
}
@ -3143,8 +3136,9 @@ uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const
median = min_block_weight;
uint64_t already_generated_coins = m_db->height() ? m_db->get_block_already_generated_coins(m_db->height() - 1) : 0;
uint64_t base_reward;
if (!get_block_reward(median, 1, already_generated_coins, base_reward, version, m_db->height()))
if (!get_base_block_reward(median, 1, already_generated_coins, base_reward, version, m_db->height()))
{
MERROR("Failed to determine block reward, using placeholder " << print_money(BLOCK_REWARD_OVERESTIMATE) << " as a high bound");
base_reward = BLOCK_REWARD_OVERESTIMATE;
@ -4023,6 +4017,52 @@ uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector
return usable;
}
bool Blockchain::calc_batched_governance_reward(uint64_t height, uint64_t &reward) const
{
reward = 0;
int hard_fork_version = get_ideal_hard_fork_version(height);
if (hard_fork_version <= network_version_9_service_nodes)
{
return true;
}
if (!height_has_governance_output(nettype(), hard_fork_version, height))
{
return true;
}
// Ignore governance reward and payout instead the last
// GOVERNANCE_BLOCK_REWARD_INTERVAL number of blocks governance rewards. We
// come back for this height's rewards in the next interval. The reward is
// 0 if it's not time to pay out the batched payments
const cryptonote::config_t &network = cryptonote::get_config(nettype(), hard_fork_version);
uint64_t num_blocks = network.GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS;
uint64_t start_height = height - num_blocks;
if (height < num_blocks)
{
start_height = 0;
num_blocks = height;
}
std::vector<std::pair<cryptonote::blobdata, cryptonote::block>> blocks;
if (!get_blocks(start_height, num_blocks, blocks))
{
LOG_ERROR("Unable to get historical blocks to calculated batched governance payment");
return false;
}
for (const auto &it : blocks)
{
cryptonote::block const &block = it.second;
if (block.major_version >= network_version_10_bulletproofs)
reward += derive_governance_from_block_reward(nettype(), block);
}
return true;
}
//------------------------------------------------------------------
// ND: Speedups:
// 1. Thread long_hash computations if possible (m_max_prepare_blocks_threads = nthreads, default = 4)

View File

@ -81,14 +81,6 @@ namespace cryptonote
class Blockchain
{
public:
enum version
{
version_7 = 7,
version_8,
version_9,
version_10_swarms,
};
/**
* @brief Now-defunct (TODO: remove) struct from in-memory blockchain
*/
@ -133,7 +125,7 @@ namespace cryptonote
class ValidateMinerTxHook
{
public:
virtual bool validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, uint64_t base_reward) const = 0;
virtual bool validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, block_reward_parts const &reward_parts) const = 0;
};
/**
@ -980,6 +972,8 @@ namespace cryptonote
bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); }
uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes);
bool calc_batched_governance_reward(uint64_t height, uint64_t &reward) const;
void lock();
void unlock();

View File

@ -43,13 +43,14 @@ using namespace epee;
#include "ringct/rctSigs.h"
#include "multisig/multisig.h"
#include "common/int-util.h"
#include "cryptonote_core/service_node_list.h"
using namespace crypto;
namespace cryptonote
{
//---------------------------------------------------------------
void classify_addresses(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::tx_destination_entry>& change_addr, size_t &num_stdaddresses, size_t &num_subaddresses, account_public_address &single_dest_subaddress)
static void classify_addresses(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::tx_destination_entry>& change_addr, size_t &num_stdaddresses, size_t &num_subaddresses, account_public_address &single_dest_subaddress)
{
num_stdaddresses = 0;
num_subaddresses = 0;
@ -101,32 +102,6 @@ namespace cryptonote
return k;
}
uint64_t get_governance_reward(uint64_t height, uint64_t base_reward)
{
return base_reward / 20;
}
uint64_t get_service_node_reward(uint64_t height, uint64_t base_reward, int hard_fork_version)
{
return hard_fork_version >= 9 ? base_reward / 2 : 0;
}
uint64_t get_portion_of_reward(uint64_t portions, uint64_t total_service_node_reward)
{
uint64_t hi, lo, rewardhi, rewardlo;
lo = mul128(total_service_node_reward, portions, &hi);
div128_64(hi, lo, STAKING_PORTIONS, &rewardhi, &rewardlo);
return rewardlo;
}
static uint64_t calculate_sum_of_portions(const std::vector<std::pair<cryptonote::account_public_address, uint64_t>>& portions, uint64_t total_service_node_reward)
{
uint64_t reward = 0;
for (size_t i = 0; i < portions.size(); i++)
reward += get_portion_of_reward(portions[i].second, total_service_node_reward);
return reward;
}
bool get_deterministic_output_key(const account_public_address& address, const keypair& tx_key, size_t output_index, crypto::public_key& output_key)
{
@ -145,21 +120,7 @@ namespace cryptonote
keypair gov_key = get_deterministic_keypair_from_height(height);
cryptonote::address_parse_info governance_wallet_address;
switch (nettype)
{
case STAGENET:
cryptonote::get_account_address_from_str(governance_wallet_address, cryptonote::STAGENET, governance_wallet_address_str);
break;
case TESTNET:
cryptonote::get_account_address_from_str(governance_wallet_address, cryptonote::TESTNET, governance_wallet_address_str);
break;
case FAKECHAIN: case MAINNET:
cryptonote::get_account_address_from_str(governance_wallet_address, cryptonote::MAINNET, governance_wallet_address_str);
break;
default:
return false;
}
cryptonote::get_account_address_from_str(governance_wallet_address, nettype, governance_wallet_address_str);
crypto::public_key correct_key;
if (!get_deterministic_output_key(governance_wallet_address.address, gov_key, output_index, correct_key))
@ -171,7 +132,105 @@ namespace cryptonote
return correct_key == output_key;
}
//---------------------------------------------------------------
const int GOVERNANCE_BASE_REWARD_DIVISOR = 20;
const int SERVICE_NODE_BASE_REWARD_DIVISOR = 2;
uint64_t governance_reward_formula(uint64_t base_reward)
{
return base_reward / GOVERNANCE_BASE_REWARD_DIVISOR;
}
bool block_has_governance_output(network_type nettype, cryptonote::block const &block)
{
bool result = height_has_governance_output(nettype, block.major_version, get_block_height(block));
return result;
}
bool height_has_governance_output(network_type nettype, int hard_fork_version, uint64_t height)
{
if (height == 0)
return false;
if (hard_fork_version <= network_version_9_service_nodes)
return true;
const cryptonote::config_t &network = cryptonote::get_config(nettype, hard_fork_version);
if (height % network.GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS != 0)
{
return false;
}
return true;
}
uint64_t derive_governance_from_block_reward(network_type nettype, const cryptonote::block &block)
{
uint64_t result = 0;
uint64_t height = get_block_height(block);
uint64_t snode_reward = 0;
uint64_t vout_end = block.miner_tx.vout.size();
if (block_has_governance_output(nettype, block))
--vout_end; // skip the governance output, the governance may be the batched amount. we want the original base reward
for (size_t vout_index = 1; vout_index < vout_end; ++vout_index)
{
tx_out const &output = block.miner_tx.vout[vout_index];
snode_reward += output.amount;
}
static_assert(SERVICE_NODE_BASE_REWARD_DIVISOR == 2 &&
GOVERNANCE_BASE_REWARD_DIVISOR == 20,
"Anytime this changes, you should revisit this code and "
"check, because we rely on the service node reward being 50\% "
"of the base reward, and does not receive any fees. This isn't "
"exactly intuitive and so changes to the reward structure may "
"make this assumption invalid.");
uint64_t base_reward = snode_reward * SERVICE_NODE_BASE_REWARD_DIVISOR;
uint64_t governance = governance_reward_formula(base_reward);
uint64_t block_reward = base_reward - governance;
uint64_t actual_reward = 0; // sanity check
for (tx_out const &output : block.miner_tx.vout) actual_reward += output.amount;
CHECK_AND_ASSERT_MES(block_reward <= actual_reward, false,
"Rederiving the base block reward from the service node reward "
"exceeded the actual amount paid in the block, derived block reward: "
<< block_reward << ", actual reward: " << actual_reward);
result = governance;
return result;
}
uint64_t service_node_reward_formula(uint64_t base_reward, int hard_fork_version)
{
return hard_fork_version >= 9 ? (base_reward / SERVICE_NODE_BASE_REWARD_DIVISOR) : 0;
}
uint64_t get_portion_of_reward(uint64_t portions, uint64_t total_service_node_reward)
{
uint64_t hi, lo, rewardhi, rewardlo;
lo = mul128(total_service_node_reward, portions, &hi);
div128_64(hi, lo, STAKING_PORTIONS, &rewardhi, &rewardlo);
return rewardlo;
}
static uint64_t calculate_sum_of_portions(const std::vector<std::pair<cryptonote::account_public_address, uint64_t>>& portions, uint64_t total_service_node_reward)
{
uint64_t reward = 0;
for (size_t i = 0; i < portions.size(); i++)
reward += get_portion_of_reward(portions[i].second, total_service_node_reward);
return reward;
}
loki_miner_tx_context::loki_miner_tx_context(network_type type, crypto::public_key winner, std::vector<std::pair<account_public_address, stake_portions>> winner_info)
: nettype(type)
, snode_winner_key(winner)
, snode_winner_info(winner_info)
, batched_governance(0)
{
}
bool construct_miner_tx(
size_t height,
size_t median_weight,
@ -182,15 +241,20 @@ namespace cryptonote
transaction& tx,
const blobdata& extra_nonce,
uint8_t hard_fork_version,
network_type nettype,
const crypto::public_key& service_node_key,
const std::vector<std::pair<account_public_address, uint64_t>>& service_node_info)
const loki_miner_tx_context &miner_tx_context)
{
tx.vin.clear();
tx.vout.clear();
tx.extra.clear();
tx.output_unlock_times.clear();
tx.is_deregister = false;
tx.version = (hard_fork_version >= network_version_9_service_nodes) ? transaction::version_3_per_output_unlock_times : transaction::version_2;
const network_type nettype = miner_tx_context.nettype;
const crypto::public_key &service_node_key = miner_tx_context.snode_winner_key;
const std::vector<std::pair<account_public_address, uint64_t>> &service_node_info =
miner_tx_context.snode_winner_info.empty() ?
service_nodes::null_winner : miner_tx_context.snode_winner_info;
keypair txkey = keypair::generate(hw::get_device("default"));
add_tx_pub_key_to_extra(tx, txkey.pub);
@ -198,7 +262,7 @@ namespace cryptonote
if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce))
return false;
keypair gov_key = get_deterministic_keypair_from_height(height);
keypair gov_key = get_deterministic_keypair_from_height(height); // NOTE: Always need since we use same key for service node
if (already_generated_coins != 0)
{
add_tx_pub_key_to_extra(tx, gov_key.pub);
@ -209,10 +273,16 @@ namespace cryptonote
txin_gen in;
in.height = height;
uint64_t block_reward;
if(!get_block_reward(median_weight, current_block_weight, already_generated_coins, block_reward, hard_fork_version, height))
loki_block_reward_context block_reward_context = {};
block_reward_context.fee = fee;
block_reward_context.height = height;
block_reward_context.snode_winner_info = miner_tx_context.snode_winner_info;
block_reward_context.batched_governance = miner_tx_context.batched_governance;
block_reward_parts reward_parts;
if(!get_loki_block_reward(median_weight, current_block_weight, already_generated_coins, hard_fork_version, reward_parts, block_reward_context))
{
LOG_PRINT_L0("Block is too big");
LOG_PRINT_L0("Failed to calculate block reward");
return false;
}
@ -221,23 +291,8 @@ namespace cryptonote
", fee " << fee);
#endif
//TODO: declining governance reward schedule
uint64_t governance_reward = 0;
uint64_t total_service_node_reward = 0;
uint64_t total_paid_service_node_reward = 0;
if (already_generated_coins != 0)
{
governance_reward = get_governance_reward(height, block_reward);
total_service_node_reward = get_service_node_reward(height, block_reward, hard_fork_version);
total_paid_service_node_reward = calculate_sum_of_portions(service_node_info, total_service_node_reward);
block_reward -= governance_reward;
block_reward -= total_paid_service_node_reward;
}
block_reward += fee;
uint64_t summary_amounts = 0;
// Miner Reward
{
crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);
crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key);
@ -251,15 +306,14 @@ namespace cryptonote
tk.key = out_eph_public_key;
tx_out out;
summary_amounts += out.amount = block_reward;
summary_amounts += out.amount = reward_parts.miner_reward();
out.target = tk;
tx.vout.push_back(out);
tx.output_unlock_times.push_back(height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW);
}
if (hard_fork_version >= 9)
if (hard_fork_version >= network_version_9_service_nodes) // Service Node Reward
{
tx.version = transaction_prefix::version_3_per_output_unlock_times;
for (size_t i = 0; i < service_node_info.size(); i++)
{
crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);
@ -273,70 +327,112 @@ namespace cryptonote
tk.key = out_eph_public_key;
tx_out out;
summary_amounts += out.amount = get_portion_of_reward(service_node_info[i].second, total_service_node_reward);
summary_amounts += out.amount = get_portion_of_reward(service_node_info[i].second, reward_parts.service_node_total);
out.target = tk;
tx.vout.push_back(out);
tx.output_unlock_times.push_back(height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW);
}
}
else
{
tx.version = transaction_prefix::version_2;
}
// Governance Distribution
if (already_generated_coins != 0)
{
std::string governance_wallet_address_str;
cryptonote::address_parse_info governance_wallet_address;
switch (nettype)
if (reward_parts.governance == 0)
{
case STAGENET:
cryptonote::get_account_address_from_str(governance_wallet_address, cryptonote::STAGENET, ::config::stagenet::GOVERNANCE_WALLET_ADDRESS);
break;
case TESTNET:
cryptonote::get_account_address_from_str(governance_wallet_address, cryptonote::TESTNET, ::config::testnet::GOVERNANCE_WALLET_ADDRESS);
break;
case FAKECHAIN: case MAINNET:
cryptonote::get_account_address_from_str(governance_wallet_address, cryptonote::MAINNET, ::config::GOVERNANCE_WALLET_ADDRESS);
break;
default:
CHECK_AND_ASSERT_MES(hard_fork_version >= network_version_10_bulletproofs, false, "Governance reward can NOT be 0 before hardfork 10, hard_fork_version: " << hard_fork_version);
}
else
{
std::string governance_wallet_address_str;
cryptonote::address_parse_info governance_wallet_address;
cryptonote::get_account_address_from_str(governance_wallet_address, nettype, *cryptonote::get_config(nettype, hard_fork_version).GOVERNANCE_WALLET_ADDRESS);
crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key);
if (!get_deterministic_output_key(governance_wallet_address.address, gov_key, tx.vout.size(), out_eph_public_key))
{
MERROR("Failed to generate deterministic output key for governance wallet output creation");
return false;
}
txout_to_key tk;
tk.key = out_eph_public_key;
tx_out out;
summary_amounts += out.amount = reward_parts.governance;
out.target = tk;
tx.vout.push_back(out);
tx.output_unlock_times.push_back(height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW);
}
crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key);
if (!get_deterministic_output_key(governance_wallet_address.address, gov_key, tx.vout.size(), out_eph_public_key))
{
MERROR("Failed to generate deterministic output key for governance wallet output creation");
return false;
}
txout_to_key tk;
tk.key = out_eph_public_key;
tx_out out;
summary_amounts += out.amount = governance_reward;
out.target = tk;
tx.vout.push_back(out);
tx.output_unlock_times.push_back(height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW);
}
CHECK_AND_ASSERT_MES(summary_amounts == (block_reward + governance_reward + total_paid_service_node_reward), false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal total block_reward = " << (block_reward + governance_reward + total_paid_service_node_reward));
uint64_t expected_amount = reward_parts.miner_reward() + reward_parts.governance + reward_parts.service_node_paid;
CHECK_AND_ASSERT_MES(summary_amounts == expected_amount, false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal total block_reward = " << expected_amount);
//lock
tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW;
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)
// << "), current_block_size=" << current_block_size << ", already_generated_coins=" << already_generated_coins << ", tx_id=" << get_transaction_hash(tx), LOG_LEVEL_2);
return true;
}
//---------------------------------------------------------------
bool get_loki_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, int hard_fork_version, block_reward_parts &result, const loki_block_reward_context &loki_context)
{
result = {};
uint64_t base_reward;
if (!get_base_block_reward(median_weight, current_block_weight, already_generated_coins, base_reward, hard_fork_version, loki_context.height))
{
MERROR("Failed to calculate base block reward");
return false;
}
if (base_reward == 0)
{
MERROR("Unexpected base reward of 0");
return false;
}
if (already_generated_coins == 0)
{
result.original_base_reward = result.adjusted_base_reward = result.base_miner = base_reward;
return true;
}
//TODO: declining governance reward schedule
result.original_base_reward = base_reward;
result.service_node_total = service_node_reward_formula(base_reward, hard_fork_version);
if (loki_context.snode_winner_info.empty()) result.service_node_paid = calculate_sum_of_portions(service_nodes::null_winner, result.service_node_total);
else result.service_node_paid = calculate_sum_of_portions(loki_context.snode_winner_info, result.service_node_total);
// TODO(loki): Having the snode total and paid was probably just a sanity check? If so we can remove the field ..
assert(result.service_node_total == result.service_node_paid);
result.adjusted_base_reward = result.original_base_reward;
if (hard_fork_version >= network_version_10_bulletproofs)
{
// NOTE: After hardfork 10, remove the governance component in the base
// reward as they are not included and batched into a later block. If we
// calculated a (governance reward > 0), then this is the batched height,
// add it to the adjusted base reward afterwards
result.governance = loki_context.batched_governance;
result.adjusted_base_reward -= governance_reward_formula(result.original_base_reward);
if (result.governance > 0)
result.adjusted_base_reward += result.governance;
}
else
{
result.governance = governance_reward_formula(result.original_base_reward);
}
result.base_miner = result.adjusted_base_reward - (result.governance + result.service_node_paid);
result.base_miner_fee = loki_context.fee;
return true;
}
crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::tx_destination_entry>& change_addr)
{
account_public_address addr = {null_pkey, null_pkey};

View File

@ -37,6 +37,32 @@
namespace cryptonote
{
//---------------------------------------------------------------
keypair get_deterministic_keypair_from_height(uint64_t height);
bool get_deterministic_output_key (const account_public_address& address, const keypair& tx_key, size_t output_index, crypto::public_key& output_key);
bool validate_governance_reward_key (uint64_t height, const std::string& governance_wallet_address_str, size_t output_index, const crypto::public_key& output_key, const cryptonote::network_type nettype);
uint64_t governance_reward_formula (uint64_t base_reward);
bool block_has_governance_output (network_type nettype, cryptonote::block const &block);
bool height_has_governance_output (network_type nettype, int hard_fork_version, uint64_t height);
uint64_t derive_governance_from_block_reward (network_type nettype, const cryptonote::block &block);
uint64_t get_portion_of_reward (uint64_t portions, uint64_t total_service_node_reward);
uint64_t service_node_reward_formula (uint64_t base_reward, int hard_fork_version);
struct loki_miner_tx_context // NOTE(loki): All the custom fields required by Loki to use construct_miner_tx
{
using stake_portions = uint64_t;
loki_miner_tx_context(network_type type = MAINNET,
crypto::public_key winner = crypto::null_pkey,
std::vector<std::pair<account_public_address, stake_portions>> winner_info = {});
network_type nettype;
crypto::public_key snode_winner_key;
std::vector<std::pair<account_public_address, stake_portions>> snode_winner_info; // NOTE: If empty we use service_nodes::null_winner
uint64_t batched_governance; // NOTE: 0 until hardfork v10, then use blockchain::calc_batched_governance_reward
};
bool construct_miner_tx(
size_t height,
size_t median_weight,
@ -47,21 +73,51 @@ namespace cryptonote
transaction& tx,
const blobdata& extra_nonce = blobdata(),
uint8_t hard_fork_version = 1,
network_type nettype = MAINNET,
const crypto::public_key& service_node_key = crypto::null_pkey,
const std::vector<std::pair<account_public_address, uint64_t>>& service_node_info={ std::pair<account_public_address, uint64_t>({ crypto::null_pkey, crypto::null_pkey }, STAKING_PORTIONS) }
);
const loki_miner_tx_context &miner_context = {});
keypair get_deterministic_keypair_from_height(uint64_t height);
struct block_reward_parts
{
// TODO(loki): There can be a difference between the total reward and the
// reward paid out to the service node? This would mean that a user can
// specify less portions but still contribute the full amount?
// Or was this just a sanity check? I don't think the first case is possible
uint64_t service_node_total;
uint64_t service_node_paid;
uint64_t get_portion_of_reward(uint64_t portions, uint64_t total_service_node_reward);
uint64_t governance;
uint64_t base_miner;
uint64_t base_miner_fee;
uint64_t get_governance_reward(uint64_t height, uint64_t base_reward);
uint64_t get_service_node_reward(uint64_t height, uint64_t base_reward, int hard_fork_version);
// NOTE: Post hardfork 10, adjusted base reward is the block reward with the
// governance amount removed. We still need the original base reward, so
// that we can calculate the 50% on the whole base amount, that should be
// allocated for the service node and fees.
bool get_deterministic_output_key(const account_public_address& address, const keypair& tx_key, size_t output_index, crypto::public_key& output_key);
// If this block contains the batched governance payment, this is
// included in the adjusted base reward.
bool validate_governance_reward_key(uint64_t height, const std::string& governance_wallet_address_str, size_t output_index, const crypto::public_key& output_key, const cryptonote::network_type nettype);
// Before hardfork 10, this is the same value as original_base_reward
uint64_t adjusted_base_reward;
uint64_t original_base_reward;
uint64_t miner_reward() { return base_miner + base_miner_fee; }
};
struct loki_block_reward_context
{
using portions = uint64_t;
uint64_t height;
uint64_t fee;
uint64_t batched_governance; // Optional: 0 hardfork v10, then must be calculated using blockchain::calc_batched_governance_reward
std::vector<std::pair<account_public_address, portions>> snode_winner_info; // Optional: Check contributor portions add up, else set empty to use service_nodes::null_winner
};
// NOTE(loki): I would combine this into get_base_block_reward, but
// cryptonote_basic as a library is to be able to trivially link with
// cryptonote_core since it would have a circular dependency on Blockchain
// NOTE: Block reward function that should be called after hard fork v10
bool get_loki_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, int hard_fork_version, block_reward_parts &result, const loki_block_reward_context &loki_context);
struct tx_source_entry
{

View File

@ -429,7 +429,7 @@ namespace service_nodes
if (iter != m_service_nodes_infos.end())
{
int hard_fork_version = m_blockchain.get_hard_fork_version(block_height);
if (hard_fork_version >= cryptonote::Blockchain::version_10_swarms)
if (hard_fork_version >= cryptonote::network_version_10_bulletproofs)
{
service_node_info const &old_info = iter->second;
uint64_t expiry_height = old_info.registration_height + get_staking_requirement_lock_blocks(m_blockchain.nettype());
@ -672,13 +672,13 @@ namespace service_nodes
int hard_fork_version = m_blockchain.get_hard_fork_version(block_height);
uint64_t lock_blocks = get_staking_requirement_lock_blocks(m_blockchain.nettype());
if (hard_fork_version >= cryptonote::Blockchain::version_10_swarms)
if (hard_fork_version >= cryptonote::network_version_10_bulletproofs)
lock_blocks += STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
if (block_height < lock_blocks)
return expired_nodes;
if (hard_fork_version >= cryptonote::Blockchain::version_10_swarms)
if (hard_fork_version >= cryptonote::network_version_10_bulletproofs)
{
for (auto &it : m_service_nodes_infos)
{
@ -775,13 +775,18 @@ namespace service_nodes
/// validates the miner TX for the next block
//
bool service_node_list::validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, uint64_t base_reward) const
bool service_node_list::validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, cryptonote::block_reward_parts const &reward_parts) const
{
std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex);
if (hard_fork_version < 9)
return true;
uint64_t total_service_node_reward = cryptonote::get_service_node_reward(height, base_reward, hard_fork_version);
// NOTE(loki): Service node reward distribution is calculated from the
// original amount, i.e. 50% of the original base reward goes to service
// nodes not 50% of the reward after removing the governance component (the
// adjusted base reward post hardfork 10).
uint64_t base_reward = reward_parts.original_base_reward;
uint64_t total_service_node_reward = cryptonote::service_node_reward_formula(base_reward, hard_fork_version);
crypto::public_key winner = select_winner(prev_id);
@ -796,8 +801,7 @@ namespace service_nodes
for (size_t i = 0; i < addresses_and_portions.size(); i++)
{
size_t vout_index = miner_tx.vout.size() - 1 /* governance */ - addresses_and_portions.size() + i;
size_t vout_index = i + 1;
uint64_t reward = cryptonote::get_portion_of_reward(addresses_and_portions[i].second, total_service_node_reward);
if (miner_tx.vout[vout_index].amount != reward)

View File

@ -125,7 +125,7 @@ namespace service_nodes
void blockchain_detached(uint64_t height) override;
void register_hooks(service_nodes::quorum_cop &quorum_cop);
void init() override;
bool validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, uint64_t base_reward) const override;
bool validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, cryptonote::block_reward_parts const &base_reward) const override;
std::vector<std::pair<cryptonote::account_public_address, uint64_t>> get_winner_addresses_and_portions(const crypto::hash& prev_id) const;
crypto::public_key select_winner(const crypto::hash& prev_id) const;
@ -300,6 +300,8 @@ namespace service_nodes
inline uint64_t get_min_node_contribution(uint64_t staking_requirement, uint64_t total_reserved) { return std::min(staking_requirement - total_reserved, staking_requirement / MAX_NUMBER_OF_CONTRIBUTORS); }
const static cryptonote::account_public_address null_address{ crypto::null_pkey, crypto::null_pkey };
const static std::vector<std::pair<cryptonote::account_public_address, uint64_t>> null_winner =
{std::pair<cryptonote::account_public_address, uint64_t>({null_address, STAKING_PORTIONS})};
}
VARIANT_TAG(binary_archive, service_nodes::service_node_list::data_members_for_serialization, 0xa0);

View File

@ -1266,8 +1266,17 @@ namespace cryptonote
fee = 0;
//baseline empty block
get_block_reward(median_weight, total_weight, already_generated_coins, best_coinbase, version, height);
loki_block_reward_context block_reward_context = {};
block_reward_context.height = height;
if (!m_blockchain.calc_batched_governance_reward(height, block_reward_context.batched_governance))
{
MERROR("Failed to calculated batched governance reward");
return false;
}
block_reward_parts reward_parts = {};
get_loki_block_reward(median_weight, total_weight, already_generated_coins, version, reward_parts, block_reward_context);
best_coinbase = reward_parts.base_miner;
size_t max_total_weight = 2 * median_weight - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
std::unordered_set<crypto::key_image> k_images;
@ -1297,14 +1306,15 @@ namespace cryptonote
// start using the optimal filling algorithm from v5
if (version >= 5)
{
// If we're getting lower coinbase tx,
// stop including more tx
uint64_t block_reward;
if(!get_block_reward(median_weight, total_weight + meta.weight, already_generated_coins, block_reward, version, height))
// If we're getting lower coinbase tx, stop including more tx
block_reward_parts reward_parts_other = {};
if(!get_loki_block_reward(median_weight, total_weight + meta.weight, already_generated_coins, version, reward_parts_other, block_reward_context))
{
LOG_PRINT_L2(" would exceed maximum block weight");
continue;
}
uint64_t block_reward = reward_parts_other.base_miner;
coinbase = block_reward + fee + meta.fee;
if (coinbase < template_accept_threshold(best_coinbase))
{

View File

@ -2074,7 +2074,7 @@ static void print_service_node_list_state(cryptonote::network_type nettype, int
// Print Expiry Info
{
uint64_t expiry_height = entry.registration_height + service_nodes::get_staking_requirement_lock_blocks(nettype);
if (hard_fork_version >= cryptonote::Blockchain::version_10_swarms)
if (hard_fork_version >= cryptonote::network_version_10_bulletproofs)
expiry_height += STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
if (curr_height)

View File

@ -5199,7 +5199,7 @@ bool simple_wallet::register_service_node_main(
if (response.service_node_states.size() >= 1)
{
bool can_reregister = false;
if (m_wallet->use_fork_rules(cryptonote::Blockchain::version_10_swarms, 0))
if (m_wallet->use_fork_rules(cryptonote::network_version_10_bulletproofs, 0))
{
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry const &node_info = response.service_node_states[0];
uint64_t expiry_height = node_info.registration_height + staking_requirement_lock_blocks;

View File

@ -272,3 +272,69 @@ bool gen_block_reward::check_block_rewards(cryptonote::core& /*c*/, size_t /*ev_
return true;
}
gen_batched_governance_reward::gen_batched_governance_reward()
{
REGISTER_CALLBACK_METHOD(gen_batched_governance_reward, check_batched_governance_amount_matches);
}
static uint64_t expected_total_governance_paid = 0;
bool gen_batched_governance_reward::generate(std::vector<test_event_entry>& events) const
{
const get_test_options<gen_batched_governance_reward> test_options = {};
const config_t &network = cryptonote::get_config(cryptonote::FAKECHAIN, network_version_10_bulletproofs);
linear_chain_generator batched_governance_generator(events);
{
batched_governance_generator.rewind_until_version(test_options.hard_forks, network_version_10_bulletproofs);
uint64_t blocks_to_gen = network.GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS - batched_governance_generator.height();
batched_governance_generator.rewind_blocks_n(blocks_to_gen);
}
{
// NOTE(loki): Since hard fork 8 we have an emissions curve change, so if
// you don't atleast progress and generate blocks from hf8 you will run into
// problems
std::vector<test_event_entry> unused_events;
linear_chain_generator no_batched_governance_generator(unused_events);
no_batched_governance_generator.rewind_until_version(test_options.hard_forks, network_version_9_service_nodes);
while(no_batched_governance_generator.height() < batched_governance_generator.height())
no_batched_governance_generator.create_block();
// NOTE(loki): Skip the last block as that is the batched payout height, we
// don't include the governance reward of that height, that gets picked up
// in the next batch.
const std::vector<cryptonote::block>& blockchain = no_batched_governance_generator.blocks();
for (size_t block_height = 1; block_height < blockchain.size() - 1; ++block_height)
{
const cryptonote::block &block = blockchain[block_height];
expected_total_governance_paid += block.miner_tx.vout.back().amount;
}
}
DO_CALLBACK(events, "check_batched_governance_amount_matches");
return true;
}
bool gen_batched_governance_reward::check_batched_governance_amount_matches(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
DEFINE_TESTS_ERROR_CONTEXT("gen_batched_governance_reward::check_batched_governance_amount_matches");
uint64_t height = c.get_current_blockchain_height();
std::vector<cryptonote::block> blockchain;
if (!c.get_blocks((uint64_t)0, (size_t)height, blockchain))
return false;
uint64_t governance = 0;
for (size_t block_height = 1; block_height < blockchain.size(); ++block_height)
{
const cryptonote::block &block = blockchain[block_height];
if (cryptonote::block_has_governance_output(cryptonote::FAKECHAIN, block))
governance += block.miner_tx.vout.back().amount;
}
CHECK_EQ(governance, expected_total_governance_paid);
return true;
}

View File

@ -47,3 +47,19 @@ private:
size_t m_invalid_block_index;
std::vector<size_t> m_checked_blocks_indices;
};
struct gen_batched_governance_reward : public test_chain_unit_base
{
gen_batched_governance_reward();
bool generate(std::vector<test_event_entry>& events) const;
bool check_batched_governance_amount_matches(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
};
template<> struct get_test_options<gen_batched_governance_reward>
{
const std::vector<std::pair<uint8_t, uint64_t>> hard_forks = { std::make_pair(cryptonote::network_version_7, 0),
std::make_pair(cryptonote::network_version_8, 1),
std::make_pair(cryptonote::network_version_9_service_nodes, 2),
std::make_pair(cryptonote::network_version_10_bulletproofs, 3) };
const cryptonote::test_options test_options = { hard_forks };
};

View File

@ -409,6 +409,72 @@ bool gen_block_miner_tx_has_no_out::generate(std::vector<test_event_entry>& even
return true;
}
static bool construct_miner_tx_with_extra_output(cryptonote::transaction& tx,
const cryptonote::account_public_address& miner_address,
size_t height,
uint64_t already_generated_coins,
const cryptonote::account_public_address& extra_address)
{
keypair txkey = keypair::generate(hw::get_device("default"));
add_tx_pub_key_to_extra(tx, txkey.pub);
keypair gov_key = get_deterministic_keypair_from_height(height);
if (already_generated_coins != 0) {
add_tx_pub_key_to_extra(tx, gov_key.pub);
}
txin_gen in;
in.height = height;
tx.vin.push_back(in);
// This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE
const int hard_fork_version = 7; // NOTE(loki): We know this test doesn't need the new block reward formula
uint64_t block_reward;
if (!get_base_block_reward(0, 0, already_generated_coins, block_reward, 1, 0)) {
LOG_PRINT_L0("Block is too big");
return false;
}
uint64_t governance_reward = 0;
if (already_generated_coins != 0) {
governance_reward = governance_reward_formula(block_reward);
block_reward -= governance_reward;
}
tx.version = 1;
tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW;
/// half of the miner reward goes to the other account
const auto miner_reward = block_reward / 2;
/// miner reward
tx.vout.push_back({miner_reward, get_output_key(txkey, miner_address, 0)});
/// extra reward
tx.vout.push_back({miner_reward, get_output_key(txkey, extra_address, 1)});
/// governance reward
if (already_generated_coins != 0) {
const cryptonote::network_type nettype = cryptonote::FAKECHAIN;
cryptonote::address_parse_info governance_wallet_address;
cryptonote::get_account_address_from_str(governance_wallet_address, nettype, *cryptonote::get_config(nettype, hard_fork_version).GOVERNANCE_WALLET_ADDRESS);
crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key);
if (!get_deterministic_output_key(
governance_wallet_address.address, gov_key, tx.vout.size(), out_eph_public_key)) {
MERROR("Failed to generate deterministic output key for governance wallet output creation");
return false;
}
tx.vout.push_back({governance_reward, out_eph_public_key});
tx.output_unlock_times.push_back(height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW);
}
return true;
}
bool gen_block_miner_tx_has_out_to_alice::generate(std::vector<test_event_entry>& events) const
{
BLOCK_VALIDATION_INIT_GENERATE();

View File

@ -64,7 +64,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve
for (size_t i = 0; i < NUM_MINERS; ++i)
miner_accounts[i].generate();
generator.set_hf_version(8);
generator.m_hf_version = 8;
for (size_t n = 0; n < NUM_UNLOCKED_BLOCKS; ++n) {
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, miner_accounts[n % NUM_MINERS],
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version,
@ -193,7 +193,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(rct_txes);
generator.set_hf_version(10);
generator.m_hf_version = 10;
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes, blk_last, miner_account,
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version,
10, 10, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
@ -228,9 +228,25 @@ bool gen_bp_tx_validation_base::check_bp(const cryptonote::transaction &tx, size
return true;
}
// TODO(doyle): Revisit this. Is there some rule prohibiting a tx fee greater
// than the block reward? Monero is unaffected because they have multiple
// outputs of varying sizes in their miner tx, so the tx fee (inputs-outputs)
// (because they don't use a change addr) doesn't eclipse the reward and doesn't
// trigger the "base reward calculation bug" assert, whereas we do since we only
// have 1 output. So my fix is to make it so we don't generate a tx that makes
// too high of a fee from the change amount.
// Further addendum. In Loki hardfork 10, we also introduce batching governance
// payments- so most block heights will remove the governance output from the
// reward. So if we send less than the governance amount (~6ish loki from the
// start of the chain), then we'll eclipse the reward again and overflow, so
// most of these tests have again been modified to ensure that we use atleast
// 6 loki from the block reward.
// - 2018/10/29
bool gen_bp_tx_valid_1::generate(std::vector<test_event_entry>& events) const
{
const uint64_t amounts_paid[] = {10, (uint64_t)-1};
const uint64_t amounts_paid[] = {MK_COINS(10), (uint64_t)-1};
const size_t bp_sizes[] = {1, (size_t)-1};
const rct::RangeProofType range_proof_type[] = {rct::RangeProofPaddedBulletproof};
return generate_with(events, 1, amounts_paid, true, range_proof_type, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_1"); });
@ -245,7 +261,7 @@ bool gen_bp_tx_invalid_1_1::generate(std::vector<test_event_entry>& events) cons
bool gen_bp_tx_valid_2::generate(std::vector<test_event_entry>& events) const
{
const uint64_t amounts_paid[] = {5, 5, (uint64_t)-1};
const uint64_t amounts_paid[] = {MK_COINS(5), MK_COINS(5), (uint64_t)-1};
const size_t bp_sizes[] = {2, (size_t)-1};
const rct::RangeProofType range_proof_type[] = {rct::RangeProofPaddedBulletproof};
return generate_with(events, 1, amounts_paid, true, range_proof_type, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_2"); });
@ -253,7 +269,8 @@ bool gen_bp_tx_valid_2::generate(std::vector<test_event_entry>& events) const
bool gen_bp_tx_valid_3::generate(std::vector<test_event_entry>& events) const
{
const uint64_t amounts_paid[] = {50, 50, 50, (uint64_t)-1};
// const uint64_t amounts_paid[] = {50, 50, 50, (uint64_t)-1};
const uint64_t amounts_paid[] = {MK_COINS(28), MK_COINS(28), MK_COINS(28), (uint64_t)-1};
const size_t bp_sizes[] = {4, (size_t)-1};
const rct::RangeProofType range_proof_type[] = { rct::RangeProofPaddedBulletproof };
return generate_with(events, 1, amounts_paid, true, range_proof_type, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_3"); });
@ -261,7 +278,8 @@ bool gen_bp_tx_valid_3::generate(std::vector<test_event_entry>& events) const
bool gen_bp_tx_valid_16::generate(std::vector<test_event_entry>& events) const
{
const uint64_t amounts_paid[] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, (uint64_t)-1};
// const uint64_t amounts_paid[] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, (uint64_t)-1};
const uint64_t amounts_paid[] = {MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), MK_COINS(1), (uint64_t)-1};
const size_t bp_sizes[] = {16, (size_t)-1};
const rct::RangeProofType range_proof_type[] = { rct::RangeProofPaddedBulletproof };
return generate_with(events, 1, amounts_paid, true, range_proof_type, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_16"); });
@ -283,14 +301,6 @@ bool gen_bp_tx_invalid_16_16::generate(std::vector<test_event_entry>& events) co
bool gen_bp_txs_valid_2_and_2::generate(std::vector<test_event_entry>& events) const
{
// TODO(doyle): Revisit this. Is there some rule prohibiting a tx fee greater
// than the block reward? Monero is unaffected because they have multiple
// outputs of varying sizes, so the tx fee (inputs-outputs) (because they
// don't use a change addr) doesn't eclipse the reward and doesn't trigger the
// "base reward calculation bug" assert, whereas we do since we only have
// 1 output. So my fix is to make it so we don't generate a tx that makes too
// high of a fee from the change amount - 2018/09/09
//const uint64_t amounts_paid[] = {1000, 1000, (size_t)-1, 1000, 1000, (uint64_t)-1};
const uint64_t amounts_paid[] = {MK_COINS(50), MK_COINS(50), (size_t)-1, MK_COINS(50), MK_COINS(50), (uint64_t)-1};

View File

@ -88,11 +88,35 @@ void linear_chain_generator::create_block(const std::vector<cryptonote::transact
blocks_.push_back(blk);
}
void linear_chain_generator::rewind_until_version(const std::vector<std::pair<uint8_t, uint64_t>> &hard_forks, int hard_fork_version)
{
if (hard_forks.size() > 1)
{
gen_.m_hf_version = hard_forks[0].first;
if (blocks_.size() == 0) create_genesis_block();
for (size_t i = 0; i < hard_forks.size() - 1 && gen_.m_hf_version != hard_fork_version; ++i)
{
uint64_t curr_fork_height = hard_forks[i].second;
uint64_t next_fork_height = hard_forks[i + 1].second;
assert(next_fork_height > curr_fork_height);
uint64_t blocks_till_next_hardfork = next_fork_height - curr_fork_height;
rewind_blocks_n(blocks_till_next_hardfork - 1);
gen_.m_hf_version = hard_forks[i + 1].first;
create_block();
}
assert(gen_.m_hf_version == hard_fork_version);
}
}
void linear_chain_generator::rewind_until_v9()
{
gen_.set_hf_version(8);
gen_.m_hf_version = 8;
create_block();
gen_.set_hf_version(9);
gen_.m_hf_version = 9;
create_block();
}
@ -336,6 +360,25 @@ void test_generator::get_block_chain(std::vector<block_info>& blockchain, const
std::reverse(blockchain.begin(), blockchain.end());
}
// TODO(loki): Copypasta
void test_generator::get_block_chain(std::vector<cryptonote::block>& blockchain, const crypto::hash& head, size_t n) const
{
crypto::hash curr = head;
while (null_hash != curr && blockchain.size() < n)
{
auto it = m_blocks_info.find(curr);
if (m_blocks_info.end() == it)
{
throw std::runtime_error("block hash wasn't found");
}
blockchain.push_back(it->second.block);
curr = it->second.prev_id;
}
std::reverse(blockchain.begin(), blockchain.end());
}
void test_generator::get_last_n_block_weights(std::vector<size_t>& block_weights, const crypto::hash& head, size_t n) const
{
std::vector<block_info> blockchain;
@ -365,9 +408,45 @@ uint64_t test_generator::get_already_generated_coins(const cryptonote::block& bl
void test_generator::add_block(const cryptonote::block& blk, size_t txs_weight, std::vector<size_t>& block_weights, uint64_t already_generated_coins)
{
const size_t block_weight = txs_weight + get_transaction_weight(blk.miner_tx);
uint64_t block_reward;
cryptonote::get_block_reward(misc_utils::median(block_weights), block_weight, already_generated_coins, block_reward, hf_version_, 0);
m_blocks_info.insert({get_block_hash(blk), block_info(blk.prev_id, already_generated_coins + block_reward, block_weight)});
cryptonote::get_base_block_reward(misc_utils::median(block_weights), block_weight, already_generated_coins, block_reward, m_hf_version, 0);
m_blocks_info.insert({get_block_hash(blk), block_info(blk.prev_id, already_generated_coins + block_reward, block_weight, blk)});
}
static void manual_calc_batched_governance(const test_generator &generator, const crypto::hash &head, loki_miner_tx_context &miner_tx_context, int hard_fork_version, uint64_t height)
{
miner_tx_context.batched_governance = 0;
if (hard_fork_version >= cryptonote::network_version_10_bulletproofs &&
cryptonote::height_has_governance_output(cryptonote::FAKECHAIN, hard_fork_version, height))
{
const cryptonote::config_t &network = cryptonote::get_config(cryptonote::FAKECHAIN, hard_fork_version);
uint64_t num_blocks = network.GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS;
uint64_t start_height = height - num_blocks;
if (height < num_blocks)
{
start_height = 0;
num_blocks = height;
}
std::vector<block> blockchain;
blockchain.reserve(num_blocks);
generator.get_block_chain(blockchain, head, num_blocks);
for (const block &entry : blockchain)
{
uint64_t block_height = cryptonote::get_block_height(entry);
if (block_height < start_height)
continue;
if (entry.major_version >= network_version_10_bulletproofs)
miner_tx_context.batched_governance += cryptonote::derive_governance_from_block_reward(cryptonote::FAKECHAIN, entry);
}
}
}
bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, const crypto::hash& prev_id,
@ -376,8 +455,8 @@ bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, co
const crypto::public_key& sn_pub_key /* = crypto::null_key */, const std::vector<sn_contributor_t>& sn_infos)
{
/// a temporary workaround
blk.major_version = hf_version_;
blk.minor_version = hf_version_;
blk.major_version = m_hf_version;
blk.minor_version = m_hf_version;
blk.timestamp = timestamp;
blk.prev_id = prev_id;
@ -402,9 +481,13 @@ bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, co
blk.miner_tx = AUTO_VAL_INIT(blk.miner_tx);
size_t target_block_weight = txs_weight + get_transaction_weight(blk.miner_tx);
cryptonote::loki_miner_tx_context miner_tx_context(cryptonote::FAKECHAIN, sn_pub_key, sn_infos);
manual_calc_batched_governance(*this, prev_id, miner_tx_context, m_hf_version, height);
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(), hf_version_, cryptonote::MAINNET, sn_pub_key, sn_infos))
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(), m_hf_version, miner_tx_context))
return false;
size_t actual_block_weight = txs_weight + get_transaction_weight(blk.miner_tx);
@ -504,9 +587,12 @@ bool test_generator::construct_block_manually(block& blk, const block& prev_bloc
}
else
{
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, 0, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), hf_version_))
cryptonote::loki_miner_tx_context miner_tx_context(cryptonote::FAKECHAIN);
manual_calc_batched_governance(*this, prev_id, miner_tx_context, m_hf_version, height);
size_t current_block_weight = txs_weight + get_transaction_weight(blk.miner_tx);
if (!construct_miner_tx(height, misc_utils::median(block_weights), already_generated_coins, current_block_weight, 0, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), m_hf_version, miner_tx_context))
return false;
}
@ -975,127 +1061,17 @@ void fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t
blk.timestamp++;
}
static crypto::public_key get_output_key(const keypair& txkey,
const cryptonote::account_public_address& addr,
size_t output_index)
crypto::public_key get_output_key(const keypair& txkey,
const cryptonote::account_public_address& addr,
size_t output_index)
{
crypto::key_derivation derivation;
crypto::generate_key_derivation(addr.m_view_public_key, txkey.sec, derivation);
crypto::public_key out_eph_public_key;
crypto::derive_public_key(derivation, 1, addr.m_spend_public_key, out_eph_public_key);
crypto::derive_public_key(derivation, output_index, addr.m_spend_public_key, out_eph_public_key);
return out_eph_public_key;
}
bool construct_miner_tx_with_extra_output(cryptonote::transaction& tx,
const cryptonote::account_public_address& miner_address,
size_t height,
uint64_t already_generated_coins,
const cryptonote::account_public_address& extra_address)
{
keypair txkey = keypair::generate(hw::get_device("default"));
add_tx_pub_key_to_extra(tx, txkey.pub);
keypair gov_key = get_deterministic_keypair_from_height(height);
if (already_generated_coins != 0) {
add_tx_pub_key_to_extra(tx, gov_key.pub);
}
txin_gen in;
in.height = height;
tx.vin.push_back(in);
// This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE
uint64_t block_reward;
if (!get_block_reward(0, 0, already_generated_coins, block_reward, 1, 0)) {
LOG_PRINT_L0("Block is too big");
return false;
}
const int hard_fork_version = 7;
uint64_t governance_reward = 0;
if (already_generated_coins != 0) {
governance_reward = get_governance_reward(height, block_reward);
block_reward -= governance_reward;
}
tx.version = 1;
tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW;
/// half of the miner reward goes to the other account
const auto miner_reward = block_reward / 2;
/// miner reward
tx.vout.push_back({miner_reward, get_output_key(txkey, miner_address, 0)});
/// extra reward
tx.vout.push_back({miner_reward, get_output_key(txkey, extra_address, 1)});
/// governance reward
if (already_generated_coins != 0) {
cryptonote::address_parse_info governance_wallet_address;
cryptonote::get_account_address_from_str(
governance_wallet_address, cryptonote::MAINNET, ::config::GOVERNANCE_WALLET_ADDRESS);
crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key);
if (!get_deterministic_output_key(
governance_wallet_address.address, gov_key, tx.vout.size(), out_eph_public_key)) {
MERROR("Failed to generate deterministic output key for governance wallet output creation");
return false;
}
tx.vout.push_back({governance_reward, out_eph_public_key});
tx.output_unlock_times.push_back(height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW);
}
return true;
}
bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins,
const account_public_address& miner_address, transaction& tx, uint64_t fee,
keypair* p_txkey/* = 0*/)
{
keypair txkey;
txkey = keypair::generate(hw::get_device("default"));
add_tx_pub_key_to_extra(tx, txkey.pub);
if (0 != p_txkey)
*p_txkey = txkey;
txin_gen in;
in.height = height;
tx.vin.push_back(in);
// This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE
uint64_t block_reward;
if (!get_block_reward(0, 0, already_generated_coins, block_reward, 1, 0))
{
LOG_PRINT_L0("Block is too big");
return false;
}
block_reward += fee;
crypto::key_derivation derivation;
crypto::public_key out_eph_public_key;
crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation);
crypto::derive_public_key(derivation, 0, miner_address.m_spend_public_key, out_eph_public_key);
tx_out out;
out.amount = block_reward;
out.target = txout_to_key(out_eph_public_key);
tx.vout.push_back(out);
tx.version = 1;
tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW;
return true;
}
transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const block& blk_head,
const account_base& acc_from, const account_base& acc_to, uint64_t amount, uint64_t fee)
{

View File

@ -174,16 +174,18 @@ public:
{
}
block_info(crypto::hash a_prev_id, uint64_t an_already_generated_coins, size_t a_block_weight)
block_info(crypto::hash a_prev_id, uint64_t an_already_generated_coins, size_t a_block_weight, cryptonote::block a_block)
: prev_id(a_prev_id)
, already_generated_coins(an_already_generated_coins)
, block_weight(a_block_weight)
, block(a_block)
{
}
crypto::hash prev_id;
uint64_t already_generated_coins;
size_t block_weight;
cryptonote::block block;
};
enum block_fields
@ -199,7 +201,9 @@ public:
bf_hf_version= 1 << 8
};
void get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const;
void get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const;
void get_block_chain(std::vector<cryptonote::block>& blockchain, const crypto::hash& head, size_t n) const;
void get_last_n_block_weights(std::vector<size_t>& block_weights, const crypto::hash& head, size_t n) const;
uint64_t get_already_generated_coins(const crypto::hash& blk_id) const;
uint64_t get_already_generated_coins(const cryptonote::block& blk) const;
@ -222,13 +226,12 @@ public:
bool construct_block_manually_tx(cryptonote::block& blk, const cryptonote::block& prev_block,
const cryptonote::account_base& miner_acc, const std::vector<crypto::hash>& tx_hashes, size_t txs_size);
explicit test_generator(uint8_t hf_version = 7) : hf_version_(hf_version) {}
explicit test_generator(int hf_version = 7) : m_hf_version(hf_version) {}
void set_hf_version(uint8_t ver) { hf_version_ = ver; }
int m_hf_version;
private:
std::unordered_map<crypto::hash, block_info> m_blocks_info;
uint8_t hf_version_;
};
/// ------------ Service Nodes -----------
@ -299,7 +302,8 @@ class linear_chain_generator
: gen_(), events_(events)
{ }
uint64_t height() const { return get_block_height(blocks_.back()); }
uint64_t height() const { return get_block_height(blocks_.back()); }
const std::vector<cryptonote::block>& blocks() const { return blocks_; }
cryptonote::account_base create_account();
@ -310,6 +314,7 @@ class linear_chain_generator
cryptonote::block create_block_on_fork(const cryptonote::block& prev, const std::vector<cryptonote::transaction>& txs = {});
void rewind_until_v9();
void rewind_until_version(const std::vector<std::pair<uint8_t, uint64_t>> &hard_forks, int hard_fork_version);
void rewind_blocks_n(int n);
void rewind_blocks();
@ -393,11 +398,7 @@ class dereg_tx_builder {
inline cryptonote::difficulty_type get_test_difficulty() {return 1;}
void fill_nonce(cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height);
bool construct_miner_tx_with_extra_output(cryptonote::transaction& tx,
const cryptonote::account_public_address& miner_address,
size_t height,
uint64_t already_generated_coins,
const cryptonote::account_public_address& extra_address);
crypto::public_key get_output_key(const cryptonote::keypair& txkey, const cryptonote::account_public_address& addr, size_t output_index);
bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins,
const cryptonote::account_public_address& miner_address, cryptonote::transaction& tx,
@ -431,11 +432,11 @@ class TxBuilder {
const cryptonote::account_base& m_from;
const cryptonote::account_base& m_to;
uint64_t m_amount;
uint64_t m_fee;
uint64_t m_unlock_time;
std::vector<uint8_t> m_extra;
/// optional fields
boost::optional<uint64_t> m_fee;
boost::optional<std::vector<uint8_t>> m_extra;
boost::optional<uint64_t> m_unlock_time;
bool m_per_output_unlock = false;
bool m_is_staking = false;
@ -455,6 +456,8 @@ public:
, m_from(from)
, m_to(to)
, m_amount(amount)
, m_fee(TESTS_DEFAULT_FEE)
, m_unlock_time(0)
{}
TxBuilder&& with_fee(uint64_t fee) {
@ -497,24 +500,16 @@ public:
std::vector<cryptonote::tx_destination_entry> destinations;
uint64_t change_amount;
const auto fee = m_fee ? *m_fee : TESTS_DEFAULT_FEE;
const auto nmix = 9;
fill_tx_sources_and_destinations(
m_events, m_head, m_from, m_to, m_amount, fee, nmix, sources, destinations, &change_amount);
m_events, m_head, m_from, m_to, m_amount, m_fee, nmix, sources, destinations, &change_amount);
const bool is_subaddr = false;
cryptonote::tx_destination_entry change_addr{ change_amount, m_from.get_keys().m_account_address, is_subaddr };
std::vector<uint8_t> extra;
if (m_extra) extra = *m_extra;
const auto unlock_time = m_unlock_time ? *m_unlock_time : 0;
return cryptonote::construct_tx(
m_from.get_keys(), sources, destinations, change_addr, extra, m_tx, unlock_time, m_is_staking, m_per_output_unlock);
m_from.get_keys(), sources, destinations, change_addr, m_extra, m_tx, m_unlock_time, m_is_staking, m_per_output_unlock);
}
};

View File

@ -103,74 +103,6 @@ gen_simple_chain_001::gen_simple_chain_001()
REGISTER_CALLBACK("verify_callback_2", gen_simple_chain_001::verify_callback_2);
}
static void my_construct_tx(const eventV& events,
cryptonote::transaction& tx,
std::vector<cryptonote::block>& blocks,
const cryptonote::account_base& from,
const cryptonote::account_base& to,
uint64_t amount)
{
std::vector<tx_source_entry> sources;
{
sources.resize(1);
tx_source_entry& src = sources.back();
src.amount = blocks[0].miner_tx.vout[0].amount;
src.rct = true;
const auto index_in_tx = 0;
for (int m = 0; m < 10; ++m) {
const auto sender = boost::get<txout_to_key>(blocks[m].miner_tx.vout[index_in_tx].target).key;
const auto tx_index = (m == 0) ? 0 : (1 + (m - 1) * 2);
src.push_output(tx_index, sender, blocks[m].miner_tx.vout[index_in_tx].amount);
}
src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(blocks[0].miner_tx);
src.real_output = 0;
src.mask = rct::identity();
src.real_output_in_tx_index = index_in_tx;
}
std::vector<tx_destination_entry> destinations;
destinations.push_back({amount, to.get_keys().m_account_address, false /* is subaddr */});
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
/// not sure what this is
tx_destination_entry change_addr{amount, from.get_keys().m_account_address, false /* is subaddr */ };
const auto spend_pk = from.get_keys().m_account_address.m_spend_public_key;
uint64_t unlock_time = 0;
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; // not sure what this is
subaddresses[spend_pk] = { 0, 0 };
const bool rct = true;
const bool staking = false;
const bool per_output_unlock = false;
rct::multisig_out* msout = nullptr;
construct_tx_and_get_tx_key(from.get_keys(),
subaddresses,
sources,
destinations,
change_addr,
{},
tx,
unlock_time,
tx_key,
additional_tx_keys,
rct,
rct::RangeProofBorromean,
msout,
staking,
per_output_unlock);
}
void make_rct_tx(eventV& events,
std::vector<cryptonote::transaction>& txs,
const cryptonote::block& blk_head,

View File

@ -109,6 +109,7 @@ int main(int argc, char* argv[])
MLOG(el::Level::Info, "Running all tests\n");
}
if (run_all || command_line::get_arg(vm, arg_service_nodes))
{
GENERATE_AND_PLAY(gen_service_nodes);
@ -122,6 +123,8 @@ int main(int argc, char* argv[])
if (run_all)
{
GENERATE_AND_PLAY(gen_batched_governance_reward); // Loki Governance
GENERATE_AND_PLAY(gen_simple_chain_001);
GENERATE_AND_PLAY(gen_simple_chain_split_1);
GENERATE_AND_PLAY(gen_chain_switch_1);
@ -280,7 +283,6 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(gen_multisig_tx_valid_25_1_2_many_inputs);
GENERATE_AND_PLAY(gen_multisig_tx_valid_48_1_234);
GENERATE_AND_PLAY(gen_multisig_tx_valid_48_1_234_many_inputs);
#endif
}

View File

@ -31,6 +31,7 @@
#include "gtest/gtest.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_core/cryptonote_tx_utils.h"
using namespace cryptonote;
@ -42,17 +43,21 @@ namespace
protected:
static const size_t current_block_weight = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 / 2;
bool m_block_not_too_big;
union
{
bool m_block_not_too_big;
bool m_block_reward_calc_success;
};
uint64_t m_block_reward;
};
#define TEST_ALREADY_GENERATED_COINS(already_generated_coins, expected_reward) \
m_block_not_too_big = get_block_reward(0, current_block_weight, already_generated_coins, m_block_reward,7,0); \
m_block_not_too_big = get_base_block_reward(0, current_block_weight, already_generated_coins, m_block_reward,7,0); \
ASSERT_TRUE(m_block_not_too_big); \
ASSERT_EQ(m_block_reward, expected_reward);
#define TEST_ALREADY_GENERATED_COINS_V2(already_generated_coins, expected_reward, h) \
m_block_not_too_big = get_block_reward(0, current_block_weight, already_generated_coins, m_block_reward,8,h); \
m_block_not_too_big = get_base_block_reward(0, current_block_weight, already_generated_coins, m_block_reward,8,h); \
ASSERT_TRUE(m_block_not_too_big); \
ASSERT_EQ(m_block_reward, expected_reward);
@ -86,20 +91,52 @@ namespace
TEST_ALREADY_GENERATED_COINS(MONEY_SUPPLY - ((1 << 20) - 1), UINT64_C(0));
}
TEST_F(block_reward_and_already_generated_coins, reward_parity_between_orig_and_loki_algo)
{
uint64_t already_generated_coins = UINT64_C(22500000000000000)+116*720*90;
m_block_reward_calc_success = true;
cryptonote::block_reward_parts reward_parts_v7 = {};
cryptonote::loki_block_reward_context reward_context_v7 = {};
m_block_reward_calc_success &= get_loki_block_reward(0, current_block_weight, already_generated_coins, 7, reward_parts_v7, reward_context_v7);
cryptonote::block_reward_parts reward_parts_v8 = {};
cryptonote::loki_block_reward_context reward_context_v8 = {};
m_block_reward_calc_success &= get_loki_block_reward(0, current_block_weight, already_generated_coins, 8, reward_parts_v8, reward_context_v8);
cryptonote::block_reward_parts reward_parts_v9 = {};
cryptonote::loki_block_reward_context reward_context_v9 = {};
m_block_reward_calc_success &= get_loki_block_reward(0, current_block_weight, already_generated_coins, 9, reward_parts_v9, reward_context_v9);
uint64_t reward_v7_orig = 0;
m_block_reward_calc_success &= cryptonote::get_base_block_reward(0, current_block_weight, already_generated_coins, reward_v7_orig, 7, 0);
uint64_t reward_v8_orig = 0;
m_block_reward_calc_success &= cryptonote::get_base_block_reward(0, current_block_weight, already_generated_coins, reward_v8_orig, 8, 0);
uint64_t reward_v9_orig = 0;
m_block_reward_calc_success &= cryptonote::get_base_block_reward(0, current_block_weight, already_generated_coins, reward_v9_orig, 9, 0);
ASSERT_TRUE(m_block_reward_calc_success); \
ASSERT_EQ(reward_parts_v7.adjusted_base_reward, reward_v7_orig);
ASSERT_EQ(reward_parts_v8.adjusted_base_reward, reward_v8_orig);
ASSERT_EQ(reward_parts_v9.adjusted_base_reward, reward_v9_orig);
}
//--------------------------------------------------------------------------------------------------------------------
class block_reward_and_current_block_weight : public ::testing::Test
{
protected:
virtual void SetUp()
{
m_block_not_too_big = get_block_reward(0, 0, already_generated_coins, m_standard_block_reward, 1, 0);
m_block_not_too_big = get_base_block_reward(0, 0, already_generated_coins, m_standard_block_reward, 1, 0);
ASSERT_TRUE(m_block_not_too_big);
ASSERT_LT(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1, m_standard_block_reward);
}
void do_test(size_t median_block_weight, size_t current_block_weight)
{
m_block_not_too_big = get_block_reward(median_block_weight, current_block_weight, already_generated_coins, m_block_reward, 1, 0);
m_block_not_too_big = get_base_block_reward(median_block_weight, current_block_weight, already_generated_coins, m_block_reward, 1, 0);
}
static const uint64_t already_generated_coins = UINT64_C(22500000000000000);
@ -183,14 +220,14 @@ namespace
m_last_block_weights_median = 7 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1;
m_block_not_too_big = get_block_reward(epee::misc_utils::median(m_last_block_weights), 0, already_generated_coins, m_standard_block_reward, 1, 0);
m_block_not_too_big = get_base_block_reward(epee::misc_utils::median(m_last_block_weights), 0, already_generated_coins, m_standard_block_reward, 1, 0);
ASSERT_TRUE(m_block_not_too_big);
ASSERT_LT(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1, m_standard_block_reward);
}
void do_test(size_t current_block_weight)
{
m_block_not_too_big = get_block_reward(epee::misc_utils::median(m_last_block_weights), current_block_weight, already_generated_coins, m_block_reward, 1, 0);
m_block_not_too_big = get_base_block_reward(epee::misc_utils::median(m_last_block_weights), current_block_weight, already_generated_coins, m_block_reward, 1, 0);
}
static const uint64_t already_generated_coins = UINT64_C(22500000000000000);

View File

@ -38,6 +38,7 @@ namespace
bool bulletproof,
bool per_output_unlock)
{
std::uint64_t source_amount = 0;
std::vector<cryptonote::tx_source_entry> actual_sources;
for (auto const& source : sources)