@ -887,6 +887,14 @@ cryptonote::account_public_address wallet2::get_subaddress(const cryptonote::sub
return hwdev . get_subaddress ( m_account . get_keys ( ) , index ) ;
}
//----------------------------------------------------------------------------------------------------
boost : : optional < cryptonote : : subaddress_index > wallet2 : : get_subaddress_index ( const cryptonote : : account_public_address & address ) const
{
auto index = m_subaddresses . find ( address . m_spend_public_key ) ;
if ( index = = m_subaddresses . end ( ) )
return boost : : none ;
return index - > second ;
}
//----------------------------------------------------------------------------------------------------
crypto : : public_key wallet2 : : get_subaddress_spend_public_key ( const cryptonote : : subaddress_index & index ) const
{
hw : : device & hwdev = m_account . get_device ( ) ;
@ -1100,7 +1108,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
// (that is, the prunable stuff may or may not be included)
if ( ! miner_tx & & ! pool )
process_unconfirmed ( txid , tx , height ) ;
std : : vector < size_t > outs ;
std : : unordered_map < cryptonote : : subaddress_index , uint64_t > tx_money_got_in_outs ; // per receiving subaddress index
crypto : : public_key tx_pub_key = null_pkey ;
@ -1118,6 +1125,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
uint64_t total_received_1 = 0 ;
while ( ! tx . vout . empty ( ) )
{
std : : vector < size_t > outs ;
// if tx.vout is not empty, we loop through all tx pubkeys
tx_extra_pub_key pub_key_field ;
@ -2385,7 +2393,7 @@ bool wallet2::refresh(uint64_t & blocks_fetched, bool& received_money, bool& ok)
return ok ;
}
//----------------------------------------------------------------------------------------------------
bool wallet2 : : get_ outpu t_distribution( uint64_t & start_height , std : : vector < uint64_t > & distribution )
bool wallet2 : : get_ rc t_distribution( uint64_t & start_height , std : : vector < uint64_t > & distribution )
{
uint32_t rpc_version ;
boost : : optional < std : : string > result = m_node_rpc_proxy . get_rpc_version ( rpc_version ) ;
@ -5909,22 +5917,42 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
bool is_shortly_after_segregation_fork = height > = segregation_fork_height & & height < segregation_fork_height + SEGREGATION_FORK_VICINITY ;
bool is_after_segregation_fork = height > = segregation_fork_height ;
// if we have at least one rct out, get the distribution, or fall back to the previous system
uint64_t rct_start_height ;
std : : vector < uint64_t > rct_offsets ;
bool has_rct = false ;
for ( size_t idx : selected_transfers )
if ( m_transfers [ idx ] . is_rct ( ) )
{ has_rct = true ; break ; }
const bool has_rct_distribution = has_rct & & get_rct_distribution ( rct_start_height , rct_offsets ) ;
if ( has_rct_distribution )
{
// check we're clear enough of rct start, to avoid corner cases below
THROW_WALLET_EXCEPTION_IF ( rct_offsets . size ( ) < = CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE ,
error : : get_output_distribution , " Not enough rct outputs " ) ;
}
// get histogram for the amounts we need
cryptonote : : COMMAND_RPC_GET_OUTPUT_HISTOGRAM : : request req_t = AUTO_VAL_INIT ( req_t ) ;
cryptonote : : COMMAND_RPC_GET_OUTPUT_HISTOGRAM : : response resp_t = AUTO_VAL_INIT ( resp_t ) ;
m_daemon_rpc_mutex . lock ( ) ;
// request histogram for all outputs, except 0 if we have the rct distribution
for ( size_t idx : selected_transfers )
req_t . amounts . push_back ( m_transfers [ idx ] . is_rct ( ) ? 0 : m_transfers [ idx ] . amount ( ) ) ;
std : : sort ( req_t . amounts . begin ( ) , req_t . amounts . end ( ) ) ;
auto end = std : : unique ( req_t . amounts . begin ( ) , req_t . amounts . end ( ) ) ;
req_t . amounts . resize ( std : : distance ( req_t . amounts . begin ( ) , end ) ) ;
req_t . unlocked = true ;
req_t . recent_cutoff = time ( NULL ) - RECENT_OUTPUT_ZONE ;
bool r = net_utils : : invoke_http_json_rpc ( " /json_rpc " , " get_output_histogram " , req_t , resp_t , m_http_client , rpc_timeout ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " transfer_selected " ) ;
THROW_WALLET_EXCEPTION_IF ( resp_t . status = = CORE_RPC_STATUS_BUSY , error : : daemon_busy , " get_output_histogram " ) ;
THROW_WALLET_EXCEPTION_IF ( resp_t . status ! = CORE_RPC_STATUS_OK , error : : get_histogram_error , resp_t . status ) ;
if ( ! m_transfers [ idx ] . is_rct ( ) | | ! has_rct_distribution )
req_t . amounts . push_back ( m_transfers [ idx ] . is_rct ( ) ? 0 : m_transfers [ idx ] . amount ( ) ) ;
if ( ! req_t . amounts . empty ( ) )
{
std : : sort ( req_t . amounts . begin ( ) , req_t . amounts . end ( ) ) ;
auto end = std : : unique ( req_t . amounts . begin ( ) , req_t . amounts . end ( ) ) ;
req_t . amounts . resize ( std : : distance ( req_t . amounts . begin ( ) , end ) ) ;
req_t . unlocked = true ;
req_t . recent_cutoff = time ( NULL ) - RECENT_OUTPUT_ZONE ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = net_utils : : invoke_http_json_rpc ( " /json_rpc " , " get_output_histogram " , req_t , resp_t , m_http_client , rpc_timeout ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " transfer_selected " ) ;
THROW_WALLET_EXCEPTION_IF ( resp_t . status = = CORE_RPC_STATUS_BUSY , error : : daemon_busy , " get_output_histogram " ) ;
THROW_WALLET_EXCEPTION_IF ( resp_t . status ! = CORE_RPC_STATUS_OK , error : : get_histogram_error , resp_t . status ) ;
}
// if we want to segregate fake outs pre or post fork, get distribution
std : : unordered_map < uint64_t , std : : pair < uint64_t , uint64_t > > segregation_limit ;
@ -5980,6 +6008,36 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
COMMAND_RPC_GET_OUTPUTS_BIN : : request req = AUTO_VAL_INIT ( req ) ;
COMMAND_RPC_GET_OUTPUTS_BIN : : response daemon_resp = AUTO_VAL_INIT ( daemon_resp ) ;
struct gamma_engine
{
typedef uint64_t result_type ;
static constexpr result_type min ( ) { return 0 ; }
static constexpr result_type max ( ) { return std : : numeric_limits < result_type > : : max ( ) ; }
result_type operator ( ) ( ) { return crypto : : rand < result_type > ( ) ; }
} engine ;
static const double shape = 19.28 /*16.94*/ ;
//static const double shape = m_testnet ? 17.02 : 17.28;
static const double scale = 1 / 1.61 ;
std : : gamma_distribution < double > gamma ( shape , scale ) ;
auto pick_gamma = [ & ] ( )
{
double x = gamma ( engine ) ;
x = exp ( x ) ;
uint64_t block_offset = x / DIFFICULTY_TARGET_V2 ; // this assumes constant target over the whole rct range
if ( block_offset > = rct_offsets . size ( ) - 1 )
return std : : numeric_limits < uint64_t > : : max ( ) ; // bad pick
block_offset = rct_offsets . size ( ) - 2 - block_offset ;
THROW_WALLET_EXCEPTION_IF ( block_offset > = rct_offsets . size ( ) - 1 , error : : wallet_internal_error , " Bad offset calculation " ) ;
THROW_WALLET_EXCEPTION_IF ( rct_offsets [ block_offset + 1 ] < rct_offsets [ block_offset ] ,
error : : get_output_distribution , " Decreasing offsets in rct distribution: " +
std : : to_string ( block_offset ) + " : " + std : : to_string ( rct_offsets [ block_offset ] ) + " , " +
std : : to_string ( block_offset + 1 ) + " : " + std : : to_string ( rct_offsets [ block_offset + 1 ] ) ) ;
uint64_t n_rct = rct_offsets [ block_offset + 1 ] - rct_offsets [ block_offset ] ;
if ( n_rct = = 0 )
return rct_offsets [ block_offset ] ? rct_offsets [ block_offset ] - 1 : 0 ;
return rct_offsets [ block_offset ] + crypto : : rand < uint64_t > ( ) % n_rct ;
} ;
size_t num_selected_transfers = 0 ;
for ( size_t idx : selected_transfers )
{
@ -5990,6 +6048,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// request more for rct in base recent (locked) coinbases are picked, since they're locked for longer
size_t requested_outputs_count = base_requested_outputs_count + ( td . is_rct ( ) ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0 ) ;
size_t start = req . outputs . size ( ) ;
bool use_histogram = amount ! = 0 | | ! has_rct_distribution ;
const bool output_is_pre_fork = td . m_block_height < segregation_fork_height ;
uint64_t num_outs = 0 , num_recent_outs = 0 ;
@ -6045,26 +6104,41 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
num_post_fork_outs = num_outs - segregation_limit [ amount ] . first ;
}
LOG_PRINT_L1 ( " " < < num_outs < < " unlocked outputs of size " < < print_money ( amount ) ) ;
THROW_WALLET_EXCEPTION_IF ( num_outs = = 0 , error : : wallet_internal_error ,
" histogram reports no unlocked outputs for " + boost : : lexical_cast < std : : string > ( amount ) + " , not even ours " ) ;
THROW_WALLET_EXCEPTION_IF ( num_recent_outs > num_outs , error : : wallet_internal_error ,
" histogram reports more recent outs than outs for " + boost : : lexical_cast < std : : string > ( amount ) ) ;
if ( use_histogram )
{
LOG_PRINT_L1 ( " " < < num_outs < < " unlocked outputs of size " < < print_money ( amount ) ) ;
THROW_WALLET_EXCEPTION_IF ( num_outs = = 0 , error : : wallet_internal_error ,
" histogram reports no unlocked outputs for " + boost : : lexical_cast < std : : string > ( amount ) + " , not even ours " ) ;
THROW_WALLET_EXCEPTION_IF ( num_recent_outs > num_outs , error : : wallet_internal_error ,
" histogram reports more recent outs than outs for " + boost : : lexical_cast < std : : string > ( amount ) ) ;
}
else
{
// the base offset of the first rct output in the first unlocked block (or the one to be if there's none)
num_outs = rct_offsets [ rct_offsets . size ( ) - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE ] ;
LOG_PRINT_L1 ( " " < < num_outs < < " unlocked rct outputs " ) ;
THROW_WALLET_EXCEPTION_IF ( num_outs = = 0 , error : : wallet_internal_error ,
" histogram reports no unlocked rct outputs, not even ours " ) ;
}
// how many fake outs to draw on a pre-fork triangular distribution
// how many fake outs to draw on a pre-fork distribution
size_t pre_fork_outputs_count = requested_outputs_count * pre_fork_num_out_ratio ;
size_t post_fork_outputs_count = requested_outputs_count * post_fork_num_out_ratio ;
// how many fake outs to draw otherwise
size_t normal_output_count = requested_outputs_count - pre_fork_outputs_count - post_fork_outputs_count ;
// X% of those outs are to be taken from recent outputs
size_t recent_outputs_count = normal_output_count * RECENT_OUTPUT_RATIO ;
if ( recent_outputs_count = = 0 )
recent_outputs_count = 1 ; // ensure we have at least one, if possible
if ( recent_outputs_count > num_recent_outs )
recent_outputs_count = num_recent_outs ;
if ( td . m_global_output_index > = num_outs - num_recent_outs & & recent_outputs_count > 0 )
- - recent_outputs_count ; // if the real out is recent, pick one less recent fake out
size_t recent_outputs_count = 0 ;
if ( use_histogram )
{
// X% of those outs are to be taken from recent outputs
recent_outputs_count = normal_output_count * RECENT_OUTPUT_RATIO ;
if ( recent_outputs_count = = 0 )
recent_outputs_count = 1 ; // ensure we have at least one, if possible
if ( recent_outputs_count > num_recent_outs )
recent_outputs_count = num_recent_outs ;
if ( td . m_global_output_index > = num_outs - num_recent_outs & & recent_outputs_count > 0 )
- - recent_outputs_count ; // if the real out is recent, pick one less recent fake out
}
LOG_PRINT_L1 ( " Fake output makeup: " < < requested_outputs_count < < " requested: " < < recent_outputs_count < < " recent, " < <
pre_fork_outputs_count < < " pre-fork, " < < post_fork_outputs_count < < " post-fork, " < <
( requested_outputs_count - recent_outputs_count - pre_fork_outputs_count - post_fork_outputs_count ) < < " full-chain " ) ;
@ -6144,7 +6218,26 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
uint64_t i ;
const char * type = " " ;
if ( num_found - 1 < recent_outputs_count ) // -1 to account for the real one we seeded with
if ( amount = = 0 & & has_rct_distribution )
{
// gamma distribution
if ( num_found - 1 < recent_outputs_count + pre_fork_outputs_count )
{
do i = pick_gamma ( ) ; while ( i > = segregation_limit [ amount ] . first ) ;
type = " pre-fork gamma " ;
}
else if ( num_found - 1 < recent_outputs_count + pre_fork_outputs_count + post_fork_outputs_count )
{
do i = pick_gamma ( ) ; while ( i < segregation_limit [ amount ] . first | | i > = num_outs ) ;
type = " post-fork gamma " ;
}
else
{
do i = pick_gamma ( ) ; while ( i > = num_outs ) ;
type = " gamma " ;
}
}
else if ( num_found - 1 < recent_outputs_count ) // -1 to account for the real one we seeded with
{
// triangular distribution over [a,b) with a=0, mode c=b=up_index_limit
uint64_t r = crypto : : rand < uint64_t > ( ) % ( ( uint64_t ) 1 < < 53 ) ;
@ -6209,7 +6302,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// get the keys for those
m_daemon_rpc_mutex . lock ( ) ;
r = epee : : net_utils : : invoke_http_bin ( " /get_outs.bin " , req , daemon_resp , m_http_client , rpc_timeout ) ;
bool r = epee : : net_utils : : invoke_http_bin ( " /get_outs.bin " , req , daemon_resp , m_http_client , rpc_timeout ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " get_outs.bin " ) ;
THROW_WALLET_EXCEPTION_IF ( daemon_resp . status = = CORE_RPC_STATUS_BUSY , error : : daemon_busy , " get_outs.bin " ) ;
@ -8119,7 +8212,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) const
result = m_node_rpc_proxy . get_earliest_height ( version , earliest_height ) ;
throw_on_rpc_response_error ( result , " get_hard_fork_info " ) ;
bool close_enough = height > = earliest_height - early_blocks & & earliest_height ! = std : : numeric_limits < uint64_t > : : max ( ) ; // start using the rules that many blocks beforehand
bool close_enough = height > = earliest_height - early_blocks ; // start using the rules that many blocks beforehand
if ( close_enough )
LOG_PRINT_L2 ( " Using v " < < ( unsigned ) version < < " rules " ) ;
else
@ -8902,6 +8995,7 @@ std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t,
if ( account_minreserve )
{
THROW_WALLET_EXCEPTION_IF ( account_minreserve - > second = = 0 , error : : wallet_internal_error , " Proved amount must be greater than 0 " ) ;
// minimize the number of outputs included in the proof, by only picking the N largest outputs that can cover the requested min reserve amount
std : : sort ( selected_transfers . begin ( ) , selected_transfers . end ( ) , [ & ] ( const size_t a , const size_t b )
{ return m_transfers [ a ] . amount ( ) > m_transfers [ b ] . amount ( ) ; } ) ;
@ -9567,6 +9661,17 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
std : : unordered_set < crypto : : hash > spent_txids ; // For each spent key image, search for a tx in m_transfers that uses it as input.
std : : vector < size_t > swept_transfers ; // If such a spending tx wasn't found in m_transfers, this means the spending tx
// was created by sweep_all, so we can't know the spent height and other detailed info.
std : : unordered_map < crypto : : key_image , crypto : : hash > spent_key_images ;
for ( const transfer_details & td : m_transfers )
{
for ( const cryptonote : : txin_v & in : td . m_tx . vin )
{
if ( in . type ( ) = = typeid ( cryptonote : : txin_to_key ) )
spent_key_images . insert ( std : : make_pair ( boost : : get < cryptonote : : txin_to_key > ( in ) . k_image , td . m_txid ) ) ;
}
}
for ( size_t i = 0 ; i < signed_key_images . size ( ) ; + + i )
{
transfer_details & td = m_transfers [ i ] ;
@ -9580,28 +9685,11 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
if ( i < daemon_resp . spent_status . size ( ) & & daemon_resp . spent_status [ i ] = = COMMAND_RPC_IS_KEY_IMAGE_SPENT : : SPENT_IN_BLOCKCHAIN )
{
bool is_spent_tx_found = false ;
for ( auto it = m_transfers . rbegin ( ) ; & ( * it ) ! = & td ; + + it )
{
bool is_spent_tx = false ;
for ( const cryptonote : : txin_v & in : it - > m_tx . vin )
{
if ( in . type ( ) = = typeid ( cryptonote : : txin_to_key ) & & td . m_key_image = = boost : : get < cryptonote : : txin_to_key > ( in ) . k_image )
{
is_spent_tx = true ;
break ;
}
}
if ( is_spent_tx )
{
is_spent_tx_found = true ;
spent_txids . insert ( it - > m_txid ) ;
break ;
}
}
if ( ! is_spent_tx_found )
const std : : unordered_map < crypto : : key_image , crypto : : hash > : : const_iterator skii = spent_key_images . find ( td . m_key_image ) ;
if ( skii = = spent_key_images . end ( ) )
swept_transfers . push_back ( i ) ;
else
spent_txids . insert ( skii - > second ) ;
}
}
MDEBUG ( " Total: " < < print_money ( spent ) < < " spent, " < < print_money ( unspent ) < < " unspent " ) ;
@ -9724,11 +9812,10 @@ 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 ; // c a hnge is unknown
pd . m_change = ( uint64_t ) - 1 ; // c ha nge is unknown
pd . m_amount_in = pd . m_amount_out = td . amount ( ) ; // fee is unknown
std : : string err ;
pd . m_block_height = get_daemon_blockchain_height ( err ) ; // spent block height is unknown, so hypothetically set to the highest
crypto : : hash spent_txid = crypto : : rand < crypto : : hash > ( ) ; // spent txid is unknown, so hypothetically set to random
pd . m_block_height = 0 ; // spent block height is unknown
const crypto : : hash & spent_txid = crypto : : null_hash ; // spent txid is unknown
m_confirmed_txs . insert ( std : : make_pair ( spent_txid , pd ) ) ;
}
}
@ -9823,18 +9910,20 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
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 ) ;
THROW_WALLET_EXCEPTION_IF ( td . m_tx . vout [ td . m_internal_output_index ] . target . type ( ) ! = typeid ( cryptonote : : txout_to_key ) ,
error : : wallet_internal_error , " Unsupported output type " ) ;
const crypto : : public_key & out_key = boost : : get < cryptonote : : txout_to_key > ( td . m_tx . vout [ td . m_internal_output_index ] . target ) . key ;
bool r = cryptonote : : generate_key_image_helper ( m_account . get_keys ( ) , m_subaddresses , out_key , tx_pub_key , additional_tx_pub_keys , td . m_internal_output_index , in_ephemeral , td . m_key_image , m_account . get_device ( ) ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : wallet_internal_error , " Failed to generate key image " ) ;
expand_subaddresses ( td . m_subaddr_index ) ;
td . m_key_image_known = true ;
td . m_key_image_partial = false ;
THROW_WALLET_EXCEPTION_IF ( in_ephemeral . pub ! = boost: : get < cryptonote : : tx out_to_key> ( td . m_tx . vout [ td . m_internal_output_index ] . target ) . key,
THROW_WALLET_EXCEPTION_IF ( in_ephemeral . pub ! = out_key,
error : : wallet_internal_error , " key_image generated ephemeral public key not matched with output_key at index " + boost : : lexical_cast < std : : string > ( i ) ) ;
m_key_images [ td . m_key_image ] = m_transfers . size ( ) ;
m_pub_keys [ td . get_public_key ( ) ] = m_transfers . size ( ) ;
m_transfers . push_back ( td) ;
m_transfers . push_back ( s td: : move ( td ) ) ;
}
return m_transfers . size ( ) ;