From e731941cf0c0005d4860f1833038565490686c51 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Fri, 9 Oct 2015 13:05:19 -0400 Subject: [PATCH 1/4] wallet.cpp: Allow specifying wildcard in keys when running import_balance Also, don't put private keys in error messages. --- .../wallet/include/graphene/wallet/wallet.hpp | 4 +- libraries/wallet/wallet.cpp | 118 +++++++++++++----- 2 files changed, 88 insertions(+), 34 deletions(-) diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index de5c4f63..36007aa4 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -575,10 +575,10 @@ class wallet_api bool import_account_keys( string filename, string password, string src_account_name, string dest_account_name ); /** - * This call will construct a transaction that will claim all balances controled + * This call will construct transaction(s) that will claim all balances controled * by wif_keys and deposit them into the given account. */ - signed_transaction import_balance( string account_name_or_id, const vector& wif_keys, bool broadcast ); + vector< signed_transaction > import_balance( string account_name_or_id, const vector& wif_keys, bool broadcast ); /** Transforms a brain key to reduce the chance of errors when re-entering the key from memory. * diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index a28ec302..d940861d 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -624,7 +624,7 @@ public: { fc::optional optional_private_key = wif_to_key(wif_key); if (!optional_private_key) - FC_THROW("Invalid private key ${key}", ("key", wif_key)); + FC_THROW("Invalid private key"); graphene::chain::public_key_type wif_pub_key = optional_private_key->get_public_key(); account_object account = get_account( account_name_or_id ); @@ -646,6 +646,8 @@ public: return all_keys_for_account.find(wif_pub_key) != all_keys_for_account.end(); } + vector< signed_transaction > import_balance( string name_or_id, const vector& wif_keys, bool broadcast ); + bool load_wallet_file(string wallet_filename = "") { // TODO: Merge imported wallet with existing wallet, @@ -2571,7 +2573,7 @@ bool wallet_api::import_key(string account_name_or_id, string wif_key) // backup wallet fc::optional optional_private_key = wif_to_key(wif_key); if (!optional_private_key) - FC_THROW("Invalid private key ${key}", ("key", wif_key)); + FC_THROW("Invalid private key"); string shorthash = detail::address_to_shorthash(optional_private_key->get_public_key()); copy_wallet_file( "before-import-key-" + shorthash ); @@ -3119,65 +3121,117 @@ void wallet_api::set_password( string password ) lock(); } -signed_transaction wallet_api::import_balance( string name_or_id, const vector& wif_keys, bool broadcast ) +vector< signed_transaction > wallet_api::import_balance( string name_or_id, const vector& wif_keys, bool broadcast ) +{ + return my->import_balance( name_or_id, wif_keys, broadcast ); +} + +namespace detail { + +vector< signed_transaction > wallet_api_impl::import_balance( string name_or_id, const vector& wif_keys, bool broadcast ) { try { FC_ASSERT(!is_locked()); + const dynamic_global_property_object& dpo = _remote_db->get_dynamic_global_properties(); account_object claimer = get_account( name_or_id ); + uint32_t max_ops_per_tx = 30; - vector
addrs; - map keys; - for( auto wif_key : wif_keys ) + map< address, private_key_type > keys; // local index of address -> private key + vector< address > addrs; + bool has_wildcard = false; + addrs.reserve( wif_keys.size() ); + for( const string& wif_key : wif_keys ) { - auto priv_key = wif_to_key(wif_key); - FC_ASSERT( priv_key, "Invalid Private Key", ("key",wif_key) ); - addrs.push_back( priv_key->get_public_key() ); - keys[addrs.back()] = *priv_key; + if( wif_key == "*" ) + { + if( has_wildcard ) + continue; + for( const public_key_type& pub : _wallet.extra_keys[ claimer.id ] ) + { + addrs.push_back( pub ); + auto it = _keys.find( pub ); + if( it != _keys.end() ) + { + fc::optional< fc::ecc::private_key > privkey = wif_to_key( it->second ); + FC_ASSERT( privkey ); + keys[ addrs.back() ] = *privkey; + } + else + { + wlog( "Somehow _keys has no private key for extra_keys public key ${k}", ("k", pub) ); + } + } + has_wildcard = true; + } + else + { + optional< private_key_type > key = wif_to_key( wif_key ); + FC_ASSERT( key.valid(), "Invalid private key" ); + addrs.push_back( key->get_public_key() ); + keys[addrs.back()] = *key; + } } - auto balances = my->_remote_db->get_balance_objects( addrs ); + vector< balance_object > balances = _remote_db->get_balance_objects( addrs ); wdump((balances)); addrs.clear(); set bal_types; for( auto b : balances ) bal_types.insert( b.balance.asset_id ); - set
required_addrs; - signed_transaction trx; - for( auto a : bal_types ) + struct claim_tx + { + vector< balance_claim_operation > ops; + set< address > addrs; + }; + vector< claim_tx > claim_txs; + + for( const asset_id_type& a : bal_types ) { balance_claim_operation op; op.deposit_to_account = claimer.id; - for( const auto& b : balances ) + for( const balance_object& b : balances ) { if( b.balance.asset_id == a ) { - op.total_claimed = b.vesting_policy.valid()? asset(0, b.balance.asset_id) : b.balance; + op.total_claimed = b.available( dpo.time ); + if( op.total_claimed.amount == 0 ) + continue; op.balance_to_claim = b.id; op.balance_owner_key = keys[b.owner].get_public_key(); - trx.operations.push_back(op); - required_addrs.insert(b.owner); + if( (claim_txs.empty()) || (claim_txs.back().ops.size() >= max_ops_per_tx) ) + claim_txs.emplace_back(); + claim_txs.back().ops.push_back(op); + claim_txs.back().addrs.insert(b.owner); } } } - my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees ); - trx.validate(); + vector< signed_transaction > result; - auto tx = sign_transaction( trx, false ); - - for( auto a : required_addrs ) - tx.sign( keys[a], my->_chain_id ); + for( const claim_tx& ctx : claim_txs ) + { + signed_transaction tx; + tx.operations.reserve( ctx.ops.size() ); + for( const balance_claim_operation& op : ctx.ops ) + tx.operations.emplace_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + signed_transaction signed_tx = sign_transaction( tx, false ); + for( const address& addr : ctx.addrs ) + signed_tx.sign( keys[addr], _chain_id ); + // if the key for a balance object was the same as a key for the account we're importing it into, + // we may end up with duplicate signatures, so remove those + boost::erase(signed_tx.signatures, boost::unique(boost::sort(signed_tx.signatures))); + result.push_back( signed_tx ); + if( broadcast ) + _remote_net_broadcast->broadcast_transaction(signed_tx); + } - // if the key for a balance object was the same as a key for the account we're importing it into, - // we may end up with duplicate signatures, so remove those - boost::erase(tx.signatures, boost::unique(boost::sort(tx.signatures))); - - if( broadcast ) - my->_remote_net_broadcast->broadcast_transaction(tx); - - return tx; + return result; } FC_CAPTURE_AND_RETHROW( (name_or_id) ) } +} + map wallet_api::dump_private_keys() { FC_ASSERT(!is_locked()); From a6d6b7316ac4bfeec4b43cbfe5412fa449146a1d Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Fri, 9 Oct 2015 13:57:05 -0400 Subject: [PATCH 2/4] wallet.cpp: Keep going when we fail to import a key --- libraries/wallet/wallet.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index d940861d..060064c4 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2628,14 +2628,28 @@ map wallet_api::import_accounts( string filename, string password if( should_proceed ) { + uint32_t import_successes = 0; + uint32_t import_failures = 0; // TODO: First check that all private keys match public keys for( const auto& encrypted_key : item.encrypted_private_keys ) { - const auto plain_text = fc::aes_decrypt( password_hash, encrypted_key ); - const auto private_key = fc::raw::unpack( plain_text ); + try + { + const auto plain_text = fc::aes_decrypt( password_hash, encrypted_key ); + const auto private_key = fc::raw::unpack( plain_text ); - import_key( item.account_name, string( graphene::utilities::key_to_wif( private_key ) ) ); + import_key( item.account_name, string( graphene::utilities::key_to_wif( private_key ) ) ); + ++import_successes; + } + catch( const fc::exception& e ) + { + elog( "Couldn't import key due to exception ${e}", ("e", e.to_detail_string()) ); + ++import_failures; + } } + ilog( "successfully imported ${n} keys for account ${name}", ("n", import_successes)("name", item.account_name) ); + if( import_failures > 0 ) + elog( "failed to import ${n} keys for account ${name}", ("n", import_failures)("name", item.account_name) ); } } From 434efcaa47fe803b6cf204a1809107da5694d4c2 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Fri, 9 Oct 2015 15:27:14 -0400 Subject: [PATCH 3/4] asset_evaluator.cpp: Fix compiler warning --- libraries/chain/asset_evaluator.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 971581dd..c7ef30ed 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -520,8 +520,6 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_ { FC_ASSERT(o.feed.settlement_price.quote.asset_id == bitasset.options.short_backing_asset); - bool is_nop = false; - try { // these two changes should go in price_feed::validate() when creating new network @@ -559,7 +557,6 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_ ("n", d.head_block_num()+1) ("name", o.publisher(d).name) ); - is_nop = true; } } catch( const fc::exception& e ) From 3023a37b8072498f0c0ed6c6c0621421bbf2f38b Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Fri, 9 Oct 2015 15:36:31 -0400 Subject: [PATCH 4/4] genesis_util: Implement two new utilities --- programs/genesis_util/python_format.py | 14 ++++++ programs/genesis_util/upgrade_members.py | 61 ++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100755 programs/genesis_util/python_format.py create mode 100755 programs/genesis_util/upgrade_members.py diff --git a/programs/genesis_util/python_format.py b/programs/genesis_util/python_format.py new file mode 100755 index 00000000..bb518fc5 --- /dev/null +++ b/programs/genesis_util/python_format.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import argparse +import json +import sys + +if len(sys.argv) < 3: + print("syntax: "+sys.argv[0]+" INFILE OUTFILE") + sys.exit(0) + +with open(sys.argv[1], "r") as infile: + genesis = json.load(infile) +with open(sys.argv[2], "w") as outfile: + json.dump(genesis, outfile, indent=2, sort_keys=True) diff --git a/programs/genesis_util/upgrade_members.py b/programs/genesis_util/upgrade_members.py new file mode 100755 index 00000000..3ca42258 --- /dev/null +++ b/programs/genesis_util/upgrade_members.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +import argparse +import json +import re +import sys + +def dump_json(obj, out, pretty): + if pretty: + json.dump(obj, out, indent=2, sort_keys=True) + else: + json.dump(obj, out, separators=(",", ":"), sort_keys=True) + return + +re_init = re.compile(r"^init[0-9]+$") + +def load_names(infile): + names = set() + for line in infile: + if '#' in line: + line = line[:line.index('#')] + line = line.strip() + if line == "": + continue + names.add(line) + return names + +def main(): + parser = argparse.ArgumentParser(description="Remove entities from snapshot") + parser.add_argument("-o", "--output", metavar="OUT", default="-", help="output filename (default: stdout)") + parser.add_argument("-i", "--input", metavar="IN", default="-", help="input filename (default: stdin)") + parser.add_argument("-n", "--names", metavar="NAMES", default="", help="file containing names to upgrade") + parser.add_argument("-p", "--pretty", action="store_true", default=False, help="pretty print output") + opts = parser.parse_args() + + if opts.input == "-": + genesis = json.load(sys.stdin) + else: + with open(opts.input, "r") as f: + genesis = json.load(f) + + if opts.names == "-": + names = load_names(sys.stdin) + else: + with open(opts.names, "r") as f: + names = load_names(f) + + for account in genesis["initial_accounts"]: + if account["name"] in names: + account["is_lifetime_member"] = True + + if opts.output == "-": + dump_json( genesis, sys.stdout, opts.pretty ) + sys.stdout.flush() + else: + with open(opts.output, "w") as f: + dump_json( genesis, f, opts.pretty ) + return + +if __name__ == "__main__": + main()