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:
Doyle 2019-01-25 14:15:52 +11:00 committed by GitHub
parent 101f1799eb
commit 3a7b6b59eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 2186 additions and 841 deletions

View File

@ -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 {

View File

@ -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;

View File

@ -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;
}
//---------------------------------------------------------------

View File

@ -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);

View File

@ -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);

View File

@ -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;
};

View File

@ -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

View File

@ -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");

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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(

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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

View File

@ -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);
}

View File

@ -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"

View File

@ -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);

View File

@ -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()
};
};
}

View File

@ -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";

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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()

View File

@ -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>

View File

@ -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
{

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}
};

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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;
}

View File

@ -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:

View File

@ -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;

View File

@ -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);

View File

@ -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);
}
}