diff --git a/libraries/protocol/include/hive/protocol/transaction_util.hpp b/libraries/protocol/include/hive/protocol/transaction_util.hpp index e6c1d5986a..20241113ad 100644 --- a/libraries/protocol/include/hive/protocol/transaction_util.hpp +++ b/libraries/protocol/include/hive/protocol/transaction_util.hpp @@ -84,5 +84,40 @@ void verify_authority(const vector& auth_containers, */ void collect_potential_keys( std::vector< public_key_type >* keys, const account_name_type& account, const std::string& str ); +/** + * Class that encapsulates process of detecting which keys need to be used for signing transaction. + */ +class signing_keys_collector +{ +public: + virtual ~signing_keys_collector() {} + + // disables automatic detection and adds given account's active authority to set of used authorities + void use_active_authority( const account_name_type& account_name ); + // disables automatic detection and adds given account's owner authority to set of used authorities + void use_owner_authority( const account_name_type& account_name ); + // disables automatic detection and adds given account's posting authority to set of used authorities + void use_posting_authority( const account_name_type& account_name ); + // clears set of used authorities and enables automatic detection + void use_automatic_authority(); + + bool automatic_detection_enabled() const { return automatic_detection; } + + virtual void collect_signing_keys( flat_set< public_key_type >* keys, const transaction& tx ); + + // called before get_active/get_owner/get_posting for each given account + virtual void prepare_account_authority_data( const std::vector< account_name_type >& accounts ) = 0; + + virtual const authority& get_active( const account_name_type& account_name ) const = 0; + virtual const authority& get_owner( const account_name_type& account_name ) const = 0; + virtual const authority& get_posting( const account_name_type& account_name ) const = 0; + +protected: + flat_set use_active; + flat_set use_owner; + flat_set use_posting; + bool automatic_detection = true; +}; + } } // hive::protocol diff --git a/libraries/protocol/transaction_util.cpp b/libraries/protocol/transaction_util.cpp index b324b42144..4d204a2193 100644 --- a/libraries/protocol/transaction_util.cpp +++ b/libraries/protocol/transaction_util.cpp @@ -244,4 +244,153 @@ void collect_potential_keys( std::vector< public_key_type >* keys, keys->push_back( generate_key( "memo" ) ); } +void signing_keys_collector::use_active_authority( const account_name_type& account_name ) +{ + automatic_detection = false; + use_active.emplace( account_name ); + // note: we are not enforcing rule that says posting and active/owner are mutually exclusive +} + +void signing_keys_collector::use_owner_authority( const account_name_type& account_name ) +{ + automatic_detection = false; + use_owner.emplace( account_name ); + // note: we are not enforcing rule that says posting and active/owner are mutually exclusive +} + +void signing_keys_collector::use_posting_authority( const account_name_type& account_name ) +{ + automatic_detection = false; + use_posting.emplace( account_name ); + // note: we are not enforcing rule that says posting and active/owner are mutually exclusive +} + +void signing_keys_collector::use_automatic_authority() +{ + automatic_detection = true; + use_active.clear(); + use_owner.clear(); + use_posting.clear(); +} + +void signing_keys_collector::collect_signing_keys( flat_set< public_key_type >* keys, const transaction& tx ) +{ + flat_set< account_name_type > req_active_approvals; + flat_set< account_name_type > req_owner_approvals; + flat_set< account_name_type > req_posting_approvals; + flat_set< account_name_type > req_witness_approvals; + vector< authority > other_auths; + + if( automatic_detection ) + { + tx.get_required_authorities( req_active_approvals, req_owner_approvals, req_posting_approvals, req_witness_approvals, other_auths ); + } + else + { + req_active_approvals.insert( use_active.begin(), use_active.end() ); + req_owner_approvals.insert( use_owner.begin(), use_owner.end() ); + req_posting_approvals.insert( use_posting.begin(), use_posting.end() ); + } + + for( const auto& auth : other_auths ) + for( const auto& a : auth.account_auths ) + req_active_approvals.insert( a.first ); + + // std::merge lets us de-duplicate account_id's that occur in both sets, and dump them into + // a vector at the same time + vector< account_name_type > v_approving_accounts; + std::merge( req_active_approvals.begin(), req_active_approvals.end(), + req_owner_approvals.begin(), req_owner_approvals.end(), + std::back_inserter( v_approving_accounts ) ); + // in correct case above and below are mutually exclusive (at least for now, since mixing posting + // with active/owner is forbidden), but we are not enforcing it here + for( const auto& a : req_posting_approvals ) + v_approving_accounts.push_back( a ); + + /// TODO: handle the op that must be signed using witness keys + + prepare_account_authority_data( v_approving_accounts ); + + auto get_authorities = [&]( flat_set< account_name_type > authorities_names, + authority::classification type ) -> std::vector< account_name_type > + { + std::vector< account_name_type > v_authorities_names_next_level, all_extra_approving_accounts; + + uint32_t depth = 1; + while( depth <= HIVE_MAX_SIG_CHECK_DEPTH ) + { + flat_set< account_name_type > authorities_names_next_level; + + for( const auto& name : authorities_names ) + { + const authority* current_auth = nullptr; + switch( type ) + { + case authority::owner: current_auth = &get_owner( name ); break; + case authority::active: current_auth = &get_active( name ); break; + case authority::posting: current_auth = &get_posting( name ); break; + } + for( const auto& kv : current_auth->account_auths ) + authorities_names_next_level.insert( kv.first ); + } + + if( authorities_names_next_level.empty() ) + break; + + for( const auto& name : authorities_names_next_level ) + { + v_authorities_names_next_level.emplace_back( name ); + all_extra_approving_accounts.emplace_back( name ); + } + authorities_names = std::move( authorities_names_next_level ); + + prepare_account_authority_data( v_authorities_names_next_level ); + ++depth; + if( type != authority::posting ) + type = authority::active; + } + + return all_extra_approving_accounts; + }; + + if( automatic_detection ) + { + auto additional_approving_accounts = get_authorities( req_active_approvals, authority::active ); + req_active_approvals.insert( additional_approving_accounts.begin(), additional_approving_accounts.end() ); + additional_approving_accounts = get_authorities( req_owner_approvals, authority::owner ); + req_active_approvals.insert( additional_approving_accounts.begin(), additional_approving_accounts.end() ); + additional_approving_accounts = get_authorities( req_posting_approvals, authority::posting ); + req_posting_approvals.insert( additional_approving_accounts.begin(), additional_approving_accounts.end() ); + } + + for( const auto& name : req_active_approvals ) + { + auto v_approving_keys = get_active( name ).get_keys(); + wdump( ( v_approving_keys ) ); + keys->insert( v_approving_keys.begin(), v_approving_keys.end() ); + } + for( const auto& name : req_owner_approvals ) + { + auto v_approving_keys = get_owner( name ).get_keys(); + wdump( ( v_approving_keys ) ); + keys->insert( v_approving_keys.begin(), v_approving_keys.end() ); + } + + for( const auto& name : req_posting_approvals ) + { + auto v_approving_keys = get_posting( name ).get_keys(); + wdump( ( v_approving_keys ) ); + keys->insert( v_approving_keys.begin(), v_approving_keys.end() ); + } + + for( const authority& a : other_auths ) + { + for( const auto& kv : a.key_auths ) + { + wdump( ( kv.first ) ); + keys->insert( kv.first ); + } + } +} + } } // hive::protocol diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 266e0179cd..af9acabf0b 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -168,7 +169,7 @@ class wallet_api_impl wallet_api_impl( wallet_api& s, const wallet_data& initial_data, const chain_id_type& hive_chain_id, const fc::api< hive::plugins::wallet_bridge_api::wallet_bridge_api >& remote_api, transaction_serialization_type transaction_serialization, const std::string& store_transaction ) : self( s ), _wallet( initial_data ), _hive_chain_id( hive_chain_id ), _chosen_transaction_serialization(transaction_serialization), - _remote_wallet_bridge_api(remote_api), _store_transaction(store_transaction) + _remote_wallet_bridge_api( remote_api ), _key_collector( *this ), _store_transaction( store_transaction ) { wallet_transaction_serialization::transaction_serialization = transaction_serialization; serialization_mode_controller::set_pack( transaction_serialization ); @@ -460,16 +461,17 @@ class wallet_api_impl void use_authority( authority_type type, const account_name_type& account_name ) { - _authorities_to_use[type].push_back(account_name); - _use_automatic_authority = false; + switch( type ) + { + case authority_type::active: _key_collector.use_active_authority( account_name ); break; + case authority_type::owner: _key_collector.use_owner_authority( account_name ); break; + case authority_type::posting: _key_collector.use_posting_authority( account_name ); break; + } } void use_automatic_authority() { - _use_automatic_authority = true; - _authorities_to_use[active].clear(); - _authorities_to_use[owner].clear(); - _authorities_to_use[posting].clear(); + _key_collector.use_automatic_authority(); } // sets the expiration time and reference block @@ -529,166 +531,11 @@ class wallet_api_impl { require_online(); static const authority null_auth( 1, public_key_type(), 0 ); - flat_set< account_name_type > req_active_approvals; - flat_set< account_name_type > req_owner_approvals; - flat_set< account_name_type > req_posting_approvals; - flat_set< account_name_type > req_witness_approvals; - vector< authority > other_auths; - - if( _use_automatic_authority == true ) - { - tx.get_required_authorities( req_active_approvals, req_owner_approvals, req_posting_approvals, req_witness_approvals, other_auths ); - } - else - { - req_active_approvals.insert(_authorities_to_use[active].begin(), _authorities_to_use[active].end()); - req_owner_approvals.insert(_authorities_to_use[owner].begin(), _authorities_to_use[owner].end()); - req_posting_approvals.insert(_authorities_to_use[posting].begin(), _authorities_to_use[posting].end()); - } - - for( const auto& auth : other_auths ) - for( const auto& a : auth.account_auths ) - req_active_approvals.insert(a.first); - - // std::merge lets us de-duplicate account_id's that occur in both - // sets, and dump them into a vector (as required by remote_db api) - // at the same time - vector< account_name_type > v_approving_account_names; - std::merge(req_active_approvals.begin(), req_active_approvals.end(), - req_owner_approvals.begin() , req_owner_approvals.end(), - std::back_inserter( v_approving_account_names ) ); - - for( const auto& a : req_posting_approvals ) - v_approving_account_names.push_back(a); - - /// TODO: handle the op that must be signed using witness keys - - /// TODO: fetch the accounts specified via other_auths as well. - - auto approving_account_objects = get_accounts( variant(v_approving_account_names) ); - - FC_ASSERT( approving_account_objects.size() == v_approving_account_names.size(), "", ("aco.size:", approving_account_objects.size())("acn",v_approving_account_names.size()) ); - - flat_map< string, database_api::api_account_object > approving_account_lut; - for( const auto& approving_acct : approving_account_objects ) - approving_account_lut[ approving_acct.name ] = approving_acct; - - /// recursively check one layer deeper in the authority tree for keys - std::function< void(flat_set< account_name_type > &, authority_type, uint32_t) > recursively_get_authorities = - [&](flat_set< account_name_type > &authorities_names, authority_type type, uint32_t depth) - { - if(depth > HIVE_MAX_SIG_CHECK_DEPTH) - return; - - flat_set< account_name_type > authorities_names_next_level; - for(account_name_type& acct_name : authorities_names) - { - const auto it = approving_account_lut.find( acct_name ); - if( it == approving_account_lut.end() ) - continue; - const database_api::api_account_object& acct = it->second; - flat_map< account_name_type, weight_type > account_auths; - if( type == active ) - account_auths = acct.active.account_auths; - else if( type == owner ) - account_auths = acct.owner.account_auths; - else - account_auths = acct.posting.account_auths; - for(const auto& kv : account_auths) - authorities_names_next_level.insert(kv.first); - } - - vector< account_name_type > v_authorities_names_next_level; - for(const auto& name : authorities_names_next_level) - v_authorities_names_next_level.push_back(name); - - auto approving_account_objects = get_accounts( variant(v_authorities_names_next_level) ); - for( const auto& approving_acct : approving_account_objects ) - approving_account_lut[ approving_acct.name ] = approving_acct; - - recursively_get_authorities(authorities_names_next_level, type, depth+1); - for(const auto& name : authorities_names_next_level) - authorities_names.insert(name); - }; - - if( _use_automatic_authority == true ) - { - recursively_get_authorities(req_active_approvals, active, 1); - recursively_get_authorities(req_owner_approvals, owner, 1); - recursively_get_authorities(req_posting_approvals, posting, 1); - } - - auto get_account_from_lut = [&]( const std::string& name ) -> fc::optional< const database_api::api_account_object* > - { - fc::optional< const database_api::api_account_object* > result; - auto it = approving_account_lut.find( name ); - if( it != approving_account_lut.end() ) - { - result = &(it->second); - } - else - { - elog( "Tried to access authority for account ${a} but not cached.", ("a", name) ); - } - - return result; - }; flat_set approving_key_set; - for( account_name_type& acct_name : req_active_approvals ) - { - const auto it = approving_account_lut.find( acct_name ); - if( it == approving_account_lut.end() ) - continue; - - const database_api::api_account_object& acct = it->second; - vector v_approving_keys = acct.active.get_keys(); - wdump((v_approving_keys)); - for( const public_key_type& approving_key : v_approving_keys ) - { - wdump((approving_key)); - approving_key_set.insert( approving_key ); - } - } - - for( account_name_type& acct_name : req_posting_approvals ) - { - const auto it = approving_account_lut.find( acct_name ); - if( it == approving_account_lut.end() ) - continue; - - const database_api::api_account_object& acct = it->second; - vector v_approving_keys = acct.posting.get_keys(); - wdump((v_approving_keys)); - for( const public_key_type& approving_key : v_approving_keys ) - { - wdump((approving_key)); - approving_key_set.insert( approving_key ); - } - } - - for( const account_name_type& acct_name : req_owner_approvals ) - { - const auto it = approving_account_lut.find( acct_name ); - if( it == approving_account_lut.end() ) - continue; - - const database_api::api_account_object& acct = it->second; - vector v_approving_keys = acct.owner.get_keys(); - for( const public_key_type& approving_key : v_approving_keys ) - { - wdump((approving_key)); - approving_key_set.insert( approving_key ); - } - } - for( const authority& a : other_auths ) - { - for( const auto& k : a.key_auths ) - { - wdump((k.first)); - approving_key_set.insert( k.first ); - } - } + // we need local copy to remain stateless, so other threads making the same call won't cause conflicts + signing_keys_collector collector = _key_collector; + collector.collect_signing_keys( &approving_key_set, tx ); initialize_transaction_header(tx); @@ -712,37 +559,16 @@ class wallet_api_impl std::vector keys_to_sign; - if( _use_automatic_authority == true ) + if( collector.automatic_detection_enabled() ) { const signed_transaction& signature_source = new_tx->get_transaction(); auto minimal_signing_keys = signature_source.minimize_required_signatures( _hive_chain_id, available_keys, - [&]( const string& account_name ) -> const authority& - { - auto maybe_account = get_account_from_lut( account_name ); - if( maybe_account.valid() ) - return (*maybe_account)->active; - - return null_auth; - }, - [&]( const string& account_name ) -> const authority& - { - auto maybe_account = get_account_from_lut( account_name ); - if( maybe_account.valid() ) - return (*maybe_account)->owner; - - return null_auth; - }, - [&]( const string& account_name ) -> const authority& - { - auto maybe_account = get_account_from_lut( account_name ); - if( maybe_account.valid() ) - return (*maybe_account)->posting; - - return null_auth; - }, + [&]( const string& account_name ) { return collector.get_account( account_name ).active; }, + [&]( const string& account_name ) { return collector.get_account( account_name ).owner; }, + [&]( const string& account_name ) { return collector.get_account( account_name ).posting; }, [&]( const string& witness_name ) -> public_key_type { auto maybe_witness = get_witness(witness_name); @@ -845,6 +671,54 @@ class wallet_api_impl FC_CAPTURE_AND_LOG(()) } + class signing_keys_collector : public hive::protocol::signing_keys_collector + { + public: + signing_keys_collector( wallet_api_impl& _self ) : self( _self ) {} + virtual ~signing_keys_collector() {} + + virtual void collect_signing_keys( flat_set< public_key_type >* keys, const transaction& tx ) override + { + approving_account_lut.clear(); + hive::protocol::signing_keys_collector::collect_signing_keys( keys, tx ); + // approving_account_lut remains filled until next call + } + + virtual void prepare_account_authority_data( const std::vector< account_name_type >& accounts ) override + { + auto approving_account_objects = self.get_accounts( variant( accounts ) ); + FC_ASSERT( approving_account_objects.size() == accounts.size(), "Some requested accounts were not found", + ( "found.size:", approving_account_objects.size() )( "requested.size", accounts.size() ) ); + for( const auto& account : approving_account_objects ) + approving_account_lut[ account.name ] = account; + } + + const database_api::api_account_object& get_account( const account_name_type& account_name ) const + { + auto it = approving_account_lut.find( account_name ); + FC_ASSERT( it != approving_account_lut.end(), + "Tried to access authority for account ${a} but not cached.", ( "a", account_name ) ); + return it->second; + } + + virtual const authority& get_active( const account_name_type& account_name ) const override + { + return get_account( account_name ).active; + } + virtual const authority& get_owner( const account_name_type& account_name ) const override + { + return get_account( account_name ).owner; + } + virtual const authority& get_posting( const account_name_type& account_name ) const override + { + return get_account( account_name ).posting; + } + + private: + wallet_api_impl& self; + flat_map< account_name_type, database_api::api_account_object > approving_account_lut; + }; + string _wallet_filename; wallet_data _wallet; chain_id_type _hive_chain_id; @@ -858,9 +732,7 @@ class wallet_api_impl static_variant_map _operation_which_map = create_static_variant_map< operation >(); - typedef map > authorities_type; - authorities_type _authorities_to_use; - bool _use_automatic_authority = true; + signing_keys_collector _key_collector; std::string _store_transaction;