BitShares-Core  5.0.0
BitShares blockchain implementation and command-line interface software
wallet_api_impl.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2017 Cryptonomex, Inc., and contributors.
3  *
4  * The MIT License
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 #include <boost/algorithm/string/replace.hpp>
25 #include <boost/range/adaptors.hpp>
26 
28 #include <fc/popcount.hpp>
29 #include <fc/git_revision.hpp>
31 #include <fc/io/fstream.hpp>
32 
34 #include "wallet_api_impl.hpp"
36 
37 #ifndef WIN32
38 # include <sys/types.h>
39 # include <sys/stat.h>
40 #endif
41 
42 // explicit instantiation for later use
43 namespace fc {
44  template class api<graphene::wallet::wallet_api, identity_member_with_optionals>;
45 }
46 
47 /****
48  * General methods for wallet impl object (ctor, info, about, wallet file, etc.)
49  */
50 
51 namespace graphene { namespace wallet { namespace detail {
52 
53  wallet_api_impl::wallet_api_impl( wallet_api& s, const wallet_data& initial_data, fc::api<login_api> rapi )
54  : self(s),
55  _chain_id(initial_data.chain_id),
56  _remote_api(rapi),
57  _remote_db(rapi->database()),
58  _remote_net_broadcast(rapi->network_broadcast()),
59  _remote_hist(rapi->history())
60  {
61  try {
62  _custom_operations = rapi->custom_operations();
63  }
64  catch(const fc::exception& e)
65  {
66  wlog("Custom operations API is not active on server.");
67  }
68  chain_id_type remote_chain_id = _remote_db->get_chain_id();
69  if( remote_chain_id != _chain_id )
70  {
71  FC_THROW( "Remote server gave us an unexpected chain_id",
72  ("remote_chain_id", remote_chain_id)
73  ("chain_id", _chain_id) );
74  }
75  init_prototype_ops();
76 
77  _remote_db->set_block_applied_callback( [this](const variant& block_id )
78  {
79  on_block_applied( block_id );
80  } );
81 
83  _wallet.ws_server = initial_data.ws_server;
84  _wallet.ws_user = initial_data.ws_user;
85  _wallet.ws_password = initial_data.ws_password;
86  }
87 
89  {
90  try
91  {
92  _remote_db->cancel_all_subscriptions();
93  }
94  catch (const fc::exception& e)
95  {
96  // Right now the wallet_api has no way of knowing if the connection to the
97  // witness has already disconnected (via the witness node exiting first).
98  // If it has exited, cancel_all_subscriptsions() will throw and there's
99  // nothing we can do about it.
100  // dlog("Caught exception ${e} while canceling database subscriptions", ("e", e));
101  }
102  }
103 
105  {
106  auto chain_props = get_chain_properties();
107  auto global_props = get_global_properties();
108  auto dynamic_props = get_dynamic_global_properties();
110  result["head_block_num"] = dynamic_props.head_block_number;
111  result["head_block_id"] = fc::variant(dynamic_props.head_block_id, 1);
112  result["head_block_age"] = fc::get_approximate_relative_time_string(dynamic_props.time,
114  " old");
115  result["next_maintenance_time"] =
116  fc::get_approximate_relative_time_string(dynamic_props.next_maintenance_time);
117  result["chain_id"] = chain_props.chain_id;
118  stringstream participation;
119  participation << fixed << std::setprecision(2) << (100.0*fc::popcount(dynamic_props.recent_slots_filled)) / 128.0;
120  result["participation"] = participation.str();
121  result["active_witnesses"] = fc::variant(global_props.active_witnesses, GRAPHENE_MAX_NESTED_OBJECTS);
122  result["active_committee_members"] =
123  fc::variant(global_props.active_committee_members, GRAPHENE_MAX_NESTED_OBJECTS);
124  return result;
125  }
126 
127  /***
128  * @brief return basic information about this program
129  */
131  {
132  string client_version( graphene::utilities::git_revision_description );
133  const size_t pos = client_version.find( '/' );
134  if( pos != string::npos && client_version.size() > pos )
135  client_version = client_version.substr( pos + 1 );
136 
138  //result["blockchain_name"] = BLOCKCHAIN_NAME;
139  //result["blockchain_description"] = BTS_BLOCKCHAIN_DESCRIPTION;
140  result["client_version"] = client_version;
141  result["graphene_revision"] = graphene::utilities::git_revision_sha;
142  result["graphene_revision_age"] = fc::get_approximate_relative_time_string( fc::time_point_sec(
144  result["fc_revision"] = fc::git_revision_sha;
147  result["compile_date"] = "compiled on " __DATE__ " at " __TIME__;
148  result["boost_version"] = boost::replace_all_copy(std::string(BOOST_LIB_VERSION), "_", ".");
149  result["openssl_version"] = OPENSSL_VERSION_TEXT;
150 
151  std::string bitness = boost::lexical_cast<std::string>(8 * sizeof(int*)) + "-bit";
152 #if defined(__APPLE__)
153  std::string os = "osx";
154 #elif defined(__linux__)
155  std::string os = "linux";
156 #elif defined(_MSC_VER)
157  std::string os = "win32";
158 #else
159  std::string os = "other";
160 #endif
161  result["build"] = os + " " + bitness;
162 
163  return result;
164  }
165 
167  {
168  ilog( "Quitting Cli Wallet ..." );
169 
170  throw fc::canceled_exception();
171  }
172 
174  {
175  return _remote_db->get_chain_properties();
176  }
178  {
179  return _remote_db->get_global_properties();
180  }
182  {
183  return _remote_db->get_dynamic_global_properties();
184  }
185 
187  {
188  fc::async([this]{resync();}, "Resync after block");
189  }
190 
192  {
193  for( auto& op : tx.operations )
194  s.set_fee(op);
195  }
196 
198  {
199  auto it = _prototype_ops.find( operation_name );
200  if( it == _prototype_ops.end() )
201  FC_THROW("Unsupported operation: \"${operation_name}\"", ("operation_name", operation_name));
202  return it->second;
203  }
204 
205  void wallet_api_impl::init_prototype_ops()
206  {
207  operation op;
208  int64_t op_count = op.count();
209  for( int64_t t=0; t<op_count; t++ )
210  {
211  op.set_which( t );
213  }
214  return;
215  }
216 
217  int wallet_api_impl::find_first_unused_derived_key_index(const fc::ecc::private_key& parent_key)
218  {
219  int first_unused_index = 0;
220  int number_of_consecutive_unused_keys = 0;
221  for (int key_index = 0; ; ++key_index)
222  {
223  fc::ecc::private_key derived_private_key = derive_private_key(key_to_wif(parent_key), key_index);
224  graphene::chain::public_key_type derived_public_key = derived_private_key.get_public_key();
225  if( _keys.find(derived_public_key) == _keys.end() )
226  {
227  if (number_of_consecutive_unused_keys)
228  {
229  ++number_of_consecutive_unused_keys;
230  if (number_of_consecutive_unused_keys > 5)
231  return first_unused_index;
232  }
233  else
234  {
235  first_unused_index = key_index;
236  number_of_consecutive_unused_keys = 1;
237  }
238  }
239  else
240  {
241  // key_index is used
242  first_unused_index = 0;
243  number_of_consecutive_unused_keys = 0;
244  }
245  }
246  }
247 
248  void wallet_api_impl::enable_umask_protection()
249  {
250 #ifdef __unix__
251  _old_umask = umask( S_IRWXG | S_IRWXO );
252 #endif
253  }
254 
255  void wallet_api_impl::disable_umask_protection()
256  {
257 #ifdef __unix__
258  umask( _old_umask );
259 #endif
260  }
261 
262  bool wallet_api_impl::copy_wallet_file( string destination_filename )
263  {
264  fc::path src_path = get_wallet_filename();
265  if( !fc::exists( src_path ) )
266  return false;
267  fc::path dest_path = destination_filename + _wallet_filename_extension;
268  int suffix = 0;
269  while( fc::exists(dest_path) )
270  {
271  ++suffix;
272  dest_path = destination_filename + "-" + to_string( suffix ) + _wallet_filename_extension;
273  }
274  wlog( "backing up wallet ${src} to ${dest}",
275  ("src", src_path)
276  ("dest", dest_path) );
277 
278  fc::path dest_parent = fc::absolute(dest_path).parent_path();
279  try
280  {
281  enable_umask_protection();
282  if( !fc::exists( dest_parent ) )
283  fc::create_directories( dest_parent );
284  fc::copy( src_path, dest_path );
285  disable_umask_protection();
286  }
287  catch(...)
288  {
289  disable_umask_protection();
290  throw;
291  }
292  return true;
293  }
294 
295  /***
296  * @brief returns true if the wallet is unlocked
297  */
299  {
300  return _checksum == fc::sha512();
301  }
302 
303  void wallet_api_impl::resync()
304  {
305  fc::scoped_lock<fc::mutex> lock(_resync_mutex);
306  // this method is used to update wallet_data annotations
307  // e.g. wallet has been restarted and was not notified
308  // of events while it was down
309  //
310  // everything that is done "incremental style" when a push
311  // notification is received, should also be done here
312  // "batch style" by querying the blockchain
313 
315  {
316  // make a vector of the account names pending registration
317  std::vector<string> pending_account_names =
318  boost::copy_range<std::vector<string> >(boost::adaptors::keys(_wallet.pending_account_registrations));
319 
320  // look those up on the blockchain
321  std::vector<fc::optional<graphene::chain::account_object >>
322  pending_account_objects = _remote_db->lookup_account_names( pending_account_names );
323 
324  // if any of them exist, claim them
325  for( const fc::optional<graphene::chain::account_object>& optional_account : pending_account_objects )
326  if( optional_account )
327  claim_registered_account(*optional_account);
328  }
329 
331  {
332  // make a vector of the owner accounts for witnesses pending registration
333  std::vector<string> pending_witness_names =
334  boost::copy_range<std::vector<string> >(boost::adaptors::keys(_wallet.pending_witness_registrations));
335 
336  // look up the owners on the blockchain
337  std::vector<fc::optional<graphene::chain::account_object>> owner_account_objects =
338  _remote_db->lookup_account_names(pending_witness_names);
339 
340  // if any of them have registered witnesses, claim them
341  for( const fc::optional<graphene::chain::account_object>& optional_account : owner_account_objects )
342  if (optional_account)
343  {
344  std::string account_id = account_id_to_string(optional_account->id);
345  fc::optional<witness_object> witness_obj = _remote_db->get_witness_by_account(account_id);
346  if (witness_obj)
347  claim_registered_witness(optional_account->name);
348  }
349  }
350  }
351 
353  {
354  return _wallet_filename;
355  }
356 
357  bool wallet_api_impl::load_wallet_file(string wallet_filename)
358  {
359  // TODO: Merge imported wallet with existing wallet,
360  // instead of replacing it
361  if( wallet_filename == "" )
362  wallet_filename = _wallet_filename;
363 
364  if( ! fc::exists( wallet_filename ) )
365  return false;
366 
368  if( _wallet.chain_id != _chain_id )
369  FC_THROW( "Wallet chain ID does not match",
370  ("wallet.chain_id", _wallet.chain_id)
371  ("chain_id", _chain_id) );
372 
373  size_t account_pagination = 100;
374  vector< std::string > account_ids_to_send;
375  size_t n = _wallet.my_accounts.size();
376  account_ids_to_send.reserve( std::min( account_pagination, n ) );
377  auto it = _wallet.my_accounts.begin();
378 
379  for( size_t start=0; start<n; start+=account_pagination )
380  {
381  size_t end = std::min( start+account_pagination, n );
382  assert( end > start );
383  account_ids_to_send.clear();
384  std::vector< account_object > old_accounts;
385  for( size_t i=start; i<end; i++ )
386  {
387  assert( it != _wallet.my_accounts.end() );
388  old_accounts.push_back( *it );
389  std::string account_id = account_id_to_string(old_accounts.back().id);
390  account_ids_to_send.push_back( account_id );
391  ++it;
392  }
393  std::vector< optional< account_object > > accounts = _remote_db->get_accounts(account_ids_to_send, {});
394  // server response should be same length as request
395  FC_ASSERT( accounts.size() == account_ids_to_send.size() );
396  size_t i = 0;
397  for( const optional< account_object >& acct : accounts )
398  {
399  account_object& old_acct = old_accounts[i];
400  if( !acct.valid() )
401  {
402  elog( "Could not find account ${id} : \"${name}\" does not exist on the chain!",
403  ("id", old_acct.id)("name", old_acct.name) );
404  i++;
405  continue;
406  }
407  // this check makes sure the server didn't send results
408  // in a different order, or accounts we didn't request
409  FC_ASSERT( acct->id == old_acct.id );
410  if( fc::json::to_string(*acct) != fc::json::to_string(old_acct) )
411  {
412  wlog( "Account ${id} : \"${name}\" updated on chain", ("id", acct->id)("name", acct->name) );
413  }
414  _wallet.update_account( *acct );
415  i++;
416  }
417  }
418 
419  return true;
420  }
421 
422  void wallet_api_impl::save_wallet_file(string wallet_filename)
423  {
424  //
425  // Serialize in memory, then save to disk
426  //
427  // This approach lessens the risk of a partially written wallet
428  // if exceptions are thrown in serialization
429  //
430 
431  encrypt_keys();
432 
433  if( wallet_filename == "" )
434  wallet_filename = _wallet_filename;
435 
436  wlog( "saving wallet to file ${fn}", ("fn", wallet_filename) );
437 
438  string data = fc::json::to_pretty_string( _wallet );
439 
440  try
441  {
442  enable_umask_protection();
443  //
444  // Parentheses on the following declaration fails to compile,
445  // due to the Most Vexing Parse. Thanks, C++
446  //
447  // http://en.wikipedia.org/wiki/Most_vexing_parse
448  //
449  std::string tmp_wallet_filename = wallet_filename + ".tmp";
450  fc::ofstream outfile{ fc::path( tmp_wallet_filename ) };
451  outfile.write( data.c_str(), data.length() );
452  outfile.flush();
453  outfile.close();
454 
455  wlog( "saved successfully wallet to tmp file ${fn}", ("fn", tmp_wallet_filename) );
456 
457  std::string wallet_file_content;
458  fc::read_file_contents(tmp_wallet_filename, wallet_file_content);
459 
460  if (wallet_file_content == data) {
461  wlog( "validated successfully tmp wallet file ${fn}", ("fn", tmp_wallet_filename) );
462 
463  fc::rename( tmp_wallet_filename, wallet_filename );
464 
465  wlog( "renamed successfully tmp wallet file ${fn}", ("fn", tmp_wallet_filename) );
466  }
467  else
468  {
469  FC_THROW("tmp wallet file cannot be validated ${fn}", ("fn", tmp_wallet_filename) );
470  }
471 
472  wlog( "successfully saved wallet to file ${fn}", ("fn", wallet_filename) );
473 
474  disable_umask_protection();
475  }
476  catch(...)
477  {
478  string ws_password = _wallet.ws_password;
479  _wallet.ws_password = "";
480  wlog("wallet file content is next: ${data}", ("data", fc::json::to_pretty_string( _wallet ) ) );
481  _wallet.ws_password = ws_password;
482 
483  disable_umask_protection();
484  throw;
485  }
486  }
487 
488 }}} // namespace graphene::wallet::detail
static string to_string(const variant &v, output_formatting format=stringify_large_ints_and_doubles, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:638
map< string, vector< string > > pending_account_registrations
bool exists(const path &p)
Definition: filesystem.cpp:209
std::string get_approximate_relative_time_string(const time_point_sec &event_time, const time_point_sec &relative_to_time=fc::time_point::now(), const std::string &ago=" ago")
Definition: time.cpp:70
auto async(Functor &&f, const char *desc FC_TASK_NAME_DEFAULT_ARG, priority prio=priority()) -> fc::future< decltype(f())>
Definition: thread.hpp:227
dynamic_global_property_object get_dynamic_global_properties() const
const char *const git_revision_sha
T as(uint32_t max_depth) const
Definition: variant.hpp:336
fc::api< custom_operations_api > _custom_operations
vector< operation > operations
Definition: transaction.hpp:89
#define GRAPHENE_MAX_NESTED_OBJECTS
Definition: config.hpp:31
contains all of the parameters necessary to calculate the fee for any operation
This class represents an account on the object graphAccounts are the primary unit of authority on the...
Maintains global state information (committee_member list, current fees)This is an implementation det...
global_property_object get_global_properties() const
tracks the blockchain state in an extensible manner
Definition: database.hpp:70
An order-perserving dictionary of variant&#39;s.
Definition: api.cpp:56
#define elog(FORMAT,...)
Definition: logger.hpp:129
bool copy_wallet_file(string destination_filename)
make a copy of the wallet file Note: this will not overwrite. It simply adds a version suffix...
Used to generate a useful error report when an exception is thrown.At each level in the stack where t...
Definition: exception.hpp:56
flat_map< string, operation > _prototype_ops
void rename(const path &from, const path &to)
Definition: filesystem.cpp:302
fc::static_variant< transfer_operation, limit_order_create_operation, limit_order_cancel_operation, call_order_update_operation, fill_order_operation, account_create_operation, account_update_operation, account_whitelist_operation, account_upgrade_operation, account_transfer_operation, asset_create_operation, asset_update_operation, asset_update_bitasset_operation, asset_update_feed_producers_operation, asset_issue_operation, asset_reserve_operation, asset_fund_fee_pool_operation, asset_settle_operation, asset_global_settle_operation, asset_publish_feed_operation, witness_create_operation, witness_update_operation, proposal_create_operation, proposal_update_operation, proposal_delete_operation, withdraw_permission_create_operation, withdraw_permission_update_operation, withdraw_permission_claim_operation, withdraw_permission_delete_operation, committee_member_create_operation, committee_member_update_operation, committee_member_update_global_parameters_operation, vesting_balance_create_operation, vesting_balance_withdraw_operation, worker_create_operation, custom_operation, assert_operation, balance_claim_operation, override_transfer_operation, transfer_to_blind_operation, blind_transfer_operation, transfer_from_blind_operation, asset_settle_cancel_operation, asset_claim_fees_operation, fba_distribute_operation, bid_collateral_operation, execute_bid_operation, asset_claim_pool_operation, asset_update_issuer_operation, htlc_create_operation, htlc_redeem_operation, htlc_redeemed_operation, htlc_extend_operation, htlc_refund_operation, custom_authority_create_operation, custom_authority_update_operation, custom_authority_delete_operation, ticket_create_operation, ticket_update_operation, liquidity_pool_create_operation, liquidity_pool_delete_operation, liquidity_pool_deposit_operation, liquidity_pool_withdraw_operation, liquidity_pool_exchange_operation >
void create_directories(const path &p)
Definition: filesystem.cpp:210
#define FC_THROW(...)
Definition: exception.hpp:366
void read_file_contents(const fc::path &filename, std::string &result)
Definition: fstream.cpp:107
chain_property_object get_chain_properties() const
visitor::result_type visit(visitor &v)
#define wlog(FORMAT,...)
Definition: logger.hpp:123
map< string, string > pending_witness_registrations
static variant from_file(const fc::path &p, parse_type ptype=legacy_parser, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:755
static constexpr size_t count()
public_key get_public_key() const
provides stack-based nullable value similar to boost::optional
Definition: optional.hpp:20
std::string key_to_wif(const fc::sha256 &private_secret)
const uint32_t git_revision_unix_timestamp
fc::ecc::private_key derive_private_key(const std::string &prefix_string, int sequence_number)
Definition: wallet_sign.cpp:54
object_id_type id
Definition: object.hpp:69
bool update_account(const account_object &acct)
operation get_prototype_operation(string operation_name)
#define ilog(FORMAT,...)
Definition: logger.hpp:117
account_multi_index_type my_accounts
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
string name
The account&#39;s name. This name must be unique among all account names on the graph. May not be empty.
const char *const git_revision_sha
adds a signature to a transaction
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object&#39;s.
Definition: variant.hpp:198
void save_wallet_file(string wallet_filename="")
static string to_pretty_string(const variant &v, output_formatting format=stringify_large_ints_and_doubles, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:736
const uint32_t git_revision_unix_timestamp
std::string to_string(double)
Definition: string.cpp:73
bool load_wallet_file(string wallet_filename="")
Definition: api.hpp:15
map< public_key_type, string > _keys
void set_which(tag_type tag)
fc::path parent_path() const
Definition: filesystem.cpp:163
Maintains global state information (committee_member list, current fees)This is an implementation det...
uint8_t popcount(uint64_t v)
Definition: popcount.cpp:31
asset set_fee(operation &op, const price &core_exchange_rate=price::unit_price()) const
const char *const git_revision_description
void copy(const path &from, const path &to)
Definition: filesystem.cpp:241
static time_point now()
Definition: time.cpp:13
an elliptic curve private key.
Definition: elliptic.hpp:89
void on_block_applied(const variant &block_id)
wraps boost::filesystem::path to provide platform independent path manipulation.
Definition: filesystem.hpp:28
void set_operation_fees(signed_transaction &tx, const fee_schedule &s)
path absolute(const path &p)
Definition: filesystem.cpp:341
An order-perserving dictionary of variant&#39;s.
Definition: api.hpp:120