Merge pull request #932 from Doy-lee/ClassifyStakes

Classify stakes in show/export_transfers
This commit is contained in:
Doyle 2020-04-24 10:18:47 +10:00 committed by GitHub
commit 9510e2b5ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 333 additions and 243 deletions

View file

@ -412,7 +412,7 @@ uint8_t HardFork::get(uint64_t height) const
CRITICAL_REGION_LOCAL(lock);
if (height > db.height()) {
assert(false);
return INVALID_HF_VERSION_FOR_HEIGHT;
return INVALID_HF_VERSION;
}
if (height == db.height()) {
return get_current_version();

View file

@ -46,8 +46,8 @@ namespace cryptonote
time_t time;
};
constexpr static uint8_t INVALID_HF_VERSION_FOR_HEIGHT = 255;
constexpr static uint64_t INVALID_HF_VERSION_HEIGHT = static_cast<uint64_t>(-1);
constexpr static uint8_t INVALID_HF_VERSION = 255;
constexpr static uint64_t INVALID_HF_VERSION_HEIGHT = static_cast<uint64_t>(-1);
typedef enum {
LikelyForked,
UpdateNeeded,

View file

@ -41,6 +41,7 @@ extern "C" {
#include "wallet/wallet2.h"
#include "cryptonote_tx_utils.h"
#include "cryptonote_basic/tx_extra.h"
#include "cryptonote_basic/hardfork.h"
#include "int-util.h"
#include "common/scoped_message_writer.h"
#include "common/i18n.h"
@ -326,15 +327,7 @@ namespace service_nodes
return result;
}
struct parsed_tx_contribution
{
cryptonote::account_public_address address;
uint64_t transferred;
crypto::secret_key tx_key;
std::vector<service_node_info::contribution_t> locked_contributions;
};
static uint64_t get_tx_output_amount(const cryptonote::transaction& tx, int i, crypto::key_derivation const &derivation, hw::device& hwdev)
static uint64_t get_staking_output_contribution(const cryptonote::transaction& tx, int i, crypto::key_derivation const &derivation, hw::device& hwdev)
{
if (tx.vout[i].target.type() != typeid(cryptonote::txout_to_key))
{
@ -372,6 +365,177 @@ namespace service_nodes
return money_transferred;
}
bool tx_get_staking_components(cryptonote::transaction_prefix const &tx, staking_components *contribution, crypto::hash const &txid)
{
staking_components contribution_unused_ = {};
if (!contribution) contribution = &contribution_unused_;
if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, contribution->service_node_pubkey))
return false; // Is not a contribution TX don't need to check it.
if (!cryptonote::get_service_node_contributor_from_tx_extra(tx.extra, contribution->address))
return false;
if (!cryptonote::get_tx_secret_key_from_tx_extra(tx.extra, contribution->tx_key))
{
LOG_PRINT_L1("TX: There was a service node contributor but no secret key in the tx extra for tx: " << txid);
return false;
}
return true;
}
bool tx_get_staking_components(cryptonote::transaction const &tx, staking_components *contribution)
{
bool result = tx_get_staking_components(tx, contribution, cryptonote::get_transaction_hash(tx));
return result;
}
bool tx_get_staking_components_and_amounts(cryptonote::network_type nettype,
uint8_t hf_version,
cryptonote::transaction const &tx,
uint64_t block_height,
staking_components *contribution)
{
staking_components contribution_unused_ = {};
if (!contribution) contribution = &contribution_unused_;
if (!tx_get_staking_components(tx, contribution))
return false;
// A cryptonote transaction is constructed as follows
// P = Hs(aR)G + B
// P := Stealth Address
// a := Receiver's secret view key
// B := Receiver's public spend key
// R := TX Public Key
// G := Elliptic Curve
// In Loki we pack into the tx extra information to reveal information about the TX
// A := Public View Key (we pack contributor into tx extra, 'parsed_contribution.address')
// r := TX Secret Key (we pack secret key into tx extra, 'parsed_contribution.tx_key`)
// Calulate 'Derivation := Hs(Ar)G'
crypto::key_derivation derivation;
if (!crypto::generate_key_derivation(contribution->address.m_view_public_key, contribution->tx_key, derivation))
{
LOG_PRINT_L1("TX: Failed to generate key derivation on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
return false;
}
hw::device &hwdev = hw::get_device("default");
contribution->transferred = 0;
bool stake_decoded = true;
if (hf_version >= cryptonote::network_version_11_infinite_staking || hf_version == cryptonote::HardFork::INVALID_HF_VERSION)
{
// In Infinite Staking, we lock the key image that would be generated if
// you tried to send your stake and prevent it from being transacted on
// the network whilst you are a Service Node. To do this, we calculate
// the future key image that would be generated when they user tries to
// spend the staked funds. A key image is derived from the ephemeral, one
// time transaction private key, 'x' in the Cryptonote Whitepaper.
// This is only possible to generate if they are the staking to themselves
// as you need the recipients private keys to generate the key image that
// would be generated, when they want to spend it in the future.
cryptonote::tx_extra_tx_key_image_proofs key_image_proofs;
if (!get_tx_key_image_proofs_from_tx_extra(tx.extra, key_image_proofs))
{
LOG_PRINT_L1("TX: Didn't have key image proofs in the tx_extra, rejected on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
stake_decoded = false;
}
for (size_t output_index = 0; stake_decoded && output_index < tx.vout.size(); ++output_index)
{
uint64_t transferred = get_staking_output_contribution(tx, output_index, derivation, hwdev);
if (transferred == 0)
continue;
// So prove that the destination stealth address can be decoded using the
// staker's packed address, which means that the recipient of the
// contribution is themselves (and hence they have the necessary secrets
// to generate the future key image).
// i.e Verify the packed information is valid by computing the stealth
// address P' (which should equal P if matching) using
// 'Derivation := Hs(Ar)G' (we calculated earlier) instead of 'Hs(aR)G'
// P' = Hs(Ar)G + B
// = Hs(aR)G + B
// = Derivation + B
// = P
crypto::public_key ephemeral_pub_key;
{
// P' := Derivation + B
if (!hwdev.derive_public_key(derivation, output_index, contribution->address.m_spend_public_key, ephemeral_pub_key))
{
LOG_PRINT_L1("TX: Could not derive TX ephemeral key on height: " << block_height << " for tx: " << get_transaction_hash(tx) << " for output: " << output_index);
continue;
}
// Stealth address public key should match the public key referenced in the TX only if valid information is given.
const auto& out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[output_index].target);
if (out_to_key.key != ephemeral_pub_key)
{
LOG_PRINT_L1("TX: Derived TX ephemeral key did not match tx stored key on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx) << " for output: " << output_index);
continue;
}
}
// To prevent the staker locking any arbitrary key image, the provided
// key image is included and verified in a ring signature which
// guarantees that 'the staker proves that he knows such 'x' (one time
// ephemeral secret key) and that (the future key image) P = xG'.
// Consequently the key image is not falsified and actually the future
// key image.
// The signer can try falsify the key image, but the equation used to
// construct the key image is re-derived by the verifier, false key
// images will not match the re-derived key image.
crypto::public_key const *ephemeral_pub_key_ptr = &ephemeral_pub_key;
for (auto proof = key_image_proofs.proofs.begin(); proof != key_image_proofs.proofs.end(); proof++)
{
if (!crypto::check_ring_signature((const crypto::hash &)(proof->key_image), proof->key_image, &ephemeral_pub_key_ptr, 1, &proof->signature))
continue;
contribution->locked_contributions.emplace_back(service_node_info::contribution_t::version_t::v0, ephemeral_pub_key, proof->key_image, transferred);
contribution->transferred += transferred;
key_image_proofs.proofs.erase(proof);
break;
}
}
}
if (hf_version < cryptonote::network_version_11_infinite_staking || (hf_version == cryptonote::HardFork::INVALID_HF_VERSION && !stake_decoded))
{
// Pre Infinite Staking, we only need to prove the amount sent is
// sufficient to become a contributor to the Service Node and that there
// is sufficient lock time on the staking output.
for (size_t i = 0; i < tx.vout.size(); i++)
{
bool has_correct_unlock_time = false;
{
uint64_t unlock_time = tx.unlock_time;
if (tx.version >= cryptonote::txversion::v3_per_output_unlock_times)
unlock_time = tx.output_unlock_times[i];
uint64_t min_height = block_height + staking_num_lock_blocks(nettype);
has_correct_unlock_time = unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && unlock_time >= min_height;
}
if (has_correct_unlock_time)
{
contribution->transferred += get_staking_output_contribution(tx, i, derivation, hwdev);
stake_decoded = true;
}
}
}
return stake_decoded;
}
/// Makes a copy of the given service_node_info and replaces the shared_ptr with a pointer to the copy.
/// Returns the non-const service_node_info (which is now held by the passed-in shared_ptr lvalue ref).
static service_node_info &duplicate_info(std::shared_ptr<const service_node_info> &info_ptr) {
@ -639,153 +803,6 @@ namespace service_nodes
return false;
}
static bool get_contribution(cryptonote::network_type nettype, int hf_version, const cryptonote::transaction& tx, uint64_t block_height, parsed_tx_contribution &parsed_contribution)
{
if (!cryptonote::get_service_node_contributor_from_tx_extra(tx.extra, parsed_contribution.address))
return false;
if (!cryptonote::get_tx_secret_key_from_tx_extra(tx.extra, parsed_contribution.tx_key))
{
LOG_PRINT_L1("TX: There was a service node contributor but no secret key in the tx extra on height: " << block_height << " for tx: " << get_transaction_hash(tx));
return false;
}
// A cryptonote transaction is constructed as follows
// P = Hs(aR)G + B
// P := Stealth Address
// a := Receiver's secret view key
// B := Receiver's public spend key
// R := TX Public Key
// G := Elliptic Curve
// In Loki we pack into the tx extra information to reveal information about the TX
// A := Public View Key (we pack contributor into tx extra, 'parsed_contribution.address')
// r := TX Secret Key (we pack secret key into tx extra, 'parsed_contribution.tx_key`)
// Calulate 'Derivation := Hs(Ar)G'
crypto::key_derivation derivation;
if (!crypto::generate_key_derivation(parsed_contribution.address.m_view_public_key, parsed_contribution.tx_key, derivation))
{
LOG_PRINT_L1("TX: Failed to generate key derivation on height: " << block_height << " for tx: " << get_transaction_hash(tx));
return false;
}
hw::device& hwdev = hw::get_device("default");
parsed_contribution.transferred = 0;
if (hf_version >= cryptonote::network_version_11_infinite_staking)
{
// In Infinite Staking, we lock the key image that would be generated if
// you tried to send your stake and prevent it from being transacted on
// the network whilst you are a Service Node. To do this, we calculate
// the future key image that would be generated when they user tries to
// spend the staked funds. A key image is derived from the ephemeral, one
// time transaction private key, 'x' in the Cryptonote Whitepaper.
// This is only possible to generate if they are the staking to themselves
// as you need the recipients private keys to generate the key image that
// would be generated, when they want to spend it in the future.
cryptonote::tx_extra_tx_key_image_proofs key_image_proofs;
if (!get_tx_key_image_proofs_from_tx_extra(tx.extra, key_image_proofs))
{
LOG_PRINT_L1("TX: Didn't have key image proofs in the tx_extra, rejected on height: " << block_height << " for tx: " << get_transaction_hash(tx));
return false;
}
for (size_t output_index = 0; output_index < tx.vout.size(); ++output_index)
{
uint64_t transferred = get_tx_output_amount(tx, output_index, derivation, hwdev);
if (transferred == 0)
continue;
// So prove that the destination stealth address can be decoded using the
// staker's packed address, which means that the recipient of the
// contribution is themselves (and hence they have the necessary secrets
// to generate the future key image).
// i.e Verify the packed information is valid by computing the stealth
// address P' (which should equal P if matching) using
// 'Derivation := Hs(Ar)G' (we calculated earlier) instead of 'Hs(aR)G'
// P' = Hs(Ar)G + B
// = Hs(aR)G + B
// = Derivation + B
// = P
crypto::public_key ephemeral_pub_key;
{
// P' := Derivation + B
if (!hwdev.derive_public_key(derivation, output_index, parsed_contribution.address.m_spend_public_key, ephemeral_pub_key))
{
LOG_PRINT_L1("TX: Could not derive TX ephemeral key on height: " << block_height << " for tx: " << get_transaction_hash(tx) << " for output: " << output_index);
continue;
}
// Stealth address public key should match the public key referenced in the TX only if valid information is given.
const auto& out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[output_index].target);
if (out_to_key.key != ephemeral_pub_key)
{
LOG_PRINT_L1("TX: Derived TX ephemeral key did not match tx stored key on height: " << block_height << " for tx: " << get_transaction_hash(tx) << " for output: " << output_index);
continue;
}
}
// To prevent the staker locking any arbitrary key image, the provided
// key image is included and verified in a ring signature which
// guarantees that 'the staker proves that he knows such 'x' (one time
// ephemeral secret key) and that (the future key image) P = xG'.
// Consequently the key image is not falsified and actually the future
// key image.
// The signer can try falsify the key image, but the equation used to
// construct the key image is re-derived by the verifier, false key
// images will not match the re-derived key image.
crypto::public_key const *ephemeral_pub_key_ptr = &ephemeral_pub_key;
for (auto proof = key_image_proofs.proofs.begin(); proof != key_image_proofs.proofs.end(); proof++)
{
if (!crypto::check_ring_signature((const crypto::hash &)(proof->key_image), proof->key_image, &ephemeral_pub_key_ptr, 1, &proof->signature))
continue;
parsed_contribution.locked_contributions.emplace_back(
service_node_info::contribution_t::version_t::v0,
ephemeral_pub_key,
proof->key_image,
transferred
);
parsed_contribution.transferred += transferred;
key_image_proofs.proofs.erase(proof);
break;
}
}
}
else
{
// Pre Infinite Staking, we only need to prove the amount sent is
// sufficient to become a contributor to the Service Node and that there
// is sufficient lock time on the staking output.
for (size_t i = 0; i < tx.vout.size(); i++)
{
bool has_correct_unlock_time = false;
{
uint64_t unlock_time = tx.unlock_time;
if (tx.version >= cryptonote::txversion::v3_per_output_unlock_times)
unlock_time = tx.output_unlock_times[i];
uint64_t min_height = block_height + staking_num_lock_blocks(nettype);
has_correct_unlock_time = unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && unlock_time >= min_height;
}
if (has_correct_unlock_time)
parsed_contribution.transferred += get_tx_output_amount(tx, i, derivation, hwdev);
}
}
return true;
}
bool is_registration_tx(cryptonote::network_type nettype, uint8_t hf_version, const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, service_node_info& info)
{
crypto::public_key service_node_key;
@ -847,22 +864,22 @@ namespace service_nodes
uint64_t staking_requirement = get_staking_requirement(nettype, block_height, hf_version);
cryptonote::account_public_address address;
parsed_tx_contribution parsed_contribution = {};
if (!get_contribution(nettype, hf_version, tx, block_height, parsed_contribution))
staking_components stake = {};
if (!tx_get_staking_components_and_amounts(nettype, hf_version, tx, block_height, &stake))
{
LOG_PRINT_L1("Register TX: Had service node registration fields, but could not decode contribution on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
return false;
}
const uint64_t min_transfer = get_min_node_contribution(hf_version, staking_requirement, info.total_reserved, info.total_num_locked_contributions());
if (parsed_contribution.transferred < min_transfer)
if (stake.transferred < min_transfer)
{
LOG_PRINT_L1("Register TX: Contribution transferred: " << parsed_contribution.transferred << " didn't meet the minimum transfer requirement: " << min_transfer << " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
LOG_PRINT_L1("Register TX: Contribution transferred: " << stake.transferred << " didn't meet the minimum transfer requirement: " << min_transfer << " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
return false;
}
size_t total_num_of_addr = service_node_addresses.size();
if (std::find(service_node_addresses.begin(), service_node_addresses.end(), parsed_contribution.address) == service_node_addresses.end())
if (std::find(service_node_addresses.begin(), service_node_addresses.end(), stake.address) == service_node_addresses.end())
total_num_of_addr++;
if (total_num_of_addr > MAX_NUMBER_OF_CONTRIBUTORS)
@ -996,38 +1013,34 @@ namespace service_nodes
uint64_t const block_height = cryptonote::get_block_height(block);
uint8_t const hf_version = block.major_version;
crypto::public_key pubkey;
if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, pubkey))
return false; // Is not a contribution TX don't need to check it.
parsed_tx_contribution parsed_contribution = {};
if (!get_contribution(nettype, hf_version, tx, block_height, parsed_contribution))
staking_components stake = {};
if (!tx_get_staking_components_and_amounts(nettype, hf_version, tx, block_height, &stake))
{
LOG_PRINT_L1("TX: Could not decode contribution for service node: " << pubkey << " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
LOG_PRINT_L1("TX: Could not decode contribution for service node: " << stake.service_node_pubkey << " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
return false;
}
auto iter = service_nodes_infos.find(pubkey);
auto iter = service_nodes_infos.find(stake.service_node_pubkey);
if (iter == service_nodes_infos.end())
{
LOG_PRINT_L1("TX: Contribution received for service node: " << pubkey <<
", but could not be found in the service node list on height: " << block_height <<
" for tx: " << cryptonote::get_transaction_hash(tx )<< "\n"
"This could mean that the service node was deregistered before the contribution was processed.");
LOG_PRINT_L1("TX: Contribution received for service node: "
<< stake.service_node_pubkey << ", but could not be found in the service node list on height: "
<< block_height << " for tx: " << cryptonote::get_transaction_hash(tx)
<< "\n"
"This could mean that the service node was deregistered before the contribution was processed.");
return false;
}
const service_node_info& curinfo = *iter->second;
if (curinfo.is_fully_funded())
{
LOG_PRINT_L1("TX: Service node: " << pubkey <<
" is already fully funded, but contribution received on height: " << block_height <<
" for tx: " << cryptonote::get_transaction_hash(tx));
LOG_PRINT_L1("TX: Service node: " << stake.service_node_pubkey
<< " is already fully funded, but contribution received on height: "
<< block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
return false;
}
if (!cryptonote::get_tx_secret_key_from_tx_extra(tx.extra, parsed_contribution.tx_key))
if (!cryptonote::get_tx_secret_key_from_tx_extra(tx.extra, stake.tx_key))
{
LOG_PRINT_L1("TX: Failed to get tx secret key from contribution received on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
return false;
@ -1037,7 +1050,7 @@ namespace service_nodes
bool new_contributor = true;
size_t contributor_position = 0;
for (size_t i = 0; i < contributors.size(); i++)
if (contributors[i].address == parsed_contribution.address){
if (contributors[i].address == stake.address){
contributor_position = i;
new_contributor = false;
break;
@ -1048,17 +1061,17 @@ namespace service_nodes
bool too_many_contributions = false;
if (hf_version >= cryptonote::network_version_11_infinite_staking)
// As of HF11 we allow up to 4 stakes total.
too_many_contributions = curinfo.total_num_locked_contributions() + parsed_contribution.locked_contributions.size() > MAX_NUMBER_OF_CONTRIBUTORS;
too_many_contributions = curinfo.total_num_locked_contributions() + stake.locked_contributions.size() > MAX_NUMBER_OF_CONTRIBUTORS;
else
// Before HF11 we allowed up to 4 contributors, but each can contribute multiple times
too_many_contributions = new_contributor && contributors.size() >= MAX_NUMBER_OF_CONTRIBUTORS;
if (too_many_contributions)
{
LOG_PRINT_L1("TX: Already hit the max number of contributions: " << MAX_NUMBER_OF_CONTRIBUTORS <<
" for contributor: " << cryptonote::get_account_address_as_str(nettype, false, parsed_contribution.address) <<
" on height: " << block_height <<
" for tx: " << cryptonote::get_transaction_hash(tx));
LOG_PRINT_L1("TX: Already hit the max number of contributions: "
<< MAX_NUMBER_OF_CONTRIBUTORS
<< " for contributor: " << cryptonote::get_account_address_as_str(nettype, false, stake.address)
<< " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
return false;
}
}
@ -1070,13 +1083,11 @@ namespace service_nodes
? 1 // Follow-up contributions from an existing contributor could be any size before HF11
: get_min_node_contribution(hf_version, curinfo.staking_requirement, curinfo.total_reserved, curinfo.total_num_locked_contributions());
if (parsed_contribution.transferred < min_contribution)
if (stake.transferred < min_contribution)
{
LOG_PRINT_L1("TX: Amount " << parsed_contribution.transferred <<
" did not meet min " << min_contribution <<
" for service node: " << pubkey <<
" on height: " << block_height <<
" for tx: " << cryptonote::get_transaction_hash(tx));
LOG_PRINT_L1("TX: Amount " << stake.transferred << " did not meet min " << min_contribution
<< " for service node: " << stake.service_node_pubkey << " on height: "
<< block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
return false;
}
}
@ -1090,7 +1101,7 @@ namespace service_nodes
{
contributor_position = info.contributors.size();
info.contributors.emplace_back();
info.contributors.back().address = parsed_contribution.address;
info.contributors.back().address = stake.address;
}
service_node_info::contributor_t& contributor = info.contributors[contributor_position];
@ -1098,10 +1109,10 @@ namespace service_nodes
// increase total_reserved so much that it is >= staking_requirement
uint64_t can_increase_reserved_by = info.staking_requirement - info.total_reserved;
uint64_t max_amount = contributor.reserved + can_increase_reserved_by;
parsed_contribution.transferred = std::min(max_amount - contributor.amount, parsed_contribution.transferred);
stake.transferred = std::min(max_amount - contributor.amount, stake.transferred);
contributor.amount += parsed_contribution.transferred;
info.total_contributed += parsed_contribution.transferred;
contributor.amount += stake.transferred;
info.total_contributed += stake.transferred;
if (contributor.amount > contributor.reserved)
{
@ -1113,10 +1124,10 @@ namespace service_nodes
info.last_reward_transaction_index = index;
if (hf_version >= cryptonote::network_version_11_infinite_staking)
for (const auto &contribution : parsed_contribution.locked_contributions)
for (const auto &contribution : stake.locked_contributions)
contributor.locked_contributions.push_back(contribution);
LOG_PRINT_L1("Contribution of " << parsed_contribution.transferred << " received for service node " << pubkey);
LOG_PRINT_L1("Contribution of " << stake.transferred << " received for service node " << stake.service_node_pubkey);
if (info.is_fully_funded()) {
info.active_since_height = block_height;
return true;

View file

@ -560,6 +560,18 @@ namespace service_nodes
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);
uint64_t offset_testing_quorum_height(quorum_type type, uint64_t height);
struct staking_components
{
crypto::public_key service_node_pubkey;
cryptonote::account_public_address address;
uint64_t transferred;
crypto::secret_key tx_key;
std::vector<service_node_info::contribution_t> locked_contributions;
};
bool tx_get_staking_components (cryptonote::transaction_prefix const &tx_prefix, staking_components *contribution, crypto::hash const &txid);
bool tx_get_staking_components (cryptonote::transaction const &tx, staking_components *contribution);
bool tx_get_staking_components_and_amounts(cryptonote::network_type nettype, uint8_t hf_version, cryptonote::transaction const &tx, uint64_t block_height, staking_components *contribution);
struct converted_registration_args
{
bool success;

View file

@ -2696,7 +2696,7 @@ namespace cryptonote
for (size_t height = start; height != end;)
{
uint8_t hf_version = m_core.get_hard_fork_version(height);
if (hf_version != HardFork::INVALID_HF_VERSION_FOR_HEIGHT)
if (hf_version != HardFork::INVALID_HF_VERSION)
{
auto start_quorum_iterator = static_cast<service_nodes::quorum_type>(0);
auto end_quorum_iterator = service_nodes::max_quorum_type_for_hf(hf_version);

View file

@ -205,7 +205,7 @@ namespace
const char* USAGE_CHECK_SPEND_PROOF("check_spend_proof <txid> <signature_file> [<message>]");
const char* USAGE_GET_RESERVE_PROOF("get_reserve_proof (all|<amount>) [<message>]");
const char* USAGE_CHECK_RESERVE_PROOF("check_reserve_proof <address> <signature_file> [<message>]");
const char* USAGE_SHOW_TRANSFERS("show_transfers [in|out|all|pending|failed|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]");
const char* USAGE_SHOW_TRANSFERS("show_transfers [in] [out] [stake] [all] [pending] [failed] [coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]");
const char* USAGE_EXPORT_TRANSFERS("export_transfers [in|out|all|pending|failed] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<path>]");
const char* USAGE_UNSPENT_OUTPUTS("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]");
const char* USAGE_RESCAN_BC("rescan_bc [hard|soft|keep_ki] [start_height=0]");
@ -2813,10 +2813,10 @@ simple_wallet::simple_wallet()
tr(R"(Show the incoming/outgoing transfers within an optional height range.
Output format:
In or Coinbase: Block Number, "block"|"in", Time, Amount, Transaction Hash, Payment ID, Subaddress Index, "-", Note
Out: Block Number, "out", Time, Amount*, Transaction Hash, Payment ID, Fee, Destinations, Input addresses**, "-", Note
Pool: "pool", "in", Time, Amount, Transaction Hash, Payment ID, Subaddress Index, "-", Note, Double Spend Note
Pending or Failed: "failed"|"pending", "out", Time, Amount*, Transaction Hash, Payment ID, Fee, Input addresses**, "-", Note
In or Coinbase: Block Number, "block"|"in", Lock, Checkpointed, Time, Amount, Transaction Hash, Payment ID, Subaddress Index, "-", Note
Out: Block Number, "out", Lock, Checkpointed, Time, Amount*, Transaction Hash, Payment ID, Fee, Destinations, Input addresses**, "-", Note
Pool: "pool", "in", Lock, Checkpointed, Time, Amount, Transaction Hash, Payment ID, Subaddress Index, "-", Note, Double Spend Note
Pending or Failed: "failed"|"pending", "out", Lock, Checkpointed, Time, Amount*, Transaction Hash, Payment ID, Fee, Input addresses**, "-", Note
* Excluding change and fee.
** Set of address indices used as inputs in this transfer.)"));
@ -8048,35 +8048,44 @@ bool simple_wallet::check_reserve_proof(const std::vector<std::string> &args)
static bool parse_get_transfers_args(std::vector<std::string>& local_args, tools::wallet2::get_transfers_args_t& args)
{
// optional in/out selector
if (local_args.size() > 0) {
while (local_args.size() > 0)
{
if (local_args[0] == "in" || local_args[0] == "incoming") {
args.out = args.pending = args.failed = false;
args.in = args.coinbase = true;
local_args.erase(local_args.begin());
}
else if (local_args[0] == "out" || local_args[0] == "outgoing") {
args.in = args.pool = args.coinbase = false;
args.out = args.stake = true;
local_args.erase(local_args.begin());
}
else if (local_args[0] == "pending") {
args.in = args.out = args.failed = args.coinbase = false;
args.pending = true;
local_args.erase(local_args.begin());
}
else if (local_args[0] == "failed") {
args.in = args.out = args.pending = args.pool = args.coinbase = false;
args.failed = true;
local_args.erase(local_args.begin());
}
else if (local_args[0] == "pool") {
args.in = args.out = args.pending = args.failed = args.coinbase = false;
args.pool = true;
local_args.erase(local_args.begin());
}
else if (local_args[0] == "coinbase") {
args.in = args.out = args.pending = args.failed = args.pool = false;
args.coinbase = true;
local_args.erase(local_args.begin());
}
else if (local_args[0] == "all" || local_args[0] == "both") {
else if (local_args[0] == "stake") {
args.stake = true;
local_args.erase(local_args.begin());
}
else if (local_args[0] == "all" || local_args[0] == "both") {
args.in = args.out = args.stake = args.pending = args.failed = args.pool = args.coinbase = true;
local_args.erase(local_args.begin());
}
else
{
break;
}
}
// subaddr_index
@ -8120,7 +8129,7 @@ static bool parse_get_transfers_args(std::vector<std::string>& local_args, tools
// mutates local_args as it parses and consumes arguments
bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vector<tools::transfer_view>& transfers)
{
tools::wallet2::get_transfers_args_t args;
tools::wallet2::get_transfers_args_t args = {};
if (!parse_get_transfers_args(local_args, args))
{
return false;

View file

@ -51,6 +51,7 @@ using namespace epee;
#include "rpc/core_rpc_server.h"
#include "misc_language.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_basic/hardfork.h"
#include "multisig/multisig.h"
#include "common/boost_serialization_helper.h"
#include "common/command_line.h"
@ -2425,7 +2426,11 @@ void wallet2::process_unconfirmed(const crypto::hash &txid, const cryptonote::tr
if(unconf_it != m_unconfirmed_txs.end()) {
if (store_tx_info()) {
try {
m_confirmed_txs.emplace(txid, confirmed_transfer_details(unconf_it->second, height));
// TODO(doyle): LNS introduces tx type stake, we can use this to quickly determine if a transaction is staking
// transaction without having to parse tx_extra.
bool stake = service_nodes::tx_get_staking_components(tx, nullptr /*stake*/);
tools::pay_type pay_type = stake ? tools::pay_type::stake : tools::pay_type::out;
m_confirmed_txs.insert(std::make_pair(txid, confirmed_transfer_details(unconf_it->second, height)));
}
catch (...) {
// can fail if the tx has unexpected input types
@ -2462,6 +2467,9 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans
}
entry.first->second.m_subaddr_account = subaddr_account;
entry.first->second.m_subaddr_indices = subaddr_indices;
bool stake = service_nodes::tx_get_staking_components(tx, nullptr /*stake*/);
entry.first->second.m_pay_type = stake ? tools::pay_type::stake : tools::pay_type::out;
}
entry.first->second.m_rings.clear();
@ -5973,7 +5981,7 @@ transfer_view wallet2::wallet2::make_transfer_view(const crypto::hash &txid, con
td.address = d.original.empty() ? get_account_address_as_str(nettype(), d.is_subaddress, d.addr) : d.original;
}
result.pay_type = pay_type::out;
result.pay_type = pd.m_pay_type;
result.subaddr_index = { pd.m_subaddr_account, 0 };
for (uint32_t i: pd.m_subaddr_indices)
result.subaddr_indices.push_back({pd.m_subaddr_account, i});
@ -6008,7 +6016,7 @@ transfer_view wallet2::make_transfer_view(const crypto::hash &txid, const tools:
td.address = d.original.empty() ? get_account_address_as_str(nettype(), d.is_subaddress, d.addr) : d.original;
}
result.pay_type = pay_type::unspecified;
result.pay_type = pd.m_pay_type;
result.type = is_failed ? "failed" : "pending";
result.subaddr_index = { pd.m_subaddr_account, 0 };
for (uint32_t i: pd.m_subaddr_indices)
@ -6057,6 +6065,9 @@ void wallet2::get_transfers(get_transfers_args_t args, std::vector<transfer_view
args.max_height = std::min<uint64_t>(args.max_height, CRYPTONOTE_MAX_BLOCK_NUMBER);
}
int args_count = args.in + args.out + args.stake + args.pending + args.failed + args.pool + args.coinbase;
if (args_count == 0) args.in = args.out = args.stake = args.pending = args.failed = args.pool = args.coinbase = true;
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> in;
std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> out;
std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> pending_or_failed;
@ -6072,7 +6083,7 @@ void wallet2::get_transfers(get_transfers_args_t args, std::vector<transfer_view
size += in.size();
}
if (args.out)
if (args.out || args.stake)
{
get_payments_out(out, args.min_height, args.max_height, account_index, args.subaddr_indices);
size += out.size();
@ -6095,7 +6106,14 @@ void wallet2::get_transfers(get_transfers_args_t args, std::vector<transfer_view
for (const auto &i : in)
transfers.push_back(make_transfer_view(i.second.m_tx_hash, i.first, i.second));
for (const auto &o : out)
transfers.push_back(make_transfer_view(o.first, o.second));
{
bool add_entry = true;
if (args.stake && args_count == 1)
add_entry = o.second.m_pay_type == tools::pay_type::stake;
if (add_entry)
transfers.push_back(make_transfer_view(o.first, o.second));
}
for (const auto &pof : pending_or_failed)
{
bool is_failed = pof.second.m_state == tools::wallet2::unconfirmed_transfer_details::failed;
@ -6634,6 +6652,8 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amo
utd.m_timestamp = time(NULL);
utd.m_subaddr_account = subaddr_account;
utd.m_subaddr_indices = subaddr_indices;
bool stake = service_nodes::tx_get_staking_components(tx, nullptr /*stake*/);
utd.m_pay_type = stake ? tools::pay_type::stake : tools::pay_type::out;
for (const auto &in: tx.vin)
{
if (in.type() != typeid(cryptonote::txin_to_key))
@ -13389,10 +13409,12 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
{
const transfer_details& td = m_transfers[n];
confirmed_transfer_details pd;
pd.m_change = (uint64_t)-1; // change is unknown
pd.m_amount_in = pd.m_amount_out = td.amount(); // fee is unknown
pd.m_block_height = 0; // spent block height is unknown
const crypto::hash &spent_txid = crypto::null_hash; // spent txid is unknown
pd.m_change = (uint64_t)-1; // change is unknown
pd.m_amount_in = pd.m_amount_out = td.amount(); // fee is unknown
pd.m_block_height = 0; // spent block height is unknown
const crypto::hash &spent_txid = crypto::null_hash; // spent txid is unknown
bool stake = service_nodes::tx_get_staking_components(td.m_tx, nullptr /*stake*/, td.m_txid);
pd.m_pay_type = stake ? tools::pay_type::stake : tools::pay_type::out;
m_confirmed_txs.insert(std::make_pair(spent_txid, pd));
}
PERF_TIMER_STOP(import_key_images_G);

View file

@ -511,6 +511,7 @@ private:
uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer
std::set<uint32_t> m_subaddr_indices; // set of address indices used as inputs in this transfer
std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> m_rings; // relative
tools::pay_type m_pay_type = tools::pay_type::out;
};
struct confirmed_transfer_details
@ -527,10 +528,25 @@ private:
uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer
std::set<uint32_t> m_subaddr_indices; // set of address indices used as inputs in this transfer
std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> m_rings; // relative
tools::pay_type m_pay_type = tools::pay_type::out;
confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(crypto::null_hash), m_timestamp(0), m_unlock_time(0), m_subaddr_account((uint32_t)-1) {}
confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height):
m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_unlock_times(utd.m_tx.output_unlock_times), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices), m_rings(utd.m_rings) {}
confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height)
: m_amount_in(utd.m_amount_in)
, m_amount_out(utd.m_amount_out)
, m_change(utd.m_change)
, m_block_height(height)
, m_dests(utd.m_dests)
, m_payment_id(utd.m_payment_id)
, m_timestamp(utd.m_timestamp)
, m_unlock_time(utd.m_tx.unlock_time)
, m_unlock_times(utd.m_tx.output_unlock_times)
, m_subaddr_account(utd.m_subaddr_account)
, m_subaddr_indices(utd.m_subaddr_indices)
, m_rings(utd.m_rings)
, m_pay_type(utd.m_pay_type)
{
}
};
struct tx_construction_data
@ -997,12 +1013,13 @@ private:
struct get_transfers_args_t
{
bool in = true;
bool out = true;
bool pending = true;
bool failed = true;
bool pool = true;
bool coinbase = true;
bool in = false;
bool out = false;
bool stake = false;
bool pending = false;
bool failed = false;
bool pool = false;
bool coinbase = false;
bool filter_by_height = false;
uint64_t min_height = 0;
uint64_t max_height = CRYPTONOTE_MAX_BLOCK_NUMBER;
@ -1828,8 +1845,8 @@ BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1)
BOOST_CLASS_VERSION(tools::wallet2::payment_details, 6)
BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1)
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8)
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 7)
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 9)
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 8)
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)
@ -1934,6 +1951,8 @@ namespace boost
{
a & x.m_tx;
}
if (ver < 9)
x.m_pay_type = tools::pay_type::out;
if (ver < 1)
return;
a & x.m_dests;
@ -1966,6 +1985,9 @@ namespace boost
if (ver < 8)
return;
a & x.m_rings;
if (ver < 9)
return;
a & x.m_pay_type;
}
template <class Archive>
@ -1975,6 +1997,8 @@ namespace boost
a & x.m_amount_out;
a & x.m_change;
a & x.m_block_height;
if (ver < 8)
x.m_pay_type = tools::pay_type::out;
if (ver < 1)
return;
a & x.m_dests;
@ -2017,6 +2041,9 @@ namespace boost
if (ver < 7)
return;
a & x.m_unlock_times;
if (ver < 8)
return;
a & x.m_pay_type;
}
template <class Archive>
@ -2035,12 +2062,13 @@ namespace boost
if (ver < 3)
x.m_fee = 0;
if (ver < 4)
x.m_type = tools::pay_type::unspecified;
x.m_type = tools::pay_type::in;
if (ver < 5)
x.m_unmined_blink = false;
if (ver < 6)
x.m_was_blink = false;
if (ver < 1) return;
a & x.m_timestamp;
if (ver < 2) return;

View file

@ -83,6 +83,8 @@ namespace tools
bool mempool; // States if the transaction is sitting in the mempool. `true if the transaction is, `false` if not.
uint32_t mixin; // The number of other signatures (aside from yours) in the ring signature that authorises the transaction.
// TODO(loki): Also the pay type, is it a stake? But since this is undocumented and not used, not implemented yet
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(id)
KV_SERIALIZE(hash)

View file

@ -2324,12 +2324,13 @@ namespace tools
return false;
}
wallet2::get_transfers_args_t args;
wallet2::get_transfers_args_t args = {};
args.in = req.in;
args.out = req.out;
args.pending = req.pending;
args.failed = req.failed;
args.pool = req.pool;
args.stake = req.stake;
args.filter_by_height = req.filter_by_height;
args.min_height = req.min_height;
args.max_height = req.max_height;
@ -2351,7 +2352,7 @@ namespace tools
{
res.in.push_back(entry);
}
else if (entry.pay_type == tools::pay_type::out)
else if (entry.pay_type == tools::pay_type::out || entry.pay_type == tools::pay_type::stake)
{
res.out.push_back(entry);
}
@ -2377,10 +2378,11 @@ namespace tools
wallet2::get_transfers_args_t args;
args.in = req.in;
args.out = req.out;
args.stake = req.stake;
args.pending = req.pending;
args.failed = req.failed;
args.pool = req.pool;
// args.coinbase = req.coinbase;
args.coinbase = req.coinbase;
args.filter_by_height = req.filter_by_height;
args.min_height = req.min_height;
args.max_height = req.max_height;

View file

@ -1561,16 +1561,18 @@ namespace wallet_rpc
};
LOKI_RPC_DOC_INTROSPECT
// Returns a list of transfers.
// Returns a list of transfers, by default all transfer types are included. If all requested type fields are false, then all transfers will be queried.
struct COMMAND_RPC_GET_TRANSFERS
{
struct request_t
{
bool in; // (Optional) Include incoming transfers.
bool out; // (Optional) Include outgoing transfers.
bool stake; // (Optional) Include outgoing stakes.
bool pending; // (Optional) Include pending transfers.
bool failed; // (Optional) Include failed transfers.
bool pool; // (Optional) Include transfers from the daemon's transaction pool.
bool coinbase; // (Optional) Include transfers from the daemon's transaction pool.
bool filter_by_height; // (Optional) Filter transfers by block height.
uint64_t min_height; // (Optional) Minimum block height to scan for transfers, if filtering by height is enabled.
@ -1580,11 +1582,13 @@ namespace wallet_rpc
bool all_accounts; // If true, return transfers for all accounts, subaddr_indices and account_index are ignored
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(in);
KV_SERIALIZE(out);
KV_SERIALIZE(pending);
KV_SERIALIZE(failed);
KV_SERIALIZE(pool);
KV_SERIALIZE_OPT(in, true);
KV_SERIALIZE_OPT(out, true);
KV_SERIALIZE_OPT(stake, true);
KV_SERIALIZE_OPT(pending, true);
KV_SERIALIZE_OPT(failed, true);
KV_SERIALIZE_OPT(pool, true);
KV_SERIALIZE_OPT(coinbase, true);
KV_SERIALIZE(filter_by_height);
KV_SERIALIZE(min_height);
KV_SERIALIZE_OPT(max_height, (uint64_t)CRYPTONOTE_MAX_BLOCK_NUMBER);