@ -1198,12 +1198,6 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
m_account_public_address { crypto : : null_pkey , crypto : : null_pkey } ,
m_subaddress_lookahead_major ( SUBADDRESS_LOOKAHEAD_MAJOR ) ,
m_subaddress_lookahead_minor ( SUBADDRESS_LOOKAHEAD_MINOR ) ,
m_light_wallet ( false ) ,
m_light_wallet_scanned_block_height ( 0 ) ,
m_light_wallet_blockchain_height ( 0 ) ,
m_light_wallet_connected ( false ) ,
m_light_wallet_balance ( 0 ) ,
m_light_wallet_unlocked_balance ( 0 ) ,
m_original_keys_available ( false ) ,
m_message_store ( http_client_factory - > create ( ) ) ,
m_key_device_type ( hw : : device : : device_type : : SOFTWARE ) ,
@ -3605,38 +3599,6 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
return ;
}
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.
tools : : 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 ;
// 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 ;
@ -5576,16 +5538,6 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout, b
return false ;
}
// TODO: Add light wallet version check.
if ( m_light_wallet ) {
m_rpc_version = 0 ;
if ( version )
* version = 0 ;
if ( ssl )
* ssl = m_light_wallet_connected ; // light wallet is always SSL
return m_light_wallet_connected ;
}
{
boost : : lock_guard < boost : : recursive_mutex > lock ( m_daemon_rpc_mutex ) ;
if ( ! m_http_client - > is_connected ( ssl ) )
@ -6111,8 +6063,6 @@ boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epe
uint64_t wallet2 : : balance ( uint32_t index_major , bool strict ) const
{
uint64_t amount = 0 ;
if ( m_light_wallet )
return m_light_wallet_balance ;
for ( const auto & i : balance_per_subaddress ( index_major , strict ) )
amount + = i . second ;
return amount ;
@ -6125,8 +6075,6 @@ uint64_t wallet2::unlocked_balance(uint32_t index_major, bool strict, uint64_t *
* blocks_to_unlock = 0 ;
if ( time_to_unlock )
* time_to_unlock = 0 ;
if ( m_light_wallet )
return m_light_wallet_unlocked_balance ;
for ( const auto & i : unlocked_balance_per_subaddress ( index_major , strict ) )
{
amount + = i . second . first ;
@ -6661,43 +6609,25 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const
void wallet2 : : commit_tx ( pending_tx & ptx )
{
using namespace cryptonote ;
if ( m_light_wallet )
// 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 ;
req . do_sanity_checks = true ;
COMMAND_RPC_SEND_RAW_TX : : response daemon_send_resp ;
{
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_nettype ) ;
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 ) ) ;
{
const boost : : lock_guard < boost : : recursive_mutex > lock { m_daemon_rpc_mutex } ;
bool r = epee : : net_utils : : invoke_http_json ( " /submit_raw_tx " , oreq , ores , * m_http_client , rpc_timeout , " POST " ) ;
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 , get_rpc_status ( ores . status ) , ores . error ) ;
}
const boost : : lock_guard < boost : : recursive_mutex > lock { m_daemon_rpc_mutex } ;
bool r = epee : : net_utils : : invoke_http_json ( " /sendrawtransaction " , req , daemon_send_resp , * m_http_client , rpc_timeout ) ;
THROW_ON_RPC_RESPONSE_ERROR ( r , { } , daemon_send_resp , " sendrawtransaction " , error : : tx_rejected , ptx . tx , get_rpc_status ( daemon_send_resp . status ) , get_text_reason ( daemon_send_resp ) ) ;
}
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 ;
req . do_sanity_checks = true ;
COMMAND_RPC_SEND_RAW_TX : : response daemon_send_resp ;
{
const boost : : lock_guard < boost : : recursive_mutex > lock { m_daemon_rpc_mutex } ;
bool r = epee : : net_utils : : invoke_http_json ( " /sendrawtransaction " , req , daemon_send_resp , * m_http_client , rpc_timeout ) ;
THROW_ON_RPC_RESPONSE_ERROR ( r , { } , daemon_send_resp , " sendrawtransaction " , error : : tx_rejected , ptx . tx , get_rpc_status ( daemon_send_resp . status ) , get_text_reason ( daemon_send_resp ) ) ;
}
// 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 ) ) ;
}
// 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 ;
@ -7670,13 +7600,6 @@ uint64_t wallet2::get_dynamic_base_fee_estimate()
//----------------------------------------------------------------------------------------------------
uint64_t wallet2 : : get_base_fee ( )
{
if ( m_light_wallet )
{
if ( use_fork_rules ( HF_VERSION_PER_BYTE_FEE ) )
return m_light_wallet_per_kb_fee / 1024 ;
else
return m_light_wallet_per_kb_fee ;
}
bool use_dyn_fee = use_fork_rules ( HF_VERSION_DYNAMIC_FEE , - 30 * 1 ) ;
if ( ! use_dyn_fee )
return FEE_PER_KB ;
@ -7720,10 +7643,6 @@ uint64_t wallet2::get_base_fee(uint32_t priority)
//----------------------------------------------------------------------------------------------------
uint64_t wallet2 : : get_fee_quantization_mask ( )
{
if ( m_light_wallet )
{
return 1 ; // TODO
}
bool use_per_byte_fee = use_fork_rules ( HF_VERSION_PER_BYTE_FEE , 0 ) ;
if ( ! use_per_byte_fee )
return 1 ;
@ -8198,113 +8117,6 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out
return true ;
}
void wallet2 : : light_wallet_get_outs ( std : : vector < std : : vector < tools : : wallet2 : : get_outs_entry > > & outs , const std : : vector < size_t > & selected_transfers , size_t fake_outputs_count ) {
MDEBUG ( " LIGHTWALLET - Getting random outs " ) ;
tools : : COMMAND_RPC_GET_RANDOM_OUTS : : request oreq ;
tools : : 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 ;
{
const boost : : lock_guard < boost : : recursive_mutex > lock { m_daemon_rpc_mutex } ;
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 received from light wallet node. Error: " + ores . Error ) ;
}
// Check if we got enough outputs for each amount
for ( auto & out : ores . amount_outs ) {
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 ( ) ) ;
std : : unordered_set < crypto : : public_key > valid_public_keys_cache ;
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 ( ) , crypto : : random_device { } ) ;
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 , valid_public_keys_cache ) ) {
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 ) ) ;
}
}
std : : pair < std : : set < uint64_t > , size_t > outs_unique ( const std : : vector < std : : vector < tools : : wallet2 : : get_outs_entry > > & outs )
{
std : : set < uint64_t > unique ;
@ -8355,11 +8167,6 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
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 )
{
uint64_t segregation_fork_height = get_segregation_fork_height ( ) ;
@ -9619,476 +9426,6 @@ 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 ;
tools : : COMMAND_RPC_LOGIN : : request request ;
tools : : COMMAND_RPC_LOGIN : : response response ;
request . address = get_account ( ) . get_public_address_str ( m_nettype ) ;
request . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
// Always create account if it doesn't exist.
request . create_account = true ;
m_daemon_rpc_mutex . lock ( ) ;
bool connected = invoke_http_json ( " /login " , request , response , 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 successful login.
// m_transfers.clear();
// m_payments.clear();
// m_unconfirmed_payments.clear();
}
return m_light_wallet_connected ;
}
bool wallet2 : : light_wallet_import_wallet_request ( tools : : COMMAND_RPC_IMPORT_WALLET_REQUEST : : response & response )
{
MDEBUG ( " Light wallet import wallet request " ) ;
tools : : COMMAND_RPC_IMPORT_WALLET_REQUEST : : request oreq ;
oreq . address = get_account ( ) . get_public_address_str ( m_nettype ) ;
oreq . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = invoke_http_json ( " /import_wallet_request " , oreq , response , 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 " ) ;
tools : : COMMAND_RPC_GET_UNSPENT_OUTS : : request oreq ;
tools : : COMMAND_RPC_GET_UNSPENT_OUTS : : response ores ;
oreq . amount = " 0 " ;
oreq . address = get_account ( ) . get_public_address_str ( m_nettype ) ;
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 = invoke_http_json ( " /get_unspent_outs " , oreq , ores , 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 ( 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 & & ! m_multisig ;
td . m_key_image_request = false ;
td . m_key_image_partial = m_multisig ;
td . m_amount = o . amount ;
td . m_pk_index = 0 ;
td . m_internal_output_index = o . index ;
td . m_spent = spent ;
td . m_frozen = false ;
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 ( tools : : COMMAND_RPC_GET_ADDRESS_INFO : : response & response )
{
MTRACE ( __FUNCTION__ ) ;
tools : : COMMAND_RPC_GET_ADDRESS_INFO : : request request ;
request . address = get_account ( ) . get_public_address_str ( m_nettype ) ;
request . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = invoke_http_json ( " /get_address_info " , request , response , 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 " ) ;
tools : : COMMAND_RPC_GET_ADDRESS_TXS : : request ireq ;
tools : : COMMAND_RPC_GET_ADDRESS_TXS : : response ires ;
ireq . address = get_account ( ) . get_public_address_str ( m_nettype ) ;
ireq . view_key = string_tools : : pod_to_hex ( get_account ( ) . get_keys ( ) . m_view_secret_key ) ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = invoke_http_json ( " /get_address_txs " , ireq , ires , 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_pd . m_tx_hash ) ;
// for balance calculation
uint64_t wallet_total_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_fee = 0 ; // TODO
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_fee = 0 ; // TODO
payment . m_block_height = t . height ;
payment . m_unlock_time = t . unlock_time ;
payment . m_timestamp = t . timestamp ;
payment . m_coinbase = t . coinbase ;
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 ) ;
// assume false as we don't get that info from the light wallet server
crypto : : hash payment_id ;
THROW_WALLET_EXCEPTION_IF ( ! epee : : string_tools : : hex_to_pod ( t . payment_id , payment_id ) ,
error : : wallet_internal_error , " Failed to parse payment id " ) ;
emplace_or_replace ( m_unconfirmed_payments , payment_id , pool_payment_details { payment , false } ) ;
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 , false ) ;
// Calculate wallet balance
m_light_wallet_balance = ires . total_received - wallet_total_sent ;
// MyMonero doesn't 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 ;
bool r = generate_key_derivation ( tx_pub_key , get_account ( ) . get_keys ( ) . m_view_secret_key , derivation ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : wallet_internal_error , " Failed to generate 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
serializable_map < uint64_t , crypto : : key_image > index_keyimage_map ;
serializable_unordered_map < crypto : : public_key , serializable_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 ;
// Subaddresses aren't supported in mymonero/openmonero yet. Roll out the original scheme:
// compute D = a*R
// compute P = Hs(D || i)*G + B
// compute x = Hs(D || i) + b (and check if P==x*G)
// compute I = x*Hp(P)
const account_keys & ack = get_account ( ) . get_keys ( ) ;
crypto : : key_derivation derivation ;
bool r = crypto : : generate_key_derivation ( tx_public_key , ack . m_view_secret_key , derivation ) ;
CHECK_AND_ASSERT_MES ( r , false , " failed to generate_key_derivation( " < < tx_public_key < < " , " < < ack . m_view_secret_key < < " ) " ) ;
r = crypto : : derive_public_key ( derivation , out_index , ack . m_account_address . m_spend_public_key , in_ephemeral . pub ) ;
CHECK_AND_ASSERT_MES ( r , false , " failed to derive_public_key ( " < < derivation < < " , " < < out_index < < " , " < < ack . m_account_address . m_spend_public_key < < " ) " ) ;
crypto : : derive_secret_key ( derivation , out_index , ack . m_spend_secret_key , in_ephemeral . sec ) ;
crypto : : public_key out_pkey_test ;
r = crypto : : secret_key_to_public_key ( in_ephemeral . sec , out_pkey_test ) ;
CHECK_AND_ASSERT_MES ( r , false , " failed to secret_key_to_public_key( " < < in_ephemeral . sec < < " ) " ) ;
CHECK_AND_ASSERT_MES ( in_ephemeral . pub = = out_pkey_test , false , " derived secret key doesn't match derived public key " ) ;
crypto : : generate_key_image ( in_ephemeral . pub , in_ephemeral . sec , 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
@ -10113,10 +9450,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
auto original_dsts = dsts ;
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 ;
@ -11179,9 +10512,6 @@ 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_WALLET_EXCEPTION_IF ( result , error : : wallet_internal_error , " Failed to get height " ) ;