@ -525,7 +525,7 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia
}
//----------------------------------------------------------------------------------------------------
bool wallet2 : : init ( std : : string daemon_address , boost : : optional < epee : : net_utils : : http : : login > daemon_login , uint64_t upper_transaction_size_limit )
bool wallet2 : : init ( std : : string daemon_address , boost : : optional < epee : : net_utils : : http : : login > daemon_login , uint64_t upper_transaction_size_limit , bool ssl )
{
m_checkpoints . init_default_checkpoints ( m_testnet ) ;
if ( m_http_client . is_connected ( ) )
@ -534,7 +534,10 @@ bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::
m_upper_transaction_size_limit = upper_transaction_size_limit ;
m_daemon_address = std : : move ( daemon_address ) ;
m_daemon_login = std : : move ( daemon_login ) ;
return m_http_client . set_server ( get_daemon_address ( ) , get_daemon_login ( ) ) ;
// When switching from light wallet to full wallet, we need to reset the height we got from lw node.
if ( m_light_wallet )
m_local_bc_height = m_blockchain . size ( ) ;
return m_http_client . set_server ( get_daemon_address ( ) , get_daemon_login ( ) , ssl ) ;
}
//----------------------------------------------------------------------------------------------------
bool wallet2 : : is_deterministic ( ) const
@ -1513,6 +1516,34 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei
error = true ;
}
}
void wallet2 : : remove_obsolete_pool_txs ( const std : : vector < crypto : : hash > & tx_hashes )
{
// remove pool txes to us that aren't in the pool anymore
std : : unordered_map < crypto : : hash , wallet2 : : payment_details > : : iterator uit = m_unconfirmed_payments . begin ( ) ;
while ( uit ! = m_unconfirmed_payments . end ( ) )
{
const crypto : : hash & txid = uit - > second . m_tx_hash ;
bool found = false ;
for ( const auto & it2 : tx_hashes )
{
if ( it2 = = txid )
{
found = true ;
break ;
}
}
auto pit = uit + + ;
if ( ! found )
{
MDEBUG ( " Removing " < < txid < < " from unconfirmed payments, not found in pool " ) ;
m_unconfirmed_payments . erase ( pit ) ;
if ( 0 ! = m_callback )
m_callback - > on_pool_tx_removed ( txid ) ;
}
}
}
//----------------------------------------------------------------------------------------------------
void wallet2 : : update_pool_state ( bool refreshed )
{
@ -1590,28 +1621,8 @@ void wallet2::update_pool_state(bool refreshed)
// the in transfers list instead (or nowhere if it just
// disappeared without being mined)
if ( refreshed )
{
std : : unordered_map < crypto : : hash , wallet2 : : payment_details > : : iterator uit = m_unconfirmed_payments . begin ( ) ;
while ( uit ! = m_unconfirmed_payments . end ( ) )
{
const crypto : : hash & txid = uit - > second . m_tx_hash ;
bool found = false ;
for ( const auto & it2 : res . tx_hashes )
{
if ( it2 = = txid )
{
found = true ;
break ;
}
}
auto pit = uit + + ;
if ( ! found )
{
MDEBUG ( " Removing " < < txid < < " from unconfirmed payments, not found in pool " ) ;
m_unconfirmed_payments . erase ( pit ) ;
}
}
}
remove_obsolete_pool_txs ( res . tx_hashes ) ;
MDEBUG ( " update_pool_state done second loop " ) ;
// gather txids of new pool txes to us
@ -1828,6 +1839,39 @@ bool wallet2::delete_address_book_row(std::size_t row_id) {
//----------------------------------------------------------------------------------------------------
void wallet2 : : refresh ( uint64_t start_height , uint64_t & blocks_fetched , bool & received_money )
{
if ( m_light_wallet ) {
// MyMonero get_address_info needs to be called occasionally to trigger wallet sync.
// This call is not really needed for other purposes and can be removed if mymonero changes their backend.
cryptonote : : COMMAND_RPC_GET_ADDRESS_INFO : : response res ;
// Get basic info
if ( light_wallet_get_address_info ( res ) ) {
// Last stored block height
uint64_t prev_height = m_light_wallet_blockchain_height ;
// Update lw heights
m_light_wallet_scanned_block_height = res . scanned_block_height ;
m_light_wallet_blockchain_height = res . blockchain_height ;
m_local_bc_height = res . blockchain_height ;
// If new height - call new_block callback
if ( m_light_wallet_blockchain_height ! = prev_height )
{
MDEBUG ( " new block since last time! " ) ;
m_callback - > on_lw_new_block ( m_light_wallet_blockchain_height - 1 ) ;
}
m_light_wallet_connected = true ;
MDEBUG ( " lw scanned block height: " < < m_light_wallet_scanned_block_height ) ;
MDEBUG ( " lw blockchain height: " < < m_light_wallet_blockchain_height ) ;
MDEBUG ( m_light_wallet_blockchain_height - m_light_wallet_scanned_block_height < < " blocks behind " ) ;
// TODO: add wallet created block info
light_wallet_get_address_txs ( ) ;
} else
m_light_wallet_connected = false ;
// Lighwallet refresh done
return ;
}
received_money = false ;
blocks_fetched = 0 ;
uint64_t added_blocks = 0 ;
@ -2603,6 +2647,12 @@ bool wallet2::check_connection(uint32_t *version, uint32_t timeout)
boost : : lock_guard < boost : : mutex > lock ( m_daemon_rpc_mutex ) ;
// TODO: Add light wallet version check.
if ( m_light_wallet ) {
version = 0 ;
return m_light_wallet_connected ;
}
if ( ! m_http_client . is_connected ( ) )
{
m_node_rpc_proxy . invalidate ( ) ;
@ -2896,6 +2946,8 @@ void wallet2::store_to(const std::string &path, const std::string &password)
uint64_t wallet2 : : balance ( uint32_t index_major ) const
{
uint64_t amount = 0 ;
if ( m_light_wallet )
return m_light_wallet_unlocked_balance ;
for ( const auto & i : balance_per_subaddress ( index_major ) )
amount + = i . second ;
return amount ;
@ -2904,6 +2956,8 @@ uint64_t wallet2::balance(uint32_t index_major) const
uint64_t wallet2 : : unlocked_balance ( uint32_t index_major ) const
{
uint64_t amount = 0 ;
if ( m_light_wallet )
return m_light_wallet_balance ;
for ( const auto & i : unlocked_balance_per_subaddress ( index_major ) )
amount + = i . second ;
return amount ;
@ -3105,10 +3159,15 @@ void wallet2::rescan_blockchain(bool refresh)
//----------------------------------------------------------------------------------------------------
bool wallet2 : : is_transfer_unlocked ( const transfer_details & td ) const
{
if ( ! is_tx_spendtime_unlocked ( td . m_tx . unlock_time , td . m_block_height ) )
return is_transfer_unlocked ( td . m_tx . unlock_time , td . m_block_height ) ;
}
//----------------------------------------------------------------------------------------------------
bool wallet2 : : is_transfer_unlocked ( uint64_t unlock_time , uint64_t block_height ) const
{
if ( ! is_tx_spendtime_unlocked ( unlock_time , block_height ) )
return false ;
if ( td . m_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > m_blockchain . size ( ) )
if ( block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > m_ local_bc_height )
return false ;
return true ;
@ -3119,7 +3178,7 @@ bool wallet2::is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_heig
if ( unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER )
{
//interpret as block index
if ( m_ blockchain. size ( ) - 1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS > = unlock_time )
if ( m_ local_bc_height - 1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS > = unlock_time )
return true ;
else
return false ;
@ -3424,25 +3483,42 @@ crypto::hash8 wallet2::get_short_payment_id(const pending_tx &ptx) const
void wallet2 : : commit_tx ( pending_tx & ptx )
{
using namespace cryptonote ;
crypto : : hash txid ;
COMMAND_RPC_SEND_RAW_TX : : request req ;
req . tx_as_hex = epee : : string_tools : : buff_to_hex_nodelimer ( tx_to_blob ( ptx . tx ) ) ;
req . do_not_relay = false ;
COMMAND_RPC_SEND_RAW_TX : : response daemon_send_resp ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /sendrawtransaction " , req , daemon_send_resp , m_http_client , rpc_timeout ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " sendrawtransaction " ) ;
THROW_WALLET_EXCEPTION_IF ( daemon_send_resp . status = = CORE_RPC_STATUS_BUSY , error : : daemon_busy , " sendrawtransaction " ) ;
THROW_WALLET_EXCEPTION_IF ( daemon_send_resp . status ! = CORE_RPC_STATUS_OK , error : : tx_rejected , ptx . tx , daemon_send_resp . status , daemon_send_resp . reason ) ;
// sanity checks
for ( size_t idx : ptx . selected_transfers )
if ( m_light_wallet )
{
THROW_WALLET_EXCEPTION_IF ( idx > = m_transfers . size ( ) , error : : wallet_internal_error ,
" Bad output index in selected transfers: " + boost : : lexical_cast < std : : string > ( idx ) ) ;
cryptonote : : COMMAND_RPC_SUBMIT_RAW_TX : : request oreq ;
cryptonote : : COMMAND_RPC_SUBMIT_RAW_TX : : response ores ;
oreq . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
oreq . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
oreq . tx = epee : : string_tools : : buff_to_hex_nodelimer ( tx_to_blob ( ptx . tx ) ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /submit_raw_tx " , oreq , ores , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " submit_raw_tx " ) ;
// MyMonero and OpenMonero use different status strings
THROW_WALLET_EXCEPTION_IF ( ores . status ! = " OK " & & ores . status ! = " success " , error : : tx_rejected , ptx . tx , ores . status , ores . error ) ;
}
else
{
// Normal submit
COMMAND_RPC_SEND_RAW_TX : : request req ;
req . tx_as_hex = epee : : string_tools : : buff_to_hex_nodelimer ( tx_to_blob ( ptx . tx ) ) ;
req . do_not_relay = false ;
COMMAND_RPC_SEND_RAW_TX : : response daemon_send_resp ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /sendrawtransaction " , req , daemon_send_resp , m_http_client , rpc_timeout ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " sendrawtransaction " ) ;
THROW_WALLET_EXCEPTION_IF ( daemon_send_resp . status = = CORE_RPC_STATUS_BUSY , error : : daemon_busy , " sendrawtransaction " ) ;
THROW_WALLET_EXCEPTION_IF ( daemon_send_resp . status ! = CORE_RPC_STATUS_OK , error : : tx_rejected , ptx . tx , daemon_send_resp . status , daemon_send_resp . reason ) ;
// sanity checks
for ( size_t idx : ptx . selected_transfers )
{
THROW_WALLET_EXCEPTION_IF ( idx > = m_transfers . size ( ) , error : : wallet_internal_error ,
" Bad output index in selected transfers: " + boost : : lexical_cast < std : : string > ( idx ) ) ;
}
}
crypto : : hash txid ;
txid = get_transaction_hash ( ptx . tx ) ;
crypto : : hash payment_id = crypto : : null_hash ;
@ -3864,6 +3940,8 @@ uint64_t wallet2::get_dynamic_per_kb_fee_estimate()
//----------------------------------------------------------------------------------------------------
uint64_t wallet2 : : get_per_kb_fee ( )
{
if ( m_light_wallet )
return m_light_wallet_per_kb_fee ;
bool use_dyn_fee = use_fork_rules ( HF_VERSION_DYNAMIC_FEE , - 720 * 1 ) ;
if ( ! use_dyn_fee )
return FEE_PER_KB ;
@ -3989,10 +4067,134 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
}
}
bool wallet2 : : tx_add_fake_output ( std : : vector < std : : vector < tools : : wallet2 : : get_outs_entry > > & outs , uint64_t global_index , const crypto : : public_key & tx_public_key , const rct : : key & mask , uint64_t real_index , bool unlocked ) const
{
if ( ! unlocked ) // don't add locked outs
return false ;
if ( global_index = = real_index ) // don't re-add real one
return false ;
auto item = std : : make_tuple ( global_index , tx_public_key , mask ) ;
if ( std : : find ( outs . back ( ) . begin ( ) , outs . back ( ) . end ( ) , item ) ! = outs . back ( ) . end ( ) ) // don't add duplicates
return false ;
outs . back ( ) . push_back ( item ) ;
return true ;
}
void wallet2 : : light_wallet_get_outs ( std : : vector < std : : vector < tools : : wallet2 : : get_outs_entry > > & outs , const std : : list < size_t > & selected_transfers , size_t fake_outputs_count ) {
MDEBUG ( " LIGHTWALLET - Getting random outs " ) ;
cryptonote : : COMMAND_RPC_GET_RANDOM_OUTS : : request oreq ;
cryptonote : : COMMAND_RPC_GET_RANDOM_OUTS : : response ores ;
size_t light_wallet_requested_outputs_count = ( size_t ) ( ( fake_outputs_count + 1 ) * 1.5 + 1 ) ;
// Amounts to ask for
// MyMonero api handle amounts and fees as strings
for ( size_t idx : selected_transfers ) {
const uint64_t ask_amount = m_transfers [ idx ] . is_rct ( ) ? 0 : m_transfers [ idx ] . amount ( ) ;
std : : ostringstream amount_ss ;
amount_ss < < ask_amount ;
oreq . amounts . push_back ( amount_ss . str ( ) ) ;
}
oreq . count = light_wallet_requested_outputs_count ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /get_random_outs " , oreq , ores , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " get_random_outs " ) ;
THROW_WALLET_EXCEPTION_IF ( ores . amount_outs . empty ( ) , error : : wallet_internal_error , " No outputs recieved from light wallet node. Error: " + ores . Error ) ;
// Check if we got enough outputs for each amount
for ( auto & out : ores . amount_outs ) {
const uint64_t out_amount = boost : : lexical_cast < uint64_t > ( out . amount ) ;
THROW_WALLET_EXCEPTION_IF ( out . outputs . size ( ) < light_wallet_requested_outputs_count , error : : wallet_internal_error , " Not enough outputs for amount: " + boost : : lexical_cast < std : : string > ( out . amount ) ) ;
MDEBUG ( out . outputs . size ( ) < < " outputs for amount " + boost : : lexical_cast < std : : string > ( out . amount ) + " received from light wallet node " ) ;
}
MDEBUG ( " selected transfers size: " < < selected_transfers . size ( ) ) ;
for ( size_t idx : selected_transfers )
{
// Create new index
outs . push_back ( std : : vector < get_outs_entry > ( ) ) ;
outs . back ( ) . reserve ( fake_outputs_count + 1 ) ;
// add real output first
const transfer_details & td = m_transfers [ idx ] ;
const uint64_t amount = td . is_rct ( ) ? 0 : td . amount ( ) ;
outs . back ( ) . push_back ( std : : make_tuple ( td . m_global_output_index , td . get_public_key ( ) , rct : : commit ( td . amount ( ) , td . m_mask ) ) ) ;
MDEBUG ( " added real output " < < string_tools : : pod_to_hex ( td . get_public_key ( ) ) ) ;
// Even if the lightwallet server returns random outputs, we pick them randomly.
std : : vector < size_t > order ;
order . resize ( light_wallet_requested_outputs_count ) ;
for ( size_t n = 0 ; n < order . size ( ) ; + + n )
order [ n ] = n ;
std : : shuffle ( order . begin ( ) , order . end ( ) , std : : default_random_engine ( crypto : : rand < unsigned > ( ) ) ) ;
LOG_PRINT_L2 ( " Looking for " < < ( fake_outputs_count + 1 ) < < " outputs with amounts " < < print_money ( td . is_rct ( ) ? 0 : td . amount ( ) ) ) ;
MDEBUG ( " OUTS SIZE: " < < outs . back ( ) . size ( ) ) ;
for ( size_t o = 0 ; o < light_wallet_requested_outputs_count & & outs . back ( ) . size ( ) < fake_outputs_count + 1 ; + + o )
{
// Random pick
size_t i = order [ o ] ;
// Find which random output key to use
bool found_amount = false ;
size_t amount_key ;
for ( amount_key = 0 ; amount_key < ores . amount_outs . size ( ) ; + + amount_key )
{
if ( boost : : lexical_cast < uint64_t > ( ores . amount_outs [ amount_key ] . amount ) = = amount ) {
found_amount = true ;
break ;
}
}
THROW_WALLET_EXCEPTION_IF ( ! found_amount , error : : wallet_internal_error , " Outputs for amount " + boost : : lexical_cast < std : : string > ( ores . amount_outs [ amount_key ] . amount ) + " not found " ) ;
LOG_PRINT_L2 ( " Index " < < i < < " / " < < light_wallet_requested_outputs_count < < " : idx " < < ores . amount_outs [ amount_key ] . outputs [ i ] . global_index < < " (real " < < td . m_global_output_index < < " ), unlocked " < < " (always in light) " < < " , key " < < ores . amount_outs [ 0 ] . outputs [ i ] . public_key ) ;
// Convert light wallet string data to proper data structures
crypto : : public_key tx_public_key ;
rct : : key mask = AUTO_VAL_INIT ( mask ) ; // decrypted mask - not used here
rct : : key rct_commit = AUTO_VAL_INIT ( rct_commit ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , ores . amount_outs [ amount_key ] . outputs [ i ] . public_key ) , error : : wallet_internal_error , " Invalid public_key " ) ;
string_tools : : hex_to_pod ( ores . amount_outs [ amount_key ] . outputs [ i ] . public_key , tx_public_key ) ;
const uint64_t global_index = ores . amount_outs [ amount_key ] . outputs [ i ] . global_index ;
if ( ! light_wallet_parse_rct_str ( ores . amount_outs [ amount_key ] . outputs [ i ] . rct , tx_public_key , 0 , mask , rct_commit , false ) )
rct_commit = rct : : zeroCommit ( td . amount ( ) ) ;
if ( tx_add_fake_output ( outs , global_index , tx_public_key , rct_commit , td . m_global_output_index , true ) ) {
MDEBUG ( " added fake output " < < ores . amount_outs [ amount_key ] . outputs [ i ] . public_key ) ;
MDEBUG ( " index " < < global_index ) ;
}
}
THROW_WALLET_EXCEPTION_IF ( outs . back ( ) . size ( ) < fake_outputs_count + 1 , error : : wallet_internal_error , " Not enough fake outputs found " ) ;
// Real output is the first. Shuffle outputs
MTRACE ( outs . back ( ) . size ( ) < < " outputs added. Sorting outputs by index: " ) ;
std : : sort ( outs . back ( ) . begin ( ) , outs . back ( ) . end ( ) , [ ] ( const get_outs_entry & a , const get_outs_entry & b ) { return std : : get < 0 > ( a ) < std : : get < 0 > ( b ) ; } ) ;
// Print output order
for ( auto added_out : outs . back ( ) )
MTRACE ( std : : get < 0 > ( added_out ) ) ;
}
}
void wallet2 : : get_outs ( std : : vector < std : : vector < tools : : wallet2 : : get_outs_entry > > & outs , const std : : list < size_t > & selected_transfers , size_t fake_outputs_count )
{
LOG_PRINT_L2 ( " fake_outputs_count: " < < fake_outputs_count ) ;
outs . clear ( ) ;
if ( m_light_wallet & & fake_outputs_count > 0 ) {
light_wallet_get_outs ( outs , selected_transfers , fake_outputs_count ) ;
return ;
}
if ( fake_outputs_count > 0 )
{
// get histogram for the amounts we need
@ -4189,14 +4391,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
{
size_t i = base + order [ o ] ;
LOG_PRINT_L2 ( " Index " < < i < < " / " < < requested_outputs_count < < " : idx " < < req . outputs [ i ] . index < < " (real " < < td . m_global_output_index < < " ), unlocked " < < daemon_resp . outs [ i ] . unlocked < < " , key " < < daemon_resp . outs [ i ] . key ) ;
if ( req . outputs [ i ] . index = = td . m_global_output_index ) // don't re-add real one
continue ;
if ( ! daemon_resp . outs [ i ] . unlocked ) // don't add locked outs
continue ;
auto item = std : : make_tuple ( req . outputs [ i ] . index , daemon_resp . outs [ i ] . key , daemon_resp . outs [ i ] . mask ) ;
if ( std : : find ( outs . back ( ) . begin ( ) , outs . back ( ) . end ( ) , item ) ! = outs . back ( ) . end ( ) ) // don't add duplicates
continue ;
outs . back ( ) . push_back ( item ) ;
tx_add_fake_output ( outs , req . outputs [ i ] . index , daemon_resp . outs [ i ] . key , daemon_resp . outs [ i ] . mask , td . m_global_output_index , daemon_resp . outs [ i ] . unlocked ) ;
}
if ( outs . back ( ) . size ( ) < fake_outputs_count + 1 )
{
@ -4218,7 +4413,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
const transfer_details & td = m_transfers [ idx ] ;
std : : vector < get_outs_entry > v ;
const rct : : key mask = td . is_rct ( ) ? rct : : commit ( td . amount ( ) , td . m_mask ) : rct : : zeroCommit ( td . amount ( ) ) ;
v . push_back ( std : : make_tuple ( td . m_global_output_index , boost: : get < txout_to_key > ( td . m_tx . vout [ td . m_internal_output_index ] . target ) . key , mask ) ) ;
v . push_back ( std : : make_tuple ( td . m_global_output_index , td. get_public_key ( ) , mask ) ) ;
outs . push_back ( v ) ;
}
}
@ -4433,6 +4628,9 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
src . rct = td . is_rct ( ) ;
//paste mixin transaction
THROW_WALLET_EXCEPTION_IF ( outs . size ( ) < out_index + 1 , error : : wallet_internal_error , " outs.size() < out_index + 1 " ) ;
THROW_WALLET_EXCEPTION_IF ( outs [ out_index ] . size ( ) < fake_outputs_count , error : : wallet_internal_error , " fake_outputs_count > random outputs found " ) ;
typedef cryptonote : : tx_source_entry : : output_entry tx_output_entry ;
for ( size_t n = 0 ; n < fake_outputs_count + 1 ; + + n )
{
@ -4454,7 +4652,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
tx_output_entry real_oe ;
real_oe . first = td . m_global_output_index ;
real_oe . second . dest = rct : : pk2rct ( boost: : get < txout_to_key > ( td . m_tx . vout [ td . m_internal_output_index ] . target ) . key ) ;
real_oe . second . dest = rct : : pk2rct ( td. get_public_key ( ) ) ;
real_oe . second . mask = rct : : commit ( td . amount ( ) , td . m_mask ) ;
* it_to_replace = real_oe ;
src . real_out_tx_key = get_tx_pub_key_from_extra ( td . m_tx , td . m_pk_index ) ;
@ -4701,6 +4899,445 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr
return count ;
}
bool wallet2 : : light_wallet_login ( bool & new_address )
{
MDEBUG ( " Light wallet login request " ) ;
m_light_wallet_connected = false ;
cryptonote : : COMMAND_RPC_LOGIN : : request request ;
cryptonote : : COMMAND_RPC_LOGIN : : response response ;
request . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
request . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
// Always create account if it doesnt exist.
request . create_account = true ;
m_daemon_rpc_mutex . lock ( ) ;
bool connected = epee : : net_utils : : invoke_http_json ( " /login " , request , response , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
// MyMonero doesn't send any status message. OpenMonero does.
m_light_wallet_connected = connected & & ( response . status . empty ( ) | | response . status = = " success " ) ;
new_address = response . new_address ;
MDEBUG ( " Status: " < < response . status ) ;
MDEBUG ( " Reason: " < < response . reason ) ;
MDEBUG ( " New wallet: " < < response . new_address ) ;
if ( m_light_wallet_connected )
{
// Clear old data on successfull login.
// m_transfers.clear();
// m_payments.clear();
// m_unconfirmed_payments.clear();
}
return m_light_wallet_connected ;
}
bool wallet2 : : light_wallet_import_wallet_request ( cryptonote : : COMMAND_RPC_IMPORT_WALLET_REQUEST : : response & response )
{
MDEBUG ( " Light wallet import wallet request " ) ;
cryptonote : : COMMAND_RPC_IMPORT_WALLET_REQUEST : : request oreq ;
oreq . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
oreq . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /import_wallet_request " , oreq , response , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " import_wallet_request " ) ;
return true ;
}
void wallet2 : : light_wallet_get_unspent_outs ( )
{
MDEBUG ( " Getting unspent outs " ) ;
cryptonote : : COMMAND_RPC_GET_UNSPENT_OUTS : : request oreq ;
cryptonote : : COMMAND_RPC_GET_UNSPENT_OUTS : : response ores ;
oreq . amount = " 0 " ;
oreq . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
oreq . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
// openMonero specific
oreq . dust_threshold = boost : : lexical_cast < std : : string > ( : : config : : DEFAULT_DUST_THRESHOLD ) ;
// below are required by openMonero api - but are not used.
oreq . mixin = 0 ;
oreq . use_dust = true ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /get_unspent_outs " , oreq , ores , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " get_unspent_outs " ) ;
THROW_WALLET_EXCEPTION_IF ( ores . status = = " error " , error : : wallet_internal_error , ores . reason ) ;
m_light_wallet_per_kb_fee = ores . per_kb_fee ;
std : : unordered_map < crypto : : hash , bool > transfers_txs ;
for ( const auto & t : m_transfers )
transfers_txs . emplace ( t . m_txid , t . m_spent ) ;
MDEBUG ( " FOUND " < < ores . outputs . size ( ) < < " outputs " ) ;
// return if no outputs found
if ( ores . outputs . empty ( ) )
return ;
// Clear old outputs
m_transfers . clear ( ) ;
for ( const auto & o : ores . outputs ) {
bool spent = false ;
bool add_transfer = true ;
crypto : : key_image unspent_key_image ;
crypto : : public_key tx_public_key = AUTO_VAL_INIT ( tx_public_key ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , o . tx_pub_key ) , error : : wallet_internal_error , " Invalid tx_pub_key field " ) ;
string_tools : : hex_to_pod ( o . tx_pub_key , tx_public_key ) ;
for ( const std : : string & ski : o . spend_key_images ) {
spent = false ;
// Check if key image is ours
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , ski ) , error : : wallet_internal_error , " Invalid key image " ) ;
string_tools : : hex_to_pod ( ski , unspent_key_image ) ;
if ( light_wallet_key_image_is_ours ( unspent_key_image , tx_public_key , o . index ) ) {
MTRACE ( " Output " < < o . public_key < < " is spent. Key image: " < < ski ) ;
spent = true ;
break ;
} {
MTRACE ( " Unspent output found. " < < o . public_key ) ;
}
}
// Check if tx already exists in m_transfers.
crypto : : hash txid ;
crypto : : public_key tx_pub_key ;
crypto : : public_key public_key ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , o . tx_hash ) , error : : wallet_internal_error , " Invalid tx_hash field " ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , o . public_key ) , error : : wallet_internal_error , " Invalid public_key field " ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , o . tx_pub_key ) , error : : wallet_internal_error , " Invalid tx_pub_key field " ) ;
string_tools : : hex_to_pod ( o . tx_hash , txid ) ;
string_tools : : hex_to_pod ( o . public_key , public_key ) ;
string_tools : : hex_to_pod ( o . tx_pub_key , tx_pub_key ) ;
for ( auto & t : m_transfers ) {
if ( t . get_public_key ( ) = = public_key ) {
t . m_spent = spent ;
add_transfer = false ;
break ;
}
}
if ( ! add_transfer )
continue ;
m_transfers . push_back ( boost : : value_initialized < transfer_details > ( ) ) ;
transfer_details & td = m_transfers . back ( ) ;
td . m_block_height = o . height ;
td . m_global_output_index = o . global_index ;
td . m_txid = txid ;
// Add to extra
add_tx_pub_key_to_extra ( td . m_tx , tx_pub_key ) ;
td . m_key_image = unspent_key_image ;
td . m_key_image_known = ! m_watch_only ;
td . m_amount = o . amount ;
td . m_pk_index = 0 ;
td . m_internal_output_index = o . index ;
td . m_spent = spent ;
tx_out txout ;
txout . target = txout_to_key ( public_key ) ;
txout . amount = td . m_amount ;
td . m_tx . vout . resize ( td . m_internal_output_index + 1 ) ;
td . m_tx . vout [ td . m_internal_output_index ] = txout ;
// Add unlock time and coinbase bool got from get_address_txs api call
std : : unordered_map < crypto : : hash , address_tx > : : const_iterator found = m_light_wallet_address_txs . find ( txid ) ;
THROW_WALLET_EXCEPTION_IF ( found = = m_light_wallet_address_txs . end ( ) , error : : wallet_internal_error , " Lightwallet: tx not found in m_light_wallet_address_txs " ) ;
bool miner_tx = found - > second . m_coinbase ;
td . m_tx . unlock_time = found - > second . m_unlock_time ;
if ( ! o . rct . empty ( ) )
{
// Coinbase tx's
if ( miner_tx )
{
td . m_mask = rct : : identity ( ) ;
}
else
{
// rct txs
// decrypt rct mask, calculate commit hash and compare against blockchain commit hash
rct : : key rct_commit ;
light_wallet_parse_rct_str ( o . rct , tx_pub_key , td . m_internal_output_index , td . m_mask , rct_commit , true ) ;
bool valid_commit = ( rct_commit = = rct : : commit ( td . amount ( ) , td . m_mask ) ) ;
if ( ! valid_commit )
{
MDEBUG ( " output index: " < < o . global_index ) ;
MDEBUG ( " mask: " + string_tools : : pod_to_hex ( td . m_mask ) ) ;
MDEBUG ( " calculated commit: " + string_tools : : pod_to_hex ( rct : : commit ( td . amount ( ) , td . m_mask ) ) ) ;
MDEBUG ( " expected commit: " + string_tools : : pod_to_hex ( rct_commit ) ) ;
MDEBUG ( " amount: " < < td . amount ( ) ) ;
}
THROW_WALLET_EXCEPTION_IF ( ! valid_commit , error : : wallet_internal_error , " Lightwallet: rct commit hash mismatch! " ) ;
}
td . m_rct = true ;
}
else
{
td . m_mask = rct : : identity ( ) ;
td . m_rct = false ;
}
if ( ! spent )
set_unspent ( m_transfers . size ( ) - 1 ) ;
m_key_images [ td . m_key_image ] = m_transfers . size ( ) - 1 ;
m_pub_keys [ td . get_public_key ( ) ] = m_transfers . size ( ) - 1 ;
}
}
bool wallet2 : : light_wallet_get_address_info ( cryptonote : : COMMAND_RPC_GET_ADDRESS_INFO : : response & response )
{
MTRACE ( __FUNCTION__ ) ;
cryptonote : : COMMAND_RPC_GET_ADDRESS_INFO : : request request ;
request . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
request . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /get_address_info " , request , response , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " get_address_info " ) ;
// TODO: Validate result
return true ;
}
void wallet2 : : light_wallet_get_address_txs ( )
{
MDEBUG ( " Refreshing light wallet " ) ;
cryptonote : : COMMAND_RPC_GET_ADDRESS_TXS : : request ireq ;
cryptonote : : COMMAND_RPC_GET_ADDRESS_TXS : : response ires ;
ireq . address = get_account ( ) . get_public_address_str ( m_testnet ) ;
ireq . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = epee : : net_utils : : invoke_http_json ( " /get_address_txs " , ireq , ires , m_http_client , rpc_timeout , " POST " ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " get_address_txs " ) ;
//OpenMonero sends status=success, Mymonero doesn't.
THROW_WALLET_EXCEPTION_IF ( ( ! ires . status . empty ( ) & & ires . status ! = " success " ) , error : : no_connection_to_daemon , " get_address_txs " ) ;
// Abort if no transactions
if ( ires . transactions . empty ( ) )
return ;
// Create searchable vectors
std : : vector < crypto : : hash > payments_txs ;
for ( const auto & p : m_payments )
payments_txs . push_back ( p . second . m_tx_hash ) ;
std : : vector < crypto : : hash > unconfirmed_payments_txs ;
for ( const auto & up : m_unconfirmed_payments )
unconfirmed_payments_txs . push_back ( up . second . m_tx_hash ) ;
// for balance calculation
uint64_t wallet_total_sent = 0 ;
uint64_t wallet_total_unlocked_sent = 0 ;
// txs in pool
std : : vector < crypto : : hash > pool_txs ;
for ( const auto & t : ires . transactions ) {
const uint64_t total_received = t . total_received ;
uint64_t total_sent = t . total_sent ;
// Check key images - subtract fake outputs from total_sent
for ( const auto & so : t . spent_outputs )
{
crypto : : public_key tx_public_key ;
crypto : : key_image key_image ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , so . tx_pub_key ) , error : : wallet_internal_error , " Invalid tx_pub_key field " ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , so . key_image ) , error : : wallet_internal_error , " Invalid key_image field " ) ;
string_tools : : hex_to_pod ( so . tx_pub_key , tx_public_key ) ;
string_tools : : hex_to_pod ( so . key_image , key_image ) ;
if ( ! light_wallet_key_image_is_ours ( key_image , tx_public_key , so . out_index ) ) {
THROW_WALLET_EXCEPTION_IF ( so . amount > t . total_sent , error : : wallet_internal_error , " Lightwallet: total sent is negative! " ) ;
total_sent - = so . amount ;
}
}
// Do not add tx if empty.
if ( total_sent = = 0 & & total_received = = 0 )
continue ;
crypto : : hash payment_id = null_hash ;
crypto : : hash tx_hash ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , t . payment_id ) , error : : wallet_internal_error , " Invalid payment_id field " ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , t . hash ) , error : : wallet_internal_error , " Invalid hash field " ) ;
string_tools : : hex_to_pod ( t . payment_id , payment_id ) ;
string_tools : : hex_to_pod ( t . hash , tx_hash ) ;
// lightwallet specific info
bool incoming = ( total_received > total_sent ) ;
address_tx address_tx ;
address_tx . m_tx_hash = tx_hash ;
address_tx . m_incoming = incoming ;
address_tx . m_amount = incoming ? total_received - total_sent : total_sent - total_received ;
address_tx . m_block_height = t . height ;
address_tx . m_unlock_time = t . unlock_time ;
address_tx . m_timestamp = t . timestamp ;
address_tx . m_coinbase = t . coinbase ;
address_tx . m_mempool = t . mempool ;
m_light_wallet_address_txs . emplace ( tx_hash , address_tx ) ;
// populate data needed for history (m_payments, m_unconfirmed_payments, m_confirmed_txs)
// INCOMING transfers
if ( total_received > total_sent ) {
payment_details payment ;
payment . m_tx_hash = tx_hash ;
payment . m_amount = total_received - total_sent ;
payment . m_block_height = t . height ;
payment . m_unlock_time = t . unlock_time ;
payment . m_timestamp = t . timestamp ;
if ( t . mempool ) {
if ( std : : find ( unconfirmed_payments_txs . begin ( ) , unconfirmed_payments_txs . end ( ) , tx_hash ) = = unconfirmed_payments_txs . end ( ) ) {
pool_txs . push_back ( tx_hash ) ;
m_unconfirmed_payments . emplace ( tx_hash , payment ) ;
if ( 0 ! = m_callback ) {
m_callback - > on_lw_unconfirmed_money_received ( t . height , payment . m_tx_hash , payment . m_amount ) ;
}
}
} else {
if ( std : : find ( payments_txs . begin ( ) , payments_txs . end ( ) , tx_hash ) = = payments_txs . end ( ) ) {
m_payments . emplace ( tx_hash , payment ) ;
if ( 0 ! = m_callback ) {
m_callback - > on_lw_money_received ( t . height , payment . m_tx_hash , payment . m_amount ) ;
}
}
}
// Outgoing transfers
} else {
uint64_t amount_sent = total_sent - total_received ;
cryptonote : : transaction dummy_tx ; // not used by light wallet
// increase wallet total sent
wallet_total_sent + = total_sent ;
if ( t . mempool )
{
// Handled by add_unconfirmed_tx in commit_tx
// If sent from another wallet instance we need to add it
if ( m_unconfirmed_txs . find ( tx_hash ) = = m_unconfirmed_txs . end ( ) )
{
unconfirmed_transfer_details utd ;
utd . m_amount_in = amount_sent ;
utd . m_amount_out = amount_sent ;
utd . m_change = 0 ;
utd . m_payment_id = payment_id ;
utd . m_timestamp = t . timestamp ;
utd . m_state = wallet2 : : unconfirmed_transfer_details : : pending ;
m_unconfirmed_txs . emplace ( tx_hash , utd ) ;
}
}
else
{
// Only add if new
auto confirmed_tx = m_confirmed_txs . find ( tx_hash ) ;
if ( confirmed_tx = = m_confirmed_txs . end ( ) ) {
// tx is added to m_unconfirmed_txs - move to confirmed
if ( m_unconfirmed_txs . find ( tx_hash ) ! = m_unconfirmed_txs . end ( ) )
{
process_unconfirmed ( tx_hash , dummy_tx , t . height ) ;
}
// Tx sent by another wallet instance
else
{
confirmed_transfer_details ctd ;
ctd . m_amount_in = amount_sent ;
ctd . m_amount_out = amount_sent ;
ctd . m_change = 0 ;
ctd . m_payment_id = payment_id ;
ctd . m_block_height = t . height ;
ctd . m_timestamp = t . timestamp ;
m_confirmed_txs . emplace ( tx_hash , ctd ) ;
}
if ( 0 ! = m_callback )
{
m_callback - > on_lw_money_spent ( t . height , tx_hash , amount_sent ) ;
}
}
// If not new - check the amount and update if necessary.
// when sending a tx to same wallet the receiving amount has to be credited
else
{
if ( confirmed_tx - > second . m_amount_in ! = amount_sent | | confirmed_tx - > second . m_amount_out ! = amount_sent )
{
MDEBUG ( " Adjusting amount sent/received for tx: < " + t . hash + " >. Is tx sent to own wallet? " < < print_money ( amount_sent ) < < " != " < < print_money ( confirmed_tx - > second . m_amount_in ) ) ;
confirmed_tx - > second . m_amount_in = amount_sent ;
confirmed_tx - > second . m_amount_out = amount_sent ;
confirmed_tx - > second . m_change = 0 ;
}
}
}
}
}
// TODO: purge old unconfirmed_txs
remove_obsolete_pool_txs ( pool_txs ) ;
// Calculate wallet balance
m_light_wallet_balance = ires . total_received - wallet_total_sent ;
// MyMonero doesnt send unlocked balance
if ( ires . total_received_unlocked > 0 )
m_light_wallet_unlocked_balance = ires . total_received_unlocked - wallet_total_sent ;
else
m_light_wallet_unlocked_balance = m_light_wallet_balance ;
}
bool wallet2 : : light_wallet_parse_rct_str ( const std : : string & rct_string , const crypto : : public_key & tx_pub_key , uint64_t internal_output_index , rct : : key & decrypted_mask , rct : : key & rct_commit , bool decrypt ) const
{
// rct string is empty if output is non RCT
if ( rct_string . empty ( ) )
return false ;
// rct_string is a string with length 64+64+64 (<rct commit> + <encrypted mask> + <rct amount>)
rct : : key encrypted_mask ;
std : : string rct_commit_str = rct_string . substr ( 0 , 64 ) ;
std : : string encrypted_mask_str = rct_string . substr ( 64 , 64 ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , rct_commit_str ) , error : : wallet_internal_error , " Invalid rct commit hash: " + rct_commit_str ) ;
THROW_WALLET_EXCEPTION_IF ( string_tools : : validate_hex ( 64 , encrypted_mask_str ) , error : : wallet_internal_error , " Invalid rct mask: " + encrypted_mask_str ) ;
string_tools : : hex_to_pod ( rct_commit_str , rct_commit ) ;
string_tools : : hex_to_pod ( encrypted_mask_str , encrypted_mask ) ;
if ( decrypt ) {
// Decrypt the mask
crypto : : key_derivation derivation ;
generate_key_derivation ( tx_pub_key , get_account ( ) . get_keys ( ) . m_view_secret_key , derivation ) ;
crypto : : secret_key scalar ;
crypto : : derivation_to_scalar ( derivation , internal_output_index , scalar ) ;
sc_sub ( decrypted_mask . bytes , encrypted_mask . bytes , rct : : hash_to_scalar ( rct : : sk2rct ( scalar ) ) . bytes ) ;
}
return true ;
}
bool wallet2 : : light_wallet_key_image_is_ours ( const crypto : : key_image & key_image , const crypto : : public_key & tx_public_key , uint64_t out_index )
{
// Lookup key image from cache
std : : map < uint64_t , crypto : : key_image > index_keyimage_map ;
std : : unordered_map < crypto : : public_key , std : : map < uint64_t , crypto : : key_image > > : : const_iterator found_pub_key = m_key_image_cache . find ( tx_public_key ) ;
if ( found_pub_key ! = m_key_image_cache . end ( ) ) {
// pub key found. key image for index cached?
index_keyimage_map = found_pub_key - > second ;
std : : map < uint64_t , crypto : : key_image > : : const_iterator index_found = index_keyimage_map . find ( out_index ) ;
if ( index_found ! = index_keyimage_map . end ( ) )
return key_image = = index_found - > second ;
}
// Not in cache - calculate key image
crypto : : key_image calculated_key_image ;
cryptonote : : keypair in_ephemeral ;
cryptonote : : generate_key_image_helper ( get_account ( ) . get_keys ( ) , tx_public_key , out_index , in_ephemeral , calculated_key_image ) ;
index_keyimage_map . emplace ( out_index , calculated_key_image ) ;
m_key_image_cache . emplace ( tx_public_key , index_keyimage_map ) ;
return key_image = = calculated_key_image ;
}
// Another implementation of transaction creation that is hopefully better
// While there is anything left to pay, it goes through random outputs and tries
// to fill the next destination/amount. If it fully fills it, it will use the
@ -4718,6 +5355,10 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr
// usable balance.
std : : vector < wallet2 : : pending_tx > wallet2 : : create_transactions_2 ( std : : vector < cryptonote : : tx_destination_entry > dsts , const size_t fake_outs_count , const uint64_t unlock_time , uint32_t priority , const std : : vector < uint8_t > & extra , uint32_t subaddr_account , std : : set < uint32_t > subaddr_indices , bool trusted_daemon )
{
if ( m_light_wallet ) {
// Populate m_transfers
light_wallet_get_unspent_outs ( ) ;
}
std : : vector < std : : pair < uint32_t , std : : vector < size_t > > > unused_transfers_indices_per_subaddr ;
std : : vector < std : : pair < uint32_t , std : : vector < size_t > > > unused_dust_indices_per_subaddr ;
uint64_t needed_money ;
@ -4880,7 +5521,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
std : : vector < size_t > preferred_inputs ;
uint64_t rct_outs_needed = 2 * ( fake_outs_count + 1 ) ;
rct_outs_needed + = 100 ; // some fudge factor since we don't know how many are locked
if ( use_rct & & get_num_rct_outputs ( ) > = rct_outs_needed )
if ( use_rct )
{
// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
// will get us a known fee.
@ -5334,6 +5975,9 @@ void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height)
//----------------------------------------------------------------------------------------------------
bool wallet2 : : use_fork_rules ( uint8_t version , int64_t early_blocks )
{
// TODO: How to get fork rule info from light wallet node?
if ( m_light_wallet )
return true ;
uint64_t height , earliest_height ;
boost : : optional < std : : string > result = m_node_rpc_proxy . get_height ( height ) ;
throw_on_rpc_response_error ( result , " get_info " ) ;