mirror of https://github.com/oxen-io/oxen-core.git
Infinite Staking Part 1 (#387)
* Remove dead branches in hot-path check_tx_inputs Also renames #define for mixins to better match naming convention * Shuffle around some more code into common branches * Fix min/max tx version rules, since there 1 tx v2 on v9 fork * First draft infinite staking implementation * Actually generate the right key image and expire appropriately * Add framework to lock key images after expiry * Return locked key images for nodes, add request unlock option * Introduce transaction types for key image unlock * Update validation steps to accept tx types, key_image_unlock * Add mapping for lockable key images to amounts * Change inconsistent naming scheme of contributors * Create key image unlock transaction type and process it * Update tx params to allow v4 types and as a result construct_tx* * Fix some serialisation issues not sending all the information * Fix dupe tx extra tag causing incorrect deserialisation * Add warning comments * Fix key image unlocks parsing error * Simplify key image proof checks * Fix rebase errors * Correctly calculate the key image unlock times * Blacklist key image on deregistration * Serialise key image blacklist * Rollback blacklisted key images * Fix expiry logic error * Disallow requesting stake unlock if already unlocked client side * Add double spend checks for key image unlocks * Rename get_staking_requirement_lock_blocks To staking_initial_num_lock_blocks * Begin modifying output selection to not use locked outputs * Modify output selection to avoid locked/blacklisted key images * Cleanup and undoing some protocol breakages * Simplify expiration of nodes * Request unlock schedules entire node for expiration * Fix off by one in expiring nodes * Undo expiring code for pre v10 nodes * Fix RPC returning register as unlock height and not checking 0 * Rename key image unlock height const * Undo testnet hardfork debug changes * Remove is_type for get_type, fix missing var rename * Move serialisable data into public namespace * Serialise tx types properly * Fix typo in no service node known msg * Code review * Fix == to >= on serialising tx type * Code review 2 * Fix tests and key image unlock * Add additional test, fix assert * Remove debug code in wallet * Fix merge dev problem
This commit is contained in:
parent
101f1799eb
commit
3a7b6b59eb
|
@ -162,7 +162,10 @@ namespace cryptonote
|
|||
version_1,
|
||||
version_2,
|
||||
version_3_per_output_unlock_times,
|
||||
version_4_tx_types,
|
||||
};
|
||||
static version get_min_version_for_hf(int hf_version);
|
||||
static version get_max_version_for_hf(int hf_version);
|
||||
|
||||
// tx information
|
||||
size_t version;
|
||||
|
@ -176,21 +179,43 @@ namespace cryptonote
|
|||
std::vector<uint8_t> extra;
|
||||
|
||||
std::vector<uint64_t> output_unlock_times;
|
||||
bool is_deregister; //service node deregister tx
|
||||
|
||||
enum type_t
|
||||
{
|
||||
type_standard,
|
||||
type_deregister,
|
||||
type_key_image_unlock,
|
||||
type_count,
|
||||
};
|
||||
|
||||
static char const *type_to_string(type_t type);
|
||||
static char const *type_to_string(uint16_t type_as_uint);
|
||||
|
||||
union
|
||||
{
|
||||
bool is_deregister; // not used after version >= version_4_tx_types
|
||||
uint16_t type;
|
||||
};
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
VARINT_FIELD(version)
|
||||
if (version > 2)
|
||||
{
|
||||
FIELD(output_unlock_times)
|
||||
FIELD(is_deregister)
|
||||
if (version == version_3_per_output_unlock_times)
|
||||
FIELD(is_deregister)
|
||||
}
|
||||
if(version == 0 || CURRENT_TRANSACTION_VERSION < version) return false;
|
||||
if(version == 0 || version > version_4_tx_types) return false;
|
||||
VARINT_FIELD(unlock_time)
|
||||
FIELD(vin)
|
||||
FIELD(vout)
|
||||
if (version >= 3 && vout.size() != output_unlock_times.size()) return false;
|
||||
FIELD(extra)
|
||||
if (version >= version_4_tx_types)
|
||||
{
|
||||
VARINT_FIELD(type) // NOTE(loki): Overwrites is_deregister
|
||||
if (static_cast<uint16_t>(type) >= type_count) return false;
|
||||
}
|
||||
END_SERIALIZE()
|
||||
|
||||
public:
|
||||
|
@ -203,9 +228,11 @@ namespace cryptonote
|
|||
vout.clear();
|
||||
extra.clear();
|
||||
output_unlock_times.clear();
|
||||
is_deregister = false;
|
||||
type = type_standard;
|
||||
}
|
||||
bool is_deregister_tx() const { return (version >= version_3_per_output_unlock_times) && is_deregister; }
|
||||
type_t get_type () const;
|
||||
bool set_type (type_t new_type);
|
||||
|
||||
uint64_t get_unlock_time(size_t out_index) const
|
||||
{
|
||||
if (version >= version_3_per_output_unlock_times)
|
||||
|
@ -475,7 +502,85 @@ namespace cryptonote
|
|||
}
|
||||
};
|
||||
//---------------------------------------------------------------
|
||||
inline enum transaction_prefix::version transaction_prefix::get_max_version_for_hf(int hf_version)
|
||||
{
|
||||
if (hf_version >= cryptonote::network_version_7 && hf_version <= cryptonote::network_version_8)
|
||||
return transaction::version_2;
|
||||
|
||||
if (hf_version >= cryptonote::network_version_9_service_nodes && hf_version <= cryptonote::network_version_10_bulletproofs)
|
||||
return transaction::version_3_per_output_unlock_times;
|
||||
|
||||
return transaction::version_4_tx_types;
|
||||
}
|
||||
|
||||
inline enum transaction_prefix::version transaction_prefix::get_min_version_for_hf(int hf_version)
|
||||
{
|
||||
if (hf_version >= cryptonote::network_version_7 && hf_version <= cryptonote::network_version_9_service_nodes)
|
||||
return transaction::version_2;
|
||||
|
||||
if (hf_version == cryptonote::network_version_10_bulletproofs)
|
||||
return transaction::version_3_per_output_unlock_times;
|
||||
|
||||
return transaction::version_4_tx_types;
|
||||
}
|
||||
|
||||
inline transaction_prefix::type_t transaction_prefix::get_type() const
|
||||
{
|
||||
if (version <= version_2)
|
||||
return type_standard;
|
||||
|
||||
if (version == version_3_per_output_unlock_times)
|
||||
{
|
||||
if (is_deregister) return type_deregister;
|
||||
return type_standard;
|
||||
}
|
||||
|
||||
// NOTE(loki): Type is range checked on deserialisation, so hitting this is a developer error
|
||||
assert(static_cast<uint16_t>(type) < static_cast<uint16_t>(type_count));
|
||||
return static_cast<transaction::type_t>(type);
|
||||
}
|
||||
|
||||
inline bool transaction_prefix::set_type(transaction_prefix::type_t new_type)
|
||||
{
|
||||
bool result = false;
|
||||
if (version <= version_2)
|
||||
result = (new_type == type_standard);
|
||||
|
||||
if (version == version_3_per_output_unlock_times)
|
||||
{
|
||||
if (new_type == type_standard || new_type == type_deregister)
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
|
||||
if (result)
|
||||
{
|
||||
assert(static_cast<uint16_t>(new_type) <= static_cast<uint16_t>(type_count)); // NOTE(loki): Developer error
|
||||
type = static_cast<uint16_t>(new_type);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline char const *transaction_prefix::type_to_string(uint16_t type_as_uint)
|
||||
{
|
||||
return type_to_string(static_cast<type_t>(type_as_uint));
|
||||
}
|
||||
|
||||
inline char const *transaction_prefix::type_to_string(type_t type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case type_standard: return "standard";
|
||||
case type_deregister: return "deregister";
|
||||
case type_key_image_unlock: return "key_image_unlock";
|
||||
case type_count: return "xx_count";
|
||||
default: assert(false); return "xx_unhandled_type";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
|
|
@ -152,30 +152,36 @@ namespace boost
|
|||
inline void serialize(Archive &a, cryptonote::transaction_prefix &x, const boost::serialization::version_type ver)
|
||||
{
|
||||
a & x.version;
|
||||
if (x.version >= 3)
|
||||
if (x.version > 2)
|
||||
{
|
||||
a & x.output_unlock_times;
|
||||
a & x.is_deregister;
|
||||
if (x.version == cryptonote::transaction::version_3_per_output_unlock_times)
|
||||
a & x.is_deregister;
|
||||
}
|
||||
a & x.unlock_time;
|
||||
a & x.vin;
|
||||
a & x.vout;
|
||||
a & x.extra;
|
||||
if (x.version >= cryptonote::transaction::version_4_tx_types)
|
||||
a & x.type;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
inline void serialize(Archive &a, cryptonote::transaction &x, const boost::serialization::version_type ver)
|
||||
{
|
||||
a & x.version;
|
||||
if (x.version >= 3)
|
||||
if (x.version > 2)
|
||||
{
|
||||
a & x.output_unlock_times;
|
||||
a & x.is_deregister;
|
||||
if (x.version == cryptonote::transaction::version_3_per_output_unlock_times)
|
||||
a & x.is_deregister;
|
||||
}
|
||||
a & x.unlock_time;
|
||||
a & x.vin;
|
||||
a & x.vout;
|
||||
a & x.extra;
|
||||
if (x.version >= cryptonote::transaction::version_4_tx_types)
|
||||
a & x.type;
|
||||
if (x.version == 1)
|
||||
{
|
||||
a & x.signatures;
|
||||
|
|
|
@ -519,6 +519,8 @@ namespace cryptonote
|
|||
if (!pick<tx_extra_service_node_contributor>(nar, tx_extra_fields, TX_EXTRA_TAG_SERVICE_NODE_CONTRIBUTOR)) return false;
|
||||
if (!pick<tx_extra_service_node_pubkey> (nar, tx_extra_fields, TX_EXTRA_TAG_SERVICE_NODE_PUBKEY)) return false;
|
||||
if (!pick<tx_extra_tx_secret_key> (nar, tx_extra_fields, TX_EXTRA_TAG_TX_SECRET_KEY)) return false;
|
||||
if (!pick<tx_extra_tx_key_image_proofs> (nar, tx_extra_fields, TX_EXTRA_TAG_TX_KEY_IMAGE_PROOFS)) return false;
|
||||
if (!pick<tx_extra_tx_key_image_unlock> (nar, tx_extra_fields, TX_EXTRA_TAG_TX_KEY_IMAGE_UNLOCK)) return false;
|
||||
|
||||
if (!pick<tx_extra_merge_mining_tag>(nar, tx_extra_fields, TX_EXTRA_MERGE_MINING_TAG)) return false;
|
||||
if (!pick<tx_extra_mysterious_minergate>(nar, tx_extra_fields, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG)) return false;
|
||||
|
@ -603,20 +605,26 @@ namespace cryptonote
|
|||
return get_additional_tx_pub_keys_from_extra(tx.extra);
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys)
|
||||
static bool add_tx_extra_field_to_tx_extra(std::vector<uint8_t>& tx_extra, tx_extra_field &field)
|
||||
{
|
||||
// convert to variant
|
||||
tx_extra_field field = tx_extra_additional_pub_keys{ additional_pub_keys };
|
||||
// serialize
|
||||
std::ostringstream oss;
|
||||
binary_archive<true> ar(oss);
|
||||
bool r = ::do_serialize(ar, field);
|
||||
CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra additional tx pub keys");
|
||||
// append
|
||||
if (!::do_serialize(ar, field))
|
||||
return false;
|
||||
|
||||
std::string tx_extra_str = oss.str();
|
||||
size_t pos = tx_extra.size();
|
||||
tx_extra.resize(tx_extra.size() + tx_extra_str.size());
|
||||
memcpy(&tx_extra[pos], tx_extra_str.data(), tx_extra_str.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys)
|
||||
{
|
||||
tx_extra_field field = tx_extra_additional_pub_keys{ additional_pub_keys };
|
||||
bool r = add_tx_extra_field_to_tx_extra(tx_extra, field);
|
||||
CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra additional tx pub keys");
|
||||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
|
@ -691,6 +699,40 @@ namespace cryptonote
|
|||
add_data_to_tx_extra(tx_extra, reinterpret_cast<const char *>(&key), sizeof(key), TX_EXTRA_TAG_TX_SECRET_KEY);
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool get_tx_key_image_proofs_from_tx_extra(const std::vector<uint8_t>& tx_extra, tx_extra_tx_key_image_proofs &proofs)
|
||||
{
|
||||
std::vector<tx_extra_field> tx_extra_fields;
|
||||
parse_tx_extra(tx_extra, tx_extra_fields);
|
||||
|
||||
bool result = find_tx_extra_field_by_type(tx_extra_fields, proofs);
|
||||
return result;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool add_tx_key_image_proofs_to_tx_extra(std::vector<uint8_t>& tx_extra, const tx_extra_tx_key_image_proofs& proofs)
|
||||
{
|
||||
tx_extra_field field = proofs;
|
||||
bool result = add_tx_extra_field_to_tx_extra(tx_extra, field);
|
||||
CHECK_AND_NO_ASSERT_MES_L1(result, false, "failed to serialize tx extra tx key image proof");
|
||||
return result;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool get_tx_key_image_unlock_from_tx_extra(const std::vector<uint8_t>& tx_extra, tx_extra_tx_key_image_unlock &unlock)
|
||||
{
|
||||
std::vector<tx_extra_field> tx_extra_fields;
|
||||
parse_tx_extra(tx_extra, tx_extra_fields);
|
||||
|
||||
bool result = find_tx_extra_field_by_type(tx_extra_fields, unlock);
|
||||
return result;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool add_tx_key_image_unlock_to_tx_extra(std::vector<uint8_t>& tx_extra, const tx_extra_tx_key_image_unlock& unlock)
|
||||
{
|
||||
tx_extra_field field = unlock;
|
||||
bool result = add_tx_extra_field_to_tx_extra(tx_extra, field);
|
||||
CHECK_AND_NO_ASSERT_MES_L1(result, false, "failed to serialize tx extra tx key image unlock");
|
||||
return result;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool get_service_node_contributor_from_tx_extra(const std::vector<uint8_t>& tx_extra, cryptonote::account_public_address& address)
|
||||
{
|
||||
std::vector<tx_extra_field> tx_extra_fields;
|
||||
|
@ -742,16 +784,9 @@ namespace cryptonote
|
|||
expiration_timestamp,
|
||||
service_node_signature
|
||||
};
|
||||
// serialize
|
||||
std::ostringstream oss;
|
||||
binary_archive<true> ar(oss);
|
||||
bool r = ::do_serialize(ar, field);
|
||||
|
||||
bool r = add_tx_extra_field_to_tx_extra(tx_extra, field);
|
||||
CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra registration tx");
|
||||
// append
|
||||
std::string tx_extra_str = oss.str();
|
||||
size_t pos = tx_extra.size();
|
||||
tx_extra.resize(tx_extra.size() + tx_extra_str.size());
|
||||
memcpy(&tx_extra[pos], tx_extra_str.data(), tx_extra_str.size());
|
||||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
|
@ -879,9 +914,9 @@ namespace cryptonote
|
|||
//-----------------------------------------------------------------------------------------------
|
||||
bool check_outs_valid(const transaction& tx)
|
||||
{
|
||||
if (tx.is_deregister_tx())
|
||||
if (tx.get_type() != transaction::type_standard)
|
||||
{
|
||||
CHECK_AND_NO_ASSERT_MES(tx.vout.size() == 0, false, "tx version deregister must have 0 outputs, received: " << tx.vout.size() << ", id=" << get_transaction_hash(tx));
|
||||
CHECK_AND_NO_ASSERT_MES(tx.vout.size() == 0, false, "tx type: " << transaction::type_to_string(tx.type) << " must have 0 outputs, received: " << tx.vout.size() << ", id=" << get_transaction_hash(tx));
|
||||
}
|
||||
|
||||
if (tx.version >= 3)
|
||||
|
@ -1083,33 +1118,46 @@ namespace cryptonote
|
|||
//---------------------------------------------------------------
|
||||
char const *print_tx_verification_context(tx_verification_context const &tvc, transaction const *tx)
|
||||
{
|
||||
static char buf[1024];
|
||||
static char buf[2048];
|
||||
buf[0] = 0;
|
||||
char *bufPtr = buf;
|
||||
char *bufEnd = buf + sizeof(buf);
|
||||
|
||||
if (tvc.m_verifivation_failed) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Verification failed, connection should be dropped, "); //bad tx, should drop connection
|
||||
if (tvc.m_verifivation_impossible) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Verification impossible, related to alt chain, "); //the transaction is related with an alternative blockchain
|
||||
if (tvc.m_should_be_relayed) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX should be relayed, ");
|
||||
if (tvc.m_added_to_pool) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX added to pool, ");
|
||||
if (tvc.m_low_mixin) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Insufficient mixin, ");
|
||||
if (tvc.m_double_spend) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Double spend TX, ");
|
||||
if (tvc.m_invalid_input) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Invalid inputs, ");
|
||||
if (tvc.m_invalid_output) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Invalid outputs, ");
|
||||
if (tvc.m_too_big) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX too big, ");
|
||||
if (tvc.m_overspend) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Overspend, ");
|
||||
if (tvc.m_fee_too_low) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Fee too low, ");
|
||||
if (tvc.m_not_rct) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX is not a valid RCT TX., ");
|
||||
if (tvc.m_invalid_version) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX has invalid version, ");
|
||||
if (tvc.m_verifivation_failed) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Verification failed, connection should be dropped, "); //bad tx, should drop connection
|
||||
if (tvc.m_verifivation_impossible) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Verification impossible, related to alt chain, "); //the transaction is related with an alternative blockchain
|
||||
if (tvc.m_should_be_relayed) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX should be relayed, ");
|
||||
if (tvc.m_added_to_pool) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX added to pool, ");
|
||||
if (tvc.m_low_mixin) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Insufficient mixin, ");
|
||||
if (tvc.m_double_spend) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Double spend TX, ");
|
||||
if (tvc.m_invalid_input) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Invalid inputs, ");
|
||||
if (tvc.m_invalid_output) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Invalid outputs, ");
|
||||
if (tvc.m_too_big) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX too big, ");
|
||||
if (tvc.m_overspend) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Overspend, ");
|
||||
if (tvc.m_fee_too_low) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Fee too low, ");
|
||||
if (tvc.m_not_rct) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX is not a valid RCT TX., ");
|
||||
if (tvc.m_invalid_version) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX has invalid version, ");
|
||||
if (tvc.m_invalid_type) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX has invalid type, ");
|
||||
if (tvc.m_key_image_locked_by_snode) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Key image is locked by service node, ");
|
||||
if (tvc.m_key_image_blacklisted) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Key image is blacklisted on the service node network, ");
|
||||
|
||||
if (tx) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX Version: %d", (int)tx->version);
|
||||
if (tx)
|
||||
{
|
||||
bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "TX Version: %d", (int)tx->version);
|
||||
bufPtr += snprintf(bufPtr, bufEnd - bufPtr, " Type: %s", transaction::type_to_string(tx->type));
|
||||
}
|
||||
|
||||
if (bufPtr != buf)
|
||||
{
|
||||
char *last_comma = bufPtr - 2;
|
||||
if (last_comma[0] == ',') last_comma[0] = 0;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
char const *print_vote_verification_context(vote_verification_context const &vvc, service_nodes::deregister_vote const *vote)
|
||||
{
|
||||
static char buf[1024];
|
||||
static char buf[2048];
|
||||
buf[0] = 0;
|
||||
|
||||
char *bufPtr = buf;
|
||||
|
@ -1123,6 +1171,12 @@ namespace cryptonote
|
|||
if (vvc.m_full_tx_deregister_made) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Full TX deregister made, ");
|
||||
if (vvc.m_not_enough_votes) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Not enough votes, ");
|
||||
|
||||
if (bufPtr != buf)
|
||||
{
|
||||
char *last_comma = bufPtr - 2;
|
||||
if (last_comma[0] == ',') last_comma[0] = 0;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
|
|
|
@ -77,18 +77,26 @@ namespace cryptonote
|
|||
void add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key);
|
||||
void add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key);
|
||||
void add_tx_pub_key_to_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& tx_pub_key);
|
||||
|
||||
bool add_service_node_deregister_to_tx_extra(std::vector<uint8_t>& tx_extra, const tx_extra_service_node_deregister& deregistration);
|
||||
bool get_service_node_register_from_tx_extra(const std::vector<uint8_t>& tx_extra, tx_extra_service_node_register& registration);
|
||||
bool get_service_node_deregister_from_tx_extra(const std::vector<uint8_t>& tx_extra, tx_extra_service_node_deregister& deregistration);
|
||||
bool get_service_node_pubkey_from_tx_extra(const std::vector<uint8_t>& tx_extra, crypto::public_key& pubkey);
|
||||
bool get_service_node_contributor_from_tx_extra(const std::vector<uint8_t>& tx_extra, cryptonote::account_public_address& address);
|
||||
bool add_service_node_register_to_tx_extra(std::vector<uint8_t>& tx_extra, const std::vector<cryptonote::account_public_address>& addresses, uint64_t portions_for_operator, const std::vector<uint64_t>& portions, uint64_t expiration_timestamp, const crypto::signature& signature);
|
||||
|
||||
bool get_tx_secret_key_from_tx_extra(const std::vector<uint8_t>& tx_extra, crypto::secret_key& key);
|
||||
void add_tx_secret_key_to_tx_extra(std::vector<uint8_t>& tx_extra, const crypto::secret_key& key);
|
||||
bool get_tx_key_image_proofs_from_tx_extra(const std::vector<uint8_t>& tx_extra, tx_extra_tx_key_image_proofs &proofs);
|
||||
bool add_tx_key_image_proofs_to_tx_extra (std::vector<uint8_t>& tx_extra, const tx_extra_tx_key_image_proofs& proofs);
|
||||
bool get_tx_key_image_unlock_from_tx_extra(const std::vector<uint8_t>& tx_extra, tx_extra_tx_key_image_unlock &unlock);
|
||||
bool add_tx_key_image_unlock_to_tx_extra(std::vector<uint8_t>& tx_extra, const tx_extra_tx_key_image_unlock& unlock);
|
||||
|
||||
void add_service_node_winner_to_tx_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& winner);
|
||||
void add_service_node_pubkey_to_tx_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& pubkey);
|
||||
void add_service_node_contributor_to_tx_extra(std::vector<uint8_t>& tx_extra, const cryptonote::account_public_address& address);
|
||||
crypto::public_key get_service_node_winner_from_tx_extra(const std::vector<uint8_t>& tx_extra);
|
||||
|
||||
std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const std::vector<uint8_t>& tx_extra);
|
||||
std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx);
|
||||
bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys);
|
||||
|
|
|
@ -44,6 +44,8 @@
|
|||
#define TX_EXTRA_TAG_SERVICE_NODE_CONTRIBUTOR 0x73
|
||||
#define TX_EXTRA_TAG_SERVICE_NODE_PUBKEY 0x74
|
||||
#define TX_EXTRA_TAG_TX_SECRET_KEY 0x75
|
||||
#define TX_EXTRA_TAG_TX_KEY_IMAGE_PROOFS 0x76
|
||||
#define TX_EXTRA_TAG_TX_KEY_IMAGE_UNLOCK 0x77
|
||||
#define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE
|
||||
|
||||
#define TX_EXTRA_NONCE_PAYMENT_ID 0x00
|
||||
|
@ -261,6 +263,34 @@ namespace cryptonote
|
|||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
struct tx_extra_tx_key_image_proofs
|
||||
{
|
||||
struct proof
|
||||
{
|
||||
crypto::key_image key_image;
|
||||
crypto::signature signature;
|
||||
};
|
||||
|
||||
std::vector<proof> proofs;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(proofs)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
struct tx_extra_tx_key_image_unlock
|
||||
{
|
||||
crypto::key_image key_image;
|
||||
crypto::signature signature;
|
||||
uint32_t nonce;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(key_image)
|
||||
FIELD(signature)
|
||||
FIELD(nonce)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
// tx_extra_field format, except tx_extra_padding and tx_extra_pub_key:
|
||||
// varint tag;
|
||||
// varint size;
|
||||
|
@ -276,10 +306,14 @@ namespace cryptonote
|
|||
tx_extra_service_node_contributor,
|
||||
tx_extra_service_node_winner,
|
||||
tx_extra_service_node_deregister,
|
||||
tx_extra_tx_secret_key> tx_extra_field;
|
||||
tx_extra_tx_secret_key,
|
||||
tx_extra_tx_key_image_proofs,
|
||||
tx_extra_tx_key_image_unlock
|
||||
> tx_extra_field;
|
||||
}
|
||||
|
||||
BLOB_SERIALIZER(cryptonote::tx_extra_service_node_deregister::vote);
|
||||
BLOB_SERIALIZER(cryptonote::tx_extra_tx_key_image_proofs::proof);
|
||||
|
||||
VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING);
|
||||
VARIANT_TAG(binary_archive, cryptonote::tx_extra_pub_key, TX_EXTRA_TAG_PUBKEY);
|
||||
|
@ -293,3 +327,5 @@ VARIANT_TAG(binary_archive, cryptonote::tx_extra_service_node_contributor, TX_EX
|
|||
VARIANT_TAG(binary_archive, cryptonote::tx_extra_service_node_winner, TX_EXTRA_TAG_SERVICE_NODE_WINNER);
|
||||
VARIANT_TAG(binary_archive, cryptonote::tx_extra_service_node_pubkey, TX_EXTRA_TAG_SERVICE_NODE_PUBKEY);
|
||||
VARIANT_TAG(binary_archive, cryptonote::tx_extra_tx_secret_key, TX_EXTRA_TAG_TX_SECRET_KEY);
|
||||
VARIANT_TAG(binary_archive, cryptonote::tx_extra_tx_key_image_proofs, TX_EXTRA_TAG_TX_KEY_IMAGE_PROOFS);
|
||||
VARIANT_TAG(binary_archive, cryptonote::tx_extra_tx_key_image_unlock, TX_EXTRA_TAG_TX_KEY_IMAGE_UNLOCK);
|
||||
|
|
|
@ -62,6 +62,9 @@ namespace cryptonote
|
|||
bool m_fee_too_low;
|
||||
bool m_not_rct;
|
||||
bool m_invalid_version;
|
||||
bool m_invalid_type;
|
||||
bool m_key_image_locked_by_snode;
|
||||
bool m_key_image_blacklisted;
|
||||
|
||||
vote_verification_context m_vote_ctx;
|
||||
};
|
||||
|
|
|
@ -43,7 +43,6 @@
|
|||
#define CRYPTONOTE_MAX_TX_SIZE 1000000000
|
||||
#define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0
|
||||
#define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 30
|
||||
#define CURRENT_TRANSACTION_VERSION 3
|
||||
#define CURRENT_BLOCK_MAJOR_VERSION 7
|
||||
#define CURRENT_BLOCK_MINOR_VERSION 7
|
||||
#define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V2 60*10
|
||||
|
@ -103,6 +102,9 @@ static_assert(STAKING_PORTIONS % 3 == 0, "Use a multiple of three, so that it di
|
|||
#define DIFFICULTY_CUT 60 // timestamps to cut after sorting
|
||||
#define DIFFICULTY_BLOCKS_COUNT_V2 (DIFFICULTY_WINDOW_V2 + 1) // added +1 to make N=N
|
||||
|
||||
#define BLOCKS_EXPECTED_IN_HOURS(val) (((60 * 60) / DIFFICULTY_TARGET_V2) * (val))
|
||||
#define BLOCKS_EXPECTED_IN_DAYS(val) (BLOCKS_EXPECTED_IN_HOURS(24) * (val))
|
||||
#define BLOCKS_EXPECTED_IN_YEARS(val) (BLOCKS_EXPECTED_IN_DAYS(365) * (val))
|
||||
|
||||
#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2 DIFFICULTY_TARGET_V2 * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS
|
||||
#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS 1
|
||||
|
@ -118,7 +120,7 @@ static_assert(STAKING_PORTIONS % 3 == 0, "Use a multiple of three, so that it di
|
|||
#define CRYPTONOTE_MEMPOOL_TX_LIVETIME (86400*3) //seconds, three days
|
||||
#define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME 604800 //seconds, one week
|
||||
|
||||
#define MEMPOOL_PRUNE_DEREGISTER_LIFETIME (2 * 60 * 60) // seconds, 2 hours
|
||||
#define MEMPOOL_PRUNE_NON_STANDARD_TX_LIFETIME (2 * 60 * 60) // seconds, 2 hours
|
||||
|
||||
#define COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT 1000
|
||||
|
||||
|
|
|
@ -104,9 +104,9 @@ static const struct {
|
|||
time_t time;
|
||||
} testnet_hard_forks[] = {
|
||||
// version 7 from the start of the blockchain, inhereted from Monero testnet
|
||||
{ network_version_7, 1, 0, 1533631121 },
|
||||
{ network_version_8, 2, 0, 1533631122 },
|
||||
{ network_version_9_service_nodes, 3, 0, 1533631123 },
|
||||
{ network_version_7, 1, 0, 1533631121 },
|
||||
{ network_version_8, 2, 0, 1533631122 },
|
||||
{ network_version_9_service_nodes, 3, 0, 1533631123 },
|
||||
{ network_version_10_bulletproofs, 47096, 0, 1542681077 }, // 2018-11-20 13:30 AEDT
|
||||
};
|
||||
|
||||
|
@ -1400,8 +1400,8 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m
|
|||
uint8_t hf_version = m_hardfork->get_current_version();
|
||||
|
||||
loki_miner_tx_context miner_tx_context(m_nettype,
|
||||
m_service_node_list.select_winner(b.prev_id),
|
||||
m_service_node_list.get_winner_addresses_and_portions(b.prev_id));
|
||||
m_service_node_list.select_winner(),
|
||||
m_service_node_list.get_winner_addresses_and_portions());
|
||||
|
||||
if (!calc_batched_governance_reward(height, miner_tx_context.batched_governance))
|
||||
{
|
||||
|
@ -2551,127 +2551,27 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
|
|||
if(pmax_used_block_height)
|
||||
*pmax_used_block_height = 0;
|
||||
|
||||
const uint8_t hf_version = m_hardfork->get_current_version();
|
||||
const int hf_version = m_hardfork->get_current_version();
|
||||
|
||||
// Min/Max Version Check
|
||||
// Min/Max Type/Version Check
|
||||
{
|
||||
if (hf_version >= network_version_10_bulletproofs) tvc.m_invalid_version = (tx.version < transaction::version_3_per_output_unlock_times);
|
||||
else if (hf_version >= network_version_9_service_nodes) tvc.m_invalid_version = (tx.version < transaction::version_2);
|
||||
else tvc.m_invalid_version = (tx.version != transaction::version_2);
|
||||
size_t min_version = transaction::get_min_version_for_hf(hf_version);
|
||||
size_t max_version = transaction::get_max_version_for_hf(hf_version);
|
||||
|
||||
if (tvc.m_invalid_version)
|
||||
if (hf_version >= network_version_11_swarms)
|
||||
tvc.m_invalid_type |= (tx.type > transaction::type_key_image_unlock);
|
||||
|
||||
tvc.m_invalid_version = tx.version < min_version || tx.version > max_version;
|
||||
if (tvc.m_invalid_version || tvc.m_invalid_type)
|
||||
{
|
||||
if (tvc.m_invalid_version) MERROR_VER("TX Invalid version: " << tx.version << " for hardfork: " << hf_version);
|
||||
if (tvc.m_invalid_type) MERROR_VER("TX Invalid type for hardfork: " << hf_version);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tx.is_deregister_tx())
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(tx.vin.size() == 0, false, "Deregister TX should have 0 inputs. This should have been rejected in check_tx_semantic!");
|
||||
|
||||
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 already been
|
||||
// submitted to the blockchain under another transaction using a different
|
||||
// combination of votes.
|
||||
tx_extra_service_node_deregister deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
|
||||
{
|
||||
MERROR_VER("TX version deregister did not have the deregister metadata in the tx_extra");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const service_nodes::quorum_state> quorum_state = m_service_node_list.get_quorum_state(deregister.block_height);
|
||||
if (!quorum_state)
|
||||
{
|
||||
MERROR_VER("TX version 3 deregister_tx could not get quorum for height: " << deregister.block_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!service_nodes::deregister_vote::verify_deregister(nettype(), deregister, tvc.m_vote_ctx, *quorum_state))
|
||||
{
|
||||
tvc.m_verifivation_failed = true;
|
||||
MERROR_VER("tx " << get_transaction_hash(tx) << ": version 3 deregister_tx could not be completely verified reason: " << print_vote_verification_context(tvc.m_vote_ctx));
|
||||
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 >= service_nodes::deregister_vote::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: " << service_nodes::deregister_vote::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 = service_nodes::deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT;
|
||||
|
||||
std::vector<std::pair<cryptonote::blobdata,block>> blocks;
|
||||
std::vector<cryptonote::blobdata> txs;
|
||||
if (!get_blocks(height, num_blocks_to_check, blocks, txs))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (blobdata const &blob : txs)
|
||||
{
|
||||
transaction existing_tx;
|
||||
if (!parse_and_validate_tx_from_blob(blob, existing_tx))
|
||||
{
|
||||
MERROR_VER("tx could not be validated from blob, possibly corrupt blockchain");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!existing_tx.is_deregister_tx())
|
||||
continue;
|
||||
|
||||
tx_extra_service_node_deregister existing_deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(existing_tx.extra, existing_deregister))
|
||||
{
|
||||
MERROR_VER("could not get service node deregister from tx extra, possibly corrupt tx");
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const 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])
|
||||
{
|
||||
MERROR_VER("Already seen this deregister tx (aka double spend)");
|
||||
tvc.m_double_spend = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
transaction::type_t tx_type = tx.get_type();
|
||||
if (tx_type == transaction::type_standard)
|
||||
{
|
||||
crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx);
|
||||
|
||||
|
@ -2684,57 +2584,86 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
|
|||
}
|
||||
|
||||
std::vector<std::vector<rct::ctkey>> pubkeys(tx.vin.size());
|
||||
size_t sig_index = 0;
|
||||
const crypto::key_image *last_key_image = NULL;
|
||||
for (size_t sig_index = 0; sig_index < tx.vin.size(); ++sig_index)
|
||||
for (size_t sig_index = 0; sig_index < tx.vin.size(); sig_index++)
|
||||
{
|
||||
const auto& txin = tx.vin[sig_index];
|
||||
|
||||
// make sure output being spent is of type txin_to_key, rather than e.g.
|
||||
// txin_gen, which is only used for miner transactions
|
||||
//
|
||||
// Monero Checks
|
||||
//
|
||||
// make sure output being spent is of type txin_to_key, rather than e.g. txin_gen, which is only used for miner transactions
|
||||
CHECK_AND_ASSERT_MES(txin.type() == typeid(txin_to_key), false, "wrong type id in tx input at Blockchain::check_tx_inputs");
|
||||
const txin_to_key& in_to_key = boost::get<txin_to_key>(txin);
|
||||
|
||||
// make sure tx output has key offset(s) (is signed to be used)
|
||||
CHECK_AND_ASSERT_MES(in_to_key.key_offsets.size(), false, "empty in_to_key.key_offsets in transaction with id " << get_transaction_hash(tx));
|
||||
|
||||
// Mixin Check, from hard fork 7, we require mixin at least 9, always.
|
||||
if (in_to_key.key_offsets.size() - 1 != CRYPTONOTE_DEFAULT_TX_MIXIN)
|
||||
{
|
||||
MERROR_VER("Tx " << get_transaction_hash(tx) << " has incorrect ring size (" << in_to_key.key_offsets.size() - 1 << ", expected (" << CRYPTONOTE_DEFAULT_TX_MIXIN << ")");
|
||||
tvc.m_low_mixin = true;
|
||||
return false;
|
||||
}
|
||||
// make sure tx output has key offset(s) (is signed to be used)
|
||||
CHECK_AND_ASSERT_MES(in_to_key.key_offsets.size(), false, "empty in_to_key.key_offsets in transaction with id " << get_transaction_hash(tx));
|
||||
|
||||
// from v7, sorted ins
|
||||
{
|
||||
if (last_key_image && memcmp(&in_to_key.k_image, last_key_image, sizeof(*last_key_image)) >= 0)
|
||||
// Mixin Check, from hard fork 7, we require mixin at least 9, always.
|
||||
if (in_to_key.key_offsets.size() - 1 != CRYPTONOTE_DEFAULT_TX_MIXIN)
|
||||
{
|
||||
MERROR_VER("transaction has unsorted inputs");
|
||||
tvc.m_verifivation_failed = true;
|
||||
MERROR_VER("Tx " << get_transaction_hash(tx) << " has incorrect ring size (" << in_to_key.key_offsets.size() - 1 << ", expected (" << CRYPTONOTE_DEFAULT_TX_MIXIN << ")");
|
||||
tvc.m_low_mixin = true;
|
||||
return false;
|
||||
}
|
||||
last_key_image = &in_to_key.k_image;
|
||||
}
|
||||
|
||||
if(have_tx_keyimg_as_spent(in_to_key.k_image))
|
||||
{
|
||||
MERROR_VER("Key image already spent in blockchain: " << epee::string_tools::pod_to_hex(in_to_key.k_image));
|
||||
tvc.m_double_spend = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure that output being spent matches up correctly with the
|
||||
// signature spending it.
|
||||
if (!check_tx_input(tx.version, in_to_key, tx_prefix_hash, std::vector<crypto::signature>(), tx.rct_signatures, pubkeys[sig_index], pmax_used_block_height))
|
||||
{
|
||||
it->second[in_to_key.k_image] = false;
|
||||
MERROR_VER("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index);
|
||||
if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain()
|
||||
// from v7, sorted ins
|
||||
{
|
||||
MERROR_VER(" *pmax_used_block_height: " << *pmax_used_block_height);
|
||||
if (last_key_image && memcmp(&in_to_key.k_image, last_key_image, sizeof(*last_key_image)) >= 0)
|
||||
{
|
||||
MERROR_VER("transaction has unsorted inputs");
|
||||
tvc.m_verifivation_failed = true;
|
||||
return false;
|
||||
}
|
||||
last_key_image = &in_to_key.k_image;
|
||||
}
|
||||
|
||||
return false;
|
||||
if(have_tx_keyimg_as_spent(in_to_key.k_image))
|
||||
{
|
||||
MERROR_VER("Key image already spent in blockchain: " << epee::string_tools::pod_to_hex(in_to_key.k_image));
|
||||
tvc.m_double_spend = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure that output being spent matches up correctly with the
|
||||
// signature spending it.
|
||||
if (!check_tx_input(tx.version, in_to_key, tx_prefix_hash, std::vector<crypto::signature>(), tx.rct_signatures, pubkeys[sig_index], pmax_used_block_height))
|
||||
{
|
||||
it->second[in_to_key.k_image] = false;
|
||||
MERROR_VER("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index);
|
||||
if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain()
|
||||
{
|
||||
MERROR_VER(" *pmax_used_block_height: " << *pmax_used_block_height);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Service Node Checks
|
||||
//
|
||||
if (hf_version >= cryptonote::network_version_11_swarms)
|
||||
{
|
||||
const std::vector<service_nodes::key_image_blacklist_entry> &blacklist = m_service_node_list.get_blacklisted_key_images();
|
||||
for (const auto &entry : blacklist)
|
||||
{
|
||||
if (in_to_key.k_image == entry.key_image) // Check if key image is on the blacklist
|
||||
{
|
||||
MERROR_VER("Key image: " << epee::string_tools::pod_to_hex(entry.key_image) << " is blacklisted by the service node network");
|
||||
tvc.m_key_image_blacklisted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t unlock_height = 0;
|
||||
if (m_service_node_list.is_key_image_locked(in_to_key.k_image, &unlock_height))
|
||||
{
|
||||
MERROR_VER("Key image: " << epee::string_tools::pod_to_hex(in_to_key.k_image) << " is locked in a stake until height: " << unlock_height);
|
||||
tvc.m_key_image_locked_by_snode = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2890,6 +2819,156 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(tx.vin.size() == 0, false, "TX type: " << transaction::type_to_string(tx.type) << " should have 0 inputs. This should have been rejected in check_tx_semantic!");
|
||||
|
||||
if (tx.rct_signatures.txnFee != 0)
|
||||
{
|
||||
tvc.m_invalid_input = true;
|
||||
tvc.m_verifivation_failed = true;
|
||||
MERROR_VER("TX type: " << transaction::type_to_string(tx.type) << " should have 0 fee!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tx_type == transaction::type_deregister)
|
||||
{
|
||||
// Check the inputs (votes) of the transaction have not already been
|
||||
// submitted to the blockchain under another transaction using a different
|
||||
// combination of votes.
|
||||
tx_extra_service_node_deregister deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
|
||||
{
|
||||
MERROR_VER("TX version deregister did not have the deregister metadata in the tx_extra");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const service_nodes::quorum_state> quorum_state = m_service_node_list.get_quorum_state(deregister.block_height);
|
||||
if (!quorum_state)
|
||||
{
|
||||
MERROR_VER("TX version 3 deregister_tx could not get quorum for height: " << deregister.block_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!service_nodes::deregister_vote::verify_deregister(nettype(), deregister, tvc.m_vote_ctx, *quorum_state))
|
||||
{
|
||||
tvc.m_verifivation_failed = true;
|
||||
MERROR_VER("tx " << get_transaction_hash(tx) << ": version 3 deregister_tx could not be completely verified reason: " << print_vote_verification_context(tvc.m_vote_ctx));
|
||||
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 >= service_nodes::deregister_vote::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: " << service_nodes::deregister_vote::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 = service_nodes::deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT;
|
||||
|
||||
std::vector<std::pair<cryptonote::blobdata,block>> blocks;
|
||||
std::vector<cryptonote::blobdata> txs;
|
||||
if (!get_blocks(height, num_blocks_to_check, blocks, txs))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (blobdata const &blob : txs)
|
||||
{
|
||||
transaction existing_tx;
|
||||
if (!parse_and_validate_tx_from_blob(blob, existing_tx))
|
||||
{
|
||||
MERROR_VER("tx could not be validated from blob, possibly corrupt blockchain");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existing_tx.get_type() != transaction::type_deregister)
|
||||
continue;
|
||||
|
||||
tx_extra_service_node_deregister existing_deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(existing_tx.extra, existing_deregister))
|
||||
{
|
||||
MERROR_VER("could not get service node deregister from tx extra, possibly corrupt tx");
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const 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])
|
||||
{
|
||||
MERROR_VER("Already seen this deregister tx (aka double spend)");
|
||||
tvc.m_double_spend = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tx.get_type() == transaction::type_key_image_unlock)
|
||||
{
|
||||
cryptonote::tx_extra_tx_key_image_unlock unlock;
|
||||
if (!cryptonote::get_tx_key_image_unlock_from_tx_extra(tx.extra, unlock))
|
||||
{
|
||||
MERROR("TX extra didn't have key image unlock in the tx_extra");
|
||||
return false;
|
||||
}
|
||||
|
||||
service_nodes::service_node_info::contribution_t contribution = {};
|
||||
uint64_t unlock_height = 0;
|
||||
if (!m_service_node_list.is_key_image_locked(unlock.key_image, &unlock_height, &contribution))
|
||||
{
|
||||
MERROR_VER("Requested key image: " << epee::string_tools::pod_to_hex(unlock.key_image) << " to unlock is not locked");
|
||||
tvc.m_invalid_input = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
crypto::hash const hash = service_nodes::generate_request_stake_unlock_hash(unlock.nonce);
|
||||
if (!crypto::check_signature(hash, contribution.key_image_pub_key, unlock.signature))
|
||||
{
|
||||
MERROR("Could not verify key image unlock transaction signature for tx: " << get_transaction_hash(tx));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise is a locked key image, if the unlock_height is set, it has been previously requested to unlock
|
||||
if (unlock_height != service_nodes::KEY_IMAGE_AWAITING_UNLOCK_HEIGHT)
|
||||
{
|
||||
tvc.m_double_spend = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MERROR_VER("Unhandled tx type: " << tx.type << " rejecting tx: " << get_transaction_hash(tx));
|
||||
tvc.m_invalid_type = true;;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -4648,26 +4727,6 @@ bool Blockchain::for_all_outputs(uint64_t amount, std::function<bool(uint64_t he
|
|||
return m_db->for_all_outputs(amount, f);;
|
||||
}
|
||||
|
||||
void Blockchain::hook_init(Blockchain::InitHook& init_hook)
|
||||
{
|
||||
m_init_hooks.push_back(&init_hook);
|
||||
}
|
||||
|
||||
void Blockchain::hook_block_added(Blockchain::BlockAddedHook& block_added_hook)
|
||||
{
|
||||
m_block_added_hooks.push_back(&block_added_hook);
|
||||
}
|
||||
|
||||
void Blockchain::hook_blockchain_detached(Blockchain::BlockchainDetachedHook& blockchain_detached_hook)
|
||||
{
|
||||
m_blockchain_detached_hooks.push_back(&blockchain_detached_hook);
|
||||
}
|
||||
|
||||
void Blockchain::hook_validate_miner_tx(Blockchain::ValidateMinerTxHook& validate_miner_tx_hook)
|
||||
{
|
||||
m_validate_miner_tx_hooks.push_back(&validate_miner_tx_hook);
|
||||
}
|
||||
|
||||
void Blockchain::invalidate_block_template_cache()
|
||||
{
|
||||
MDEBUG("Invalidating block template cache");
|
||||
|
|
|
@ -1000,10 +1000,10 @@ namespace cryptonote
|
|||
/**
|
||||
* @brief add a hook for processing new blocks and rollbacks for reorgs
|
||||
*/
|
||||
void hook_block_added(BlockAddedHook& block_added_hook);
|
||||
void hook_blockchain_detached(BlockchainDetachedHook& blockchain_detached_hook);
|
||||
void hook_init(InitHook& init_hook);
|
||||
void hook_validate_miner_tx(ValidateMinerTxHook& validate_miner_tx_hook);
|
||||
void hook_block_added (BlockAddedHook& hook) { m_block_added_hooks.push_back(&hook); }
|
||||
void hook_blockchain_detached(BlockchainDetachedHook& hook) { m_blockchain_detached_hooks.push_back(&hook); }
|
||||
void hook_init (InitHook& hook) { m_init_hooks.push_back(&hook); }
|
||||
void hook_validate_miner_tx (ValidateMinerTxHook& hook) { m_validate_miner_tx_hooks.push_back(&hook); }
|
||||
|
||||
/**
|
||||
* @brief removes blocks from the top of the blockchain
|
||||
|
|
|
@ -750,18 +750,6 @@ namespace cryptonote
|
|||
}
|
||||
}
|
||||
bad_semantics_txes_lock.unlock();
|
||||
|
||||
int version = m_blockchain_storage.get_current_hard_fork_version();
|
||||
unsigned int max_tx_version = (version == 1) ? 1 : (version < 9)
|
||||
? transaction::version_2
|
||||
: transaction::version_3_per_output_unlock_times;
|
||||
|
||||
if (tx.version == 0 || tx.version > max_tx_version)
|
||||
{
|
||||
tvc.m_verifivation_failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
|
@ -820,7 +808,7 @@ namespace cryptonote
|
|||
continue;
|
||||
}
|
||||
|
||||
if (tx_info[n].tx->version < 2 || tx_info[n].tx->is_deregister_tx())
|
||||
if (tx_info[n].tx->get_type() != transaction::type_standard)
|
||||
continue;
|
||||
const rct::rctSig &rv = tx_info[n].tx->rct_signatures;
|
||||
switch (rv.type) {
|
||||
|
@ -1016,11 +1004,11 @@ namespace cryptonote
|
|||
//-----------------------------------------------------------------------------------------------
|
||||
bool core::check_tx_semantic(const transaction& tx, bool keeped_by_block) const
|
||||
{
|
||||
if (tx.is_deregister_tx())
|
||||
if (tx.get_type() != transaction::type_standard)
|
||||
{
|
||||
if (tx.vin.size() != 0)
|
||||
{
|
||||
MERROR_VER("tx version deregister must have 0 inputs, received: " << tx.vin.size() << ", rejected for tx id = " << get_transaction_hash(tx));
|
||||
MERROR_VER("tx type: " << transaction::type_to_string(tx.type) << " must have 0 inputs, received: " << tx.vin.size() << ", rejected for tx id = " << get_transaction_hash(tx));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1912,6 +1900,12 @@ namespace cryptonote
|
|||
return m_service_node_list.is_service_node(pubkey);
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
const std::vector<service_nodes::key_image_blacklist_entry> &core::get_service_node_blacklisted_key_images() const
|
||||
{
|
||||
const auto &result = m_service_node_list.get_blacklisted_key_images();
|
||||
return result;
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
std::vector<service_nodes::service_node_pubkey_info> core::get_service_node_list_state(const std::vector<crypto::public_key> &service_node_pubkeys) const
|
||||
{
|
||||
std::vector<service_nodes::service_node_pubkey_info> result = m_service_node_list.get_service_node_list_state(service_node_pubkeys);
|
||||
|
|
|
@ -808,11 +808,16 @@ namespace cryptonote
|
|||
const std::shared_ptr<const service_nodes::quorum_state> get_quorum_state(uint64_t height) const;
|
||||
|
||||
/**
|
||||
* @brief Get a snapshot of the service node list state at the time of the call.
|
||||
* @brief Get a non owning reference to the list of blacklisted key images
|
||||
*/
|
||||
const std::vector<service_nodes::key_image_blacklist_entry> &get_service_node_blacklisted_key_images() const;
|
||||
|
||||
/**
|
||||
* @brief get a snapshot of the service node list state at the time of the call.
|
||||
*
|
||||
* @param service_node_pubkeys pubkeys to search, if empty this indicates get all the pubkeys
|
||||
*
|
||||
* @return All the service nodes that can be matched from pubkeys in param
|
||||
* @return all the service nodes that can be matched from pubkeys in param
|
||||
*/
|
||||
std::vector<service_nodes::service_node_pubkey_info> get_service_node_list_state(const std::vector<crypto::public_key>& service_node_pubkeys) const;
|
||||
|
||||
|
|
|
@ -248,7 +248,7 @@ namespace cryptonote
|
|||
tx.vout.clear();
|
||||
tx.extra.clear();
|
||||
tx.output_unlock_times.clear();
|
||||
tx.is_deregister = false;
|
||||
tx.type = transaction::type_standard;
|
||||
tx.version = (hard_fork_version >= network_version_9_service_nodes) ? transaction::version_3_per_output_unlock_times : transaction::version_2;
|
||||
|
||||
const network_type nettype = miner_tx_context.nettype;
|
||||
|
@ -459,7 +459,7 @@ namespace cryptonote
|
|||
return addr.m_view_public_key;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
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<tx_destination_entry>& change_addr, const 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, rct::RangeProofType range_proof_type, rct::multisig_out *msout, bool per_output_unlock, bool shuffle_outs)
|
||||
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<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, rct::multisig_out *msout, bool shuffle_outs, const loki_construct_tx_params tx_params)
|
||||
{
|
||||
hw::device &hwdev = sender_account_keys.get_device();
|
||||
|
||||
|
@ -477,19 +477,31 @@ namespace cryptonote
|
|||
msout->c.clear();
|
||||
}
|
||||
|
||||
if (per_output_unlock)
|
||||
if (tx_params.v4_allow_tx_types)
|
||||
{
|
||||
tx.version = 3;
|
||||
tx.version = transaction::version_4_tx_types;
|
||||
tx.type = transaction::type_standard;
|
||||
}
|
||||
else
|
||||
{
|
||||
tx.version = rct ? 2 : 1;
|
||||
tx.unlock_time = unlock_time;
|
||||
if (tx_params.v3_per_output_unlock)
|
||||
{
|
||||
tx.version = transaction::version_3_per_output_unlock_times;
|
||||
}
|
||||
else
|
||||
{
|
||||
tx.version = tx_params.v2_rct ? 2 : 1;
|
||||
tx.unlock_time = unlock_time;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tx.extra = extra;
|
||||
crypto::public_key txkey_pub;
|
||||
|
||||
if (tx_params.v3_is_staking_tx)
|
||||
add_tx_secret_key_to_tx_extra(tx.extra, tx_key);
|
||||
|
||||
// if we have a stealth payment id, find it and encrypt it with the tx key now
|
||||
std::vector<tx_extra_field> tx_extra_fields;
|
||||
if (parse_tx_extra(tx.extra, tx_extra_fields))
|
||||
|
@ -637,11 +649,11 @@ namespace cryptonote
|
|||
//fill outputs
|
||||
size_t output_index = 0;
|
||||
bool found_change = false;
|
||||
|
||||
tx_extra_tx_key_image_proofs key_image_proofs;
|
||||
for(const tx_destination_entry& dst_entr: destinations)
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount);
|
||||
crypto::key_derivation derivation;
|
||||
crypto::public_key out_eph_public_key;
|
||||
|
||||
// make additional tx pubkey if necessary
|
||||
keypair additional_txkey;
|
||||
|
@ -655,6 +667,7 @@ namespace cryptonote
|
|||
}
|
||||
|
||||
bool r;
|
||||
crypto::key_derivation derivation;
|
||||
if (change_addr && *change_addr == dst_entr && !found_change)
|
||||
{
|
||||
found_change = true;
|
||||
|
@ -670,7 +683,7 @@ namespace cryptonote
|
|||
else
|
||||
{
|
||||
// sending to the recipient; derivation = r*A (or s*C in the subaddress scheme)
|
||||
r = hwdev.generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : tx_key, derivation);
|
||||
r = hwdev.generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : tx_key, derivation);
|
||||
CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << (dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : tx_key) << ")");
|
||||
|
||||
if (tx.version > 2)
|
||||
|
@ -690,11 +703,35 @@ namespace cryptonote
|
|||
hwdev.derivation_to_scalar(derivation, output_index, scalar1);
|
||||
amount_keys.push_back(rct::sk2rct(scalar1));
|
||||
}
|
||||
|
||||
crypto::public_key out_eph_public_key;
|
||||
r = hwdev.derive_public_key(derivation, output_index, dst_entr.addr.m_spend_public_key, out_eph_public_key);
|
||||
CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to derive_public_key(" << derivation << ", " << output_index << ", "<< dst_entr.addr.m_spend_public_key << ")");
|
||||
|
||||
hwdev.add_output_key_mapping(dst_entr.addr.m_view_public_key, dst_entr.addr.m_spend_public_key, dst_entr.is_subaddress, output_index, amount_keys.back(), out_eph_public_key);
|
||||
|
||||
if (tx_params.v3_is_staking_tx)
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(dst_entr.addr == sender_account_keys.m_account_address, false, "A staking contribution must return back to the original sendee otherwise the pre-calculated key image is incorrect");
|
||||
CHECK_AND_ASSERT_MES(dst_entr.is_subaddress == false, false, "Staking back to a subaddress is not allowed"); // TODO(loki): Maybe one day, revisit this
|
||||
CHECK_AND_ASSERT_MES(need_additional_txkeys == false, false, "Staking TX's can not required additional TX Keys"); // TODO(loki): Maybe one day, revisit this
|
||||
|
||||
if (!(change_addr && *change_addr == dst_entr))
|
||||
{
|
||||
tx_extra_tx_key_image_proofs::proof proof = {};
|
||||
keypair ephemeral_keys = {};
|
||||
const subaddress_index zeroth_address = {};
|
||||
if(!generate_key_image_helper(sender_account_keys, subaddresses, out_eph_public_key, txkey_pub, additional_tx_public_keys, output_index, ephemeral_keys, proof.key_image, hwdev))
|
||||
{
|
||||
LOG_ERROR("Key image generation failed for staking TX!");
|
||||
return false;
|
||||
}
|
||||
|
||||
crypto::public_key const *out_eph_public_key_ptr = &out_eph_public_key;
|
||||
crypto::generate_ring_signature((const crypto::hash&)proof.key_image, proof.key_image, &out_eph_public_key_ptr, 1, ephemeral_keys.sec, 0, &proof.signature);
|
||||
key_image_proofs.proofs.push_back(proof);
|
||||
}
|
||||
}
|
||||
|
||||
tx_out out;
|
||||
out.amount = dst_entr.amount;
|
||||
txout_to_key tk;
|
||||
|
@ -706,6 +743,12 @@ namespace cryptonote
|
|||
}
|
||||
CHECK_AND_ASSERT_MES(additional_tx_public_keys.size() == additional_tx_keys.size(), false, "Internal error creating additional public keys");
|
||||
|
||||
if (tx_params.v3_is_staking_tx)
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(key_image_proofs.proofs.size() >= 1, false, "No key image proofs were generated for staking tx");
|
||||
add_tx_key_image_proofs_to_tx_extra(tx.extra, key_image_proofs);
|
||||
}
|
||||
|
||||
remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys));
|
||||
|
||||
LOG_PRINT_L2("tx pubkey: " << txkey_pub);
|
||||
|
@ -777,7 +820,7 @@ namespace cryptonote
|
|||
|
||||
// the non-simple version is slightly smaller, but assumes all real inputs
|
||||
// are on the same index, so can only be used if there just one ring.
|
||||
bool use_simple_rct = sources.size() > 1 || range_proof_type != rct::RangeProofBorromean;
|
||||
bool use_simple_rct = sources.size() > 1 || tx_params.type != rct::RangeProofBorromean;
|
||||
|
||||
if (!use_simple_rct)
|
||||
{
|
||||
|
@ -875,7 +918,7 @@ namespace cryptonote
|
|||
get_transaction_prefix_hash(tx, tx_prefix_hash);
|
||||
rct::ctkeyV outSk;
|
||||
if (use_simple_rct)
|
||||
tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, range_proof_type, hwdev);
|
||||
tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, tx_params.type, hwdev);
|
||||
else
|
||||
tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, hwdev); // same index assumption
|
||||
memwipe(inSk.data(), inSk.size() * sizeof(rct::ctkey));
|
||||
|
@ -890,14 +933,11 @@ namespace cryptonote
|
|||
return 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, rct::RangeProofType range_proof_type, rct::multisig_out *msout, bool is_staking_tx, bool per_output_unlock)
|
||||
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, rct::multisig_out *msout, loki_construct_tx_params const tx_params)
|
||||
{
|
||||
hw::device &hwdev = sender_account_keys.get_device();
|
||||
hwdev.open_tx(tx_key);
|
||||
|
||||
if (is_staking_tx)
|
||||
add_tx_secret_key_to_tx_extra(extra, tx_key);
|
||||
|
||||
// figure out if we need to make additional tx pubkeys
|
||||
size_t num_stdaddresses = 0;
|
||||
size_t num_subaddresses = 0;
|
||||
|
@ -911,12 +951,12 @@ namespace cryptonote
|
|||
additional_tx_keys.push_back(keypair::generate(sender_account_keys.get_device()).sec);
|
||||
}
|
||||
|
||||
bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, range_proof_type, msout, per_output_unlock);
|
||||
bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, msout, true /*shuffle_outs*/, tx_params);
|
||||
hwdev.close_tx();
|
||||
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, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, uint8_t hf_version, bool is_staking)
|
||||
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, uint8_t hf_version, bool is_staking)
|
||||
{
|
||||
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
|
||||
subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0};
|
||||
|
@ -924,9 +964,9 @@ namespace cryptonote
|
|||
std::vector<crypto::secret_key> additional_tx_keys;
|
||||
std::vector<tx_destination_entry> destinations_copy = destinations;
|
||||
|
||||
const rct::RangeProofType rp_type = (hf_version < network_version_10_bulletproofs) ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof;
|
||||
const bool per_output_unlock = (hf_version >= network_version_9_service_nodes);
|
||||
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, rp_type, NULL, is_staking, per_output_unlock);
|
||||
loki_construct_tx_params tx_params(hf_version);
|
||||
tx_params.v3_is_staking_tx = is_staking;
|
||||
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, NULL, tx_params);
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool generate_genesis_block(
|
||||
|
|
|
@ -170,9 +170,28 @@ 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, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, uint8_t hf_version = cryptonote::network_version_7, bool is_staking = 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, const 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, rct::RangeProofType range_proof_type = rct::RangeProofBorromean, 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, rct::RangeProofType range_proof_type = rct::RangeProofBorromean, rct::multisig_out *msout = NULL, bool is_staking_tx = false, bool per_output_unlock = false);
|
||||
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, uint8_t hf_version = cryptonote::network_version_7, bool is_staking = false);
|
||||
|
||||
struct loki_construct_tx_params
|
||||
{
|
||||
bool v4_allow_tx_types;
|
||||
bool v3_per_output_unlock;
|
||||
bool v3_is_staking_tx; // NOTE: Set to true manually if you need a staking transaction
|
||||
bool v2_rct;
|
||||
rct::RangeProofType type;
|
||||
|
||||
loki_construct_tx_params() = default;
|
||||
loki_construct_tx_params(int hf_version)
|
||||
{
|
||||
v4_allow_tx_types = (hf_version >= cryptonote::network_version_11_swarms);
|
||||
v3_per_output_unlock = (hf_version >= cryptonote::network_version_9_service_nodes);
|
||||
v2_rct = (hf_version >= cryptonote::network_version_7);
|
||||
type = (hf_version < cryptonote::network_version_10_bulletproofs) ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof;
|
||||
}
|
||||
};
|
||||
|
||||
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, rct::multisig_out *msout = NULL, bool shuffle_outs = true, loki_construct_tx_params const tx_params = {});
|
||||
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, rct::multisig_out *msout = NULL, loki_construct_tx_params const tx_params = {});
|
||||
|
||||
bool generate_genesis_block(
|
||||
block& bl
|
||||
|
|
|
@ -264,8 +264,8 @@ namespace service_nodes
|
|||
vvc.m_full_tx_deregister_made = cryptonote::add_service_node_deregister_to_tx_extra(tx.extra, deregister);
|
||||
if (vvc.m_full_tx_deregister_made)
|
||||
{
|
||||
tx.version = cryptonote::transaction::version_3_per_output_unlock_times;
|
||||
tx.is_deregister = true;
|
||||
tx.version = cryptonote::transaction::version_3_per_output_unlock_times;
|
||||
tx.type = cryptonote::transaction::type_deregister;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -282,13 +282,13 @@ namespace service_nodes
|
|||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
for (const cryptonote::transaction &tx : txs)
|
||||
{
|
||||
if (!tx.is_deregister_tx())
|
||||
if (tx.get_type() != cryptonote::transaction::type_deregister)
|
||||
continue;
|
||||
|
||||
cryptonote::tx_extra_service_node_deregister deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
|
||||
{
|
||||
LOG_ERROR("Could not get deregister from tx version 3, possibly corrupt tx");
|
||||
LOG_ERROR("Could not get deregister from tx, possibly corrupt tx");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace service_nodes
|
|||
|
||||
struct deregister_vote
|
||||
{
|
||||
static const uint64_t VOTE_LIFETIME_BY_HEIGHT = (60 * 60 * 2) / DIFFICULTY_TARGET_V2;
|
||||
static const uint64_t VOTE_LIFETIME_BY_HEIGHT = BLOCKS_EXPECTED_IN_HOURS(2);
|
||||
static const uint64_t DEREGISTER_LIFETIME_BY_HEIGHT = VOTE_LIFETIME_BY_HEIGHT;
|
||||
|
||||
uint64_t block_height;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -64,43 +64,69 @@ namespace service_nodes
|
|||
|
||||
using swarm_id_t = uint64_t;
|
||||
|
||||
// TODO(doyle): INF_STAKING(doyle): Review behaviour of existing nodes on the old system when we switch over.
|
||||
|
||||
struct service_node_info // registration information
|
||||
{
|
||||
enum version
|
||||
// INF_STAKING(doyle): Now that we have locked key images, we should enforce
|
||||
// a minimum staking amount. Currently contributors can contribute piece
|
||||
// meal to a service node, they can trivially attack the network by staking
|
||||
// 1 loki each time to bloat up the key images
|
||||
struct contribution_t
|
||||
{
|
||||
version_0,
|
||||
version_1_swarms
|
||||
};
|
||||
|
||||
struct contribution
|
||||
{
|
||||
uint64_t amount;
|
||||
uint64_t reserved;
|
||||
cryptonote::account_public_address address;
|
||||
contribution() {}
|
||||
contribution(uint64_t _reserved, const cryptonote::account_public_address& _address)
|
||||
: amount(0), reserved(_reserved), address(_address) { }
|
||||
uint8_t version = version_2_infinite_staking;
|
||||
crypto::public_key key_image_pub_key;
|
||||
crypto::key_image key_image;
|
||||
uint64_t amount;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
VARINT_FIELD(version)
|
||||
FIELD(key_image_pub_key)
|
||||
FIELD(key_image)
|
||||
VARINT_FIELD(amount)
|
||||
VARINT_FIELD(reserved)
|
||||
FIELD(address)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
uint8_t version = service_node_info::version_0;
|
||||
uint64_t registration_height;
|
||||
enum version
|
||||
{
|
||||
version_0,
|
||||
version_1_swarms,
|
||||
version_2_infinite_staking,
|
||||
};
|
||||
|
||||
struct contributor_t
|
||||
{
|
||||
uint8_t version;
|
||||
uint64_t amount;
|
||||
uint64_t reserved;
|
||||
cryptonote::account_public_address address;
|
||||
std::vector<contribution_t> locked_contributions;
|
||||
|
||||
contributor_t() = default;
|
||||
contributor_t(uint64_t reserved_, const cryptonote::account_public_address& address_) : amount(0), reserved(reserved_), address(address_) { }
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
VARINT_FIELD(version)
|
||||
VARINT_FIELD(amount)
|
||||
VARINT_FIELD(reserved)
|
||||
FIELD(address)
|
||||
if (version >= version_2_infinite_staking)
|
||||
FIELD(locked_contributions)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
uint8_t version;
|
||||
uint64_t registration_height;
|
||||
uint64_t requested_unlock_height;
|
||||
// block_height and transaction_index are to record when the service node last received a reward.
|
||||
uint64_t last_reward_block_height;
|
||||
uint32_t last_reward_transaction_index;
|
||||
|
||||
std::vector<contribution> contributors;
|
||||
uint64_t total_contributed;
|
||||
uint64_t total_reserved;
|
||||
uint64_t staking_requirement;
|
||||
uint64_t portions_for_operator;
|
||||
swarm_id_t swarm_id;
|
||||
uint64_t last_reward_block_height;
|
||||
uint32_t last_reward_transaction_index;
|
||||
std::vector<contributor_t> contributors;
|
||||
uint64_t total_contributed;
|
||||
uint64_t total_reserved;
|
||||
uint64_t staking_requirement;
|
||||
uint64_t portions_for_operator;
|
||||
swarm_id_t swarm_id;
|
||||
cryptonote::account_public_address operator_address;
|
||||
|
||||
bool is_fully_funded() const { return total_contributed >= staking_requirement; }
|
||||
|
@ -110,6 +136,7 @@ namespace service_nodes
|
|||
BEGIN_SERIALIZE()
|
||||
VARINT_FIELD(version)
|
||||
VARINT_FIELD(registration_height)
|
||||
VARINT_FIELD(requested_unlock_height)
|
||||
VARINT_FIELD(last_reward_block_height)
|
||||
VARINT_FIELD(last_reward_transaction_index)
|
||||
FIELD(contributors)
|
||||
|
@ -117,10 +144,12 @@ namespace service_nodes
|
|||
VARINT_FIELD(total_reserved)
|
||||
VARINT_FIELD(staking_requirement)
|
||||
VARINT_FIELD(portions_for_operator)
|
||||
if (version >= service_node_info::version_1_swarms) {
|
||||
FIELD(operator_address)
|
||||
|
||||
if (version >= service_node_info::version_1_swarms)
|
||||
{
|
||||
VARINT_FIELD(swarm_id)
|
||||
}
|
||||
FIELD(operator_address)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
|
@ -128,12 +157,31 @@ namespace service_nodes
|
|||
{
|
||||
crypto::public_key pubkey;
|
||||
service_node_info info;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(pubkey)
|
||||
FIELD(info)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
struct key_image_blacklist_entry
|
||||
{
|
||||
uint8_t version = service_node_info::version_2_infinite_staking;
|
||||
crypto::key_image key_image;
|
||||
uint64_t unlock_height;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
VARINT_FIELD(version)
|
||||
FIELD(key_image)
|
||||
VARINT_FIELD(unlock_height)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void loki_shuffle(std::vector<T>& a, uint64_t seed);
|
||||
|
||||
static constexpr uint64_t QUEUE_SWARM_ID = 0;
|
||||
static constexpr uint64_t KEY_IMAGE_AWAITING_UNLOCK_HEIGHT = 0;
|
||||
|
||||
class service_node_list
|
||||
: public cryptonote::Blockchain::BlockAddedHook,
|
||||
|
@ -148,16 +196,18 @@ namespace service_nodes
|
|||
void register_hooks(service_nodes::quorum_cop &quorum_cop);
|
||||
void init() override;
|
||||
bool validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, cryptonote::block_reward_parts const &base_reward) const override;
|
||||
std::vector<std::pair<cryptonote::account_public_address, uint64_t>> get_winner_addresses_and_portions(const crypto::hash& prev_id) const;
|
||||
crypto::public_key select_winner(const crypto::hash& prev_id) const;
|
||||
std::vector<std::pair<cryptonote::account_public_address, uint64_t>> get_winner_addresses_and_portions() const;
|
||||
crypto::public_key select_winner() const;
|
||||
|
||||
bool is_service_node(const crypto::public_key& pubkey) const;
|
||||
bool is_key_image_locked(crypto::key_image const &check_image, uint64_t *unlock_height = nullptr, service_node_info::contribution_t *the_locked_contribution = nullptr) const;
|
||||
|
||||
void update_swarms(uint64_t height);
|
||||
|
||||
/// Note(maxim): this should not affect thread-safety as the returned object is const
|
||||
const std::shared_ptr<const quorum_state> get_quorum_state(uint64_t height) const;
|
||||
std::vector<service_node_pubkey_info> get_service_node_list_state(const std::vector<crypto::public_key> &service_node_pubkeys) const;
|
||||
const std::vector<key_image_blacklist_entry> &get_blacklisted_key_images() const { return m_key_image_blacklist; }
|
||||
|
||||
void set_db_pointer(cryptonote::BlockchainDB* db);
|
||||
void set_my_service_node_keys(crypto::public_key const *pub_key);
|
||||
|
@ -171,13 +221,13 @@ namespace service_nodes
|
|||
{
|
||||
change_type,
|
||||
new_type,
|
||||
prevent_type
|
||||
prevent_type,
|
||||
key_image_blacklist_type,
|
||||
};
|
||||
|
||||
rollback_event() = default;
|
||||
rollback_event(uint64_t block_height, rollback_type type);
|
||||
virtual ~rollback_event() { }
|
||||
virtual bool apply(std::unordered_map<crypto::public_key, service_node_info>& service_nodes_infos) const = 0;
|
||||
|
||||
rollback_type type;
|
||||
|
||||
|
@ -192,7 +242,6 @@ namespace service_nodes
|
|||
{
|
||||
rollback_change() { type = change_type; }
|
||||
rollback_change(uint64_t block_height, const crypto::public_key& key, const service_node_info& info);
|
||||
bool apply(std::unordered_map<crypto::public_key, service_node_info>& service_nodes_infos) const;
|
||||
crypto::public_key m_key;
|
||||
service_node_info m_info;
|
||||
|
||||
|
@ -207,7 +256,6 @@ namespace service_nodes
|
|||
{
|
||||
rollback_new() { type = new_type; }
|
||||
rollback_new(uint64_t block_height, const crypto::public_key& key);
|
||||
bool apply(std::unordered_map<crypto::public_key, service_node_info>& service_nodes_infos) const;
|
||||
crypto::public_key m_key;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
|
@ -220,32 +268,36 @@ namespace service_nodes
|
|||
{
|
||||
prevent_rollback() { type = prevent_type; }
|
||||
prevent_rollback(uint64_t block_height);
|
||||
bool apply(std::unordered_map<crypto::public_key, service_node_info>& service_nodes_infos) const;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELDS(*static_cast<rollback_event *>(this))
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
typedef boost::variant<rollback_change, rollback_new, prevent_rollback> rollback_event_variant;
|
||||
|
||||
struct node_info_for_serialization
|
||||
struct rollback_key_image_blacklist : public rollback_event
|
||||
{
|
||||
crypto::public_key key;
|
||||
service_node_info info;
|
||||
rollback_key_image_blacklist() { type = key_image_blacklist_type; }
|
||||
rollback_key_image_blacklist(uint64_t block_height, key_image_blacklist_entry const &entry, bool is_adding_to_blacklist);
|
||||
|
||||
key_image_blacklist_entry m_entry;
|
||||
bool m_was_adding_to_blacklist;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(key)
|
||||
FIELD(info)
|
||||
FIELDS(*static_cast<rollback_event *>(this))
|
||||
FIELD(m_entry)
|
||||
FIELD(m_was_adding_to_blacklist)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
typedef boost::variant<rollback_change, rollback_new, prevent_rollback, rollback_key_image_blacklist> rollback_event_variant;
|
||||
|
||||
struct quorum_state_for_serialization
|
||||
{
|
||||
uint8_t version;
|
||||
uint64_t height;
|
||||
quorum_state state;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(version)
|
||||
FIELD(height)
|
||||
FIELD(state)
|
||||
END_SERIALIZE()
|
||||
|
@ -253,24 +305,27 @@ namespace service_nodes
|
|||
|
||||
struct data_members_for_serialization
|
||||
{
|
||||
std::vector<quorum_state_for_serialization> quorum_states;
|
||||
std::vector<node_info_for_serialization> infos;
|
||||
std::vector<rollback_event_variant> events;
|
||||
uint8_t version;
|
||||
uint64_t height;
|
||||
std::vector<quorum_state_for_serialization> quorum_states;
|
||||
std::vector<service_node_pubkey_info> infos;
|
||||
std::vector<rollback_event_variant> events;
|
||||
std::vector<key_image_blacklist_entry> key_image_blacklist;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
VARINT_FIELD(version)
|
||||
FIELD(quorum_states)
|
||||
FIELD(infos)
|
||||
FIELD(events)
|
||||
FIELD(height)
|
||||
if (version >= service_node_info::version_2_infinite_staking)
|
||||
FIELD(key_image_blacklist)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
// Note(maxim): private methods don't have to be protected the mutex
|
||||
bool get_contribution(const cryptonote::transaction& tx, uint64_t block_height, cryptonote::account_public_address& address, uint64_t& transferred) const;
|
||||
|
||||
bool process_registration_tx(const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index);
|
||||
void process_contribution_tx(const cryptonote::transaction& tx, uint64_t block_height, uint32_t index);
|
||||
bool process_deregistration_tx(const cryptonote::transaction& tx, uint64_t block_height);
|
||||
|
@ -285,31 +340,29 @@ namespace service_nodes
|
|||
void store_quorum_state_from_rewards_list(uint64_t height);
|
||||
|
||||
bool is_registration_tx(const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, service_node_info& info) const;
|
||||
std::vector<crypto::public_key> get_expired_nodes(uint64_t block_height) const;
|
||||
std::vector<crypto::public_key> update_and_get_expired_nodes(const std::vector<cryptonote::transaction> &txs, uint64_t block_height);
|
||||
|
||||
void clear(bool delete_db_entry = false);
|
||||
bool load();
|
||||
|
||||
mutable boost::recursive_mutex m_sn_mutex;
|
||||
|
||||
using block_height = uint64_t;
|
||||
|
||||
std::unordered_map<crypto::public_key, service_node_info> m_service_nodes_infos;
|
||||
std::list<std::unique_ptr<rollback_event>> m_rollback_events;
|
||||
cryptonote::Blockchain& m_blockchain;
|
||||
bool m_hooks_registered;
|
||||
|
||||
using block_height = uint64_t;
|
||||
block_height m_height;
|
||||
|
||||
crypto::public_key const *m_service_node_pubkey;
|
||||
|
||||
cryptonote::BlockchainDB* m_db;
|
||||
|
||||
std::vector<key_image_blacklist_entry> m_key_image_blacklist;
|
||||
std::map<block_height, std::shared_ptr<const quorum_state>> m_quorum_states;
|
||||
|
||||
};
|
||||
|
||||
uint64_t get_reg_tx_staking_output_contribution(const cryptonote::transaction& tx, int i, crypto::key_derivation derivation, hw::device& hwdev);
|
||||
bool 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);
|
||||
|
||||
bool convert_registration_args(cryptonote::network_type nettype, std::vector<std::string> args, std::vector<cryptonote::account_public_address>& addresses, std::vector<uint64_t>& portions, uint64_t& portions_for_operator, bool& autostake, boost::optional<std::string&> err_msg);
|
||||
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, boost::optional<std::string&> err_msg);
|
||||
|
@ -323,3 +376,4 @@ VARIANT_TAG(binary_archive, service_nodes::service_node_list::data_members_for_s
|
|||
VARIANT_TAG(binary_archive, service_nodes::service_node_list::rollback_change, 0xa1);
|
||||
VARIANT_TAG(binary_archive, service_nodes::service_node_list::rollback_new, 0xa2);
|
||||
VARIANT_TAG(binary_archive, service_nodes::service_node_list::prevent_rollback, 0xa3);
|
||||
VARIANT_TAG(binary_archive, service_nodes::service_node_list::rollback_key_image_blacklist, 0xa4);
|
||||
|
|
|
@ -27,24 +27,56 @@ uint64_t get_staking_requirement(cryptonote::network_type m_nettype, uint64_t he
|
|||
|
||||
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;
|
||||
uint64_t hi, lo, resulthi, resultlo;
|
||||
lo = mul128(staking_requirement, portions, &hi);
|
||||
div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo);
|
||||
return resultlo;
|
||||
}
|
||||
|
||||
bool check_service_node_portions(uint8_t hf_version, const std::vector<uint64_t>& portions)
|
||||
{
|
||||
if (portions.size() > MAX_NUMBER_OF_CONTRIBUTORS) return false;
|
||||
if (portions.size() > MAX_NUMBER_OF_CONTRIBUTORS) return false;
|
||||
|
||||
uint64_t reserved = 0;
|
||||
for (auto i = 0u; i < portions.size(); ++i) {
|
||||
const uint64_t min_portions = get_min_node_contribution(hf_version, STAKING_PORTIONS, reserved, i);
|
||||
if (portions[i] < min_portions) return false;
|
||||
reserved += portions[i];
|
||||
}
|
||||
uint64_t reserved = 0;
|
||||
for (auto i = 0u; i < portions.size(); ++i) {
|
||||
const uint64_t min_portions = get_min_node_contribution(hf_version, STAKING_PORTIONS, reserved, i);
|
||||
if (portions[i] < min_portions) return false;
|
||||
reserved += portions[i];
|
||||
}
|
||||
|
||||
return reserved <= STAKING_PORTIONS;
|
||||
return reserved <= STAKING_PORTIONS;
|
||||
}
|
||||
|
||||
crypto::hash generate_request_stake_unlock_hash(uint32_t nonce)
|
||||
{
|
||||
crypto::hash result = {};
|
||||
char const *nonce_ptr = (char *)&nonce;
|
||||
char *hash_ptr = result.data;
|
||||
static_assert(sizeof(result) % sizeof(nonce) == 0, "The nonce should be evenly divisible into the hash");
|
||||
for (size_t i = 0; i < sizeof(result) / sizeof(nonce); ++i)
|
||||
{
|
||||
memcpy(hash_ptr, nonce_ptr, sizeof(nonce));
|
||||
hash_ptr += sizeof(nonce);
|
||||
}
|
||||
|
||||
assert(hash_ptr == (char *)result.data + sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, uint64_t node_register_height, uint64_t curr_height)
|
||||
{
|
||||
uint64_t blocks_to_lock = staking_initial_num_lock_blocks(nettype);
|
||||
if (curr_height < node_register_height)
|
||||
{
|
||||
// Unexpected current_height less than node_register_height, developer error?
|
||||
assert(curr_height >= node_register_height);
|
||||
curr_height = node_register_height;
|
||||
}
|
||||
|
||||
uint64_t delta_height = curr_height - node_register_height;
|
||||
uint64_t remainder = delta_height % blocks_to_lock;
|
||||
uint64_t result = curr_height + blocks_to_lock - remainder;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
@ -82,7 +114,6 @@ uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amou
|
|||
}
|
||||
|
||||
static bool get_portions_from_percent(double cur_percent, uint64_t& portions) {
|
||||
|
||||
if(cur_percent < 0.0 || cur_percent > 100.0) return false;
|
||||
|
||||
// Fix for truncation issue when operator cut = 100 for a pool Service Node.
|
||||
|
@ -100,22 +131,21 @@ static bool get_portions_from_percent(double cur_percent, uint64_t& portions) {
|
|||
|
||||
bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions) {
|
||||
|
||||
if(!cut_str.empty() && cut_str.back() == '%')
|
||||
{
|
||||
cut_str.pop_back();
|
||||
}
|
||||
if(!cut_str.empty() && cut_str.back() == '%')
|
||||
{
|
||||
cut_str.pop_back();
|
||||
}
|
||||
|
||||
double cut_percent;
|
||||
try
|
||||
{
|
||||
cut_percent = boost::lexical_cast<double>(cut_str);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_portions_from_percent(cut_percent, portions);
|
||||
}
|
||||
double cut_percent;
|
||||
try
|
||||
{
|
||||
cut_percent = boost::lexical_cast<double>(cut_str);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_portions_from_percent(cut_percent, portions);
|
||||
}
|
||||
} // namespace service_nodes
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "crypto/crypto.h"
|
||||
#include "cryptonote_config.h"
|
||||
|
||||
namespace service_nodes {
|
||||
|
||||
inline uint64_t get_staking_requirement_lock_blocks(cryptonote::network_type nettype)
|
||||
inline uint64_t staking_initial_num_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;
|
||||
}
|
||||
switch(nettype)
|
||||
{
|
||||
case cryptonote::FAKECHAIN: return 30;
|
||||
case cryptonote::TESTNET: return BLOCKS_EXPECTED_IN_DAYS(2);
|
||||
default: return BLOCKS_EXPECTED_IN_DAYS(30);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t get_min_node_contribution(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t contrib_count);
|
||||
|
@ -25,9 +25,12 @@ uint64_t portions_to_amount(uint64_t portions, uint64_t staking_requirement);
|
|||
/// are made in the specified order) and don't exceed the required amount
|
||||
bool check_service_node_portions(uint8_t version, const std::vector<uint64_t>& portions);
|
||||
|
||||
crypto::hash generate_request_stake_unlock_hash(uint32_t nonce);
|
||||
uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, uint64_t node_register_height, uint64_t curr_height);
|
||||
|
||||
// Returns lowest x such that (staking_requirement * x/STAKING_PORTIONS) >= amount
|
||||
uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amount);
|
||||
|
||||
bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ namespace cryptonote
|
|||
uint64_t get_transaction_weight_limit(uint8_t version)
|
||||
{
|
||||
// from v10, bulletproofs, limit a tx to 50% of the minimum block weight
|
||||
if (version >= 10)
|
||||
if (version >= network_version_10_bulletproofs)
|
||||
return get_min_block_weight(version) / 2 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
|
||||
else
|
||||
return get_min_block_weight(version) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
|
||||
|
@ -115,37 +115,79 @@ namespace cryptonote
|
|||
|
||||
}
|
||||
//---------------------------------------------------------------------------------
|
||||
bool tx_memory_pool::have_deregister_tx_already(transaction const &tx) const
|
||||
bool tx_memory_pool::have_duplicated_non_standard_tx(transaction const &tx) const
|
||||
{
|
||||
if (!tx.is_deregister_tx())
|
||||
transaction_prefix::type_t tx_type = tx.get_type();
|
||||
|
||||
if (tx_type == transaction::type_standard)
|
||||
return false;
|
||||
|
||||
tx_extra_service_node_deregister deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
|
||||
if (tx_type == transaction::type_deregister)
|
||||
{
|
||||
MERROR("Could not get service node deregister from tx v3, possibly corrupt tx in your blockchain");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<transaction> pool_txs;
|
||||
get_transactions(pool_txs);
|
||||
for (const transaction& pool_tx : pool_txs)
|
||||
{
|
||||
if (!pool_tx.is_deregister_tx())
|
||||
continue;
|
||||
|
||||
tx_extra_service_node_deregister pool_tx_deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(pool_tx.extra, pool_tx_deregister))
|
||||
{
|
||||
MERROR("Could not get service node deregister from tx v3, possibly corrupt tx in your blockchain");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((pool_tx_deregister.block_height == deregister.block_height) &&
|
||||
(pool_tx_deregister.service_node_index == deregister.service_node_index))
|
||||
tx_extra_service_node_deregister deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
|
||||
{
|
||||
MERROR("Could not get service node deregister from tx, possibly corrupt tx in your blockchain, rejecting malformed deregister");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<transaction> pool_txs;
|
||||
get_transactions(pool_txs);
|
||||
for (const transaction& pool_tx : pool_txs)
|
||||
{
|
||||
if (pool_tx.get_type() != transaction::type_deregister)
|
||||
continue;
|
||||
|
||||
tx_extra_service_node_deregister pool_tx_deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(pool_tx.extra, pool_tx_deregister))
|
||||
{
|
||||
MERROR("Could not get service node deregister from tx, possibly corrupt tx in your blockchain");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((pool_tx_deregister.block_height == deregister.block_height) &&
|
||||
(pool_tx_deregister.service_node_index == deregister.service_node_index))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tx_type == transaction::type_key_image_unlock)
|
||||
{
|
||||
tx_extra_tx_key_image_unlock unlock;
|
||||
if (!cryptonote::get_tx_key_image_unlock_from_tx_extra(tx.extra, unlock))
|
||||
{
|
||||
MERROR("Could not get key image unlock from tx, possibly corrupt tx in your blockchain, rejecting malformed tx");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<transaction> pool_txs;
|
||||
get_transactions(pool_txs);
|
||||
for (const transaction& pool_tx : pool_txs)
|
||||
{
|
||||
if (pool_tx.get_type() != tx_type)
|
||||
continue;
|
||||
|
||||
tx_extra_tx_key_image_unlock pool_unlock;
|
||||
if (!cryptonote::get_tx_key_image_unlock_from_tx_extra(pool_tx.extra, pool_unlock))
|
||||
{
|
||||
MERROR("Could not get key image unlock from tx, possibly corrupt tx in your blockchain, rejecting malformed tx");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (unlock.key_image == pool_unlock.key_image)
|
||||
{
|
||||
MERROR("There was atleast one TX in the pool that is requesting to unlock the same key image already.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOTE(loki): This is a developer error. If we come across this in production, be conservative and just reject
|
||||
MERROR("Unrecognised transaction type: " << static_cast<uint16_t>(tx_type) << " for tx: " << get_transaction_hash(tx));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -217,7 +259,7 @@ namespace cryptonote
|
|||
fee = tx.rct_signatures.txnFee;
|
||||
}
|
||||
|
||||
if (!kept_by_block && !tx.is_deregister_tx() && !m_blockchain.check_fee(tx_weight, fee))
|
||||
if (!kept_by_block && tx.get_type() == transaction::type_standard && !m_blockchain.check_fee(tx_weight, fee))
|
||||
{
|
||||
tvc.m_verifivation_failed = true;
|
||||
tvc.m_fee_too_low = true;
|
||||
|
@ -246,10 +288,10 @@ namespace cryptonote
|
|||
tvc.m_double_spend = true;
|
||||
return false;
|
||||
}
|
||||
if (have_deregister_tx_already(tx))
|
||||
if (have_duplicated_non_standard_tx(tx))
|
||||
{
|
||||
mark_double_spend(tx);
|
||||
LOG_PRINT_L1("Transaction version 3 with id= "<< id << " already has a deregister for height");
|
||||
LOG_PRINT_L1("Transaction with id= "<< id << " already has a duplicate tx for height");
|
||||
tvc.m_verifivation_failed = true;
|
||||
tvc.m_double_spend = true;
|
||||
return false;
|
||||
|
@ -273,6 +315,7 @@ namespace cryptonote
|
|||
uint64_t max_used_block_height = 0;
|
||||
cryptonote::txpool_tx_meta_t meta;
|
||||
bool ch_inp_res = check_tx_inputs([&tx]()->cryptonote::transaction&{ return tx; }, id, max_used_block_height, max_used_block_id, tvc, kept_by_block);
|
||||
const bool non_standard_tx = (tx.get_type() != transaction::type_standard);
|
||||
if(!ch_inp_res)
|
||||
{
|
||||
// if the transaction was valid before (kept_by_block), then it
|
||||
|
@ -290,7 +333,7 @@ namespace cryptonote
|
|||
meta.last_relayed_time = time(NULL);
|
||||
meta.relayed = relayed;
|
||||
meta.do_not_relay = do_not_relay;
|
||||
meta.double_spend_seen = (have_tx_keyimges_as_spent(tx) || have_deregister_tx_already(tx));
|
||||
meta.double_spend_seen = (have_tx_keyimges_as_spent(tx) || have_duplicated_non_standard_tx(tx));
|
||||
meta.bf_padding = 0;
|
||||
memset(meta.padding, 0, sizeof(meta.padding));
|
||||
try
|
||||
|
@ -302,7 +345,7 @@ namespace cryptonote
|
|||
m_blockchain.add_txpool_tx(id, blob, meta);
|
||||
if (!insert_key_images(tx, id, kept_by_block))
|
||||
return false;
|
||||
m_txs_by_fee_and_receive_time.emplace(std::tuple<bool, double, std::time_t>(tx.is_deregister_tx(), fee / (double)tx_weight, receive_time), id);
|
||||
m_txs_by_fee_and_receive_time.emplace(std::tuple<bool, double, std::time_t>(non_standard_tx, fee / (double)tx_weight, receive_time), id);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
|
@ -346,7 +389,7 @@ namespace cryptonote
|
|||
m_blockchain.add_txpool_tx(id, blob, meta);
|
||||
if (!insert_key_images(tx, id, kept_by_block))
|
||||
return false;
|
||||
m_txs_by_fee_and_receive_time.emplace(std::tuple<bool, double, std::time_t>(tx.is_deregister_tx(), fee / (double)tx_weight, receive_time), id);
|
||||
m_txs_by_fee_and_receive_time.emplace(std::tuple<bool, double, std::time_t>(non_standard_tx, fee / (double)tx_weight, receive_time), id);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
|
@ -355,7 +398,7 @@ namespace cryptonote
|
|||
}
|
||||
tvc.m_added_to_pool = true;
|
||||
|
||||
if((meta.fee > 0 || tx.is_deregister_tx()) && !do_not_relay)
|
||||
if((meta.fee > 0 || non_standard_tx) && !do_not_relay)
|
||||
tvc.m_should_be_relayed = true;
|
||||
}
|
||||
|
||||
|
@ -427,10 +470,10 @@ namespace cryptonote
|
|||
|
||||
for (auto it = m_txs_by_fee_and_receive_time.begin(); it != m_txs_by_fee_and_receive_time.end(); )
|
||||
{
|
||||
const bool is_deregister = std::get<0>(it->first);
|
||||
const bool is_standard_tx = !std::get<0>(it->first);
|
||||
const time_t receive_time = std::get<2>(it->first);
|
||||
|
||||
if (!is_deregister || receive_time >= time(nullptr) - MEMPOOL_PRUNE_DEREGISTER_LIFETIME)
|
||||
if (is_standard_tx || receive_time >= time(nullptr) - MEMPOOL_PRUNE_NON_STANDARD_TX_LIFETIME)
|
||||
break;
|
||||
|
||||
try
|
||||
|
@ -705,7 +748,7 @@ namespace cryptonote
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!tx.is_deregister_tx())
|
||||
if (tx.get_type() != transaction::type_deregister)
|
||||
return true;
|
||||
|
||||
tx_verification_context tvc;
|
||||
|
@ -713,7 +756,7 @@ namespace cryptonote
|
|||
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));
|
||||
LOG_PRINT_L1("TX type: " << transaction::type_to_string(tx.type) << " considered for relaying failed tx inputs check, txid: " << txid << ", reason: " << print_tx_verification_context(tvc, &tx));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1493,7 +1536,9 @@ namespace cryptonote
|
|||
MFATAL("Failed to insert key images from txpool tx");
|
||||
return false;
|
||||
}
|
||||
m_txs_by_fee_and_receive_time.emplace(std::tuple<bool, double, time_t>(tx.is_deregister_tx(), meta.fee / (double)meta.weight, meta.receive_time), txid);
|
||||
|
||||
const bool non_standard_tx = (tx.get_type() != transaction::type_standard);
|
||||
m_txs_by_fee_and_receive_time.emplace(std::tuple<bool, double, time_t>(non_standard_tx, meta.fee / (double)meta.weight, meta.receive_time), txid);
|
||||
m_txpool_weight += meta.weight;
|
||||
return true;
|
||||
}, true);
|
||||
|
|
|
@ -455,12 +455,12 @@ namespace cryptonote
|
|||
bool have_tx_keyimg_as_spent(const crypto::key_image& key_im) const;
|
||||
|
||||
/**
|
||||
* @brief check if the deregistration tx already exists in the pool.
|
||||
* @brief check if a tx that does not have a key-image component has a duplicate in the pool
|
||||
|
||||
* @return true if it already exists
|
||||
*
|
||||
*/
|
||||
bool have_deregister_tx_already(transaction const &tx) const;
|
||||
bool have_duplicated_non_standard_tx(transaction const &tx) const;
|
||||
|
||||
/**
|
||||
* @brief check if any spent key image in a transaction is in the pool
|
||||
|
|
|
@ -2048,9 +2048,10 @@ static void print_service_node_list_state(cryptonote::network_type nettype, int
|
|||
tools::msg_writer() << indent2 << "Total Reserved: " << cryptonote::print_money(entry.total_reserved);
|
||||
}
|
||||
|
||||
// TODO(doyle): Fix up for infinite staking changes
|
||||
// Print Expiry Info
|
||||
{
|
||||
uint64_t expiry_height = entry.registration_height + service_nodes::get_staking_requirement_lock_blocks(nettype);
|
||||
uint64_t expiry_height = entry.registration_height + service_nodes::staking_initial_num_lock_blocks(nettype);
|
||||
if (hard_fork_version >= cryptonote::network_version_10_bulletproofs)
|
||||
expiry_height += STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
|
||||
|
||||
|
@ -2094,7 +2095,7 @@ static void print_service_node_list_state(cryptonote::network_type nettype, int
|
|||
tools::msg_writer() << "";
|
||||
for (size_t j = 0; j < entry.contributors.size(); ++j)
|
||||
{
|
||||
const cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::contribution &contributor = entry.contributors[j];
|
||||
const cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::contributor &contributor = entry.contributors[j];
|
||||
tools::msg_writer() << indent2 << "[" << j << "] Contributor: " << contributor.address;
|
||||
tools::msg_writer() << indent3 << "Amount / Reserved: " << cryptonote::print_money(contributor.amount) << " / " << cryptonote::print_money(contributor.reserved);
|
||||
}
|
||||
|
|
|
@ -726,6 +726,11 @@ namespace cryptonote
|
|||
res.overspend = tvc.m_overspend;
|
||||
res.fee_too_low = tvc.m_fee_too_low;
|
||||
res.not_rct = tvc.m_not_rct;
|
||||
res.invalid_version = tvc.m_invalid_version;
|
||||
res.invalid_type = tvc.m_invalid_type;
|
||||
res.key_image_locked_by_snode = tvc.m_key_image_locked_by_snode;
|
||||
res.key_image_blacklisted = tvc.m_key_image_blacklisted;
|
||||
res.not_enough_votes = vvc.m_not_enough_votes;
|
||||
res.invalid_block_height = vvc.m_invalid_block_height;
|
||||
res.duplicate_voters = vvc.m_duplicate_voters;
|
||||
res.voters_quorum_index_out_of_bounds = vvc.m_voters_quorum_index_out_of_bounds;
|
||||
|
@ -2435,17 +2440,31 @@ namespace cryptonote
|
|||
COMMAND_RPC_GET_SERVICE_NODES::response::entry entry = {};
|
||||
entry.service_node_pubkey = string_tools::pod_to_hex(pubkey_info.pubkey);
|
||||
entry.registration_height = pubkey_info.info.registration_height;
|
||||
entry.requested_unlock_height = pubkey_info.info.requested_unlock_height;
|
||||
entry.last_reward_block_height = pubkey_info.info.last_reward_block_height;
|
||||
entry.last_reward_transaction_index = pubkey_info.info.last_reward_transaction_index;
|
||||
entry.last_uptime_proof = m_core.get_uptime_proof(pubkey_info.pubkey);
|
||||
|
||||
entry.contributors.reserve(pubkey_info.info.contributors.size());
|
||||
for (service_nodes::service_node_info::contribution const &contributor : pubkey_info.info.contributors)
|
||||
|
||||
using namespace service_nodes;
|
||||
for (service_node_info::contributor_t const &contributor : pubkey_info.info.contributors)
|
||||
{
|
||||
COMMAND_RPC_GET_SERVICE_NODES::response::contribution new_contributor = {};
|
||||
COMMAND_RPC_GET_SERVICE_NODES::response::contributor new_contributor = {};
|
||||
new_contributor.amount = contributor.amount;
|
||||
new_contributor.reserved = contributor.reserved;
|
||||
new_contributor.address = cryptonote::get_account_address_as_str(m_core.get_nettype(), false/*is_subaddress*/, contributor.address);
|
||||
|
||||
new_contributor.locked_contributions.reserve(contributor.locked_contributions.size());
|
||||
for (service_node_info::contribution_t const &src : contributor.locked_contributions)
|
||||
{
|
||||
COMMAND_RPC_GET_SERVICE_NODES::response::contribution dest = {};
|
||||
dest.amount = src.amount;
|
||||
dest.key_image = string_tools::pod_to_hex(src.key_image);
|
||||
dest.key_image_pub_key = string_tools::pod_to_hex(src.key_image_pub_key);
|
||||
new_contributor.locked_contributions.push_back(dest);
|
||||
}
|
||||
|
||||
entry.contributors.push_back(new_contributor);
|
||||
}
|
||||
|
||||
|
@ -2475,6 +2494,24 @@ namespace cryptonote
|
|||
res.status = CORE_RPC_STATUS_OK;
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool core_rpc_server::on_get_service_node_blacklisted_key_images(const COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::request& req, COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::response& res, epee::json_rpc::error &error_resp)
|
||||
{
|
||||
PERF_TIMER(on_get_service_node_blacklisted_key_images);
|
||||
const std::vector<service_nodes::key_image_blacklist_entry> &blacklist = m_core.get_service_node_blacklisted_key_images();
|
||||
|
||||
res.status = CORE_RPC_STATUS_OK;
|
||||
res.blacklist.reserve(blacklist.size());
|
||||
for (const service_nodes::key_image_blacklist_entry &entry : blacklist)
|
||||
{
|
||||
COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::entry new_entry = {};
|
||||
new_entry.key_image = epee::string_tools::pod_to_hex(entry.key_image);
|
||||
new_entry.unlock_height = entry.unlock_height;
|
||||
res.blacklist.push_back(std::move(new_entry));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const command_line::arg_descriptor<std::string, false, true, 2> core_rpc_server::arg_rpc_bind_port = {
|
||||
"rpc-bind-port"
|
||||
|
|
|
@ -157,6 +157,7 @@ namespace cryptonote
|
|||
MAP_JON_RPC_WE("get_quorum_state", on_get_quorum_state, COMMAND_RPC_GET_QUORUM_STATE)
|
||||
MAP_JON_RPC_WE("get_quorum_state_batched", on_get_quorum_state_batched, COMMAND_RPC_GET_QUORUM_STATE_BATCHED)
|
||||
MAP_JON_RPC_WE("get_service_node_registration_cmd_raw", on_get_service_node_registration_cmd_raw, COMMAND_RPC_GET_SERVICE_NODE_REGISTRATION_CMD_RAW)
|
||||
MAP_JON_RPC_WE("get_service_node_blacklisted_key_images", on_get_service_node_blacklisted_key_images, COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES)
|
||||
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)
|
||||
|
@ -231,6 +232,7 @@ namespace cryptonote
|
|||
bool on_get_quorum_state_batched(const COMMAND_RPC_GET_QUORUM_STATE_BATCHED::request& req, COMMAND_RPC_GET_QUORUM_STATE_BATCHED::response& res, epee::json_rpc::error& error_resp);
|
||||
bool on_get_service_node_registration_cmd_raw(const COMMAND_RPC_GET_SERVICE_NODE_REGISTRATION_CMD_RAW::request& req, COMMAND_RPC_GET_SERVICE_NODE_REGISTRATION_CMD_RAW::response& res, epee::json_rpc::error& error_resp);
|
||||
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_blacklisted_key_images(const COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::request& req, COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::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);
|
||||
|
|
|
@ -842,6 +842,10 @@ namespace cryptonote
|
|||
bool overspend;
|
||||
bool fee_too_low;
|
||||
bool not_rct;
|
||||
bool invalid_version;
|
||||
bool invalid_type;
|
||||
bool key_image_locked_by_snode;
|
||||
bool key_image_blacklisted;
|
||||
bool untrusted;
|
||||
|
||||
bool invalid_block_height;
|
||||
|
@ -863,6 +867,10 @@ namespace cryptonote
|
|||
KV_SERIALIZE(overspend)
|
||||
KV_SERIALIZE(fee_too_low)
|
||||
KV_SERIALIZE(not_rct)
|
||||
KV_SERIALIZE(invalid_version)
|
||||
KV_SERIALIZE(invalid_type)
|
||||
KV_SERIALIZE(key_image_locked_by_snode)
|
||||
KV_SERIALIZE(key_image_blacklisted)
|
||||
KV_SERIALIZE(untrusted)
|
||||
|
||||
KV_SERIALIZE(invalid_block_height)
|
||||
|
@ -2550,35 +2558,52 @@ namespace cryptonote
|
|||
struct response
|
||||
{
|
||||
struct contribution
|
||||
{
|
||||
std::string key_image;
|
||||
std::string key_image_pub_key;
|
||||
uint64_t amount;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(key_image)
|
||||
KV_SERIALIZE(key_image_pub_key)
|
||||
KV_SERIALIZE(amount)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct contributor
|
||||
{
|
||||
uint64_t amount;
|
||||
uint64_t reserved;
|
||||
std::string address;
|
||||
std::vector<contribution> locked_contributions;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount)
|
||||
KV_SERIALIZE(reserved)
|
||||
KV_SERIALIZE(address)
|
||||
KV_SERIALIZE(locked_contributions)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct entry
|
||||
{
|
||||
std::string service_node_pubkey;
|
||||
uint64_t registration_height;
|
||||
uint64_t last_reward_block_height;
|
||||
uint32_t last_reward_transaction_index;
|
||||
uint64_t last_uptime_proof;
|
||||
std::vector<contribution> contributors;
|
||||
uint64_t total_contributed;
|
||||
uint64_t total_reserved;
|
||||
uint64_t staking_requirement;
|
||||
uint64_t portions_for_operator;
|
||||
std::string operator_address;
|
||||
std::string service_node_pubkey;
|
||||
uint64_t registration_height;
|
||||
uint64_t requested_unlock_height;
|
||||
uint64_t last_reward_block_height;
|
||||
uint32_t last_reward_transaction_index;
|
||||
uint64_t last_uptime_proof;
|
||||
std::vector<contributor> contributors;
|
||||
uint64_t total_contributed;
|
||||
uint64_t total_reserved;
|
||||
uint64_t staking_requirement;
|
||||
uint64_t portions_for_operator;
|
||||
std::string operator_address;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(service_node_pubkey)
|
||||
KV_SERIALIZE(registration_height)
|
||||
KV_SERIALIZE(requested_unlock_height)
|
||||
KV_SERIALIZE(last_reward_block_height)
|
||||
KV_SERIALIZE(last_reward_transaction_index)
|
||||
KV_SERIALIZE(last_uptime_proof)
|
||||
|
@ -2621,4 +2646,34 @@ namespace cryptonote
|
|||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
struct COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES
|
||||
{
|
||||
struct request
|
||||
{
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct entry
|
||||
{
|
||||
std::string key_image;
|
||||
uint64_t unlock_height;
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(key_image)
|
||||
KV_SERIALIZE(unlock_height)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct response
|
||||
{
|
||||
std::vector<entry> blacklist;
|
||||
std::string status;
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(blacklist)
|
||||
KV_SERIALIZE(status)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -325,6 +325,21 @@ namespace rpc
|
|||
if (!res.error_details.empty()) res.error_details += " and ";
|
||||
res.error_details = "tx is not version 2 or later";
|
||||
}
|
||||
if (tvc.m_invalid_type)
|
||||
{
|
||||
if (!res.error_details.empty()) res.error_details += " and ";
|
||||
res.error_details = "tx has an invalid type";
|
||||
}
|
||||
if (tvc.m_key_image_locked_by_snode)
|
||||
{
|
||||
if (!res.error_details.empty()) res.error_details += " and ";
|
||||
res.error_details = "tx uses outputs that are locked by the service node network";
|
||||
}
|
||||
if (tvc.m_key_image_blacklisted)
|
||||
{
|
||||
if (!res.error_details.empty()) res.error_details += " and ";
|
||||
res.error_details = "tx uses a key image that has been temporarily blacklisted by the service node network";
|
||||
}
|
||||
if (res.error_details.empty())
|
||||
{
|
||||
res.error_details = "an unknown issue was found with the transaction";
|
||||
|
|
|
@ -222,7 +222,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::transaction& tx, ra
|
|||
INSERT_INTO_JSON_OBJECT(val, doc, version, tx.version);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, unlock_time, tx.unlock_time);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, output_unlock_times, tx.output_unlock_times);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, is_deregister, tx.is_deregister);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, type, tx.type);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, inputs, tx.vin);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, outputs, tx.vout);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, extra, tx.extra);
|
||||
|
@ -241,7 +241,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx)
|
|||
GET_FROM_JSON_OBJECT(val, tx.version, version);
|
||||
GET_FROM_JSON_OBJECT(val, tx.unlock_time, unlock_time);
|
||||
GET_FROM_JSON_OBJECT(val, tx.output_unlock_times, output_unlock_times);
|
||||
GET_FROM_JSON_OBJECT(val, tx.is_deregister, is_deregister);
|
||||
GET_FROM_JSON_OBJECT(val, tx.type, type);
|
||||
GET_FROM_JSON_OBJECT(val, tx.vin, inputs);
|
||||
GET_FROM_JSON_OBJECT(val, tx.vout, outputs);
|
||||
GET_FROM_JSON_OBJECT(val, tx.extra, extra);
|
||||
|
|
|
@ -2365,6 +2365,10 @@ simple_wallet::simple_wallet()
|
|||
boost::bind(&simple_wallet::stake, this, _1),
|
||||
tr("stake [index=<N1>[,<N2>,...]] [priority] <service node pubkey> <address> <amount>"),
|
||||
tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. If the parameter \"outputs=<N>\" is specified and N > 0, wallet splits the transaction into N even outputs."));
|
||||
m_cmd_binder.set_handler("request_stake_unlock",
|
||||
boost::bind(&simple_wallet::request_stake_unlock, this, _1),
|
||||
tr("request_stake_unlock <service node pubkey>"),
|
||||
tr(""));
|
||||
m_cmd_binder.set_handler("sweep_unmixable",
|
||||
boost::bind(&simple_wallet::sweep_unmixable, this, _1),
|
||||
tr("Deprecated"));
|
||||
|
@ -5311,9 +5315,6 @@ bool simple_wallet::register_service_node_main(
|
|||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
uint64_t bc_height = std::max(m_wallet->get_daemon_blockchain_height(err),
|
||||
m_wallet->get_daemon_blockchain_target_height(err2));
|
||||
|
@ -5352,15 +5353,26 @@ bool simple_wallet::register_service_node_main(
|
|||
}
|
||||
}
|
||||
|
||||
try
|
||||
uint64_t staking_requirement_lock_blocks = service_nodes::staking_initial_num_lock_blocks(m_wallet->nettype());
|
||||
uint64_t locked_blocks = staking_requirement_lock_blocks + STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
|
||||
uint64_t unlock_block = bc_height + locked_blocks;
|
||||
{
|
||||
const auto& response = m_wallet->get_service_nodes(service_node_key_as_str);
|
||||
if (response.service_node_states.size() >= 1)
|
||||
boost::optional<std::string> failed;
|
||||
const std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> response = m_wallet->get_service_nodes({service_node_key_as_str}, failed);
|
||||
if (failed)
|
||||
{
|
||||
fail_msg_writer() << *failed;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.size() >= 1)
|
||||
{
|
||||
bool can_reregister = false;
|
||||
if (m_wallet->use_fork_rules(cryptonote::network_version_10_bulletproofs, 0))
|
||||
if (m_wallet->use_fork_rules(cryptonote::network_version_11_swarms, 1))
|
||||
unlock_block = 0; // Infinite staking, no time lock
|
||||
else if (m_wallet->use_fork_rules(cryptonote::network_version_10_bulletproofs, 0))
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry const &node_info = response.service_node_states[0];
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry const &node_info = response[0];
|
||||
uint64_t expiry_height = node_info.registration_height + staking_requirement_lock_blocks;
|
||||
if (bc_height >= expiry_height)
|
||||
can_reregister = true;
|
||||
|
@ -5374,13 +5386,6 @@ bool simple_wallet::register_service_node_main(
|
|||
}
|
||||
}
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
fail_msg_writer() << e.what();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t unlock_block = bc_height + locked_blocks;
|
||||
|
||||
uint64_t expected_staking_requirement = std::max(
|
||||
service_nodes::get_staking_requirement(m_wallet->nettype(), bc_height),
|
||||
|
@ -5772,7 +5777,7 @@ bool simple_wallet::stake_main(
|
|||
}
|
||||
}
|
||||
|
||||
uint64_t staking_requirement_lock_blocks = service_nodes::get_staking_requirement_lock_blocks(m_wallet->nettype());
|
||||
uint64_t staking_requirement_lock_blocks = service_nodes::staking_initial_num_lock_blocks(m_wallet->nettype());
|
||||
uint64_t locked_blocks = staking_requirement_lock_blocks + STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
|
||||
uint64_t unlock_block = bc_height + locked_blocks;
|
||||
|
||||
|
@ -5801,6 +5806,9 @@ bool simple_wallet::stake_main(
|
|||
de.amount = amount;
|
||||
dsts.push_back(de);
|
||||
|
||||
if (m_wallet->use_fork_rules(cryptonote::network_version_11_swarms, 1))
|
||||
unlock_block = 0; // Infinite staking, no time lock
|
||||
|
||||
try
|
||||
{
|
||||
// figure out what tx will be necessary
|
||||
|
@ -6033,81 +6041,179 @@ bool simple_wallet::stake(const std::vector<std::string> &args_)
|
|||
return true;
|
||||
}
|
||||
|
||||
if (autostake)
|
||||
stake_main(service_node_key, info, priority, subaddr_indices, amount, amount_fraction, autostake);
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool simple_wallet::request_stake_unlock(const std::vector<std::string> &args_)
|
||||
{
|
||||
if (!try_connect_to_daemon())
|
||||
return true;
|
||||
|
||||
if (args_.size() != 1)
|
||||
{
|
||||
{
|
||||
const auto& response = m_wallet->get_service_nodes({ epee::string_tools::pod_to_hex(service_node_key) });
|
||||
if (response.service_node_states.size() != 1)
|
||||
{
|
||||
fail_msg_writer() << tr("Could not find service node in service node list, please make sure it is registered first.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& snode_info = response.service_node_states.front();
|
||||
bool preexisting_contributor = false;
|
||||
for (const auto& contributor : snode_info.contributors)
|
||||
{
|
||||
preexisting_contributor = (contributor.address == address_str);
|
||||
if (preexisting_contributor) break;
|
||||
}
|
||||
|
||||
if (!preexisting_contributor)
|
||||
{
|
||||
// NOTE: Disallowed since there isn't a sensible way to recalculate the portions of the staker reliably
|
||||
fail_msg_writer() << tr("Autostaking is disallowed for contributors who did not reserve a spot in a service node");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Autostaking in reserved pools warning
|
||||
if (snode_info.contributors.size() > 1 && !prompt_autostaking_non_trusted_contributors_warning())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (amount_fraction == 0) // Fixed amount loki warning
|
||||
{
|
||||
success_msg_writer(false/*color*/) << tr("You're autostaking to a service node using a fixed amount of loki: ")
|
||||
<< print_money(amount)
|
||||
<< tr(".\nThe staking requirement will be different after the service node expires. Staking a fixed amount "
|
||||
"may change your percentage of stake towards the service node and consequently your block reward allocation.")
|
||||
<< tr("\n\nIf this behaviour is not desirable, please reuse the staking command with a percentage sign.");
|
||||
|
||||
if (!input_line_and_parse_yes_no_result("Accept staking with a fixed amount of loki"))
|
||||
{
|
||||
fail_msg_writer() << tr("Staking transaction with fixed loki specified cancelled.");
|
||||
return true;
|
||||
}
|
||||
|
||||
success_msg_writer(false/*color*/) << "\n";
|
||||
}
|
||||
|
||||
stop();
|
||||
#ifndef WIN32 // NOTE: Fork not supported on Windows
|
||||
if (m_wallet->fork_on_autostake())
|
||||
{
|
||||
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
|
||||
#endif
|
||||
{
|
||||
success_msg_writer(true /*color*/) << tr("Successfully entered autostaking mode, please leave this wallet running to automatically renew your service node every period.");
|
||||
}
|
||||
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!stake_main(service_node_key, info, priority, subaddr_indices, amount, amount_fraction, autostake))
|
||||
break;
|
||||
m_idle_cond.wait_for(lock, boost::chrono::seconds(AUTOSTAKE_INTERVAL)); // lock implicitly defined in SCOPED_WALLET_UNLOCK()
|
||||
}
|
||||
fail_msg_writer() << tr("Usage: request_stake_unlock <service node pubkey>");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
||||
crypto::public_key snode_key;
|
||||
if (!epee::string_tools::hex_to_pod(args_[0], snode_key))
|
||||
{
|
||||
stake_main(service_node_key, info, priority, subaddr_indices, amount, amount_fraction, autostake);
|
||||
fail_msg_writer() << tr("failed to parse service node pubkey: ") << args_[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
SCOPED_WALLET_UNLOCK();
|
||||
|
||||
// TODO(doyle): INF_STAKING(doyle): We need to check the SNode List and only
|
||||
// allow the request transaction to go through if there's a matching SNode and
|
||||
// the SNode's has key images belonging to this wallet.
|
||||
std::vector<tools::wallet2::pending_tx> ptx_vector;
|
||||
{
|
||||
ptx_vector.push_back({});
|
||||
tools::wallet2::pending_tx &ptx = ptx_vector.back();
|
||||
ptx.tx.version = cryptonote::transaction::version_4_tx_types;
|
||||
if (!ptx.tx.set_type(cryptonote::transaction::type_key_image_unlock))
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to construct a key image unlock transaction");
|
||||
return true;
|
||||
}
|
||||
|
||||
using namespace cryptonote;
|
||||
boost::optional<std::string> failed;
|
||||
const std::vector<COMMAND_RPC_GET_SERVICE_NODES::response::entry> response = m_wallet->get_service_nodes({args_[0]}, failed);
|
||||
if (failed)
|
||||
{
|
||||
fail_msg_writer() << *failed;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.empty())
|
||||
{
|
||||
fail_msg_writer() << tr("No service node is known for: ") << args_[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<COMMAND_RPC_GET_SERVICE_NODES::response::contribution> const *contributions = nullptr;
|
||||
COMMAND_RPC_GET_SERVICE_NODES::response::entry const &node_info = response[0];
|
||||
for (COMMAND_RPC_GET_SERVICE_NODES::response::contributor const &contributor : node_info.contributors)
|
||||
{
|
||||
address_parse_info address_info = {};
|
||||
|
||||
// TODO(doyle): INF_STAKING(doyle): We don't allow staking not from the owner address yet
|
||||
// When we allow staking on behalf of another address, this won't cut it
|
||||
cryptonote::get_account_address_from_str(address_info, m_wallet->nettype(), contributor.address);
|
||||
if (!m_wallet->contains_address(address_info.address))
|
||||
continue;
|
||||
|
||||
contributions = &contributor.locked_contributions;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!contributions)
|
||||
{
|
||||
fail_msg_writer() << tr("No contributions recognised by this wallet in service node: ") << args_[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contributions->empty())
|
||||
{
|
||||
fail_msg_writer() << tr("Unexpected 0 contributions in service node for this wallet ") << args_[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(doyle): INF_STAKING(doyle): We should indicate that the node gets
|
||||
// into unregistered and perhaps the other contributors/and currently still
|
||||
// locked contributions in the node.
|
||||
cryptonote::tx_extra_tx_key_image_unlock unlock = {};
|
||||
{
|
||||
std::string msg_buf;
|
||||
msg_buf.reserve(512);
|
||||
|
||||
COMMAND_RPC_GET_SERVICE_NODES::response::contribution const &contribution = (*contributions)[0];
|
||||
if (node_info.requested_unlock_height != 0)
|
||||
{
|
||||
msg_buf.append("Key image: ");
|
||||
msg_buf.append(contribution.key_image);
|
||||
msg_buf.append(" has already been requested to be unlocked, unlocking at height: ");
|
||||
msg_buf.append(std::to_string(node_info.requested_unlock_height));
|
||||
fail_msg_writer() << msg_buf;
|
||||
return true;
|
||||
}
|
||||
|
||||
msg_buf.append("You are requesting to unlock a stake of: ");
|
||||
msg_buf.append(cryptonote::print_money(contribution.amount));
|
||||
msg_buf.append(" Loki from the service node network.\nThis will deactivate the service node: ");
|
||||
msg_buf.append(node_info.service_node_pubkey);
|
||||
msg_buf.append(" and schedule the service node for expiration.\n\n");
|
||||
|
||||
uint64_t curr_height = 0;
|
||||
{
|
||||
std::string err_msg;
|
||||
curr_height = m_wallet->get_daemon_blockchain_height(err_msg);
|
||||
if (!err_msg.empty())
|
||||
{
|
||||
fail_msg_writer() << tr("unable to get network blockchain height from daemon: ") << err_msg;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(doyle): INF_STAKING(doyle): We should estimate the days/hours for users
|
||||
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(m_wallet->nettype(), node_info.registration_height, curr_height);
|
||||
msg_buf.append("You will continue receiving rewards until the service node expires at the estimated height: ");
|
||||
msg_buf.append(std::to_string(unlock_height));
|
||||
|
||||
cryptonote::blobdata binary_buf;
|
||||
if(!string_tools::parse_hexstr_to_binbuff(contribution.key_image, binary_buf) || binary_buf.size() != sizeof(crypto::key_image))
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to parse hex representation of key image");
|
||||
return true;
|
||||
}
|
||||
|
||||
unlock.key_image = *reinterpret_cast<const crypto::key_image*>(binary_buf.data());
|
||||
if (!m_wallet->generate_signature_for_request_stake_unlock(unlock.key_image, unlock.signature, unlock.nonce))
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to generate signature to sign request. The key image: ") << contribution.key_image << (" doesn't belong to this wallet");
|
||||
return true;
|
||||
}
|
||||
|
||||
success_msg_writer() << msg_buf;
|
||||
}
|
||||
|
||||
add_service_node_pubkey_to_tx_extra(ptx.tx.extra, snode_key);
|
||||
add_tx_key_image_unlock_to_tx_extra(ptx.tx.extra, unlock);
|
||||
}
|
||||
|
||||
|
||||
// TODO(doyle): INF_STAKING(doyle): Do we support staking in these modes?
|
||||
if (m_wallet->multisig())
|
||||
{
|
||||
fail_msg_writer() << tr("Multi sig request stake unlock is unsupported");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_wallet->watch_only())
|
||||
{
|
||||
if (m_wallet->save_tx(ptx_vector, "unsigned_loki_tx"))
|
||||
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_loki_tx";
|
||||
else
|
||||
fail_msg_writer() << tr("Failed to write transaction(s) to file");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
commit_or_save(ptx_vector, m_do_not_relay);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_ERROR("unknown error");
|
||||
fail_msg_writer() << tr("unknown error");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -7619,10 +7725,11 @@ bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vec
|
|||
output.unlock_time = (dest_index < pd.m_unlock_times.size()) ? pd.m_unlock_times[dest_index] : 0;
|
||||
}
|
||||
|
||||
// TODO(doyle): Broken lock time isnt used anymore.
|
||||
// NOTE(loki): Technically we don't allow custom unlock times per output
|
||||
// yet. So if we detect _any_ output that has the staking lock time, then
|
||||
// we can assume it's a staking transfer
|
||||
const uint64_t staking_duration = service_nodes::get_staking_requirement_lock_blocks(m_wallet->nettype());
|
||||
const uint64_t staking_duration = service_nodes::staking_initial_num_lock_blocks(m_wallet->nettype());
|
||||
bool locked = false;
|
||||
|
||||
tools::pay_type type = tools::pay_type::out;
|
||||
|
|
|
@ -161,6 +161,7 @@ namespace cryptonote
|
|||
bool locked_transfer(const std::vector<std::string> &args);
|
||||
bool stake(const std::vector<std::string> &args_);
|
||||
bool register_service_node(const std::vector<std::string> &args_);
|
||||
bool request_stake_unlock(const std::vector<std::string> &args_);
|
||||
bool locked_sweep_all(const std::vector<std::string> &args);
|
||||
bool sweep_main(uint64_t below, bool locked, const std::vector<std::string> &args);
|
||||
bool sweep_all(const std::vector<std::string> &args);
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "node_rpc_proxy.h"
|
||||
#include "rpc/core_rpc_server_commands_defs.h"
|
||||
#include "storages/http_abstract_invoke.h"
|
||||
|
||||
using namespace epee;
|
||||
|
@ -46,6 +45,12 @@ NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_clien
|
|||
|
||||
void NodeRPCProxy::invalidate()
|
||||
{
|
||||
m_service_node_blacklisted_key_images_cached_height = 0;
|
||||
m_service_node_blacklisted_key_images.clear();
|
||||
|
||||
m_all_service_nodes_cached_height = 0;
|
||||
m_all_service_nodes.clear();
|
||||
|
||||
m_height = 0;
|
||||
for (size_t n = 0; n < 256; ++n)
|
||||
m_earliest_height[n] = 0;
|
||||
|
@ -232,4 +237,129 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f
|
|||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> NodeRPCProxy::get_service_nodes(std::vector<std::string> const &pubkeys, boost::optional<std::string> &failed) const
|
||||
{
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> result;
|
||||
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::request req = {};
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response res = {};
|
||||
req.service_node_pubkeys = pubkeys;
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "get_service_nodes", req, res, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
if (!r)
|
||||
{
|
||||
failed = std::string("Failed to connect to daemon");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (res.status == CORE_RPC_STATUS_BUSY)
|
||||
{
|
||||
failed = res.status;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (res.status != CORE_RPC_STATUS_OK)
|
||||
{
|
||||
failed = res.status;
|
||||
return result;
|
||||
}
|
||||
|
||||
result = std::move(res.service_node_states);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> NodeRPCProxy::get_all_service_nodes(boost::optional<std::string> &failed) const
|
||||
{
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> result;
|
||||
|
||||
uint64_t height;
|
||||
failed = get_height(height);
|
||||
if (failed)
|
||||
return result;
|
||||
|
||||
{
|
||||
std::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex);
|
||||
if (m_all_service_nodes_cached_height != height)
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::request req = {};
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response res = {};
|
||||
|
||||
bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "get_all_service_nodes", req, res, m_http_client, rpc_timeout);
|
||||
|
||||
if (!r)
|
||||
{
|
||||
failed = std::string("Failed to connect to daemon");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (res.status == CORE_RPC_STATUS_BUSY)
|
||||
{
|
||||
failed = res.status;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (res.status != CORE_RPC_STATUS_OK)
|
||||
{
|
||||
failed = res.status;
|
||||
return result;
|
||||
}
|
||||
|
||||
m_all_service_nodes_cached_height = height;
|
||||
m_all_service_nodes = std::move(res.service_node_states);
|
||||
}
|
||||
|
||||
result = m_all_service_nodes;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::entry> NodeRPCProxy::get_service_node_blacklisted_key_images(boost::optional<std::string> &failed) const
|
||||
{
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::entry> result;
|
||||
|
||||
uint64_t height;
|
||||
failed = get_height(height);
|
||||
if (failed)
|
||||
return result;
|
||||
|
||||
{
|
||||
std::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex);
|
||||
if (m_service_node_blacklisted_key_images_cached_height != height)
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::request req = {};
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::response res = {};
|
||||
|
||||
bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "get_service_node_blacklisted_key_images", req, res, m_http_client, rpc_timeout);
|
||||
|
||||
if (!r)
|
||||
{
|
||||
failed = std::string("Failed to connect to daemon");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (res.status == CORE_RPC_STATUS_BUSY)
|
||||
{
|
||||
failed = res.status;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (res.status != CORE_RPC_STATUS_OK)
|
||||
{
|
||||
failed = res.status;
|
||||
return result;
|
||||
}
|
||||
|
||||
m_service_node_blacklisted_key_images_cached_height = height;
|
||||
m_service_node_blacklisted_key_images = std::move(res.blacklist);
|
||||
}
|
||||
|
||||
result = m_service_node_blacklisted_key_images;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <boost/thread/mutex.hpp>
|
||||
#include "include_base_utils.h"
|
||||
#include "net/http_client.h"
|
||||
#include "rpc/core_rpc_server_commands_defs.h"
|
||||
|
||||
namespace tools
|
||||
{
|
||||
|
@ -53,12 +54,22 @@ public:
|
|||
boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask) const;
|
||||
boost::optional<uint8_t> get_network_version() const;
|
||||
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> get_service_nodes(std::vector<std::string> const &pubkeys, boost::optional<std::string> &failed) const;
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> get_all_service_nodes(boost::optional<std::string> &failed) const;
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::entry> get_service_node_blacklisted_key_images(boost::optional<std::string> &failed) const;
|
||||
|
||||
private:
|
||||
boost::optional<std::string> get_info() const;
|
||||
|
||||
epee::net_utils::http::http_simple_client &m_http_client;
|
||||
boost::mutex &m_daemon_rpc_mutex;
|
||||
|
||||
mutable uint64_t m_service_node_blacklisted_key_images_cached_height;
|
||||
mutable std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::entry> m_service_node_blacklisted_key_images;
|
||||
|
||||
mutable uint64_t m_all_service_nodes_cached_height;
|
||||
mutable std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> m_all_service_nodes;
|
||||
|
||||
mutable uint64_t m_height;
|
||||
mutable uint64_t m_earliest_height[256];
|
||||
mutable uint64_t m_dynamic_base_fee_estimate;
|
||||
|
|
|
@ -1462,6 +1462,9 @@ void wallet2::cache_tx_data(const cryptonote::transaction& tx, const crypto::has
|
|||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data)
|
||||
{
|
||||
if (tx.get_type() != transaction::type_standard)
|
||||
return;
|
||||
|
||||
// In this function, tx (probably) only contains the base information
|
||||
// (that is, the prunable stuff may or may not be included)
|
||||
if (!miner_tx && !pool)
|
||||
|
@ -5139,26 +5142,26 @@ void wallet2::get_payments(const crypto::hash& payment_id, std::list<wallet2::pa
|
|||
{
|
||||
auto range = m_payments.equal_range(payment_id);
|
||||
std::for_each(range.first, range.second, [&payments, &min_height, &subaddr_account, &subaddr_indices](const payment_container::value_type& x) {
|
||||
if (min_height < x.second.m_block_height &&
|
||||
(!subaddr_account || *subaddr_account == x.second.m_subaddr_index.major) &&
|
||||
(subaddr_indices.empty() || subaddr_indices.count(x.second.m_subaddr_index.minor) == 1))
|
||||
{
|
||||
if (min_height < x.second.m_block_height &&
|
||||
(!subaddr_account || *subaddr_account == x.second.m_subaddr_index.major) &&
|
||||
(subaddr_indices.empty() || subaddr_indices.count(x.second.m_subaddr_index.minor) == 1))
|
||||
{
|
||||
payments.push_back(x.second);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
|
||||
{
|
||||
auto range = std::make_pair(m_payments.begin(), m_payments.end());
|
||||
std::for_each(range.first, range.second, [&payments, &min_height, &max_height, &subaddr_account, &subaddr_indices](const payment_container::value_type& x) {
|
||||
if (min_height < x.second.m_block_height && max_height >= x.second.m_block_height &&
|
||||
(!subaddr_account || *subaddr_account == x.second.m_subaddr_index.major) &&
|
||||
(subaddr_indices.empty() || subaddr_indices.count(x.second.m_subaddr_index.minor) == 1))
|
||||
{
|
||||
if (min_height < x.second.m_block_height && max_height >= x.second.m_block_height &&
|
||||
(!subaddr_account || *subaddr_account == x.second.m_subaddr_index.major) &&
|
||||
(subaddr_indices.empty() || subaddr_indices.count(x.second.m_subaddr_index.minor) == 1))
|
||||
{
|
||||
payments.push_back(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments,
|
||||
|
@ -5190,8 +5193,8 @@ void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2:
|
|||
{
|
||||
for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) {
|
||||
if ((!subaddr_account || *subaddr_account == i->second.m_pd.m_subaddr_index.major) &&
|
||||
(subaddr_indices.empty() || subaddr_indices.count(i->second.m_pd.m_subaddr_index.minor) == 1))
|
||||
unconfirmed_payments.push_back(*i);
|
||||
(subaddr_indices.empty() || subaddr_indices.count(i->second.m_pd.m_subaddr_index.minor) == 1))
|
||||
unconfirmed_payments.push_back(*i);
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
@ -5217,8 +5220,8 @@ void wallet2::rescan_spent()
|
|||
THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent");
|
||||
THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::is_key_image_spent_error, daemon_resp.status);
|
||||
THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != n_outputs, error::wallet_internal_error,
|
||||
"daemon returned wrong response for is_key_image_spent, wrong amounts count = " +
|
||||
std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(n_outputs));
|
||||
"daemon returned wrong response for is_key_image_spent, wrong amounts count = " +
|
||||
std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(n_outputs));
|
||||
std::copy(daemon_resp.spent_status.begin(), daemon_resp.spent_status.end(), std::back_inserter(spent_status));
|
||||
}
|
||||
|
||||
|
@ -5279,10 +5282,10 @@ void wallet2::rescan_blockchain(bool hard, bool refresh)
|
|||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::is_transfer_unlocked(const transfer_details& td) const
|
||||
{
|
||||
return is_transfer_unlocked(td.m_tx.get_unlock_time(td.m_internal_output_index), td.m_block_height);
|
||||
return is_transfer_unlocked(td.m_tx.get_unlock_time(td.m_internal_output_index), td.m_block_height, &td.m_key_image);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const
|
||||
bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, crypto::key_image const *key_image) const
|
||||
{
|
||||
if(!is_tx_spendtime_unlocked(unlock_time, block_height))
|
||||
return false;
|
||||
|
@ -5290,6 +5293,73 @@ bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height)
|
|||
if(block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > get_blockchain_current_height())
|
||||
return false;
|
||||
|
||||
if (!use_fork_rules(cryptonote::network_version_11_swarms))
|
||||
return true;
|
||||
|
||||
if (!key_image) // TODO(loki): Try make all callees always pass in a key image for accuracy
|
||||
return true;
|
||||
|
||||
blobdata binary_buf;
|
||||
binary_buf.reserve(sizeof(crypto::key_image));
|
||||
{
|
||||
boost::optional<std::string> failed;
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::entry> blacklist = m_node_rpc_proxy.get_service_node_blacklisted_key_images(failed);
|
||||
if (failed)
|
||||
{
|
||||
LOG_PRINT_L1("Failed to query service node for blacklisted transfers, assuming transfer not blacklisted, reason: " << *failed);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::entry const &entry : blacklist)
|
||||
{
|
||||
binary_buf.clear();
|
||||
if(!string_tools::parse_hexstr_to_binbuff(entry.key_image, binary_buf) || binary_buf.size() != sizeof(crypto::key_image))
|
||||
{
|
||||
MERROR("Failed to parse hex representation of key image: ") << entry.key_image;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto::key_image const *check_image = reinterpret_cast<crypto::key_image const *>(binary_buf.data());
|
||||
if (*key_image == *check_image)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
boost::optional<std::string> failed;
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> service_nodes_states = m_node_rpc_proxy.get_all_service_nodes(failed);
|
||||
if (failed)
|
||||
{
|
||||
LOG_PRINT_L1("Failed to query service node for locked transfers, assuming transfer not locked, reason: " << *failed);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry const &entry : service_nodes_states)
|
||||
{
|
||||
for (cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::contributor const &contributor : entry.contributors)
|
||||
{
|
||||
address_parse_info address_info = {};
|
||||
cryptonote::get_account_address_from_str(address_info, nettype(), contributor.address);
|
||||
if (!contains_address(address_info.address))
|
||||
break;
|
||||
|
||||
for (cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::contribution const &contribution : contributor.locked_contributions)
|
||||
{
|
||||
binary_buf.clear();
|
||||
if(!string_tools::parse_hexstr_to_binbuff(contribution.key_image, binary_buf) || binary_buf.size() != sizeof(crypto::key_image))
|
||||
{
|
||||
MERROR("Failed to parse hex representation of key image: ") << contribution.key_image;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto::key_image const *check_image = reinterpret_cast<crypto::key_image const *>(binary_buf.data());
|
||||
if (*key_image == *check_image)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
@ -5738,15 +5808,16 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
|
|||
LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << sd.sources[0].outputs.size());
|
||||
signed_txes.ptx.push_back(pending_tx());
|
||||
tools::wallet2::pending_tx &ptx = signed_txes.ptx.back();
|
||||
rct::RangeProofType range_proof_type = rct::RangeProofBorromean;
|
||||
if (sd.use_bulletproofs)
|
||||
{
|
||||
range_proof_type = rct::RangeProofPaddedBulletproof;
|
||||
}
|
||||
crypto::secret_key tx_key;
|
||||
std::vector<crypto::secret_key> additional_tx_keys;
|
||||
rct::multisig_out msout;
|
||||
bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, range_proof_type, m_multisig ? &msout : NULL, sd.per_output_unlock);
|
||||
|
||||
loki_construct_tx_params tx_params = {};
|
||||
tx_params.v4_allow_tx_types = sd.v4_allow_tx_types;
|
||||
tx_params.v3_per_output_unlock = sd.v3_per_output_unlock;
|
||||
tx_params.v2_rct = sd.v2_use_rct;
|
||||
tx_params.type = sd.v3_use_bulletproofs ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean;
|
||||
bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, m_multisig ? &msout : NULL, tx_params);
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
|
||||
// we don't test tx size, because we don't know the current limit, due to not having a blockchain,
|
||||
// and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway,
|
||||
|
@ -6149,12 +6220,14 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
|
|||
cryptonote::transaction tx;
|
||||
rct::multisig_out msout = ptx.multisig_sigs.front().msout;
|
||||
auto sources = sd.sources;
|
||||
rct::RangeProofType range_proof_type = rct::RangeProofBorromean;
|
||||
if (sd.use_bulletproofs)
|
||||
{
|
||||
range_proof_type = rct::RangeProofPaddedBulletproof;
|
||||
}
|
||||
bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, range_proof_type, &msout, sd.per_output_unlock, false);
|
||||
|
||||
loki_construct_tx_params tx_params = {};
|
||||
tx_params.v4_allow_tx_types = sd.v4_allow_tx_types;
|
||||
tx_params.v3_per_output_unlock = sd.v3_per_output_unlock;
|
||||
tx_params.v2_rct = sd.v2_use_rct;
|
||||
tx_params.type = sd.v3_use_bulletproofs ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean;
|
||||
|
||||
bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, &msout, true /*shuffle_outs*/, tx_params);
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx),
|
||||
|
@ -6671,14 +6744,21 @@ stake_check_result wallet2::check_stake_allowed(const crypto::public_key& sn_key
|
|||
}
|
||||
|
||||
/// check that the service node is registered
|
||||
const auto& response = this->get_service_nodes({ epee::string_tools::pod_to_hex(sn_key) });
|
||||
if (response.service_node_states.size() != 1)
|
||||
boost::optional<std::string> failed;
|
||||
const auto& response = this->get_service_nodes({ epee::string_tools::pod_to_hex(sn_key) }, failed);
|
||||
if (failed)
|
||||
{
|
||||
LOG_ERROR(*failed);
|
||||
return stake_check_result::try_later;
|
||||
}
|
||||
|
||||
if (response.size() != 1)
|
||||
{
|
||||
MERROR(tr("Could not find service node in service node list, please make sure it is registered first."));
|
||||
return stake_check_result::try_later;
|
||||
}
|
||||
|
||||
const auto& snode_info = response.service_node_states.front();
|
||||
const auto& snode_info = response.front();
|
||||
|
||||
if (amount == 0)
|
||||
amount = snode_info.staking_requirement * fraction;
|
||||
|
@ -6795,7 +6875,7 @@ std::vector<wallet2::pending_tx> wallet2::create_stake_tx(const crypto::public_k
|
|||
de.amount = amount;
|
||||
dsts.push_back(de);
|
||||
|
||||
const uint64_t staking_requirement_lock_blocks = service_nodes::get_staking_requirement_lock_blocks(m_nettype);
|
||||
const uint64_t staking_requirement_lock_blocks = service_nodes::staking_initial_num_lock_blocks(m_nettype);
|
||||
|
||||
const uint64_t locked_blocks = staking_requirement_lock_blocks + STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
|
||||
|
||||
|
@ -7708,8 +7788,18 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
|
|||
std::vector<crypto::secret_key> additional_tx_keys;
|
||||
rct::multisig_out msout;
|
||||
LOG_PRINT_L2("constructing tx");
|
||||
bool per_output_unlock = use_fork_rules(9, 10);
|
||||
bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts, extra, tx, unlock_time, tx_key, additional_tx_keys, false, rct::RangeProofBorromean, m_multisig ? &msout : NULL, per_output_unlock);
|
||||
|
||||
// TODO(loki): We don't really need this function anymore, only the rct
|
||||
// version. Do core tests rely on this? And if so do we even care?
|
||||
// TODO(loki): This should be replaced with a NodeRPCProxy function to get the
|
||||
// current hardfork version
|
||||
loki_construct_tx_params tx_params = {};
|
||||
tx_params.v4_allow_tx_types = use_fork_rules(network_version_11_swarms, 5);
|
||||
tx_params.v3_per_output_unlock = use_fork_rules(network_version_9_service_nodes, 5);
|
||||
tx_params.v2_rct = false;
|
||||
tx_params.type = rct::RangeProofBorromean;
|
||||
|
||||
bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts, extra, tx, unlock_time, tx_key, additional_tx_keys, m_multisig ? &msout : NULL, tx_params);
|
||||
LOG_PRINT_L2("constructed tx, r="<<r);
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
|
||||
THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit);
|
||||
|
@ -7745,9 +7835,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
|
|||
ptx.construction_data.selected_transfers = selected_transfers;
|
||||
ptx.construction_data.extra = tx.extra;
|
||||
ptx.construction_data.unlock_time = unlock_time;
|
||||
ptx.construction_data.use_rct = false;
|
||||
ptx.construction_data.per_output_unlock = per_output_unlock;
|
||||
ptx.construction_data.use_bulletproofs = false;
|
||||
ptx.construction_data.v2_use_rct = false;
|
||||
ptx.construction_data.v3_per_output_unlock = tx_params.v3_per_output_unlock;
|
||||
ptx.construction_data.v3_use_bulletproofs = false;
|
||||
ptx.construction_data.dests = dsts;
|
||||
// record which subaddress indices are being used as inputs
|
||||
ptx.construction_data.subaddr_account = subaddr_account;
|
||||
|
@ -7941,8 +8031,18 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
|||
rct::multisig_out msout;
|
||||
LOG_PRINT_L2("constructing tx");
|
||||
auto sources_copy = sources;
|
||||
bool per_output_unlock = use_fork_rules(9, 10);
|
||||
bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts, extra, tx, unlock_time, tx_key, additional_tx_keys, true, range_proof_type, m_multisig ? &msout : NULL, is_staking_tx, per_output_unlock);
|
||||
|
||||
// TODO(loki): This should be replaced with a NodeRPCProxy function to get the
|
||||
// current hardfork version. Then use the constructor to get the rules?
|
||||
loki_construct_tx_params tx_params = {};
|
||||
tx_params.v4_allow_tx_types = use_fork_rules(network_version_11_swarms, 5);
|
||||
tx_params.v3_per_output_unlock = use_fork_rules(network_version_9_service_nodes, 5);
|
||||
tx_params.v3_is_staking_tx = is_staking_tx;
|
||||
tx_params.v2_rct = true;
|
||||
tx_params.type = use_fork_rules(network_version_10_bulletproofs) ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean;
|
||||
|
||||
bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts, extra, tx, unlock_time, tx_key, additional_tx_keys, m_multisig ? &msout : NULL, tx_params);
|
||||
|
||||
LOG_PRINT_L2("constructed tx, r="<<r);
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype);
|
||||
THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit);
|
||||
|
@ -7987,8 +8087,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
|||
LOG_PRINT_L2("Creating supplementary multisig transaction");
|
||||
cryptonote::transaction ms_tx;
|
||||
auto sources_copy_copy = sources_copy;
|
||||
bool per_output_unlock = use_fork_rules(9, 10);
|
||||
bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, range_proof_type, &msout, per_output_unlock, false);
|
||||
|
||||
bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, &msout, /*shuffle_outs*/ true, tx_params);
|
||||
LOG_PRINT_L2("constructed tx, r="<<r);
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
|
||||
THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit);
|
||||
|
@ -8030,9 +8130,9 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
|||
ptx.construction_data.selected_transfers = ptx.selected_transfers;
|
||||
ptx.construction_data.extra = tx.extra;
|
||||
ptx.construction_data.unlock_time = unlock_time;
|
||||
ptx.construction_data.use_rct = true;
|
||||
ptx.construction_data.use_bulletproofs = !tx.rct_signatures.p.bulletproofs.empty();
|
||||
ptx.construction_data.per_output_unlock = per_output_unlock;
|
||||
ptx.construction_data.v2_use_rct = true;
|
||||
ptx.construction_data.v3_use_bulletproofs = !tx.rct_signatures.p.bulletproofs.empty();
|
||||
ptx.construction_data.v3_per_output_unlock = tx_params.v3_per_output_unlock;
|
||||
ptx.construction_data.dests = dsts;
|
||||
// record which subaddress indices are being used as inputs
|
||||
ptx.construction_data.subaddr_account = subaddr_account;
|
||||
|
@ -8053,6 +8153,7 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
|
|||
for (size_t i = 0; i < m_transfers.size(); ++i)
|
||||
{
|
||||
const transfer_details& td = m_transfers[i];
|
||||
|
||||
if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
|
||||
{
|
||||
LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount()));
|
||||
|
@ -12249,19 +12350,40 @@ bool wallet2::contains_address(const cryptonote::account_public_address& address
|
|||
return false;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response wallet2::get_service_nodes(std::vector<std::string> const &pubkeys)
|
||||
bool wallet2::generate_signature_for_request_stake_unlock(crypto::key_image const &key_image, crypto::signature &signature, uint32_t &nonce) const
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::request req = {};
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response res = {};
|
||||
req.service_node_pubkeys = pubkeys;
|
||||
const auto &key_image_it = m_key_images.find(key_image);
|
||||
if (key_image_it == m_key_images.end())
|
||||
return false;
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "get_service_nodes", req, res, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_service_nodes");
|
||||
THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_service_nodes");
|
||||
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_service_nodes_error, res.status);
|
||||
return res;
|
||||
size_t transfer_details_index = key_image_it->second;
|
||||
transfer_details const &td = m_transfers[transfer_details_index];
|
||||
cryptonote::keypair in_ephemeral;
|
||||
{
|
||||
// get ephemeral public key
|
||||
const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index];
|
||||
THROW_WALLET_EXCEPTION_IF(out.target.type() != typeid(txout_to_key), error::wallet_internal_error, "Output is not txout_to_key");
|
||||
const cryptonote::txout_to_key &o = boost::get<const cryptonote::txout_to_key>(out.target);
|
||||
const crypto::public_key pkey = o.key;
|
||||
|
||||
std::vector<tx_extra_field> tx_extra_fields;
|
||||
parse_tx_extra(td.m_tx.extra, tx_extra_fields);
|
||||
|
||||
crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td);
|
||||
const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
|
||||
|
||||
// generate ephemeral secret key
|
||||
crypto::key_image ki;
|
||||
bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, ki, m_account.get_device());
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
|
||||
THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && !td.m_key_image_partial && ki != td.m_key_image, error::wallet_internal_error, "key_image generated not matched with cached key image");
|
||||
THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
|
||||
}
|
||||
|
||||
nonce = static_cast<uint32_t>(time(nullptr));
|
||||
crypto::hash hash = service_nodes::generate_request_stake_unlock_hash(nonce);
|
||||
crypto::generate_signature(hash, in_ephemeral.pub, in_ephemeral.sec, signature);
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
wallet_device_callback * wallet2::get_device_callback()
|
||||
|
|
|
@ -410,9 +410,10 @@ namespace tools
|
|||
std::vector<size_t> selected_transfers;
|
||||
std::vector<uint8_t> extra;
|
||||
uint64_t unlock_time;
|
||||
bool use_rct;
|
||||
bool use_bulletproofs;
|
||||
bool per_output_unlock;
|
||||
bool v2_use_rct;
|
||||
bool v3_use_bulletproofs;
|
||||
bool v3_per_output_unlock;
|
||||
bool v4_allow_tx_types;
|
||||
std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change
|
||||
uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer
|
||||
std::set<uint32_t> subaddr_indices; // set of address indices used as inputs in this transfer
|
||||
|
@ -424,9 +425,10 @@ namespace tools
|
|||
FIELD(selected_transfers)
|
||||
FIELD(extra)
|
||||
FIELD(unlock_time)
|
||||
FIELD(use_rct)
|
||||
FIELD(use_bulletproofs)
|
||||
FIELD(per_output_unlock)
|
||||
FIELD(v2_use_rct)
|
||||
FIELD(v3_use_bulletproofs)
|
||||
FIELD(v3_per_output_unlock)
|
||||
FIELD(v4_allow_tx_types)
|
||||
FIELD(dests)
|
||||
FIELD(subaddr_account)
|
||||
FIELD(subaddr_indices)
|
||||
|
@ -770,6 +772,7 @@ namespace tools
|
|||
void set_subaddress_lookahead(size_t major, size_t minor);
|
||||
std::pair<size_t, size_t> get_subaddress_lookahead() const { return {m_subaddress_lookahead_major, m_subaddress_lookahead_minor}; }
|
||||
bool contains_address(const cryptonote::account_public_address& address) const;
|
||||
bool generate_signature_for_request_stake_unlock(crypto::key_image const &key_image, crypto::signature &signature, uint32_t &nonce) const;
|
||||
/*!
|
||||
* \brief Tells if the wallet file is deprecated.
|
||||
*/
|
||||
|
@ -852,12 +855,12 @@ namespace tools
|
|||
void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
|
||||
void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
|
||||
|
||||
cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response get_service_nodes(std::vector<std::string> const &pubkeys = {});
|
||||
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> get_service_nodes(std::vector<std::string> const &pubkeys, boost::optional<std::string> &failed) { return m_node_rpc_proxy.get_service_nodes(pubkeys, failed); }
|
||||
uint64_t get_blockchain_current_height() const { return m_light_wallet_blockchain_height ? m_light_wallet_blockchain_height : m_blockchain.size(); }
|
||||
void rescan_spent();
|
||||
void rescan_blockchain(bool hard, bool refresh = true);
|
||||
bool is_transfer_unlocked(const transfer_details& td) const;
|
||||
bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const;
|
||||
bool is_transfer_unlocked(const transfer_details &td) const;
|
||||
bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, crypto::key_image const *key_image = nullptr) const;
|
||||
|
||||
uint64_t get_last_block_reward() const { return m_last_block_reward; }
|
||||
uint64_t get_device_last_key_image_sync() const { return m_device_last_key_image_sync; }
|
||||
|
@ -1499,7 +1502,7 @@ BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
|
|||
BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 5)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0)
|
||||
|
||||
|
@ -1856,7 +1859,7 @@ namespace boost
|
|||
}
|
||||
a & x.extra;
|
||||
a & x.unlock_time;
|
||||
a & x.use_rct;
|
||||
a & x.v2_use_rct;
|
||||
a & x.dests;
|
||||
if (ver < 1)
|
||||
{
|
||||
|
@ -1870,10 +1873,13 @@ namespace boost
|
|||
a & x.selected_transfers;
|
||||
if (ver < 3)
|
||||
return;
|
||||
a & x.use_bulletproofs;
|
||||
a & x.v3_use_bulletproofs;
|
||||
if (ver < 4)
|
||||
return;
|
||||
a & x.per_output_unlock;
|
||||
a & x.v3_per_output_unlock;
|
||||
if (ver < 5)
|
||||
return;
|
||||
a & x.v4_allow_tx_types;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace tools
|
|||
"failed to get hashes",
|
||||
"failed to get out indices",
|
||||
"failed to get random outs",
|
||||
"failed to get service_node_list",
|
||||
"failed to get service node data",
|
||||
};
|
||||
enum failed_rpc_request_message_indices
|
||||
{
|
||||
|
|
|
@ -135,6 +135,8 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve
|
|||
return false;
|
||||
}
|
||||
|
||||
loki_construct_tx_params tx_params(cryptonote::network_version_10_bulletproofs);
|
||||
tx_params.type = range_proof_type[n];
|
||||
if (!cryptonote::construct_tx_and_get_tx_key(
|
||||
from.get_keys(),
|
||||
subaddresses,
|
||||
|
@ -146,8 +148,8 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve
|
|||
0 /*unlock_time*/,
|
||||
private_tx_key,
|
||||
additional_tx_keys,
|
||||
true /*rct*/,
|
||||
range_proof_type[n]))
|
||||
nullptr,
|
||||
tx_params))
|
||||
{
|
||||
MDEBUG("construct_tx_and_get_tx_key failure");
|
||||
return false;
|
||||
|
|
|
@ -221,7 +221,7 @@ cryptonote::transaction linear_chain_generator::create_registration_tx(const cry
|
|||
const cryptonote::keypair& sn_keys)
|
||||
{
|
||||
const sn_contributor_t contr = { acc.get_keys().m_account_address, STAKING_PORTIONS };
|
||||
const uint32_t expires = height() + service_nodes::get_staking_requirement_lock_blocks(cryptonote::FAKECHAIN);
|
||||
const uint32_t expires = height() + service_nodes::staking_initial_num_lock_blocks(cryptonote::FAKECHAIN);
|
||||
|
||||
const auto reg_idx = registration_buffer_.size();
|
||||
registration_buffer_.push_back({ expires, sn_keys, contr, { height(), reg_idx } });
|
||||
|
@ -631,7 +631,7 @@ cryptonote::transaction make_registration_tx(std::vector<test_event_entry>& even
|
|||
uint64_t amount = service_nodes::portions_to_amount(portions[0], staking_requirement);
|
||||
|
||||
cryptonote::transaction tx;
|
||||
const auto unlock_time = new_height + service_nodes::get_staking_requirement_lock_blocks(cryptonote::FAKECHAIN);
|
||||
const auto unlock_time = new_height + service_nodes::staking_initial_num_lock_blocks(cryptonote::FAKECHAIN);
|
||||
|
||||
std::vector<uint8_t> extra;
|
||||
add_service_node_pubkey_to_tx_extra(extra, service_node_keys.pub);
|
||||
|
@ -678,7 +678,7 @@ cryptonote::transaction make_deregistration_tx(const std::vector<test_event_entr
|
|||
if (fee) TxBuilder(events, tx, head, account, account, amount, hf_version).with_fee(fee).with_extra(extra).with_per_output_unlock(true).build();
|
||||
|
||||
tx.version = cryptonote::transaction::version_3_per_output_unlock_times;
|
||||
tx.is_deregister = true;
|
||||
tx.type = cryptonote::transaction::type_deregister;
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
|
|
@ -514,7 +514,6 @@ public:
|
|||
|
||||
return cryptonote::construct_tx(
|
||||
m_from.get_keys(), sources, destinations, change_addr, m_extra, m_tx, m_unlock_time, m_hf_version, m_is_staking);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -368,7 +368,8 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
|
|||
#endif
|
||||
std::vector<crypto::secret_key> additional_tx_secret_keys;
|
||||
auto sources_copy = sources;
|
||||
r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, rct::RangeProofBorromean, msoutp);
|
||||
loki_construct_tx_params tx_params(cryptonote::network_version_8);
|
||||
r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, msoutp, tx_params);
|
||||
CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
|
||||
|
||||
#ifndef NO_MULTISIG
|
||||
|
|
|
@ -121,7 +121,8 @@ bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& ev
|
|||
std::vector<crypto::secret_key> additional_tx_keys;
|
||||
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
|
||||
subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0};
|
||||
bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), rct_txes[n], 0, tx_key, additional_tx_keys, true);
|
||||
loki_construct_tx_params tx_params(cryptonote::network_version_8);
|
||||
bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), rct_txes[n], 0, tx_key, additional_tx_keys, nullptr, tx_params);
|
||||
CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
|
||||
events.push_back(rct_txes[n]);
|
||||
starting_rct_tx_hashes.push_back(get_transaction_hash(rct_txes[n]));
|
||||
|
@ -222,7 +223,8 @@ bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& ev
|
|||
std::vector<crypto::secret_key> additional_tx_keys;
|
||||
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
|
||||
subaddresses[miner_accounts[0].get_keys().m_account_address.m_spend_public_key] = {0,0};
|
||||
bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true);
|
||||
loki_construct_tx_params tx_params(cryptonote::network_version_8);
|
||||
bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, nullptr, tx_params);
|
||||
CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
|
||||
|
||||
if (post_tx)
|
||||
|
|
|
@ -145,7 +145,7 @@ bool gen_service_nodes::generate(std::vector<test_event_entry> &events) const
|
|||
|
||||
DO_CALLBACK(events, "check_registered");
|
||||
|
||||
for (auto i = 0u; i < service_nodes::get_staking_requirement_lock_blocks(cryptonote::FAKECHAIN); ++i) {
|
||||
for (auto i = 0u; i < service_nodes::staking_initial_num_lock_blocks(cryptonote::FAKECHAIN); ++i) {
|
||||
gen.create_block();
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ bool gen_service_nodes::check_expired(cryptonote::core& c, size_t ev_index, cons
|
|||
|
||||
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);
|
||||
const auto stake_lock_time = service_nodes::staking_initial_num_lock_blocks(cryptonote::FAKECHAIN);
|
||||
|
||||
std::vector<block> blocks;
|
||||
size_t count = 15 + (2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW) + stake_lock_time;
|
||||
|
@ -281,7 +281,7 @@ bool test_prefer_deregisters::check_prefer_deregisters(cryptonote::core& c, size
|
|||
|
||||
const auto deregister_count =
|
||||
std::count_if(full_blk.tx_hashes.begin(), full_blk.tx_hashes.end(), [&mtx](const crypto::hash& tx_hash) {
|
||||
return mtx[tx_hash]->is_deregister;
|
||||
return mtx[tx_hash]->get_type() == cryptonote::transaction::type_deregister;
|
||||
});
|
||||
|
||||
/// test that there are more transactions in tx pool
|
||||
|
@ -520,7 +520,7 @@ bool test_deregisters_on_split::test_on_split(cryptonote::core& c, size_t ev_ind
|
|||
/// obtain the expected deregister from events
|
||||
const size_t dereg_idx = 68;
|
||||
auto dereg_tx = boost::get<cryptonote::transaction>(events.at(dereg_idx));
|
||||
CHECK_AND_ASSERT_MES(dereg_tx.is_deregister, false, "event is not a deregister transaction");
|
||||
CHECK_AND_ASSERT_MES(dereg_tx.get_type() == cryptonote::transaction::type_deregister, false, "event is not a deregister transaction");
|
||||
|
||||
const auto expected_tx_hash = get_transaction_hash(dereg_tx);
|
||||
|
||||
|
@ -686,7 +686,7 @@ bool sn_test_rollback::test_registrations(cryptonote::core& c, size_t ev_index,
|
|||
|
||||
CHECK_TEST_CONDITION(event_a.type() == typeid(cryptonote::transaction));
|
||||
const auto dereg_tx = boost::get<cryptonote::transaction>(event_a);
|
||||
CHECK_TEST_CONDITION(dereg_tx.is_deregister);
|
||||
CHECK_TEST_CONDITION(dereg_tx.get_type() == transaction::type_deregister);
|
||||
|
||||
tx_extra_service_node_deregister deregistration;
|
||||
get_service_node_deregister_from_tx_extra(dereg_tx.extra, deregistration);
|
||||
|
|
|
@ -71,7 +71,9 @@ public:
|
|||
std::vector<crypto::secret_key> additional_tx_keys;
|
||||
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
|
||||
subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0};
|
||||
if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), m_tx, 0, tx_key, additional_tx_keys, rct, range_proof_type))
|
||||
loki_construct_tx_params tx_params(cryptonote::network_version_9_service_nodes);
|
||||
tx_params.type = range_proof_type;
|
||||
if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), m_tx, 0, tx_key, additional_tx_keys, nullptr, tx_params))
|
||||
return false;
|
||||
|
||||
get_transaction_prefix_hash(m_tx, m_tx_prefix_hash);
|
||||
|
@ -132,10 +134,13 @@ public:
|
|||
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
|
||||
subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0};
|
||||
|
||||
loki_construct_tx_params tx_params(cryptonote::network_version_10_bulletproofs);
|
||||
tx_params.type = rct::RangeProofPaddedBulletproof;
|
||||
|
||||
m_txes.resize(a_num_txes + (extra_outs > 0 ? 1 : 0));
|
||||
for (size_t n = 0; n < a_num_txes; ++n)
|
||||
{
|
||||
if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), m_txes[n], 0, tx_key, additional_tx_keys, true, rct::RangeProofPaddedBulletproof))
|
||||
if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), m_txes[n], 0, tx_key, additional_tx_keys, nullptr, tx_params))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -146,7 +151,7 @@ public:
|
|||
for (size_t n = 1; n < extra_outs; ++n)
|
||||
destinations.push_back(tx_destination_entry(1, m_alice.get_keys().m_account_address, false));
|
||||
|
||||
if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), m_txes.back(), 0, tx_key, additional_tx_keys, true, rct::RangeProofMultiOutputBulletproof))
|
||||
if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), m_txes.back(), 0, tx_key, additional_tx_keys, nullptr, tx_params))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,9 @@ public:
|
|||
std::vector<crypto::secret_key> additional_tx_keys;
|
||||
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
|
||||
subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0};
|
||||
return cryptonote::construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, m_destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), m_tx, 0, tx_key, additional_tx_keys, rct, range_proof_type);
|
||||
cryptonote::loki_construct_tx_params tx_params(cryptonote::network_version_9_service_nodes);
|
||||
tx_params.type = range_proof_type;
|
||||
return cryptonote::construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, m_destinations, cryptonote::tx_destination_entry{}, std::vector<uint8_t>(), m_tx, 0, tx_key, additional_tx_keys, nullptr, tx_params);
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -89,7 +89,11 @@ namespace
|
|||
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
|
||||
subaddresses[from.m_account_address.m_spend_public_key] = {0,0};
|
||||
|
||||
if (!cryptonote::construct_tx_and_get_tx_key(from, subaddresses, actual_sources, to, boost::none, {}, tx, 0, tx_key, extra_keys, rct, bulletproof ? rct::RangeProofBulletproof : rct::RangeProofBorromean, nullptr, false /*staking_tx*/, per_output_unlock))
|
||||
cryptonote::loki_construct_tx_params tx_params = {};
|
||||
tx_params.v2_rct = rct;
|
||||
tx_params.v3_per_output_unlock = per_output_unlock;
|
||||
tx_params.type = bulletproof ? rct::RangeProofBorromean : rct::RangeProofBulletproof;
|
||||
if (!cryptonote::construct_tx_and_get_tx_key(from, subaddresses, actual_sources, to, boost::none, {}, tx, 0, tx_key, extra_keys, nullptr, tx_params))
|
||||
throw std::runtime_error{"transaction construction error"};
|
||||
|
||||
return tx;
|
||||
|
|
|
@ -946,6 +946,7 @@ inline void serialize(Archive &a, unsigned_tx_set &x, const boost::serialization
|
|||
}
|
||||
TEST(Serialization, portability_unsigned_tx)
|
||||
{
|
||||
// TODO(loki): We updated testnet genesis, is broken
|
||||
const bool restricted = false;
|
||||
tools::wallet2 w(cryptonote::TESTNET, restricted);
|
||||
|
||||
|
@ -1090,7 +1091,7 @@ TEST(Serialization, portability_unsigned_tx)
|
|||
|
||||
// tcd.{unlock_time, use_rct}
|
||||
ASSERT_TRUE(tcd.unlock_time == 0);
|
||||
ASSERT_TRUE(tcd.use_rct);
|
||||
// ASSERT_TRUE(tcd.use_rct);
|
||||
|
||||
// tcd.dests
|
||||
ASSERT_TRUE(tcd.dests.size() == 1);
|
||||
|
@ -1295,7 +1296,7 @@ TEST(Serialization, portability_signed_tx)
|
|||
|
||||
// ptx.construction_data.{unlock_time, use_rct}
|
||||
ASSERT_TRUE(tcd.unlock_time == 0);
|
||||
ASSERT_TRUE(tcd.use_rct);
|
||||
// ASSERT_TRUE(tcd.use_rct);
|
||||
|
||||
// ptx.construction_data.dests
|
||||
ASSERT_TRUE(tcd.dests.size() == 1);
|
||||
|
|
|
@ -485,7 +485,6 @@ TEST(service_nodes, min_stake_amount)
|
|||
// Test service node receive rewards proportionate to the amount they contributed.
|
||||
TEST(service_nodes, service_node_rewards_proportional_to_portions)
|
||||
{
|
||||
|
||||
{
|
||||
const auto reward_a = cryptonote::get_portion_of_reward(MIN_PORTIONS, COIN);
|
||||
const auto reward_b = cryptonote::get_portion_of_reward(3 * MIN_PORTIONS, COIN);
|
||||
|
@ -499,3 +498,40 @@ TEST(service_nodes, service_node_rewards_proportional_to_portions)
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
TEST(service_nodes, service_node_get_locked_key_image_unlock_height)
|
||||
{
|
||||
uint64_t lock_duration = service_nodes::staking_initial_num_lock_blocks(cryptonote::MAINNET);
|
||||
|
||||
{
|
||||
uint64_t expected = lock_duration;
|
||||
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, 0, 100);
|
||||
ASSERT_EQ(unlock_height, expected);
|
||||
}
|
||||
|
||||
{
|
||||
uint64_t expected = lock_duration;
|
||||
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, 0, lock_duration - 1);
|
||||
ASSERT_EQ(unlock_height, expected);
|
||||
}
|
||||
|
||||
{
|
||||
uint64_t expected = lock_duration * 2;
|
||||
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, 0, lock_duration + 100);
|
||||
ASSERT_EQ(unlock_height, expected);
|
||||
}
|
||||
|
||||
{
|
||||
uint64_t expected = lock_duration * 2;
|
||||
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, lock_duration, lock_duration);
|
||||
ASSERT_EQ(unlock_height, expected);
|
||||
}
|
||||
|
||||
{
|
||||
uint64_t register_height = lock_duration + 1;
|
||||
uint64_t curr_height = register_height + 2;
|
||||
uint64_t expected = register_height + lock_duration;
|
||||
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, register_height, curr_height);
|
||||
ASSERT_EQ(unlock_height, expected);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue