Version 0.4.0 Release Candidate (#216)

* core: submit uptime proof immediately after registering

* Increase visibility of autostaking prompts

* quorum_cop: changed uptime proof prune timeout to 2 hours 10 minutes

* cleanup: removed scope limiting block

* check_tx_inputs: fix deregister double spend test to include deregisters from other heights

* config: new testnet network id, genesis tx, and version bump

* wallet2: fix testnet wallet blockheight approximation

* Fix change in address format in RPC which broke parsing and pooling contributors (#184)

* Fix service node endpoints for RPC to also use stdout (#185)

* fixed some further rct core tests (#180)

* Fix service node state by calling detached hooks on failure to switch to alt chain (#188)

* fixed block verification core tests (#186)

* fixed block verification core tests

* core tests: removed gen_block_miner_tx_out_is_small which is only relevant to hardfork version 1

*  Don't consider expired deregistrations when filling block template

* Add unit tests for getting staking requirement (#191)

* First service node test (#190)

* core_tests: added service node tests

* core_tests: check balance after registration tx

* Fix underflow for popping rollback events (#189)

* Move deregistration age check into check_tx_inputs

* Zero initialise rct_signatures member txnFee is a uint64_t and has uninit values

* Enforce that deregisters must be 0 fee since we skip checks

* Add unit tests for vote validation (#193)

* Add unit tests for deregistration validation (#194)

* Mainnet checkpoint 86535, testnet 3591, 4166

* Bump version number

* Add print_sr for getting staking requirement (#198)

* Misc bugfixes (#203)

* removed unnecessary cast to double during txfee+coinbase calculation

* simplewallet: increased autostaking interval from 2 minutes to 40

* Fix casting issues from uint to int (#204)

* core_tests: check service node registration and expiration (#195)

* core_tests: check service node registration and deregistration

* core_tests for service nodes:

- include service nodes rewards when calculating account's balance
- check that service nodes rewards have been received

* fixed namespace error; reduced the scope of staking requirement constants

* On blockchain inc/dec mark deregisters relayble based on age (#201)

* Service nodes restore only 1 rollback bug (#206)

* Fix restore 1 rollback event, ensure prevent rollback is always added

* Remove adding prevent_rollback event at init

It gets called in on block added generic anyway.

* Log db exception, fix relation operators for vote/deregister lifetime (#207)

* Filter relayable deregisters w/ check_tx_inputs instead of blockchain callbacks

* Bump version to 0.3.7-beta

* fix build with GCC 8.1.0 (#211)

* Add temp hardfork rule in testnet for deregister lifetimes (#210)

* Update testnet, remove testnet forks, remove checkpoints, update blockheight estimate (#212)

* Don't ban peers for a bad vote, just drop their connection (#213)

* Update to version 0.3.0 release candidate (#215)
This commit is contained in:
Doyle 2018-09-07 15:14:28 +10:00 committed by doy-lee
parent ad3344b5cf
commit 25292b3e86
41 changed files with 1305 additions and 391 deletions

View File

@ -177,6 +177,7 @@ namespace cryptonote
ADD_CHECKPOINT(10, "4a7cd8b9bff380d48d6f3533a5e0509f8589cc77d18218b3f7218846e77738fc");
ADD_CHECKPOINT(100, "01b8d33a50713ff837f8ad7146021b8e3060e0316b5e4afc407e46cdb50b6760");
ADD_CHECKPOINT(1000, "5e3b0a1f931885bc0ab1d6ecdc625816576feae29e2f9ac94c5ccdbedb1465ac");
ADD_CHECKPOINT(86535, "52b7c5a60b97bf1efbf0d63a0aa1a313e8f0abe4627eb354b0c5a73cb1f4391e");
break;
}
return true;

View File

@ -158,7 +158,7 @@ DISABLE_VS_WARNINGS(4244 4345)
void account_base::create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey)
{
crypto::secret_key fake;
memset(&fake, 0, sizeof(fake));
memset(&unwrap(fake), 0, sizeof(fake));
create_from_keys(address, fake, viewkey);
}
//-----------------------------------------------------------------

View File

@ -350,6 +350,7 @@ namespace cryptonote
vout.clear();
extra.clear();
signatures.clear();
rct_signatures = {};
rct_signatures.type = rct::RCTTypeNull;
set_hash_valid(false);
set_blob_size_valid(false);

View File

@ -50,8 +50,6 @@
#define CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE 10
#define STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS 20
#define STAKING_REQUIREMENT_LOCK_BLOCKS (30*24*30)
#define STAKING_REQUIREMENT_LOCK_BLOCKS_TESTNET (30*24*2)
#define STAKING_PORTIONS UINT64_C(0xfffffffffffffffc)
#define MAX_NUMBER_OF_CONTRIBUTORS 4
#define MIN_PORTIONS (STAKING_PORTIONS / MAX_NUMBER_OF_CONTRIBUTORS)
@ -67,7 +65,7 @@ static_assert(STAKING_PORTIONS % 3 == 0, "Use a multiple of three, so that it di
#define UPTIME_PROOF_BUFFER_IN_SECONDS (5*60)
#define UPTIME_PROOF_FREQUENCY_IN_SECONDS (60*60)
#define UPTIME_PROOF_MAX_TIME_IN_SECONDS (UPTIME_PROOF_FREQUENCY_IN_SECONDS + (2 * UPTIME_PROOF_BUFFER_IN_SECONDS))
#define UPTIME_PROOF_MAX_TIME_IN_SECONDS (UPTIME_PROOF_FREQUENCY_IN_SECONDS * 2 + UPTIME_PROOF_BUFFER_IN_SECONDS)
// MONEY_SUPPLY - total number coins to be generated
#define MONEY_SUPPLY ((uint64_t)(-1))
@ -192,13 +190,13 @@ namespace config
uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 156;
uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 157;
uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 158;
uint16_t const P2P_DEFAULT_PORT = 38150;
uint16_t const RPC_DEFAULT_PORT = 38151;
uint16_t const ZMQ_RPC_DEFAULT_PORT = 38152;
uint16_t const P2P_DEFAULT_PORT = 38156;
uint16_t const RPC_DEFAULT_PORT = 38157;
uint16_t const ZMQ_RPC_DEFAULT_PORT = 38158;
boost::uuids::uuid const NETWORK_ID = { {
0x5e, 0x3a, 0x78, 0x65, 0xe1, 0x6f, 0xca, 0xb8, 0x02, 0xa1, 0xdc, 0x17, 0x61, 0x64, 0x15, 0xbc,
0x5f, 0x3a, 0x78, 0x65, 0xe1, 0x6f, 0xca, 0xb8, 0x02, 0xa1, 0xdc, 0x17, 0x61, 0x64, 0x15, 0xbe,
} }; // Bender's daydream
std::string const GENESIS_TX = "03011e001e01ff00018080c9db97f4fb270286d1689b3935f334d26cd9ecb7fc7eb4d67fde89a702058e58ea6811740e40764201c3e91c848f253b0d0193c945787192e1a362f00c2e7f18a80064d9ed4ad88c0f72000000000000000000000000000000000000000000000000000000000000000000";
std::string const GENESIS_TX = "03011e001e01ff00018080c9db97f4fb270259b546996f69aa71abe4238995f41d780ab1abebcac9f00e808f147bdb9e3228420112573af8c309b69a1a646f41b5212ba7d9c4590bf86e04f36c486467cfef9d3d72000000000000000000000000000000000000000000000000000000000000000000";
uint32_t const GENESIS_NONCE = 10001;
std::string const GOVERNANCE_WALLET_ADDRESS = "T6SUprTYE5rQpep9iQFxyPcKVd91DFR1fQ1Qsyqp5eYLiFc8XuYd3reRE71qDL8c3DXioUbDEpDFdaUpetnL37NS1R3rzoKxi";

View File

@ -876,6 +876,10 @@ bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain,
pop_block_from_blockchain();
}
// Revert all changes from switching to the alt chain before adding the original chain back in
for (BlockchainDetachedHook* hook : m_blockchain_detached_hooks)
hook->blockchain_detached(rollback_height);
// make sure the hard fork object updates its current version
m_hardfork->reorganize_from_chain_height(rollback_height);
@ -944,7 +948,6 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::
if(!r || !bvc.m_added_to_main_chain)
{
MERROR("Failed to switch to alternative blockchain");
// rollback_blockchain_switching should be moved to two different
// functions: rollback and apply_chain, but for now we pretend it is
// just the latter (because the rollback was done above).
@ -1106,6 +1109,11 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
money_in_use += o.amount;
partial_block_reward = false;
if (b.miner_tx.vout.size() == 0) {
MERROR_VER("miner tx has no outputs");
return false;
}
if (version == 3) {
for (auto &o: b.miner_tx.vout) {
if (!is_valid_decomposed_amount(o.amount)) {
@ -3016,6 +3024,14 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
if (tx.is_deregister_tx())
{
if (tx.rct_signatures.txnFee != 0)
{
tvc.m_invalid_input = true;
tvc.m_verifivation_failed = true;
MERROR_VER("TX version deregister should have 0 fee!");
return false;
}
// Check the inputs (votes) of the transaction have not been already been
// submitted to the blockchain under another transaction using a different
// combination of votes.
@ -3040,6 +3056,33 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
return false;
}
// Check if deregister is too old or too new to hold onto
{
const uint64_t curr_height = get_current_blockchain_height();
if (deregister.block_height >= curr_height)
{
LOG_PRINT_L1("Received deregister tx for height: " << deregister.block_height
<< " and service node: " << deregister.service_node_index
<< ", is newer than current height: " << curr_height
<< " blocks and has been rejected.");
tvc.m_vote_ctx.m_invalid_block_height = true;
tvc.m_verifivation_failed = true;
return false;
}
uint64_t delta_height = curr_height - deregister.block_height;
if (delta_height >= loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT)
{
LOG_PRINT_L1("Received deregister tx for height: " << deregister.block_height
<< " and service node: " << deregister.service_node_index
<< ", is older than: " << loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT
<< " blocks and has been rejected. The current height is: " << curr_height);
tvc.m_vote_ctx.m_invalid_block_height = true;
tvc.m_verifivation_failed = true;
return false;
}
}
const uint64_t height = deregister.block_height;
const size_t num_blocks_to_check = loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT;
@ -3066,15 +3109,27 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
continue;
}
if (existing_deregister.block_height == deregister.block_height &&
existing_deregister.service_node_index == deregister.service_node_index)
std::shared_ptr<service_nodes::quorum_state> existing_deregister_quorum_state
= m_service_node_list.get_quorum_state(existing_deregister.block_height);
if (!existing_deregister_quorum_state)
{
MERROR_VER("could not get quorum state for recent deregister tx");
continue;
}
if (existing_deregister_quorum_state->nodes_to_test[existing_deregister.service_node_index] ==
quorum_state->nodes_to_test[deregister.service_node_index])
{
tvc.m_double_spend = true;
return false;
}
}
}
else
{
return false;
}
}
return true;
}
@ -3715,7 +3770,14 @@ leave:
// appears to be a NOP *and* is called elsewhere. wat?
m_tx_pool.on_blockchain_inc(new_height, id);
m_deregister_vote_pool.remove_expired_votes(new_height);
// New height is the height of the block we just mined. We want (new_height
// + 1) because our age checks for deregister votes is now (age >=
// DEREGISTER_VOTE_LIFETIME_BY_HEIGHT) where age is derived from
// get_current_blockchain_height() which gives you the height that you are
// currently mining for, i.e. (new_height + 1). Otherwise peers will silently
// drop connection from each other when they go around P2Ping votes.
m_deregister_vote_pool.remove_expired_votes(new_height + 1);
m_deregister_vote_pool.remove_used_votes(txs);
return true;

View File

@ -1442,6 +1442,22 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
void core::do_uptime_proof_call()
{
std::vector<service_nodes::service_node_pubkey_info> states = get_service_node_list_state({ m_service_node_pubkey });
// wait one block before starting uptime proofs.
if (!states.empty() && states[0].info.registration_height + 1 < get_current_blockchain_height())
{
m_submit_uptime_proof_interval.do_call(boost::bind(&core::submit_uptime_proof, this));
}
else
{
// reset the interval so that we're ready when we register.
m_submit_uptime_proof_interval = epee::math_helper::once_a_time_seconds<UPTIME_PROOF_FREQUENCY_IN_SECONDS, true>();
}
}
//-----------------------------------------------------------------------------------------------
bool core::on_idle()
{
if(!m_starter_message_showed)
@ -1468,11 +1484,9 @@ namespace cryptonote
m_deregisters_auto_relayer.do_call(boost::bind(&core::relay_deregister_votes, this));
m_check_updates_interval.do_call(boost::bind(&core::check_updates, this));
m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this));
if (m_service_node && m_service_node_list.is_service_node(m_service_node_pubkey))
{
m_submit_uptime_proof_interval.do_call(boost::bind(&core::submit_uptime_proof, this));
m_uptime_proof_pruner.do_call(boost::bind(&service_nodes::quorum_cop::prune_uptime_proof, &m_quorum_cop));
}
if (m_service_node)
do_uptime_proof_call();
m_uptime_proof_pruner.do_call(boost::bind(&service_nodes::quorum_cop::prune_uptime_proof, &m_quorum_cop));
m_miner.on_idle();
m_mempool.on_idle();
@ -1689,32 +1703,30 @@ namespace cryptonote
//-----------------------------------------------------------------------------------------------
bool core::add_deregister_vote(const loki::service_node_deregister::vote& vote, vote_verification_context &vvc)
{
uint64_t latest_block_height = std::max(get_current_blockchain_height(), get_target_blockchain_height());
uint64_t delta_height = latest_block_height - vote.block_height;
if (vote.block_height < latest_block_height && delta_height >= loki::service_node_deregister::VOTE_LIFETIME_BY_HEIGHT)
{
uint64_t latest_block_height = std::max(get_current_blockchain_height(), get_target_blockchain_height());
uint64_t delta_height = latest_block_height - vote.block_height;
LOG_PRINT_L1("Received vote for height: " << vote.block_height
<< " and service node: " << vote.service_node_index
<< ", is older than: " << loki::service_node_deregister::VOTE_LIFETIME_BY_HEIGHT
<< " blocks and has been rejected.");
vvc.m_invalid_block_height = true;
}
else if (vote.block_height > latest_block_height)
{
LOG_PRINT_L1("Received vote for height: " << vote.block_height
<< " and service node: " << vote.service_node_index
<< ", is newer than: " << latest_block_height
<< " (latest block height) and has been rejected.");
vvc.m_invalid_block_height = true;
}
if (vote.block_height < latest_block_height && delta_height > loki::service_node_deregister::VOTE_LIFETIME_BY_HEIGHT)
{
LOG_PRINT_L1("Received vote for height: " << vote.block_height
<< " and service node: " << vote.service_node_index
<< ", is older than: " << loki::service_node_deregister::VOTE_LIFETIME_BY_HEIGHT
<< " blocks and has been rejected.");
vvc.m_invalid_block_height = true;
}
else if (vote.block_height > latest_block_height)
{
LOG_PRINT_L1("Received vote for height: " << vote.block_height
<< " and service node: " << vote.service_node_index
<< ", is newer than: " << latest_block_height
<< " (latest block height) and has been rejected.");
vvc.m_invalid_block_height = true;
}
if (vvc.m_invalid_block_height)
{
vvc.m_verification_failed = true;
return false;
}
if (vvc.m_invalid_block_height)
{
vvc.m_verification_failed = true;
return false;
}
const std::shared_ptr<service_nodes::quorum_state> quorum_state = m_service_node_list.get_quorum_state(vote.block_height);
@ -1745,6 +1757,7 @@ namespace cryptonote
return result;
}
//-----------------------------------------------------------------------------------------------
bool core::get_service_node_keys(crypto::public_key &pub_key, crypto::secret_key &sec_key) const
{
if (m_service_node)

View File

@ -817,7 +817,7 @@ namespace cryptonote
bool add_deregister_vote(const loki::service_node_deregister::vote& vote, vote_verification_context &vvc);
/**
* @brief Return the account associated to this service node.
* @brief Get the keypair for this service node.
* @param pub_key The public key for the service node, unmodified if not a service node
@ -1023,6 +1023,11 @@ namespace cryptonote
*/
bool init_service_node_key();
/**
* @brief do the uptime proof logic and calls for idle loop.
*/
void do_uptime_proof_call();
bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing)
uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so

View File

@ -812,14 +812,14 @@ namespace cryptonote
return r;
}
//---------------------------------------------------------------
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::tx_destination_entry>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time)
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::tx_destination_entry>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, bool is_staking, bool per_output_unlock)
{
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0};
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
std::vector<tx_destination_entry> destinations_copy = destinations;
return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, false, NULL);
return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, false, NULL, is_staking, per_output_unlock);
}
//---------------------------------------------------------------
bool generate_genesis_block(

View File

@ -118,7 +118,7 @@ namespace cryptonote
//---------------------------------------------------------------
crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::tx_destination_entry>& change_addr);
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::tx_destination_entry>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time);
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::tx_destination_entry>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, bool is_staking = false, bool per_output_unlock = false);
bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::tx_destination_entry>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, bool bulletproof = false, rct::multisig_out *msout = NULL, bool per_output_unlock = false, bool shuffle_outs = true);
bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::tx_destination_entry>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, bool bulletproof = false, rct::multisig_out *msout = NULL, bool is_staking_tx = false, bool per_output_unlock = false);

View File

@ -122,20 +122,7 @@ namespace service_nodes
cryptonote::vote_verification_context vvc = {};
if (!m_core.add_deregister_vote(vote, vvc))
{
if (vvc.m_invalid_block_height)
LOG_ERROR("block height was invalid: " << vote.block_height);
if (vvc.m_voters_quorum_index_out_of_bounds)
LOG_ERROR("voters quorum index specified was out of bounds: " << vote.voters_quorum_index);
if (vvc.m_duplicate_voters)
LOG_ERROR("voters index was duplicated: " << vote.voters_quorum_index);
if (vvc.m_service_node_index_out_of_bounds)
LOG_ERROR("service node index specified out of bounds: " << vote.service_node_index);
if (vvc.m_signature_not_valid)
LOG_ERROR("signature was not valid, was the signature signed properly?");
LOG_ERROR("Failed to add deregister vote reason: " << print_vote_verification_context(vvc, &vote));
}
}
}
@ -172,7 +159,7 @@ namespace service_nodes
if (!crypto::check_signature(hash, pubkey, sig))
return false;
m_uptime_proof_seen[pubkey] = timestamp;
m_uptime_proof_seen[pubkey] = now;
return true;
}

View File

@ -96,7 +96,7 @@ namespace loki
if (deregister.service_node_index >= quorum_state.nodes_to_test.size())
{
vvc.m_service_node_index_out_of_bounds = true;
LOG_PRINT_L1("Service node index in deregister vote was out of bounds: " << deregister.service_node_index << ", expected to be in range of: [0, " << quorum_state.nodes_to_test.size() << "]");
LOG_PRINT_L1("Service node index in deregister vote was out of bounds: " << deregister.service_node_index << ", expected to be in range of: [0, " << quorum_state.nodes_to_test.size() << ")");
return false;
}
@ -109,7 +109,7 @@ namespace loki
if (vote.voters_quorum_index >= quorum.size())
{
vvc.m_voters_quorum_index_out_of_bounds = true;
LOG_PRINT_L1("Voter's index in deregister vote was out of bounds: " << vote.voters_quorum_index << ", expected to be in range of: [0, " << quorum.size() << "]");
LOG_PRINT_L1("Voter's index in deregister vote was out of bounds: " << vote.voters_quorum_index << ", expected to be in range of: [0, " << quorum.size() << ")");
return false;
}
@ -117,7 +117,7 @@ namespace loki
if (++quorum_set[vote.voters_quorum_index] > 1)
{
vvc.m_duplicate_voters = true;
LOG_PRINT_L1("Voter quorum index is duplicated: " << vote.voters_quorum_index << ", expected to be in range of: [0, " << quorum.size() << "]");
LOG_PRINT_L1("Voter quorum index is duplicated: " << vote.voters_quorum_index << ", expected to be in range of: [0, " << quorum.size() << ")");
return false;
}

View File

@ -106,8 +106,6 @@ namespace service_nodes
block_added_generic(block, txs);
}
}
m_rollback_events.push_back(std::unique_ptr<rollback_event>(new prevent_rollback(current_height)));
}
std::vector<crypto::public_key> service_node_list::get_service_nodes_pubkeys() const
@ -187,7 +185,7 @@ namespace service_nodes
if (tx.version >= cryptonote::transaction::version_3_per_output_unlock_times)
unlock_time = tx.output_unlock_times[i];
return unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && unlock_time >= block_height + get_staking_requirement_lock_blocks();
return unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && unlock_time >= block_height + get_staking_requirement_lock_blocks(m_blockchain.nettype());
}
bool service_node_list::reg_tx_extract_fields(const cryptonote::transaction& tx, std::vector<cryptonote::account_public_address>& addresses, uint64_t& portions_for_operator, std::vector<uint64_t>& portions, uint64_t& expiration_timestamp, crypto::public_key& service_node_key, crypto::signature& signature, crypto::public_key& tx_pub_key) const
@ -497,9 +495,15 @@ namespace service_nodes
if (hard_fork_version < 9)
return;
while (!m_rollback_events.empty() && m_rollback_events.front()->m_block_height < block_height - ROLLBACK_EVENT_EXPIRATION_BLOCKS)
{
m_rollback_events.pop_front();
const size_t ROLLBACK_EVENT_EXPIRATION_BLOCKS = 30;
uint64_t cull_height = (block_height < ROLLBACK_EVENT_EXPIRATION_BLOCKS) ? block_height : block_height - ROLLBACK_EVENT_EXPIRATION_BLOCKS;
while (!m_rollback_events.empty() && m_rollback_events.front()->m_block_height < cull_height)
{
m_rollback_events.pop_front();
}
m_rollback_events.push_front(std::unique_ptr<rollback_event>(new prevent_rollback(cull_height)));
}
for (const crypto::public_key& pubkey : get_expired_nodes(block_height))
@ -539,7 +543,8 @@ namespace service_nodes
index++;
}
const size_t QUORUM_LIFETIME = loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT;
const size_t QUORUM_LIFETIME = (6 * loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT);
// save six times the quorum lifetime, to be sure. also to help with debugging.
const size_t cache_state_from_height = (block_height < QUORUM_LIFETIME) ? 0 : block_height - QUORUM_LIFETIME;
store_quorum_state_from_rewards_list(block_height);
@ -576,7 +581,7 @@ namespace service_nodes
{
std::vector<crypto::public_key> expired_nodes;
const uint64_t lock_blocks = get_staking_requirement_lock_blocks();
const uint64_t lock_blocks = get_staking_requirement_lock_blocks(m_blockchain.nettype());
if (block_height < lock_blocks)
return expired_nodes;
@ -796,11 +801,6 @@ namespace service_nodes
}
}
uint64_t service_node_list::get_staking_requirement_lock_blocks() const
{
return m_blockchain.nettype() == cryptonote::TESTNET || m_blockchain.nettype() == cryptonote::FAKECHAIN ? STAKING_REQUIREMENT_LOCK_BLOCKS_TESTNET : STAKING_REQUIREMENT_LOCK_BLOCKS;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
service_node_list::rollback_event::rollback_event(uint64_t block_height, rollback_type type) : m_block_height(block_height), type(type)
@ -955,7 +955,6 @@ namespace service_nodes
i->m_info = from.m_info;
i->type = rollback_event::change_type;
m_rollback_events.push_back(std::unique_ptr<rollback_event>(i));
break;
}
else if (event.type() == typeid(rollback_new))
{
@ -965,7 +964,6 @@ namespace service_nodes
i->m_key = from.m_key;
i->type = rollback_event::new_type;
m_rollback_events.push_back(std::unique_ptr<rollback_event>(i));
break;
}
else if (event.type() == typeid(prevent_rollback))
{
@ -974,10 +972,10 @@ namespace service_nodes
i->m_block_height = from.m_block_height;
i->type = rollback_event::prevent_type;
m_rollback_events.push_back(std::unique_ptr<rollback_event>(i));
break;
}
else
{
MERROR("Unhandled rollback event type in restoring data to service node list.");
return false;
}
}
@ -1150,11 +1148,28 @@ namespace service_nodes
cmd = stream.str();
return true;
}
uint64_t get_staking_requirement_lock_blocks(cryptonote::network_type nettype)
{
constexpr static uint32_t STAKING_REQUIREMENT_LOCK_BLOCKS = 30*24*30;
constexpr static uint32_t STAKING_REQUIREMENT_LOCK_BLOCKS_TESTNET = 30*24*2;
constexpr static uint32_t STAKING_REQUIREMENT_LOCK_BLOCKS_FAKENET = 30;
switch(nettype) {
case cryptonote::TESTNET: return STAKING_REQUIREMENT_LOCK_BLOCKS_TESTNET;
case cryptonote::FAKECHAIN: return STAKING_REQUIREMENT_LOCK_BLOCKS_FAKENET;
default: return STAKING_REQUIREMENT_LOCK_BLOCKS;
}
}
uint64_t get_staking_requirement(cryptonote::network_type m_nettype, uint64_t height)
{
if (m_nettype == cryptonote::TESTNET || m_nettype == cryptonote::FAKECHAIN)
return COIN * 100;
uint64_t hardfork_height = m_nettype == cryptonote::MAINNET ? 101250 : 96210 /* stagenet */;
if (height < hardfork_height) height = hardfork_height;
uint64_t height_adjusted = height - hardfork_height;
uint64_t base = 10000 * COIN;
uint64_t variable = (35000.0 * COIN) / loki_exp2(height_adjusted/129600.0);
@ -1162,5 +1177,13 @@ namespace service_nodes
uint64_t flat = 15000 * COIN;
return std::max(base + variable, height < 3628800 ? linear_up : flat);
}
uint64_t portions_to_amount(uint64_t portions, uint64_t staking_requirement)
{
uint64_t hi, lo, resulthi, resultlo;
lo = mul128(staking_requirement, portions, &hi);
div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo);
return resultlo;
}
}

View File

@ -32,8 +32,6 @@
#include <boost/variant.hpp>
#include "serialization/serialization.h"
#define ROLLBACK_EVENT_EXPIRATION_BLOCKS 30
namespace service_nodes
{
const size_t QUORUM_SIZE = 10;
@ -149,8 +147,6 @@ namespace service_nodes
std::vector<crypto::public_key> get_service_nodes_pubkeys() const;
uint64_t get_staking_requirement_lock_blocks() const;
template<typename T>
void block_added_generic(const cryptonote::block& block, const T& txs);
@ -286,8 +282,12 @@ namespace service_nodes
bool make_registration_cmd(cryptonote::network_type nettype, const std::vector<std::string> args, const crypto::public_key& service_node_pubkey,
const crypto::secret_key service_node_key, std::string &cmd, bool make_friendly);
uint64_t get_staking_requirement_lock_blocks(cryptonote::network_type m_nettype);
uint64_t get_staking_requirement(cryptonote::network_type nettype, uint64_t height);
uint64_t portions_to_amount(uint64_t portions, uint64_t staking_requirement);
const static cryptonote::account_public_address null_address{ crypto::null_pkey, crypto::null_pkey };
}

View File

@ -78,7 +78,10 @@ namespace cryptonote
uint64_t template_accept_threshold(uint64_t amount)
{
return amount * ACCEPT_THRESHOLD;
// XXX: multiplying by ACCEPT_THRESHOLD here was removed because of a need
// to accept 0 fee transactions correctly. the cast to float / double and
// back again was causing issues estimating the effect of a zero fee tx
return amount;
}
uint64_t get_transaction_size_limit(uint8_t version)
@ -249,40 +252,6 @@ namespace cryptonote
}
}
if (tx.is_deregister_tx())
{
tx_extra_service_node_deregister deregister;
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
{
LOG_PRINT_L1("Could not get service node deregister from tx v3, possibly corrupt tx in your blockchain");
return false;
}
const uint64_t curr_height = m_blockchain.get_current_blockchain_height();
if (deregister.block_height >= curr_height)
{
LOG_PRINT_L1("Received deregister tx for height: " << deregister.block_height
<< " and service node: " << deregister.service_node_index
<< ", is newer than current height: " << curr_height
<< " blocks and has been rejected.");
tvc.m_vote_ctx.m_invalid_block_height = true;
tvc.m_verifivation_failed = true;
return false;
}
uint64_t delta_height = curr_height - deregister.block_height;
if (delta_height > loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT)
{
LOG_PRINT_L1("Received deregister tx for height: " << deregister.block_height
<< " and service node: " << deregister.service_node_index
<< ", is older than: " << loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT
<< " blocks and has been rejected.");
tvc.m_vote_ctx.m_invalid_block_height = true;
tvc.m_verifivation_failed = true;
return false;
}
}
if (!m_blockchain.check_tx_outputs(tx, tvc))
{
LOG_PRINT_L1("Transaction with id= "<< id << " has at least one invalid output");
@ -699,9 +668,22 @@ namespace cryptonote
if (meta.fee == 0)
{
cryptonote::transaction tx;
if (cryptonote::parse_and_validate_tx_from_blob(bd, tx) && !tx.is_deregister_tx())
if (!cryptonote::parse_and_validate_tx_from_blob(bd, tx))
{
return true;
LOG_PRINT_L1("TX in pool could not be parsed from blob, txid: " << txid);
return true;
}
if (!tx.is_deregister_tx())
return true;
tx_verification_context tvc;
uint64_t max_used_block_height = 0;
crypto::hash max_used_block_id = null_hash;
if (!m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id, tvc, /*kept_by_block*/ false))
{
LOG_PRINT_L1("TX deregister considered for relaying failed tx inputs check, txid: " << txid << ", reason: " << print_tx_verification_context(tvc, &tx));
return true;
}
}
@ -716,6 +698,7 @@ namespace cryptonote
}
return true;
}, false);
return true;
}
//---------------------------------------------------------------------------------
@ -1094,6 +1077,7 @@ namespace cryptonote
}
}
}
//if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure
if(m_blockchain.have_tx_keyimges_as_spent(tx))
{

View File

@ -790,7 +790,7 @@ namespace cryptonote
if (vvc.m_verification_failed)
{
LOG_PRINT_CCONTEXT_L1("Deregister vote verification failed, dropping connection");
drop_connection(context, true /*add_fail*/, false /*flush_all_spans i.e. delete cached block data from this peer*/);
drop_connection(context, false /*add_fail*/, false /*flush_all_spans i.e. delete cached block data from this peer*/);
return 1;
}

View File

@ -150,6 +150,25 @@ bool t_command_parser_executor::print_sn_key(const std::vector<std::string>& arg
return result;
}
bool t_command_parser_executor::print_sr(const std::vector<std::string>& args)
{
if (args.size() != 1)
{
std::cout << "expected 1 argument, <height>, received: " << args.size() << std::endl;
return false;
}
uint64_t height = 0;
if(!epee::string_tools::get_xtype_from_string(height, args[0]))
{
std::cout << "wrong block height parameter" << std::endl;
return false;
}
bool result = m_executor.print_sr(height);
return result;
}
bool t_command_parser_executor::prepare_registration()
{
bool result = m_executor.prepare_registration();

View File

@ -79,6 +79,8 @@ public:
bool print_sn_key(const std::vector<std::string>& args);
bool print_sr(const std::vector<std::string>& args);
bool prepare_registration();
bool print_sn(const std::vector<std::string>& args);

View File

@ -107,6 +107,12 @@ t_command_server::t_command_server(
, "print_sn_key"
, "Print this daemon's service node key, if it is one and launched in service node mode."
);
m_command_lookup.set_handler(
"print_sr"
, std::bind(&t_command_parser_executor::print_sr, &m_parser, p::_1)
, "print_sr <height>"
, "Print the staking requirement for the height."
);
m_command_lookup.set_handler(
"prepare_registration"
, std::bind(&t_command_parser_executor::prepare_registration, &m_parser)

View File

@ -1041,7 +1041,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() {
}
else
{
memset(&res.pool_stats, 0, sizeof(res.pool_stats));
res.pool_stats = {};
if (!m_rpc_server->on_get_transaction_pool_stats(req, res, false) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
@ -2007,10 +2007,10 @@ bool t_rpc_command_executor::get_service_node_registration_cmd(const std::vector
tools::fail_msg_writer() << make_error(fail_message, error_resp.message);
return true;
}
tools::success_msg_writer() << res.registration_cmd;
}
tools::success_msg_writer() << res.registration_cmd;
return true;
}
@ -2037,7 +2037,7 @@ static void print_service_node_list_state(cryptonote::network_type nettype, uint
// Print Expiry Info
{
uint64_t expiry_height = entry.registration_height;
expiry_height += (nettype == cryptonote::TESTNET) ? STAKING_REQUIREMENT_LOCK_BLOCKS_TESTNET : STAKING_REQUIREMENT_LOCK_BLOCKS;
expiry_height += service_nodes::get_staking_requirement_lock_blocks(nettype);
if (curr_height)
{
@ -2097,92 +2097,104 @@ bool t_rpc_command_executor::print_sn(const std::vector<std::string> &args)
epee::json_rpc::error error_resp;
req.service_node_pubkeys = args;
cryptonote::COMMAND_RPC_GET_INFO::request get_info_req;
cryptonote::COMMAND_RPC_GET_INFO::response get_info_res;
cryptonote::network_type nettype = cryptonote::UNDEFINED;
uint64_t *curr_height = nullptr;
if (m_is_rpc)
{
if (!m_rpc_client->json_rpc_request(req, res, "get_service_node", fail_message.c_str()))
if (!m_rpc_client->rpc_request(get_info_req, get_info_res, "/getinfo", fail_message.c_str()))
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
tools::fail_msg_writer() << make_error(fail_message, get_info_res.status);
return true;
}
if (!m_rpc_client->json_rpc_request(req, res, "get_service_nodes", fail_message.c_str()))
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
}
if (get_info_res.mainnet) nettype = cryptonote::MAINNET;
else if (get_info_res.stagenet) nettype = cryptonote::STAGENET;
else if (get_info_res.testnet) nettype = cryptonote::TESTNET;
curr_height = &get_info_res.height;
}
else
{
if (m_rpc_server->on_get_info(get_info_req, get_info_res) || get_info_res.status == CORE_RPC_STATUS_OK)
curr_height = &get_info_res.height;
if (!m_rpc_server->on_get_service_nodes(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, error_resp.message);
return true;
}
nettype = m_rpc_server->nettype();
}
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *> unregistered;
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *> registered;
registered.reserve (res.service_node_states.size());
unregistered.reserve(res.service_node_states.size() * 0.5f);
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *> unregistered;
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *> registered;
registered.reserve (res.service_node_states.size());
unregistered.reserve(res.service_node_states.size() * 0.5f);
for (auto &entry : res.service_node_states)
for (auto &entry : res.service_node_states)
{
if (entry.total_contributed == entry.staking_requirement)
{
if (entry.total_contributed == entry.staking_requirement)
registered.push_back(&entry);
}
else
{
unregistered.push_back(&entry);
}
}
std::sort(unregistered.begin(), unregistered.end(),
[](const cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *a, const cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *b) {
uint64_t a_remaining = a->staking_requirement - a->total_reserved;
uint64_t b_remaining = b->staking_requirement - b->total_reserved;
if (b_remaining == a_remaining)
return b->portions_for_operator < a->portions_for_operator;
return b_remaining < a_remaining;
});
std::stable_sort(registered.begin(), registered.end(),
[](const cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *a, const cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *b) {
if (a->last_reward_block_height == b->last_reward_block_height)
return a->last_reward_transaction_index < b->last_reward_transaction_index;
return a->last_reward_block_height < b->last_reward_block_height;
});
if (unregistered.size() > 0)
{
tools::msg_writer() << "Service Node Unregistered State[" << unregistered.size()<< "]";
print_service_node_list_state(nettype, curr_height, unregistered);
}
if (registered.size() > 0)
{
tools::msg_writer() << "Service Node Registration State[" << registered.size()<< "]";
print_service_node_list_state(nettype, curr_height, registered);
}
if (unregistered.size() == 0 && registered.size() == 0)
{
if (args.size() > 0)
{
tools::msg_writer() << "No service node is currently known on the network for: ";
for (const std::string &arg : args)
{
registered.push_back(&entry);
}
else
{
unregistered.push_back(&entry);
tools::msg_writer() << arg;
}
}
std::sort(unregistered.begin(), unregistered.end(),
[](const cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *a, const cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *b) {
uint64_t a_remaining = a->staking_requirement - a->total_reserved;
uint64_t b_remaining = b->staking_requirement - b->total_reserved;
if (b_remaining == a_remaining)
return b->portions_for_operator < a->portions_for_operator;
return b_remaining < a_remaining;
});
std::stable_sort(registered.begin(), registered.end(),
[](const cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *a, const cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry *b) {
if (a->last_reward_block_height == b->last_reward_block_height)
return a->last_reward_transaction_index < b->last_reward_transaction_index;
return a->last_reward_block_height < b->last_reward_block_height;
});
uint64_t *curr_height = nullptr;
cryptonote::COMMAND_RPC_GET_HEIGHT::request height_req = {};
cryptonote::COMMAND_RPC_GET_HEIGHT::response height_res = {};
m_rpc_server->on_get_height(height_req, height_res);
if (res.status == CORE_RPC_STATUS_OK)
curr_height = &height_res.height;
if (unregistered.size() > 0)
else
{
tools::msg_writer() << "Service Node Unregistered State[" << unregistered.size()<< "]";
print_service_node_list_state(m_rpc_server->nettype(), curr_height, unregistered);
}
if (registered.size() > 0)
{
tools::msg_writer() << "Service Node Registration State[" << registered.size()<< "]";
print_service_node_list_state(m_rpc_server->nettype(), curr_height, registered);
}
if (unregistered.size() == 0 && registered.size() == 0)
{
if (args.size() > 0)
{
tools::msg_writer() << "No service node is currently known on the network for: ";
for (const std::string &arg : args)
{
tools::msg_writer() << arg;
}
}
else
{
tools::msg_writer() << "No service node is currently known on the network";
}
tools::msg_writer() << "No service node is currently known on the network";
}
}
@ -2220,6 +2232,36 @@ bool t_rpc_command_executor::print_sn_status()
return result;
}
bool t_rpc_command_executor::print_sr(uint64_t height)
{
cryptonote::COMMAND_RPC_GET_STAKING_REQUIREMENT::request req = {};
cryptonote::COMMAND_RPC_GET_STAKING_REQUIREMENT::response res = {};
std::string fail_message = "Unsuccessful";
epee::json_rpc::error error_resp;
req.height = height;
if (m_is_rpc)
{
if (!m_rpc_client->json_rpc_request(req, res, "get_staking_requirement", fail_message.c_str()))
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
}
}
else
{
epee::json_rpc::error error_resp;
if (!m_rpc_server->on_get_staking_requirement(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, error_resp.message);
return true;
}
}
tools::success_msg_writer() << "Staking Requirement: " << cryptonote::print_money(res.staking_requirement);
return true;
}
bool t_rpc_command_executor::print_sn_key()
{
cryptonote::COMMAND_RPC_GET_SERVICE_NODE_KEY::request req = {};

View File

@ -162,6 +162,8 @@ public:
bool print_sn_status();
bool print_sr(uint64_t height);
bool prepare_registration();
bool print_sn(const std::vector<std::string> &args);

View File

@ -372,7 +372,7 @@ namespace nodetool
std::set<std::string> full_addrs;
if (nettype == cryptonote::TESTNET)
{
full_addrs.insert("52.63.146.137:38150");
full_addrs.insert("52.63.146.137:38156");
}
else if (nettype == cryptonote::STAGENET)
{

View File

@ -2317,7 +2317,13 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_staking_requirement(const COMMAND_RPC_GET_STAKING_REQUIREMENT::request& req, COMMAND_RPC_GET_STAKING_REQUIREMENT::response& res, epee::json_rpc::error& error_resp)
{
PERF_TIMER(on_get_staking_requirement);
res.staking_requirement = service_nodes::get_staking_requirement(nettype(), req.height);
res.status = CORE_RPC_STATUS_OK;
return true;
}
const command_line::arg_descriptor<std::string, false, true, 2> core_rpc_server::arg_rpc_bind_port = {
"rpc-bind-port"

View File

@ -159,6 +159,7 @@ namespace cryptonote
MAP_JON_RPC_WE("get_service_node_registration_cmd", on_get_service_node_registration_cmd, COMMAND_RPC_GET_SERVICE_NODE_REGISTRATION_CMD)
MAP_JON_RPC_WE("get_service_node_key", on_get_service_node_key, COMMAND_RPC_GET_SERVICE_NODE_KEY)
MAP_JON_RPC_WE("get_service_nodes", on_get_service_nodes, COMMAND_RPC_GET_SERVICE_NODES)
MAP_JON_RPC_WE("get_staking_requirement", on_get_staking_requirement, COMMAND_RPC_GET_STAKING_REQUIREMENT)
END_JSON_RPC_MAP()
END_URI_MAP2()
@ -226,6 +227,7 @@ namespace cryptonote
bool on_get_service_node_registration_cmd(const COMMAND_RPC_GET_SERVICE_NODE_REGISTRATION_CMD::request& req, COMMAND_RPC_GET_SERVICE_NODE_REGISTRATION_CMD::response& res, epee::json_rpc::error& error_resp);
bool on_get_service_node_key(const COMMAND_RPC_GET_SERVICE_NODE_KEY::request& req, COMMAND_RPC_GET_SERVICE_NODE_KEY::response& res, epee::json_rpc::error &error_resp);
bool on_get_service_nodes(const COMMAND_RPC_GET_SERVICE_NODES::request& req, COMMAND_RPC_GET_SERVICE_NODES::response& res, epee::json_rpc::error& error_resp);
bool on_get_staking_requirement(const COMMAND_RPC_GET_STAKING_REQUIREMENT::request& req, COMMAND_RPC_GET_STAKING_REQUIREMENT::response& res, epee::json_rpc::error& error_resp);
//-----------------------
private:

View File

@ -34,6 +34,7 @@
#include "cryptonote_basic/verification_context.h"
#include "cryptonote_basic/difficulty.h"
#include "crypto/hash.h"
#include "cryptonote_config.h"
#include "cryptonote_core/service_node_deregister.h"
namespace cryptonote
@ -1581,6 +1582,8 @@ namespace cryptonote
std::vector<txpool_histo> histo;
uint32_t num_double_spends;
txpool_stats(): bytes_total(0), bytes_min(0), bytes_max(0), bytes_med(0), fee_total(0), oldest(0), txs_total(0), num_failing(0), num_10m(0), num_not_relayed(0), histo_98pc(0), num_double_spends(0) {}
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(bytes_total)
KV_SERIALIZE(bytes_min)
@ -2411,4 +2414,25 @@ namespace cryptonote
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_GET_STAKING_REQUIREMENT
{
struct request
{
uint64_t height;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(height)
END_KV_SERIALIZE_MAP()
};
struct response
{
uint64_t staking_requirement;
std::string status;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(staking_requirement)
KV_SERIALIZE(status)
END_KV_SERIALIZE_MAP()
};
};
}

View File

@ -4699,14 +4699,6 @@ bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_)
return sweep_main(0, true, args_);
}
//----------------------------------------------------------------------------------------------------
static cryptonote::account_public_address string_to_address(const std::string& s)
{
cryptonote::account_public_address address;
if (!epee::string_tools::hex_to_pod(s, address))
return service_nodes::null_address;
return address;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::register_service_node_main(
const std::vector<std::string>& service_node_key_as_str,
uint64_t expiration_timestamp,
@ -4748,7 +4740,7 @@ bool simple_wallet::register_service_node_main(
return true;
}
uint64_t staking_requirement_lock_blocks = (m_wallet->nettype() == cryptonote::TESTNET ? STAKING_REQUIREMENT_LOCK_BLOCKS_TESTNET : STAKING_REQUIREMENT_LOCK_BLOCKS);
uint64_t staking_requirement_lock_blocks = service_nodes::get_staking_requirement_lock_blocks(m_wallet->nettype());
uint64_t locked_blocks = staking_requirement_lock_blocks + STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
std::string err, err2;
@ -5049,12 +5041,12 @@ bool simple_wallet::register_service_node(const std::vector<std::string> &args_)
m_idle_thread.join();
success_msg_writer(false) << please_wait_to_be_included_in_block_msg;
#ifndef WIN32
success_msg_writer() << tr("Entering autostaking mode, forking to background...");
success_msg_writer(true /*color*/) << tr("Successfully entered autostaking mode, this wallet is moving into the background to automatically renew your service node every period.");
tools::threadpool::getInstance().stop();
posix::fork("");
tools::threadpool::getInstance().start();
#else
success_msg_writer() << tr("Entering autostaking mode, please leave this wallet running.");
success_msg_writer(true /*color*/) << tr("Successfully entered autostaking mode, please leave this wallet running to automatically renew your service node every period.");
#endif
m_idle_run.store(true, std::memory_order_relaxed);
while (true)
@ -5066,7 +5058,7 @@ bool simple_wallet::register_service_node(const std::vector<std::string> &args_)
break;
if (!m_idle_run.load(std::memory_order_relaxed))
break;
m_idle_cond.wait_for(lock, boost::chrono::seconds(120));
m_idle_cond.wait_for(lock, boost::chrono::seconds(AUTOSTAKE_INTERVAL));
}
}
else
@ -5097,7 +5089,7 @@ bool simple_wallet::stake_main(
uint64_t fetched_blocks;
m_wallet->refresh(0, fetched_blocks);
uint64_t staking_requirement_lock_blocks = (m_wallet->nettype() == cryptonote::TESTNET ? STAKING_REQUIREMENT_LOCK_BLOCKS_TESTNET : STAKING_REQUIREMENT_LOCK_BLOCKS);
uint64_t staking_requirement_lock_blocks = service_nodes::get_staking_requirement_lock_blocks(m_wallet->nettype());
uint64_t locked_blocks = staking_requirement_lock_blocks + STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
std::string err, err2;
@ -5168,7 +5160,11 @@ bool simple_wallet::stake_main(
for (const auto& contributor : snode_info.contributors)
{
if (string_to_address(contributor.address) == address)
address_parse_info info;
if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), contributor.address))
info.address = service_nodes::null_address;
if (info.address == address)
{
uint64_t max_increase_reserve = snode_info.staking_requirement - snode_info.total_reserved;
uint64_t max_increase_amount_to = contributor.reserved + max_increase_reserve;
@ -5192,7 +5188,7 @@ bool simple_wallet::stake_main(
if (amount > can_contrib_total)
{
success_msg_writer() << tr("You may only contribute up to ") << print_money(can_contrib_total) << tr(" more loki to this service node");
success_msg_writer() << tr("Automatically staking ") << print_money(can_contrib_total);
success_msg_writer() << tr("Reducing your stake from ") << print_money(amount) << tr(" to ") << print_money(can_contrib_total);
amount = can_contrib_total;
}
if (amount < must_contrib_total)
@ -5475,7 +5471,7 @@ bool simple_wallet::stake(const std::vector<std::string> &args_)
break;
if (!m_idle_run.load(std::memory_order_relaxed))
break;
m_idle_cond.wait_for(lock, boost::chrono::seconds(120));
m_idle_cond.wait_for(lock, boost::chrono::seconds(AUTOSTAKE_INTERVAL));
}
}
else

View File

@ -54,6 +54,8 @@
// Hardcode Monero's donation address (see #1447)
constexpr const char MONERO_DONATION_ADDR[] = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A";
const int AUTOSTAKE_INTERVAL = 60 * 40; // once every 40 minutes.
/*!
* \namespace cryptonote
* \brief Holds cryptonote related classes and helpers.

View File

@ -1,5 +1,5 @@
#define DEF_LOKI_VERSION_TAG "@VERSIONTAG@"
#define DEF_LOKI_VERSION "0.3.5"
#define DEF_LOKI_VERSION "0.4.0"
#define DEF_LOKI_RELEASE_NAME "Magic Mani"
#define DEF_LOKI_VERSION_FULL DEF_LOKI_VERSION "-" DEF_LOKI_VERSION_TAG

View File

@ -9163,7 +9163,7 @@ uint64_t wallet2::get_daemon_blockchain_target_height(string &err)
uint64_t wallet2::get_approximate_blockchain_height() const
{
const int seconds_per_block = DIFFICULTY_TARGET_V2;
const time_t epochTimeMiningStarted = 1525067730 + (60 * 60 * 24 * 7); // 2018-04-30 ~3:55PM + 1 week to be conservative.
const time_t epochTimeMiningStarted = (m_nettype == TESTNET || m_nettype == STAGENET ? 1536137083 : 1525067730) + (60 * 60 * 24 * 7); // 2018-04-30 ~3:55PM + 1 week to be conservative.
const time_t currentTime = time(NULL);
uint64_t approx_blockchain_height = (currentTime < epochTimeMiningStarted) ? 0 : (currentTime - epochTimeMiningStarted)/seconds_per_block;
LOG_PRINT_L2("Calculated blockchain height: " << approx_blockchain_height);

View File

@ -38,6 +38,7 @@ set(core_tests_sources
integer_overflow.cpp
multisig.cpp
ring_signature_1.cpp
service_nodes.cpp
transaction_tests.cpp
tx_validation.cpp
v2_tests.cpp
@ -55,6 +56,7 @@ set(core_tests_headers
integer_overflow.h
multisig.h
ring_signature_1.h
service_nodes.h
transaction_tests.h
tx_validation.h
v2_tests.h

View File

@ -171,7 +171,7 @@ bool gen_block_invalid_nonce::generate(std::vector<test_event_entry>& events) co
std::vector<uint64_t> timestamps;
std::vector<difficulty_type> commulative_difficulties;
if (!lift_up_difficulty(events, timestamps, commulative_difficulties, generator, 2, blk_0, miner_account))
if (!lift_up_difficulty(events, timestamps, commulative_difficulties, generator, 4, blk_0, miner_account))
return false;
// Create invalid nonce
@ -331,29 +331,12 @@ bool gen_block_miner_tx_has_2_in::generate(std::vector<test_event_entry>& events
BLOCK_VALIDATION_INIT_GENERATE();
REWIND_BLOCKS(events, blk_0r, blk_0, miner_account);
GENERATE_ACCOUNT(alice);
tx_source_entry se;
se.amount = blk_0.miner_tx.vout[0].amount;
se.push_output(0, boost::get<txout_to_key>(blk_0.miner_tx.vout[0].target).key, se.amount);
se.real_output = 0;
se.rct = false;
se.real_out_tx_key = get_tx_pub_key_from_extra(blk_0.miner_tx);
se.real_output_in_tx_index = 0;
std::vector<tx_source_entry> sources;
sources.push_back(se);
tx_destination_entry de;
de.addr = miner_account.get_keys().m_account_address;
de.amount = se.amount;
std::vector<tx_destination_entry> destinations;
destinations.push_back(de);
transaction tmp_tx;
if (!construct_tx(miner_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tmp_tx, 0))
if (!construct_tx_to_key(events, tmp_tx, blk_0r, miner_account, miner_account, blk_0.miner_tx.vout[0].amount))
return false;
MAKE_MINER_TX_MANUALLY(miner_tx, blk_0);
MAKE_MINER_TX_MANUALLY(miner_tx, blk_0r);
miner_tx.vin.push_back(tmp_tx.vin[0]);
block blk_1;
@ -376,24 +359,8 @@ bool gen_block_miner_tx_with_txin_to_key::generate(std::vector<test_event_entry>
REWIND_BLOCKS(events, blk_1r, blk_1, miner_account);
tx_source_entry se;
se.amount = blk_1.miner_tx.vout[0].amount;
se.push_output(0, boost::get<txout_to_key>(blk_1.miner_tx.vout[0].target).key, se.amount);
se.real_output = 0;
se.rct = false;
se.real_out_tx_key = get_tx_pub_key_from_extra(blk_1.miner_tx);
se.real_output_in_tx_index = 0;
std::vector<tx_source_entry> sources;
sources.push_back(se);
tx_destination_entry de;
de.addr = miner_account.get_keys().m_account_address;
de.amount = se.amount;
std::vector<tx_destination_entry> destinations;
destinations.push_back(de);
transaction tmp_tx;
if (!construct_tx(miner_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tmp_tx, 0))
if (!construct_tx_to_key(events, tmp_tx, blk_1r, miner_account, miner_account, blk_1.miner_tx.vout[0].amount))
return false;
MAKE_MINER_TX_MANUALLY(miner_tx, blk_1);
@ -408,22 +375,6 @@ bool gen_block_miner_tx_with_txin_to_key::generate(std::vector<test_event_entry>
return true;
}
bool gen_block_miner_tx_out_is_small::generate(std::vector<test_event_entry>& events) const
{
BLOCK_VALIDATION_INIT_GENERATE();
MAKE_MINER_TX_MANUALLY(miner_tx, blk_0);
miner_tx.vout[0].amount /= 2;
block blk_1;
generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx);
events.push_back(blk_1);
DO_CALLBACK(events, "check_block_purged");
return true;
}
bool gen_block_miner_tx_out_is_big::generate(std::vector<test_event_entry>& events) const
{
BLOCK_VALIDATION_INIT_GENERATE();
@ -446,6 +397,7 @@ bool gen_block_miner_tx_has_no_out::generate(std::vector<test_event_entry>& even
MAKE_MINER_TX_MANUALLY(miner_tx, blk_0);
miner_tx.vout.clear();
miner_tx.version = 1;
block blk_1;
generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx);
@ -462,19 +414,14 @@ bool gen_block_miner_tx_has_out_to_alice::generate(std::vector<test_event_entry>
GENERATE_ACCOUNT(alice);
keypair txkey;
MAKE_MINER_TX_AND_KEY_MANUALLY(miner_tx, blk_0, &txkey);
transaction miner_tx;
crypto::key_derivation derivation;
crypto::public_key out_eph_public_key;
crypto::generate_key_derivation(alice.get_keys().m_account_address.m_view_public_key, txkey.sec, derivation);
crypto::derive_public_key(derivation, 1, alice.get_keys().m_account_address.m_spend_public_key, out_eph_public_key);
const auto height = get_block_height(blk_0);
const auto coins = generator.get_already_generated_coins(blk_0);
const auto& miner_address = miner_account.get_keys().m_account_address;
const auto& alice_address = alice.get_keys().m_account_address;
tx_out out_to_alice;
out_to_alice.amount = miner_tx.vout[0].amount / 2;
miner_tx.vout[0].amount -= out_to_alice.amount;
out_to_alice.target = txout_to_key(out_eph_public_key);
miner_tx.vout.push_back(out_to_alice);
construct_miner_tx_with_extra_output(miner_tx, miner_address, height+1, coins, alice_address);
block blk_1;
generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx);
@ -507,7 +454,9 @@ bool gen_block_is_too_big::generate(std::vector<test_event_entry>& events) const
// Creating a huge miner_tx, it will have a lot of outs
MAKE_MINER_TX_MANUALLY(miner_tx, blk_0);
miner_tx.version = 1;
static const size_t tx_out_count = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 / 2;
uint64_t amount = get_outs_money_amount(miner_tx);
uint64_t portion = amount / tx_out_count;
uint64_t remainder = amount % tx_out_count;

View File

@ -110,7 +110,7 @@ struct gen_block_invalid_prev_id : public gen_block_verification_base<1>
bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/);
};
struct gen_block_invalid_nonce : public gen_block_verification_base<3>
struct gen_block_invalid_nonce : public gen_block_verification_base<5>
{
bool generate(std::vector<test_event_entry>& events) const;
};
@ -165,11 +165,6 @@ struct gen_block_miner_tx_with_txin_to_key : public gen_block_verification_base<
bool generate(std::vector<test_event_entry>& events) const;
};
struct gen_block_miner_tx_out_is_small : public gen_block_verification_base<1>
{
bool generate(std::vector<test_event_entry>& events) const;
};
struct gen_block_miner_tx_out_is_big : public gen_block_verification_base<1>
{
bool generate(std::vector<test_event_entry>& events) const;

View File

@ -71,53 +71,68 @@ bool gen_chain_switch_1::generate(std::vector<test_event_entry>& events) const
MAKE_ACCOUNT(events, recipient_account_2); // 2
MAKE_ACCOUNT(events, recipient_account_3); // 3
MAKE_ACCOUNT(events, recipient_account_4); // 4
REWIND_BLOCKS(events, blk_0r, blk_0, miner_account) // <N blocks>
MAKE_TX(events, tx_00, miner_account, recipient_account_1, MK_COINS(5), blk_0); // 5 + N
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_account, tx_00); // 6 + N
MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account); // 7 + N
REWIND_BLOCKS(events, blk_0r0, blk_0, miner_account) // <N blocks>
REWIND_BLOCKS(events, blk_0r, blk_0r0, miner_account) // <N blocks>
MAKE_TX(events, tx_00, miner_account, recipient_account_1, MK_COINS(5), blk_0r0); // 5 + 2N
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_account, tx_00); // 6 + 2N
MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account); // 7 + 2N
REWIND_BLOCKS(events, blk_2r, blk_2, miner_account) // <N blocks>
// Transactions to test account balances after switch
MAKE_TX_LIST_START(events, txs_blk_3, miner_account, recipient_account_2, MK_COINS(7), blk_2); // 8 + 2N
MAKE_TX_LIST_START(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(11), blk_2); // 9 + 2N
MAKE_TX_LIST_START(events, txs_blk_5, miner_account, recipient_account_4, MK_COINS(13), blk_2); // 10 + 2N
MAKE_TX_LIST_START(events, txs_blk_3, miner_account, recipient_account_2, MK_COINS(7), blk_2); // 8 + 3N
MAKE_TX_LIST_START(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(11), blk_2); // 9 + 3N
MAKE_TX_LIST_START(events, txs_blk_5, miner_account, recipient_account_4, MK_COINS(13), blk_2); // 10 + 3N
std::list<transaction> txs_blk_6;
txs_blk_6.push_back(txs_blk_4.front());
// Transactions, that has different order in alt block chains
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_COINS(1), blk_2); // 11 + 2N
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_COINS(1), blk_2); // 11 + 3N
txs_blk_5.push_back(txs_blk_3.back());
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_COINS(2), blk_2); // 12 + 2N
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_COINS(2), blk_2); // 12 + 3N
txs_blk_6.push_back(txs_blk_3.back());
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_2, MK_COINS(1), blk_2); // 13 + 2N
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_2, MK_COINS(1), blk_2); // 13 + 3N
txs_blk_5.push_back(txs_blk_3.back());
MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_2, MK_COINS(2), blk_2); // 14 + 2N
MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_2, MK_COINS(2), blk_2); // 14 + 3N
txs_blk_5.push_back(txs_blk_4.back());
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_3, MK_COINS(1), blk_2); // 15 + 2N
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_3, MK_COINS(1), blk_2); // 15 + 3N
txs_blk_6.push_back(txs_blk_3.back());
MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(2), blk_2); // 16 + 2N
MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(2), blk_2); // 16 + 3N
txs_blk_5.push_back(txs_blk_4.back());
MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_4, MK_COINS(1), blk_2); // 17 + 2N
MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_4, MK_COINS(1), blk_2); // 17 + 3N
txs_blk_5.push_back(txs_blk_4.back());
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_4, MK_COINS(2), blk_2); // 18 + 2N
MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_4, MK_COINS(2), blk_2); // 18 + 3N
txs_blk_6.push_back(txs_blk_3.back());
MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner_account, txs_blk_3); // 19 + 2N
MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3, miner_account, txs_blk_4); // 20 + 2N
MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner_account, txs_blk_3); // 19 + 3N
MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3, miner_account, txs_blk_4); // 20 + 3N
//split
MAKE_NEXT_BLOCK_TX_LIST(events, blk_5, blk_2r, miner_account, txs_blk_5); // 22 + 2N
MAKE_NEXT_BLOCK_TX_LIST(events, blk_6, blk_5, miner_account, txs_blk_6); // 23 + 2N
DO_CALLBACK(events, "check_split_not_switched"); // 21 + 2N
MAKE_NEXT_BLOCK(events, blk_7, blk_6, miner_account); // 24 + 2N
DO_CALLBACK(events, "check_split_switched"); // 25 + 2N
MAKE_NEXT_BLOCK_TX_LIST(events, blk_5, blk_2r, miner_account, txs_blk_5); // 22 + 3N
MAKE_NEXT_BLOCK_TX_LIST(events, blk_6, blk_5, miner_account, txs_blk_6); // 23 + 3N
DO_CALLBACK(events, "check_split_not_switched"); // 21 + 3N
MAKE_NEXT_BLOCK(events, blk_7, blk_6, miner_account); // 24 + 3N
DO_CALLBACK(events, "check_split_switched"); // 25 + 3N
return true;
}
static uint64_t transferred_in_tx(const cryptonote::account_base& account, const cryptonote::transaction& tx) {
uint64_t total_amount = 0;
for (auto i = 0u; i < tx.vout.size(); ++i) {
if(is_out_to_acc(account.get_keys(), boost::get<txout_to_key>(tx.vout[i].target), get_tx_pub_key_from_extra(tx), get_additional_tx_pub_keys_from_extra(tx), i)) {
total_amount += get_amount(account, tx, i);
}
}
return total_amount;
}
//-----------------------------------------------------------------------------------------------------
bool gen_chain_switch_1::check_split_not_switched(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
@ -131,8 +146,8 @@ bool gen_chain_switch_1::check_split_not_switched(cryptonote::core& c, size_t ev
std::list<block> blocks;
bool r = c.get_blocks(0, 10000, blocks);
CHECK_TEST_CONDITION(r);
CHECK_EQ(5 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks.size());
CHECK_TEST_CONDITION(blocks.back() == boost::get<block>(events[20 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW])); // blk_4
CHECK_EQ(5 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks.size());
CHECK_TEST_CONDITION(blocks.back() == boost::get<block>(events[20 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW])); // blk_4
CHECK_EQ(2, c.get_alternative_blocks_count());
@ -151,9 +166,9 @@ bool gen_chain_switch_1::check_split_not_switched(cryptonote::core& c, size_t ev
CHECK_EQ(1, tx_pool.size());
std::vector<size_t> tx_outs;
uint64_t transfered;
lookup_acc_outs(m_recipient_account_4.get_keys(), tx_pool.front(), get_tx_pub_key_from_extra(tx_pool.front()), get_additional_tx_pub_keys_from_extra(tx_pool.front()), tx_outs, transfered);
CHECK_EQ(MK_COINS(13), transfered);
const auto transferred = transferred_in_tx(m_recipient_account_4, tx_pool.front());
CHECK_EQ(MK_COINS(13), transferred);
m_chain_1.swap(blocks);
m_tx_pool.swap(tx_pool);
@ -169,11 +184,11 @@ bool gen_chain_switch_1::check_split_switched(cryptonote::core& c, size_t ev_ind
std::list<block> blocks;
bool r = c.get_blocks(0, 10000, blocks);
CHECK_TEST_CONDITION(r);
CHECK_EQ(6 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks.size());
CHECK_EQ(6 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks.size());
auto it = blocks.end();
--it; --it; --it;
CHECK_TEST_CONDITION(std::equal(blocks.begin(), it, m_chain_1.begin()));
CHECK_TEST_CONDITION(blocks.back() == boost::get<block>(events[24 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW])); // blk_7
CHECK_TEST_CONDITION(blocks.back() == boost::get<block>(events[24 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW])); // blk_7
std::list<block> alt_blocks;
r = c.get_alternative_blocks(alt_blocks);
@ -201,10 +216,8 @@ bool gen_chain_switch_1::check_split_switched(cryptonote::core& c, size_t ev_ind
CHECK_EQ(1, tx_pool.size());
CHECK_TEST_CONDITION(!(tx_pool.front() == m_tx_pool.front()));
std::vector<size_t> tx_outs;
uint64_t transfered;
lookup_acc_outs(m_recipient_account_2.get_keys(), tx_pool.front(), tx_outs, transfered);
CHECK_EQ(MK_COINS(7), transfered);
const auto transferred = transferred_in_tx(m_recipient_account_2, tx_pool.front());
CHECK_EQ(MK_COINS(7), transferred);
return true;
}
}

View File

@ -35,6 +35,7 @@
#include "include_base_utils.h"
#include "console_handler.h"
#include "common/rules.h"
#include "p2p/net_node.h"
#include "cryptonote_basic/cryptonote_basic.h"
@ -100,15 +101,17 @@ void test_generator::add_block(const cryptonote::block& blk, size_t tsx_size, st
const size_t block_size = tsx_size + get_object_blobsize(blk.miner_tx);
uint64_t block_reward;
cryptonote::get_block_reward(misc_utils::median(block_sizes), block_size, already_generated_coins, block_reward, hf_version_, 0);
m_blocks_info[get_block_hash(blk)] = block_info(blk.prev_id, already_generated_coins + block_reward, block_size);
m_blocks_info.insert({get_block_hash(blk), block_info(blk.prev_id, already_generated_coins + block_reward, block_size)});
}
bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, const crypto::hash& prev_id,
const cryptonote::account_base& miner_acc, uint64_t timestamp, uint64_t already_generated_coins,
std::vector<size_t>& block_sizes, const std::list<cryptonote::transaction>& tx_list)
std::vector<size_t>& block_sizes, const std::list<cryptonote::transaction>& tx_list,
const crypto::public_key& sn_pub_key /* = crypto::null_key */, const std::vector<sn_contributor_t>& sn_infos)
{
blk.major_version = CURRENT_BLOCK_MAJOR_VERSION;
blk.minor_version = CURRENT_BLOCK_MINOR_VERSION;
/// a temporary workaround
blk.major_version = hf_version_;
blk.minor_version = hf_version_;
blk.timestamp = timestamp;
blk.prev_id = prev_id;
@ -135,7 +138,8 @@ bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, co
size_t target_block_size = txs_size + get_object_blobsize(blk.miner_tx);
while (true)
{
if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, target_block_size, total_fee, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), hf_version_))
if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, target_block_size, total_fee, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), hf_version_, cryptonote::MAINNET, sn_pub_key, sn_infos))
return false;
size_t actual_block_size = txs_size + get_object_blobsize(blk.miner_tx);
@ -197,7 +201,8 @@ bool test_generator::construct_block(cryptonote::block& blk, const cryptonote::a
bool test_generator::construct_block(cryptonote::block& blk, const cryptonote::block& blk_prev,
const cryptonote::account_base& miner_acc,
const std::list<cryptonote::transaction>& tx_list/* = {}*/)
const std::list<cryptonote::transaction>& tx_list/* = {}*/,
const crypto::public_key& sn_pub_key /* = crypto::null_key */, const std::vector<sn_contributor_t>& sn_infos)
{
uint64_t height = boost::get<txin_gen>(blk_prev.miner_tx.vin.front()).height + 1;
crypto::hash prev_id = get_block_hash(blk_prev);
@ -207,7 +212,7 @@ bool test_generator::construct_block(cryptonote::block& blk, const cryptonote::b
std::vector<size_t> block_sizes;
get_last_n_block_sizes(block_sizes, prev_id, CRYPTONOTE_REWARD_BLOCKS_WINDOW);
return construct_block(blk, height, prev_id, miner_acc, timestamp, already_generated_coins, block_sizes, tx_list);
return construct_block(blk, height, prev_id, miner_acc, timestamp, already_generated_coins, block_sizes, tx_list, sn_pub_key, sn_infos);
}
bool test_generator::construct_block_manually(block& blk, const block& prev_block, const account_base& miner_acc,
@ -261,19 +266,21 @@ bool test_generator::construct_block_manually_tx(cryptonote::block& blk, const c
struct output_index {
const cryptonote::txout_target_v out;
uint64_t amount;
rct::key mask;
size_t blk_height; // block height
uint64_t unlock_time;
size_t tx_no; // index of transaction in block
size_t out_no; // index of out in transaction
size_t idx;
bool spent;
bool is_sn_reward = false;
const cryptonote::block *p_blk;
const cryptonote::transaction *p_tx;
output_index(const cryptonote::txout_target_v &_out, uint64_t _a, size_t _h, size_t tno, size_t ono, const cryptonote::block *_pb, const cryptonote::transaction *_pt)
: out(_out), amount(_a), blk_height(_h), tx_no(tno), out_no(ono), idx(0), spent(false), p_blk(_pb), p_tx(_pt) { }
output_index(const cryptonote::txout_target_v &_out, uint64_t _a, size_t _h, uint64_t ut, size_t tno, size_t ono, const cryptonote::block *_pb, const cryptonote::transaction *_pt)
: out(_out), amount(_a), blk_height(_h), unlock_time(ut), tx_no(tno), out_no(ono), idx(0), spent(false), p_blk(_pb), p_tx(_pt) { }
output_index(const output_index &other)
: out(other.out), amount(other.amount), blk_height(other.blk_height), tx_no(other.tx_no), out_no(other.out_no), idx(other.idx), spent(other.spent), p_blk(other.p_blk), p_tx(other.p_tx) { }
output_index(const output_index &other) = default;
const std::string toString() const {
std::stringstream ss;
@ -282,6 +289,7 @@ struct output_index {
<< " tx_no=" << tx_no
<< " out_no=" << out_no
<< " amount=" << amount
<< " mask=" << mask
<< " idx=" << idx
<< " spent=" << spent
<< "}";
@ -392,16 +400,27 @@ bool init_output_indices(output_index_vec& outs, output_vec& outs_mine, const st
const auto height = boost::get<txin_gen>(*blk.miner_tx.vin.begin()).height; /// replace with front?
outs.push_back({out.target, out.amount, height, i, j, &blk, vtx[i]});
const auto unlock_time = (tx.version < 3) ? tx.unlock_time : tx.output_unlock_times[j];
outs.push_back({out.target, out.amount, height, unlock_time, i, j, &blk, vtx[i]});
size_t tx_global_idx = outs.size() - 1;
outs[tx_global_idx].idx = tx_global_idx;
outs[tx_global_idx].mask = rct::zeroCommit(out.amount);
// Is out to me?
if (is_out_to_acc(from.get_keys(), boost::get<txout_to_key>(out.target), get_tx_pub_key_from_extra(tx), get_additional_tx_pub_keys_from_extra(tx), j)) {
outs_mine.push_back(tx_global_idx);
auto& out = outs.back();
if (out.amount == 0) {
out.amount = get_amount(from, tx, j);
const auto gov_key = cryptonote::get_deterministic_keypair_from_height(height);
const bool to_acc_regular = is_out_to_acc(from.get_keys(), boost::get<txout_to_key>(out.target), get_tx_pub_key_from_extra(tx), get_additional_tx_pub_keys_from_extra(tx), j);
const bool to_acc_sn_reward = to_acc_regular ? false : is_out_to_acc(from.get_keys(), boost::get<txout_to_key>(out.target), gov_key.pub, {}, j);
if (to_acc_regular || to_acc_sn_reward) {
outs_mine.push_back(tx_global_idx);
auto& oi = outs.back();
oi.is_sn_reward = to_acc_sn_reward;
if (oi.amount == 0) {
oi.amount = get_amount(from, tx, j);
oi.mask = tx.rct_signatures.outPk[j].mask;
}
}
}
}
@ -427,10 +446,14 @@ bool init_spent_output_indices(output_index_vec& outs,
crypto::public_key out_key = boost::get<txout_to_key>(oi.out).key;
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[from.get_keys().m_account_address.m_spend_public_key] = {0,0};
const auto tx_pk = oi.is_sn_reward ? get_deterministic_keypair_from_height(oi.blk_height).pub
: get_tx_pub_key_from_extra(*oi.p_tx);
generate_key_image_helper(from.get_keys(),
subaddresses,
out_key,
get_tx_pub_key_from_extra(*oi.p_tx),
tx_pk,
get_additional_tx_pub_keys_from_extra(*oi.p_tx),
oi.out_no,
in_ephemeral,
@ -483,7 +506,7 @@ static bool fill_output_entries(const std::vector<output_index>& out_indices, si
if (append)
{
const txout_to_key& otk = boost::get<txout_to_key>(oi.out);
output_entries.push_back(tx_source_entry::output_entry(oi.idx, rct::ctkey({rct::pk2rct(otk.key), rct::zeroCommit(oi.amount)})));
output_entries.push_back(tx_source_entry::output_entry(oi.idx, rct::ctkey({rct::pk2rct(otk.key), oi.mask})));
}
}
@ -518,18 +541,32 @@ bool fill_tx_sources(std::vector<tx_source_entry>& sources, const std::vector<te
cryptonote::tx_source_entry ts;
const auto& tx = *oi.p_tx;
ts.amount = oi.amount;
ts.real_output_in_tx_index = oi.out_no;
ts.real_out_tx_key = get_tx_pub_key_from_extra(*oi.p_tx); // incoming tx public key
ts.real_out_additional_tx_keys = get_additional_tx_pub_keys_from_extra(*oi.p_tx);
ts.real_out_tx_key = get_tx_pub_key_from_extra(tx); // incoming tx public key
ts.real_out_additional_tx_keys = get_additional_tx_pub_keys_from_extra(tx);
ts.mask = rct::identity();
size_t realOutput;
if (!fill_output_entries(outs, sender_out, nmix, realOutput, ts.outputs)) continue;
ts.real_output = realOutput;
ts.rct = true;
/// Filling in the mask
{
crypto::key_derivation derivation;
bool r = crypto::generate_key_derivation(ts.real_out_tx_key, from.get_keys().m_view_secret_key, derivation);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation");
crypto::secret_key amount_key;
crypto::derivation_to_scalar(derivation, oi.out_no, amount_key);
if (tx.rct_signatures.type == rct::RCTTypeSimple || tx.rct_signatures.type == rct::RCTTypeSimpleBulletproof)
rct::decodeRctSimple(
tx.rct_signatures, rct::sk2rct(amount_key), oi.out_no, ts.mask, hw::get_device("default"));
else if (tx.rct_signatures.type == rct::RCTTypeFull ||
tx.rct_signatures.type == rct::RCTTypeFullBulletproof)
rct::decodeRct(
tx.rct_signatures, rct::sk2rct(amount_key), oi.out_no, ts.mask, hw::get_device("default"));
}
if (!fill_output_entries(outs, sender_out, nmix, ts.real_output, ts.outputs)) continue;
sources.push_back(ts);
sources_amount += ts.amount;
@ -550,7 +587,7 @@ bool fill_tx_destination(tx_destination_entry &de, const cryptonote::account_bas
void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& events, const block& blk_head,
const cryptonote::account_base& from, const cryptonote::account_base& to,
uint64_t amount, uint64_t fee, size_t nmix, std::vector<tx_source_entry>& sources,
std::vector<tx_destination_entry>& destinations)
std::vector<tx_destination_entry>& destinations, uint64_t *change_amount)
{
sources.clear();
destinations.clear();
@ -564,13 +601,15 @@ void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& event
destinations.push_back(de);
tx_destination_entry de_change;
uint64_t cache_back = get_inputs_amount(sources) - (amount + fee);
if (0 < cache_back)
uint64_t cash_back = get_inputs_amount(sources) - (amount + fee);
if (0 < cash_back)
{
if (!fill_tx_destination(de_change, from, cache_back))
if (!fill_tx_destination(de_change, from, cash_back))
throw std::runtime_error("couldn't fill transaction cache back destination");
destinations.push_back(de_change);
}
if (change_amount) *change_amount = (cash_back > 0) ? cash_back : 0;
}
void fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t height)
@ -580,6 +619,86 @@ 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::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);
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*/)
@ -632,14 +751,47 @@ bool construct_tx_to_key(const std::vector<test_event_entry>& events,
bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote::transaction& tx, const block& blk_head,
const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount,
uint64_t fee, size_t nmix)
uint64_t fee, size_t nmix, bool stake, boost::optional<const register_info> reg_info, uint64_t unlock_time)
{
vector<tx_source_entry> sources;
vector<tx_destination_entry> destinations;
fill_tx_sources_and_destinations(events, blk_head, from, to, amount, fee, nmix, sources, destinations);
tx_destination_entry change_addr{ amount, from.get_keys().m_account_address, false /* is subaddr */ };
return cryptonote::construct_tx(from.get_keys(), sources, destinations, change_addr, {}, tx, 0);
uint64_t change_amount;
fill_tx_sources_and_destinations(events, blk_head, from, to, amount, fee, nmix, sources, destinations, &change_amount);
tx_destination_entry change_addr{change_amount, from.get_keys().m_account_address, false /* is subaddr */ };
std::vector<uint8_t> extra;
if (stake) {
if (!reg_info) {
LOG_ERROR("Stake tx has not registration info");
return false;
}
add_service_node_pubkey_to_tx_extra(extra, reg_info->service_node_keypair.pub);
const uint64_t exp_timestamp = time(nullptr) + STAKING_AUTHORIZATION_EXPIRATION_WINDOW;
crypto::hash hash;
bool hashed = cryptonote::get_registration_hash(reg_info->addresses, reg_info->operator_cut, reg_info->portions, exp_timestamp, hash);
if (!hashed)
{
MERROR("Could not make registration hash from addresses and portions");
return false;
}
crypto::signature signature;
crypto::generate_signature(hash, reg_info->service_node_keypair.pub, reg_info->service_node_keypair.sec, signature);
add_service_node_register_to_tx_extra(extra, reg_info->addresses, reg_info->operator_cut, reg_info->portions, exp_timestamp, signature);
add_service_node_contributor_to_tx_extra(extra, reg_info->addresses.at(0));
}
return cryptonote::construct_tx(from.get_keys(), sources, destinations, change_addr, extra, tx, unlock_time, stake, true);
}
transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const block& blk_head,
@ -673,6 +825,32 @@ uint64_t get_balance(const cryptonote::account_base& addr, const std::vector<cry
return res;
}
uint64_t get_unlocked_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx) {
if (blockchain.empty()) return 0;
uint64_t res = 0;
output_index_vec outs;
output_vec outs_mine;
map_hash2tx_t confirmed_txs;
get_confirmed_txs(blockchain, mtx, confirmed_txs);
if (!init_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr))
return false;
if (!init_spent_output_indices(outs, outs_mine, blockchain, confirmed_txs, addr))
return false;
for (const size_t out_idx : outs_mine) {
const auto unlocked = rules::is_output_unlocked(outs[out_idx].unlock_time, get_block_height(blockchain.back()));
if (outs[out_idx].spent || !unlocked) continue;
res += outs[out_idx].amount;
}
return res;
}
void get_confirmed_txs(const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs)
{
std::unordered_set<crypto::hash> confirmed_hashes;

View File

@ -55,7 +55,7 @@
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "tests.core"
#define TESTS_DEFAULT_FEE ((uint64_t)200000000) // 2 * pow(10, 8)
struct callback_entry
{
@ -106,10 +106,12 @@ struct event_visitor_settings
{
int valid_mask;
bool txs_keeped_by_block;
crypto::secret_key service_node_key;
enum settings
{
set_txs_keeped_by_block = 1 << 0
set_txs_keeped_by_block = 1 << 0,
set_service_node_key = 1 << 1
};
event_visitor_settings(int a_valid_mask = 0, bool a_txs_keeped_by_block = false)
@ -118,6 +120,13 @@ struct event_visitor_settings
{
}
static event_visitor_settings make_set_service_node_key(const crypto::secret_key& a_service_node_key)
{
event_visitor_settings settings(set_service_node_key);
settings.service_node_key = a_service_node_key;
return settings;
}
private:
friend class boost::serialization::access;
@ -126,6 +135,7 @@ private:
{
ar & valid_mask;
ar & txs_keeped_by_block;
ar & service_node_key;
}
};
@ -156,13 +166,6 @@ class test_generator
public:
struct block_info
{
block_info()
: prev_id()
, already_generated_coins(0)
, block_size(0)
{
}
block_info(crypto::hash a_prev_id, uint64_t an_already_generated_coins, size_t a_block_size)
: prev_id(a_prev_id)
, already_generated_coins(an_already_generated_coins)
@ -188,6 +191,8 @@ public:
bf_hf_version= 1 << 8
};
using sn_contributor_t = std::pair<cryptonote::account_public_address, uint64_t>;
void get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const;
void get_last_n_block_sizes(std::vector<size_t>& block_sizes, const crypto::hash& head, size_t n) const;
uint64_t get_already_generated_coins(const crypto::hash& blk_id) const;
@ -196,10 +201,12 @@ public:
void add_block(const cryptonote::block& blk, size_t tsx_size, std::vector<size_t>& block_sizes, uint64_t already_generated_coins);
bool construct_block(cryptonote::block& blk, uint64_t height, const crypto::hash& prev_id,
const cryptonote::account_base& miner_acc, uint64_t timestamp, uint64_t already_generated_coins,
std::vector<size_t>& block_sizes, const std::list<cryptonote::transaction>& tx_list);
std::vector<size_t>& block_sizes, const std::list<cryptonote::transaction>& tx_list, const crypto::public_key& sn_pub_key = crypto::null_pkey,
const std::vector<sn_contributor_t>& = {{{crypto::null_pkey, crypto::null_pkey}, STAKING_PORTIONS}});
bool construct_block(cryptonote::block& blk, const cryptonote::account_base& miner_acc, uint64_t timestamp);
bool construct_block(cryptonote::block& blk, const cryptonote::block& blk_prev, const cryptonote::account_base& miner_acc,
const std::list<cryptonote::transaction>& tx_list = std::list<cryptonote::transaction>());
const std::list<cryptonote::transaction>& tx_list = std::list<cryptonote::transaction>(), const crypto::public_key& sn_pub_key = crypto::null_pkey,
const std::vector<sn_contributor_t>& = {{{crypto::null_pkey, crypto::null_pkey}, STAKING_PORTIONS}});
bool construct_block_manually(cryptonote::block& blk, const cryptonote::block& prev_block,
const cryptonote::account_base& miner_acc, int actual_params = bf_none, uint8_t major_ver = 0,
@ -209,14 +216,24 @@ 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) {}
void set_hf_version(uint8_t ver) { hf_version_ = ver; }
private:
std::unordered_map<crypto::hash, block_info> m_blocks_info;
uint8_t hf_version_ = 7;
uint8_t hf_version_;
};
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);
bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins,
const cryptonote::account_public_address& miner_address, cryptonote::transaction& tx,
uint64_t fee, cryptonote::keypair* p_txkey = 0);
@ -226,9 +243,18 @@ bool construct_tx_to_key(const std::vector<test_event_entry>& events,
const cryptonote::account_base& from,
const cryptonote::account_base& to,
uint64_t amount);
struct register_info {
const cryptonote::keypair& service_node_keypair;
const std::vector<uint64_t>& portions;
uint64_t operator_cut;
const std::vector<cryptonote::account_public_address>& addresses;
};
bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote::transaction& tx,
const cryptonote::block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to,
uint64_t amount, uint64_t fee, size_t nmix);
uint64_t amount, uint64_t fee, size_t nmix, bool stake=false, boost::optional<const register_info> reg_info = boost::none, uint64_t unlock_time=0);
cryptonote::transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const cryptonote::block& blk_head,
const cryptonote::account_base& acc_from, const cryptonote::account_base& acc_to,
uint64_t amount, uint64_t fee);
@ -239,8 +265,12 @@ void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& event
const cryptonote::account_base& from, const cryptonote::account_base& to,
uint64_t amount, uint64_t fee, size_t nmix,
std::vector<cryptonote::tx_source_entry>& sources,
std::vector<cryptonote::tx_destination_entry>& destinations);
std::vector<cryptonote::tx_destination_entry>& destinations, uint64_t *change_amount = nullptr);
/// Get the amount transferred to `account` in `tx` as output `i`
uint64_t get_amount(const cryptonote::account_base& account, const cryptonote::transaction& tx, int i);
uint64_t get_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx);
uint64_t get_unlocked_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx);
//--------------------------------------------------------------------------
template<class t_test_class>
@ -578,7 +608,14 @@ inline bool do_replay_file(const std::string& filename)
register_callback(#METHOD, boost::bind(&CLASS::METHOD, this, _1, _2, _3));
#define MAKE_GENESIS_BLOCK(VEC_EVENTS, BLK_NAME, MINER_ACC, TS) \
test_generator generator; \
test_generator generator; \
cryptonote::block BLK_NAME; \
generator.construct_block(BLK_NAME, MINER_ACC, TS); \
VEC_EVENTS.push_back(BLK_NAME);
/// TODO: use hf_ver from test options
#define MAKE_GENESIS_BLOCK_WITH_HF_VERSION(VEC_EVENTS, BLK_NAME, MINER_ACC, TS, HF_VER) \
test_generator generator(HF_VER); \
cryptonote::block BLK_NAME; \
generator.construct_block(BLK_NAME, MINER_ACC, TS); \
VEC_EVENTS.push_back(BLK_NAME);
@ -588,6 +625,11 @@ inline bool do_replay_file(const std::string& filename)
generator.construct_block(BLK_NAME, PREV_BLOCK, MINER_ACC); \
VEC_EVENTS.push_back(BLK_NAME);
#define MAKE_NEXT_BLOCK_V2(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, WINNER, SN_INFO) \
cryptonote::block BLK_NAME; \
generator.construct_block(BLK_NAME, PREV_BLOCK, MINER_ACC, {}, WINNER, SN_INFO); \
VEC_EVENTS.push_back(BLK_NAME);
#define MAKE_NEXT_BLOCK_TX1(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, TX1) \
cryptonote::block BLK_NAME; \
{ \
@ -614,8 +656,43 @@ inline bool do_replay_file(const std::string& filename)
BLK_NAME = blk_last; \
}
#define REWIND_BLOCKS_N_V2(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, COUNT, WINNER, SN_INFO) \
cryptonote::block BLK_NAME; \
{ \
cryptonote::block blk_last = PREV_BLOCK; \
for (size_t i = 0; i < COUNT; ++i) \
{ \
MAKE_NEXT_BLOCK_V2(VEC_EVENTS, blk, blk_last, MINER_ACC, WINNER, SN_INFO); \
blk_last = blk; \
} \
BLK_NAME = blk_last; \
}
#define REWIND_BLOCKS(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC) REWIND_BLOCKS_N(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW)
inline cryptonote::transaction make_registration_tx(
std::vector<test_event_entry>& events,
const cryptonote::account_base& account,
const cryptonote::keypair& service_node_keys,
uint64_t operator_cut,
const std::vector<cryptonote::account_public_address>& addresses,
const std::vector<uint64_t>& portions,
const cryptonote::block& head)
{
const auto new_height = cryptonote::get_block_height(head) + 1;
const auto staking_requirement = service_nodes::get_staking_requirement(cryptonote::FAKECHAIN, new_height);
uint64_t amount = service_nodes::portions_to_amount(portions[0], staking_requirement);
cryptonote::transaction tx;
boost::optional<const register_info> reg_info = register_info{service_node_keys, portions, operator_cut, addresses};
const auto unlock_time = new_height + service_nodes::get_staking_requirement_lock_blocks(cryptonote::FAKECHAIN);
construct_tx_to_key(events, tx, head, account, account, amount, TESTS_DEFAULT_FEE, 9, true /* staking */, reg_info, unlock_time);
events.push_back(tx);
return tx;
}
#define MAKE_TX_MIX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \
cryptonote::transaction TX_NAME; \
construct_tx_to_key(VEC_EVENTS, TX_NAME, HEAD, FROM, TO, AMOUNT, TESTS_DEFAULT_FEE, NMIX); \
@ -638,14 +715,12 @@ inline bool do_replay_file(const std::string& filename)
std::list<cryptonote::transaction> SET_NAME; \
MAKE_TX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD);
#define MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, KEY) \
transaction TX; \
if (!construct_miner_tx_manually(get_block_height(BLK) + 1, generator.get_already_generated_coins(BLK), \
miner_account.get_keys().m_account_address, TX, 0, KEY)) \
#define MAKE_MINER_TX_MANUALLY(TX, BLK) \
transaction TX; \
if (!construct_miner_tx(get_block_height(BLK)+1, 0, generator.get_already_generated_coins(BLK), \
0, 0, miner_account.get_keys().m_account_address, TX, {}, 7)) \
return false;
#define MAKE_MINER_TX_MANUALLY(TX, BLK) MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, 0)
#define SET_EVENT_VISITOR_SETT(VEC_EVENTS, SETT, VAL) VEC_EVENTS.push_back(event_visitor_settings(SETT, VAL));
#define GENERATE(filename, genclass) \
@ -716,4 +791,3 @@ inline bool do_replay_file(const std::string& filename)
#define CHECK_EQ(v1, v2) CHECK_AND_ASSERT_MES(v1 == v2, false, "[" << perr_context << "] failed: \"" << QUOTEME(v1) << " == " << QUOTEME(v2) << "\", " << v1 << " != " << v2)
#define CHECK_NOT_EQ(v1, v2) CHECK_AND_ASSERT_MES(!(v1 == v2), false, "[" << perr_context << "] failed: \"" << QUOTEME(v1) << " != " << QUOTEME(v2) << "\", " << v1 << " == " << v2)
#define MK_COINS(amount) (UINT64_C(amount) * COIN)
#define TESTS_DEFAULT_FEE ((uint64_t)20000000000) // 2 * pow(10, 10)

View File

@ -91,6 +91,7 @@ int main(int argc, char* argv[])
}
else if (command_line::get_arg(vm, arg_generate_and_play_test_data))
{
GENERATE_AND_PLAY(gen_service_nodes);
GENERATE_AND_PLAY(gen_simple_chain_001);
GENERATE_AND_PLAY(gen_simple_chain_split_1);
GENERATE_AND_PLAY(one_block);
@ -117,7 +118,6 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(gen_block_miner_tx_has_2_tx_gen_in);
GENERATE_AND_PLAY(gen_block_miner_tx_has_2_in);
GENERATE_AND_PLAY(gen_block_miner_tx_with_txin_to_key);
GENERATE_AND_PLAY(gen_block_miner_tx_out_is_small);
GENERATE_AND_PLAY(gen_block_miner_tx_out_is_big);
GENERATE_AND_PLAY(gen_block_miner_tx_has_no_out);
GENERATE_AND_PLAY(gen_block_miner_tx_has_out_to_alice);

View File

@ -38,6 +38,7 @@
#include "double_spend.h"
#include "integer_overflow.h"
#include "ring_signature_1.h"
#include "service_nodes.h"
#include "tx_validation.h"
#include "v2_tests.h"
#include "rct.h"

View File

@ -87,7 +87,7 @@ bool gen_ring_signature_1::generate(std::vector<test_event_entry>& events) const
DO_CALLBACK(events, "check_balances_1"); // 23 + 2N
REWIND_BLOCKS(events, blk_6r, blk_6, miner_account); // <N blocks>
// 129 = 11 + 11 + 20 + 29 + 29 + 29
MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(129) + 2 * rnd_11 + rnd_20 + 3 * rnd_29 - TESTS_DEFAULT_FEE, 2, blk_6); // 24 + 3N
MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(129) + 2 * rnd_11 + rnd_20 + 3 * rnd_29 - TESTS_DEFAULT_FEE, 9, blk_6); // 24 + 3N
MAKE_NEXT_BLOCK_TX1(events, blk_7, blk_6r, miner_account, tx_0); // 25 + 3N
DO_CALLBACK(events, "check_balances_2"); // 26 + 3N
@ -144,8 +144,8 @@ gen_ring_signature_2::gen_ring_signature_2()
}
/**
* Bob has 4 inputs by 13 coins. He sends 4 * 13 coins to Alice, using ring signature with nmix = 3. Each Bob's input
* is used as mix for 3 others.
* Bob has 4 inputs by 13 coins. He sends 4 * 13 coins to Alice, using ring signature with nmix = 9. Each Bob's input
* is used as mix for 9 others.
*/
bool gen_ring_signature_2::generate(std::vector<test_event_entry>& events) const
{
@ -159,18 +159,19 @@ bool gen_ring_signature_2::generate(std::vector<test_event_entry>& events) const
MAKE_ACCOUNT(events, alice_account); // 2
MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_account); // 3
MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account); // 4
MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_account); // 5
REWIND_BLOCKS(events, blk_2b, blk_2, miner_account); // <N blocks>
MAKE_NEXT_BLOCK(events, blk_3, blk_2b, miner_account); // 5 + N
REWIND_BLOCKS(events, blk_3r, blk_3, miner_account); // <N blocks>
MAKE_TX_LIST_START(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 6 + N
MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 7 + N
MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 8 + N
MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 9 + N
MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3r, miner_account, txs_blk_4); // 10 + N
DO_CALLBACK(events, "check_balances_1"); // 11 + N
MAKE_TX_LIST_START(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 6 + 2N
MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 7 + 2N
MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 8 + 2N
MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 9 + 2N
MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3r, miner_account, txs_blk_4); // 10 + 2N
DO_CALLBACK(events, "check_balances_1"); // 11 + 2N
REWIND_BLOCKS(events, blk_4r, blk_4, miner_account); // <N blocks>
MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(52) - TESTS_DEFAULT_FEE, 3, blk_4); // 12 + 2N
MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4r, miner_account, tx_0); // 13 + 2N
DO_CALLBACK(events, "check_balances_2"); // 14 + 2N
MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(52) - TESTS_DEFAULT_FEE, 9, blk_4); // 12 + 3N
MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4r, miner_account, tx_0); // 13 + 3N
DO_CALLBACK(events, "check_balances_2"); // 14 + 3N
return true;
}
@ -183,7 +184,7 @@ bool gen_ring_signature_2::check_balances_1(cryptonote::core& c, size_t ev_index
m_alice_account = boost::get<account_base>(events[2]);
std::list<block> blocks;
bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks);
bool r = c.get_blocks(0, 100 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks);
CHECK_TEST_CONDITION(r);
std::vector<cryptonote::block> chain;
@ -201,7 +202,7 @@ bool gen_ring_signature_2::check_balances_2(cryptonote::core& c, size_t ev_index
DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_2::check_balances_2");
std::list<block> blocks;
bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks);
bool r = c.get_blocks(0, 100 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks);
CHECK_TEST_CONDITION(r);
std::vector<cryptonote::block> chain;

View File

@ -0,0 +1,144 @@
// Copyright (c) 2014-2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "chaingen.h"
#include "service_nodes.h"
using namespace std;
using namespace epee;
using namespace cryptonote;
gen_service_nodes::gen_service_nodes()
{
/// NOTE: we don't generate random keys here, because the verification will call its own constructor
constexpr char pub_key_str[] = "cf6ae1d4e902f7a85af58d6069c29f09702e25fd07cf28d359e64401002db2a1";
constexpr char sec_key_str[] = "ead4cc692c4237f62f9cefaf5e106995b2dda79a29002a546876f9ee7abcc203";
epee::string_tools::hex_to_pod(pub_key_str, m_alice_service_node_keys.pub);
epee::string_tools::hex_to_pod(sec_key_str, m_alice_service_node_keys.sec);
REGISTER_CALLBACK("check_registered", gen_service_nodes::check_registered);
REGISTER_CALLBACK("check_expired", gen_service_nodes::check_expired);
}
//-----------------------------------------------------------------------------------------------------
bool gen_service_nodes::generate(std::vector<test_event_entry> &events) const
{
uint64_t ts_start = 1338224400;
GENERATE_ACCOUNT(miner);
MAKE_GENESIS_BLOCK(events, blk_0, miner, ts_start); // 1
MAKE_ACCOUNT(events, alice);
generator.set_hf_version(8);
MAKE_NEXT_BLOCK(events, blk_a, blk_0, miner); // 2
generator.set_hf_version(9);
MAKE_NEXT_BLOCK(events, blk_b, blk_a, miner); // 3
REWIND_BLOCKS_N(events, blk_c, blk_b, miner, 10); // 13
REWIND_BLOCKS(events, blk_1, blk_c, miner); // 13 + N
MAKE_TX(events, tx_0, miner, alice, MK_COINS(101), blk_1);
MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1, miner, tx_0); // 14 + N
REWIND_BLOCKS(events, blk_3, blk_2, miner); // 14 + 2N
cryptonote::transaction alice_registration =
make_registration_tx(events, alice, m_alice_service_node_keys, 0, { alice.get_keys().m_account_address }, { STAKING_PORTIONS }, blk_3);
MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3, miner, alice_registration); // 15 + 2N
DO_CALLBACK(events, "check_registered");
std::vector<test_generator::sn_contributor_t> sn_info = {{alice.get_keys().m_account_address, STAKING_PORTIONS}};
REWIND_BLOCKS_N_V2(events, blk_5, blk_4, miner, service_nodes::get_staking_requirement_lock_blocks(cryptonote::FAKECHAIN), m_alice_service_node_keys.pub, sn_info); // 15 + 2N + M
DO_CALLBACK(events, "check_expired");
return true;
}
//-----------------------------------------------------------------------------------------------------
bool gen_service_nodes::check_registered(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events)
{
DEFINE_TESTS_ERROR_CONTEXT("gen_service_nodes::check_registered");
cryptonote::account_base alice = boost::get<cryptonote::account_base>(events[1]);
std::list<block> block_list;
bool r = c.get_blocks(0, 15 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, block_list);
CHECK_TEST_CONDITION(r);
std::vector<cryptonote::block> chain;
map_hash2tx_t mtx;
std::vector<block> blocks(block_list.begin(), block_list.end());
r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back()));
CHECK_TEST_CONDITION(r);
const uint64_t staking_requirement = MK_COINS(100);
CHECK_EQ(MK_COINS(101) - TESTS_DEFAULT_FEE - staking_requirement, get_unlocked_balance(alice, blocks, mtx));
/// check that alice is registered
const auto info_v = c.get_service_node_list_state({m_alice_service_node_keys.pub});
CHECK_EQ(info_v.empty(), false);
return true;
}
bool gen_service_nodes::check_expired(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events)
{
DEFINE_TESTS_ERROR_CONTEXT("gen_service_nodes::check_expired");
cryptonote::account_base alice = boost::get<cryptonote::account_base>(events[1]);
const auto stake_lock_time = service_nodes::get_staking_requirement_lock_blocks(cryptonote::FAKECHAIN);
std::list<block> block_list;
bool r = c.get_blocks(0, 15 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW + stake_lock_time, block_list);
CHECK_TEST_CONDITION(r);
std::vector<cryptonote::block> chain;
map_hash2tx_t mtx;
std::vector<block> blocks(block_list.begin(), block_list.end());
r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back()));
CHECK_TEST_CONDITION(r);
/// check that alice's registration expired
const auto info_v = c.get_service_node_list_state({m_alice_service_node_keys.pub});
CHECK_EQ(info_v.empty(), true);
/// check that alice received some service node rewards (TODO: check the balance precisely)
CHECK_TEST_CONDITION(get_balance(alice, blocks, mtx) > MK_COINS(101) - TESTS_DEFAULT_FEE);
return true;
}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2014-2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#pragma once
#include "chaingen.h"
/************************************************************************/
/* */
/************************************************************************/
class gen_service_nodes : public test_chain_unit_base
{
public:
gen_service_nodes();
bool generate(std::vector<test_event_entry> &events) const;
bool check_registered(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events);
bool check_expired(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &events);
private:
cryptonote::keypair m_alice_service_node_keys;
};
template<>
struct get_test_options<gen_service_nodes> {
const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(7, 0), std::make_pair(8, 1), std::make_pair(9, 2)};
const cryptonote::test_options test_options = {
hard_forks
};
};

View File

@ -56,6 +56,7 @@ set(unit_tests_sources
multisig.cpp
parse_amount.cpp
serialization.cpp
service_nodes.cpp
sha256.cpp
slow_memmem.cpp
subaddress.cpp
@ -69,7 +70,8 @@ set(unit_tests_sources
ringct.cpp
output_selection.cpp
vercmp.cpp
ringdb.cpp)
ringdb.cpp
)
set(unit_tests_headers
unit_tests_utils.h)

View File

@ -0,0 +1,326 @@
// Copyright (c) 2018, The Loki Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "gtest/gtest.h"
#include "cryptonote_core/service_node_list.h"
#include "cryptonote_core/service_node_deregister.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_config.h"
TEST(service_nodes, staking_requirement)
{
// TODO(loki): The current reference values here for the staking requirement
// at certain heights has been derived from excel, so we have to use an
// epsilon for dust amounts as amounts are off by a bit. When we switch to
// integer math we can remove the need for this. Doyle - 2018-08-28
// NOTE: Thanks for the values @Sonofotis
const uint64_t atomic_epsilon = config::DEFAULT_DUST_THRESHOLD;
// LHS of Equation
// Try underflow
{
uint64_t height = 100;
uint64_t mainnet_requirement = service_nodes::get_staking_requirement(cryptonote::MAINNET, height);
uint64_t stagenet_requirement = service_nodes::get_staking_requirement(cryptonote::STAGENET, height);
ASSERT_EQ(stagenet_requirement, (45000 * COIN));
ASSERT_EQ(mainnet_requirement, (45000 * COIN));
}
// Starting height for stagenet
{
uint64_t height = 96210;
uint64_t stagenet_requirement = service_nodes::get_staking_requirement(cryptonote::STAGENET, height);
ASSERT_EQ(stagenet_requirement, (45000 * COIN));
}
// Starting height for mainnet
{
// NOTE: The maximum staking requirement is 50,000, in atomic units is 50,000,000,000,000 < int64 range (2^63-1)
// so casting is safe.
uint64_t height = 101250;
int64_t mainnet_requirement = (int64_t)service_nodes::get_staking_requirement(cryptonote::MAINNET, height);
int64_t stagenet_requirement = (int64_t)service_nodes::get_staking_requirement(cryptonote::STAGENET, height);
ASSERT_EQ(mainnet_requirement, (45000 * COIN));
int64_t stagenet_expected = (int64_t)((44069 * COIN) + 151880000);
int64_t stagenet_delta = std::abs(stagenet_requirement - stagenet_expected);
ASSERT_LT(stagenet_delta, atomic_epsilon);
}
// Check the requirements are decreasing
{
uint64_t height = 250000;
int64_t mainnet_requirement = (int64_t)service_nodes::get_staking_requirement(cryptonote::MAINNET, height);
int64_t stagenet_requirement = (int64_t)service_nodes::get_staking_requirement(cryptonote::STAGENET, height);
int64_t mainnet_expected = (int64_t)((25796 * COIN) + 364642307);
int64_t mainnet_delta = std::abs(mainnet_requirement - mainnet_expected);
ASSERT_LT(mainnet_delta, atomic_epsilon);
int64_t stagenet_expected = (int64_t)((25376 * COIN) + 249888366);
int64_t stagenet_delta = std::abs(stagenet_requirement - stagenet_expected);
ASSERT_LT(stagenet_delta, atomic_epsilon);
}
// Bottom of the curve, generally this should be the lowest the staking requirement will be
{
uint64_t height = 1036800;
int64_t mainnet_requirement = (int64_t)service_nodes::get_staking_requirement(cryptonote::MAINNET, height);
int64_t stagenet_requirement = (int64_t)service_nodes::get_staking_requirement(cryptonote::STAGENET, height);
int64_t mainnet_expected = (int64_t)((10234 * COIN) + 967482165);
int64_t mainnet_delta = std::abs(mainnet_requirement - mainnet_expected);
ASSERT_LT(mainnet_delta, atomic_epsilon);
int64_t stagenet_expected = (int64_t)((10228 * COIN) + 718366740);
int64_t stagenet_delta = std::abs(stagenet_requirement - stagenet_expected);
ASSERT_LT(stagenet_delta, atomic_epsilon);
}
// RHS of Rewards Formula, 1st part
// Where the two equations should meet and staking formula equalizes
{
uint64_t height = 1166400;
uint64_t mainnet_requirement = service_nodes::get_staking_requirement(cryptonote::MAINNET, height);
uint64_t stagenet_requirement = service_nodes::get_staking_requirement(cryptonote::STAGENET, height);
ASSERT_EQ(mainnet_requirement, (10250 * COIN));
ASSERT_EQ(stagenet_requirement, (10250 * COIN));
}
// Checking the requirements still equal
{
uint64_t height = 1296000;
uint64_t mainnet_requirement = service_nodes::get_staking_requirement(cryptonote::MAINNET, height);
uint64_t stagenet_requirement = service_nodes::get_staking_requirement(cryptonote::STAGENET, height);
ASSERT_EQ(mainnet_requirement, (10500 * COIN));
ASSERT_EQ(stagenet_requirement, (10500 * COIN));
}
// Checking we are approaching 15000
{
uint64_t height = 3000000;
int64_t mainnet_requirement = (int64_t)service_nodes::get_staking_requirement(cryptonote::MAINNET, height);
int64_t stagenet_requirement = (int64_t)service_nodes::get_staking_requirement(cryptonote::STAGENET, height);
int64_t mainnet_expected = (int64_t)((13787 * COIN) + 37037037);
int64_t mainnet_delta = std::abs(mainnet_requirement - mainnet_expected);
ASSERT_LT(mainnet_delta, atomic_epsilon);
int64_t stagenet_expected = (int64_t)((13787 * COIN) + 37037037);
int64_t stagenet_delta = std::abs(stagenet_requirement - stagenet_expected);
ASSERT_LT(stagenet_delta, atomic_epsilon);
}
// RHS of Rewards Formula, 2nd part
// Last part of formula maxes out at 15000 if height > 3628800
{
uint64_t height = 3628800;
uint64_t mainnet_requirement = service_nodes::get_staking_requirement(cryptonote::MAINNET, height);
uint64_t stagenet_requirement = service_nodes::get_staking_requirement(cryptonote::STAGENET, height);
ASSERT_EQ(mainnet_requirement, (15000 * COIN));
ASSERT_EQ(stagenet_requirement, (15000 * COIN));
}
// Check we stay capped at 15000
{
uint64_t height = 4082400;
uint64_t mainnet_requirement = service_nodes::get_staking_requirement(cryptonote::MAINNET, height);
uint64_t stagenet_requirement = service_nodes::get_staking_requirement(cryptonote::STAGENET, height);
ASSERT_EQ(mainnet_requirement, (15000 * COIN));
ASSERT_EQ(stagenet_requirement, (15000 * COIN));
}
}
TEST(service_nodes, vote_validation)
{
// Generate a quorum and the voter
cryptonote::keypair service_node_voter = cryptonote::keypair::generate(hw::get_device("default"));
int voter_index = 0;
service_nodes::quorum_state state = {};
{
state.quorum_nodes.resize(10);
state.nodes_to_test.resize(state.quorum_nodes.size());
for (size_t i = 0; i < state.quorum_nodes.size(); ++i)
{
state.quorum_nodes[i] = (i == voter_index) ? service_node_voter.pub : cryptonote::keypair::generate(hw::get_device("default")).pub;
state.nodes_to_test[i] = cryptonote::keypair::generate(hw::get_device("default")).pub;
}
}
// Valid vote
loki::service_node_deregister::vote valid_vote = {};
{
valid_vote.block_height = 10;
valid_vote.service_node_index = 1;
valid_vote.voters_quorum_index = voter_index;
valid_vote.signature = loki::service_node_deregister::sign_vote(valid_vote.block_height, valid_vote.service_node_index, service_node_voter.pub, service_node_voter.sec);
cryptonote::vote_verification_context vvc = {};
bool result = loki::service_node_deregister::verify_vote(cryptonote::MAINNET, valid_vote, vvc, state);
if (!result)
printf("%s\n", cryptonote::print_vote_verification_context(vvc, &valid_vote));
ASSERT_TRUE(result);
}
// Voters quorum index out of bounds
{
auto vote = valid_vote;
vote.voters_quorum_index = state.quorum_nodes.size() + 10;
vote.signature = loki::service_node_deregister::sign_vote(vote.block_height, vote.service_node_index, service_node_voter.pub, service_node_voter.sec);
cryptonote::vote_verification_context vvc = {};
bool result = loki::service_node_deregister::verify_vote(cryptonote::MAINNET, vote, vvc, state);
ASSERT_FALSE(result);
}
// Voters service node index out of bounds
{
auto vote = valid_vote;
vote.service_node_index = state.nodes_to_test.size() + 10;
vote.signature = loki::service_node_deregister::sign_vote(vote.block_height, vote.service_node_index, service_node_voter.pub, service_node_voter.sec);
cryptonote::vote_verification_context vvc = {};
bool result = loki::service_node_deregister::verify_vote(cryptonote::MAINNET, vote, vvc, state);
ASSERT_FALSE(result);
}
// Signature not valid
{
auto vote = valid_vote;
cryptonote::keypair other_voter = cryptonote::keypair::generate(hw::get_device("default"));
vote.signature = loki::service_node_deregister::sign_vote(vote.block_height, vote.service_node_index, other_voter.pub, other_voter.sec);
cryptonote::vote_verification_context vvc = {};
bool result = loki::service_node_deregister::verify_vote(cryptonote::MAINNET, vote, vvc, state);
ASSERT_FALSE(result);
}
}
TEST(service_nodes, tx_extra_deregister_validation)
{
// Generate a quorum and the voter
const size_t num_voters = 10;
cryptonote::keypair voters[num_voters] = {};
service_nodes::quorum_state state = {};
{
state.quorum_nodes.resize(num_voters);
state.nodes_to_test.resize(num_voters);
for (size_t i = 0; i < state.quorum_nodes.size(); ++i)
{
voters[i] = cryptonote::keypair::generate(hw::get_device("default"));
state.quorum_nodes[i] = voters[i].pub;
state.nodes_to_test[i] = cryptonote::keypair::generate(hw::get_device("default")).pub;
}
}
// Valid deregister
cryptonote::tx_extra_service_node_deregister valid_deregister = {};
{
valid_deregister.block_height = 10;
valid_deregister.service_node_index = 1;
valid_deregister.votes.reserve(num_voters);
for (size_t i = 0; i < num_voters; ++i)
{
cryptonote::keypair const *voter = voters + i;
cryptonote::tx_extra_service_node_deregister::vote vote = {};
vote.voters_quorum_index = i;
vote.signature = loki::service_node_deregister::sign_vote(valid_deregister.block_height, valid_deregister.service_node_index, voter->pub, voter->sec);
valid_deregister.votes.push_back(vote);
}
cryptonote::vote_verification_context vvc = {};
bool result = loki::service_node_deregister::verify_deregister(cryptonote::MAINNET, valid_deregister, vvc, state);
if (!result)
printf("%s\n", cryptonote::print_vote_verification_context(vvc));
ASSERT_TRUE(result);
}
// Deregister has insufficient votes
{
auto deregister = valid_deregister;
while (deregister.votes.size() >= service_nodes::MIN_VOTES_TO_KICK_SERVICE_NODE)
deregister.votes.pop_back();
cryptonote::vote_verification_context vvc = {};
bool result = loki::service_node_deregister::verify_deregister(cryptonote::MAINNET, deregister, vvc, state);
ASSERT_FALSE(result);
}
// Deregister has duplicated voter
{
auto deregister = valid_deregister;
deregister.votes[0] = deregister.votes[1];
cryptonote::vote_verification_context vvc = {};
bool result = loki::service_node_deregister::verify_deregister(cryptonote::MAINNET, deregister, vvc, state);
ASSERT_FALSE(result);
}
// Deregister has one voter with invalid signature
{
auto deregister = valid_deregister;
deregister.votes[0].signature = deregister.votes[1].signature;
cryptonote::vote_verification_context vvc = {};
bool result = loki::service_node_deregister::verify_deregister(cryptonote::MAINNET, deregister, vvc, state);
ASSERT_FALSE(result);
}
// Deregister has one voter with index out of bounds
{
auto deregister = valid_deregister;
deregister.votes[0].voters_quorum_index = state.quorum_nodes.size() + 10;
cryptonote::vote_verification_context vvc = {};
bool result = loki::service_node_deregister::verify_deregister(cryptonote::MAINNET, deregister, vvc, state);
ASSERT_FALSE(result);
}
// Deregister service node index is out of bounds
{
auto deregister = valid_deregister;
deregister.service_node_index = state.nodes_to_test.size() + 10;
cryptonote::vote_verification_context vvc = {};
bool result = loki::service_node_deregister::verify_deregister(cryptonote::MAINNET, deregister, vvc, state);
ASSERT_FALSE(result);
}
}