diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 8dd52e08c..b189405f5 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -1773,10 +1774,12 @@ set database_api::get_required_signatures( const signed_transac set database_api_impl::get_required_signatures( const signed_transaction& trx, const flat_set& available_keys )const { wdump((trx)(available_keys)); + bool allow_non_immediate_owner = ( _db.head_block_time() >= HARDFORK_1002_TIME ); auto result = trx.get_required_signatures( _db.get_chain_id(), available_keys, [&]( account_id_type id ){ return &id(_db).active; }, [&]( account_id_type id ){ return &id(_db).owner; }, + allow_non_immediate_owner, _db.get_global_properties().parameters.max_authority_depth ); wdump((result)); return result; @@ -1794,7 +1797,11 @@ set
database_api::get_potential_address_signatures( const signed_transa set database_api_impl::get_potential_signatures( const signed_transaction& trx )const { wdump((trx)); + auto chain_time = _db.head_block_time(); + bool allow_non_immediate_owner = ( chain_time >= HARDFORK_1002_TIME ); + set result; + trx.get_required_signatures( _db.get_chain_id(), flat_set(), @@ -1812,15 +1819,28 @@ set database_api_impl::get_potential_signatures( const signed_t result.insert(k); return &auth; }, + allow_non_immediate_owner, _db.get_global_properties().parameters.max_authority_depth ); + // Insert keys in required "other" authories + flat_set required_active; + flat_set required_owner; + vector other; + trx.get_required_authorities( required_active, required_owner, other ); + for( const auto& auth : other ) + for( const auto& key : auth.get_keys() ) + result.insert( key ); + wdump((result)); return result; } set
database_api_impl::get_potential_address_signatures( const signed_transaction& trx )const { + auto chain_time = _db.head_block_time(); + bool allow_non_immediate_owner = ( chain_time >= HARDFORK_1002_TIME ); + set
result; trx.get_required_signatures( _db.get_chain_id(), @@ -1839,6 +1859,7 @@ set
database_api_impl::get_potential_address_signatures( const signed_t result.insert(k); return &auth; }, + allow_non_immediate_owner, _db.get_global_properties().parameters.max_authority_depth ); return result; @@ -1851,9 +1872,11 @@ bool database_api::verify_authority( const signed_transaction& trx )const bool database_api_impl::verify_authority( const signed_transaction& trx )const { + bool allow_non_immediate_owner = ( _db.head_block_time() >= HARDFORK_1002_TIME ); trx.verify_authority( _db.get_chain_id(), [&]( account_id_type id ){ return &id(_db).active; }, [&]( account_id_type id ){ return &id(_db).owner; }, + allow_non_immediate_owner, _db.get_global_properties().parameters.max_authority_depth ); return true; } diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 9b2c7f36a..8ae737eb6 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -680,9 +680,10 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx if( !(skip & (skip_transaction_signatures | skip_authority_check) ) ) { + bool allow_non_immediate_owner = ( head_block_time() >= HARDFORK_1002_TIME ); auto get_active = [&]( account_id_type id ) { return &id(*this).active; }; auto get_owner = [&]( account_id_type id ) { return &id(*this).owner; }; - trx.verify_authority( chain_id, get_active, get_owner, get_global_properties().parameters.max_authority_depth ); + trx.verify_authority( chain_id, get_active, get_owner, allow_non_immediate_owner, get_global_properties().parameters.max_authority_depth ); } //Skip all manner of expiration and TaPoS checking if we're on block 1; It's impossible that the transaction is diff --git a/libraries/chain/hardfork.d/1002.hf b/libraries/chain/hardfork.d/1002.hf new file mode 100644 index 000000000..f2fbed540 --- /dev/null +++ b/libraries/chain/hardfork.d/1002.hf @@ -0,0 +1,7 @@ +// added transaction size check +// Bug fix of update commitee member update +// bitshares-core issue #584 Owner keys of non-immediately required accounts can not authorize a transaction +// https://github.com/bitshares/bitshares-core/issues/584 +#ifndef HARDFORK_1002_TIME +#define HARDFORK_1002_TIME (fc::time_point_sec( 1567488600 )) //Tuesday, 3 September 2019 05:30:00 GMT +#endif diff --git a/libraries/chain/include/graphene/chain/protocol/transaction.hpp b/libraries/chain/include/graphene/chain/protocol/transaction.hpp index 4d529a277..62d2009ef 100644 --- a/libraries/chain/include/graphene/chain/protocol/transaction.hpp +++ b/libraries/chain/include/graphene/chain/protocol/transaction.hpp @@ -141,13 +141,27 @@ namespace graphene { namespace chain { const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; + /** + * Checks whether signatures in this signed transaction are sufficient to authorize the transaction. + * Throws an exception when failed. + * + * @param chain_id the ID of a block chain + * @param get_active callback function to retrieve active authorities of a given account + * @param get_owner callback function to retrieve owner authorities of a given account + * @param allow_non_immediate_owner whether to allow owner authority of non-immediately + * required accounts to authorize operations in the transaction + * @param max_recursion maximum level of recursion when verifying, since an account + * can have another account in active authorities and/or owner authorities + */ void verify_authority( const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; /** @@ -162,6 +176,7 @@ namespace graphene { namespace chain { const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH ) const; @@ -171,11 +186,31 @@ namespace graphene { namespace chain { /// Removes all operations and signatures void clear() { operations.clear(); signatures.clear(); } + + /** Removes all signatures */ + void clear_signatures() { signatures.clear(); } }; + /** + * Checks whether given public keys and approvals are sufficient to authorize given operations. + * Throws an exception when failed. + * + * @param ops a vector of operations + * @param sigs a set of public keys + * @param get_active callback function to retrieve active authorities of a given account + * @param get_owner callback function to retrieve owner authorities of a given account + * @param allow_non_immediate_owner whether to allow owner authority of non-immediately + * required accounts to authorize operations + * @param max_recursion maximum level of recursion when verifying, since an account + * can have another account in active authorities and/or owner authorities + * @param allow_committee whether to allow the special "committee account" to authorize the operations + * @param active_approvals accounts that approved the operations with their active authories + * @param owner_approvals accounts that approved the operations with their owner authories + */ void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH, bool allow_committe = false, const flat_set& active_aprovals = flat_set(), diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index 565964a51..705ec4028 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -32,10 +33,12 @@ bool proposal_object::is_authorized_to_execute(database& db) const transaction_evaluation_state dry_run_eval(&db); try { + bool allow_non_immediate_owner = ( db.head_block_time() >= HARDFORK_1002_TIME ); verify_authority( proposed_transaction.operations, available_key_approvals, [&]( account_id_type id ){ return &id(db).active; }, [&]( account_id_type id ){ return &id(db).owner; }, + allow_non_immediate_owner, db.get_global_properties().parameters.max_authority_depth, true, /* allow committeee */ available_active_approvals, diff --git a/libraries/chain/protocol/transaction.cpp b/libraries/chain/protocol/transaction.cpp index 5faf1c0a1..063a790dc 100644 --- a/libraries/chain/protocol/transaction.cpp +++ b/libraries/chain/protocol/transaction.cpp @@ -97,6 +97,8 @@ void transaction::get_required_authorities( flat_set& active, f { for( const auto& op : operations ) operation_get_required_authorities( op, active, owner, other ); + for( const auto& account : owner ) + active.erase( account ); } @@ -159,7 +161,7 @@ struct sign_state bool check_authority( account_id_type id ) { if( approved_by.find(id) != approved_by.end() ) return true; - return check_authority( get_active(id) ); + return check_authority( get_active(id) ) || ( allow_non_immediate_owner && check_authority( get_owner(id) ) ); } /** @@ -194,7 +196,8 @@ struct sign_state { if( depth == max_recursion ) continue; - if( check_authority( get_active( a.first ), depth+1 ) ) + if( check_authority( get_active( a.first ), depth+1 ) + || ( allow_non_immediate_owner && check_authority( get_owner( a.first ), depth+1 ) ) ) { approved_by.insert( a.first ); total_weight += a.second; @@ -225,9 +228,16 @@ struct sign_state } sign_state( const flat_set& sigs, - const std::function& a, + const std::function& active, + const std::function& owner, + bool allow_owner, + uint32_t max_recursion_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH, const flat_set& keys = flat_set() ) - :get_active(a),available_keys(keys) + : get_active(active), + get_owner(owner), + allow_non_immediate_owner(allow_owner), + max_recursion(max_recursion_depth), + available_keys(keys) { for( const auto& key : sigs ) provided_signatures[ key ] = false; @@ -235,17 +245,20 @@ struct sign_state } const std::function& get_active; - const flat_set& available_keys; + const std::function& get_owner; + const bool allow_non_immediate_owner; + const uint32_t max_recursion; + const flat_set& available_keys; flat_map provided_signatures; flat_set approved_by; - uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH; }; void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion_depth, bool allow_committe, const flat_set& active_aprovals, @@ -262,8 +275,7 @@ void verify_authority( const vector& ops, const flat_set signed_transaction::get_required_signatures( const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion_depth )const { flat_set required_active; @@ -325,23 +338,23 @@ set signed_transaction::get_required_signatures( vector other; get_required_authorities( required_active, required_owner, other ); - - sign_state s(get_signature_keys( chain_id ),get_active,available_keys); - s.max_recursion = max_recursion_depth; + const flat_set& signature_keys = get_signature_keys(chain_id); + sign_state s( signature_keys, get_active, get_owner, allow_non_immediate_owner, max_recursion_depth, available_keys ); for( const auto& auth : other ) s.check_authority(&auth); for( auto& owner : required_owner ) s.check_authority( get_owner( owner ) ); for( auto& active : required_active ) - s.check_authority( active ); + s.check_authority( active ) || s.check_authority( get_owner( active ) ); s.remove_unused_signatures(); set result; for( auto& provided_sig : s.provided_signatures ) - if( available_keys.find( provided_sig.first ) != available_keys.end() ) + if( available_keys.find( provided_sig.first ) != available_keys.end() + && signature_keys.find( provided_sig.first ) == signature_keys.end() ) result.insert( provided_sig.first ); return result; @@ -352,6 +365,7 @@ set signed_transaction::minimize_required_signatures( const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion ) const { @@ -363,7 +377,7 @@ set signed_transaction::minimize_required_signatures( result.erase( k ); try { - graphene::chain::verify_authority( operations, result, get_active, get_owner, max_recursion ); + graphene::chain::verify_authority( operations, result, get_active, get_owner, allow_non_immediate_owner, max_recursion ); continue; // element stays erased if verify_authority is ok } catch( const tx_missing_owner_auth& e ) {} @@ -378,9 +392,15 @@ void signed_transaction::verify_authority( const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion )const { try { - graphene::chain::verify_authority( operations, get_signature_keys( chain_id ), get_active, get_owner, max_recursion ); + graphene::chain::verify_authority( operations, + get_signature_keys( chain_id ), + get_active, + get_owner, + allow_non_immediate_owner, + max_recursion ); } FC_CAPTURE_AND_RETHROW( (*this) ) } } } // graphene::chain diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index f5efbb9d7..14103abb8 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -79,7 +80,10 @@ BOOST_AUTO_TEST_CASE( any_two_of_three ) try { account_update_operation op; op.account = nathan.id; - op.active = authority(2, public_key_type(nathan_key1.get_public_key()), 1, public_key_type(nathan_key2.get_public_key()), 1, public_key_type(nathan_key3.get_public_key()), 1); + op.active = authority(2, + public_key_type(nathan_key1.get_public_key()), 1, + public_key_type(nathan_key2.get_public_key()), 1, + public_key_type(nathan_key3.get_public_key()), 1); op.owner = *op.active; trx.operations.push_back(op); sign(trx, nathan_key1); @@ -1159,10 +1163,12 @@ BOOST_FIXTURE_TEST_CASE( get_required_signatures_test, database_fixture ) set ref_set ) -> bool { - //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, false ); + set result_set2 = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, true ); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; set_auth( well_id, authority( 60, alice_id, 50, bob_id, 50 ) ); @@ -1273,10 +1279,12 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) set ref_set ) -> bool { - //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, false ); + set result_set2 = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, true ); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; auto chk_min = [&]( @@ -1285,10 +1293,12 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) set ref_set ) -> bool { - //wdump( (tx)(available_keys) ); - set result_set = tx.minimize_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.minimize_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, false ); + set result_set2 = tx.minimize_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, true ); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; set_auth( roco_id, authority( 2, styx_id, 1, thud_id, 2 ) ); @@ -1305,9 +1315,510 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) BOOST_CHECK( chk( tx, { alice_public_key, bob_public_key }, { alice_public_key, bob_public_key } ) ); BOOST_CHECK( chk_min( tx, { alice_public_key, bob_public_key }, { alice_public_key } ) ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), fc::exception ); sign( tx, alice_private_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + } + catch(fc::exception& e) + { + edump((e.to_detail_string())); + throw; + } +} + +/* + * Active vs Owner https://github.com/bitshares/bitshares-core/issues/584 + * + * All weights and all thresholds are 1, so every single key should be able to sign if within max_depth + * + * Bob --+--(a)--+-- Alice --+--(a)--+-- Daisy --(a/o)-- Daisy_active_key / Daisy_owner_key + * | | | | + * | | | +-- Alice_active_key + * | | | + * | | +--(o)--+-- Cindy --(a/o)-- Cindy_active_key / Cindy_owner_key + * | | | + * | | +-- Alice_owner_key + * | | + * | +-- Bob_active_key + * | + * +--(o)--+-- Edwin --+--(a)--+-- Gavin --(a/o)-- Gavin_active_key / Gavin_owner_key + * | | | + * | | +-- Edwin_active_key + * | | + * | +--(o)--+-- Frank --(a/o)-- Frank_active_key / Frank_owner_key + * | | + * | +-- Edwin_owner_key + * | + * +-- Bob_owner_key + */ +BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) +{ + try + { + ACTORS( + (alice)(bob)(cindy)(daisy)(edwin)(frank)(gavin) + ); + + transfer( account_id_type(), bob_id, asset(100000) ); + + auto set_auth = [&]( + account_id_type aid, + const authority& active, + const authority& owner + ) + { + signed_transaction tx; + account_update_operation op; + op.account = aid; + op.active = active; + op.owner = owner; + tx.operations.push_back( op ); + set_expiration( db, tx ); + PUSH_TX( db, tx, database::skip_transaction_signatures ); + } ; + + auto get_active = [&]( + account_id_type aid + ) -> const authority* + { + return &(aid(db).active); + } ; + + auto get_owner = [&]( + account_id_type aid + ) -> const authority* + { + return &(aid(db).owner); + } ; + + auto chk = [&]( + const signed_transaction& tx, + bool after_hf_584, + flat_set available_keys, + set ref_set + ) -> bool + { + //wdump( (tx)(available_keys) ); + set result_set = tx.get_required_signatures(db.get_chain_id(), available_keys, + get_active, get_owner, after_hf_584); + //wdump( (result_set)(ref_set) ); + return result_set == ref_set; + } ; + + fc::ecc::private_key alice_active_key = fc::ecc::private_key::regenerate(fc::digest("alice_active")); + fc::ecc::private_key alice_owner_key = fc::ecc::private_key::regenerate(fc::digest("alice_owner")); + fc::ecc::private_key bob_active_key = fc::ecc::private_key::regenerate(fc::digest("bob_active")); + fc::ecc::private_key bob_owner_key = fc::ecc::private_key::regenerate(fc::digest("bob_owner")); + fc::ecc::private_key cindy_active_key = fc::ecc::private_key::regenerate(fc::digest("cindy_active")); + fc::ecc::private_key cindy_owner_key = fc::ecc::private_key::regenerate(fc::digest("cindy_owner")); + fc::ecc::private_key daisy_active_key = fc::ecc::private_key::regenerate(fc::digest("daisy_active")); + fc::ecc::private_key daisy_owner_key = fc::ecc::private_key::regenerate(fc::digest("daisy_owner")); + fc::ecc::private_key edwin_active_key = fc::ecc::private_key::regenerate(fc::digest("edwin_active")); + fc::ecc::private_key edwin_owner_key = fc::ecc::private_key::regenerate(fc::digest("edwin_owner")); + fc::ecc::private_key frank_active_key = fc::ecc::private_key::regenerate(fc::digest("frank_active")); + fc::ecc::private_key frank_owner_key = fc::ecc::private_key::regenerate(fc::digest("frank_owner")); + fc::ecc::private_key gavin_active_key = fc::ecc::private_key::regenerate(fc::digest("gavin_active")); + fc::ecc::private_key gavin_owner_key = fc::ecc::private_key::regenerate(fc::digest("gavin_owner")); + + public_key_type alice_active_pub( alice_active_key.get_public_key() ); + public_key_type alice_owner_pub( alice_owner_key.get_public_key() ); + public_key_type bob_active_pub( bob_active_key.get_public_key() ); + public_key_type bob_owner_pub( bob_owner_key.get_public_key() ); + public_key_type cindy_active_pub( cindy_active_key.get_public_key() ); + public_key_type cindy_owner_pub( cindy_owner_key.get_public_key() ); + public_key_type daisy_active_pub( daisy_active_key.get_public_key() ); + public_key_type daisy_owner_pub( daisy_owner_key.get_public_key() ); + public_key_type edwin_active_pub( edwin_active_key.get_public_key() ); + public_key_type edwin_owner_pub( edwin_owner_key.get_public_key() ); + public_key_type frank_active_pub( frank_active_key.get_public_key() ); + public_key_type frank_owner_pub( frank_owner_key.get_public_key() ); + public_key_type gavin_active_pub( gavin_active_key.get_public_key() ); + public_key_type gavin_owner_pub( gavin_owner_key.get_public_key() ); + + set_auth( alice_id, authority( 1, alice_active_pub, 1, daisy_id, 1 ), authority( 1, alice_owner_pub, 1, cindy_id, 1 ) ); + set_auth( bob_id, authority( 1, bob_active_pub, 1, alice_id, 1 ), authority( 1, bob_owner_pub, 1, edwin_id, 1 ) ); + + set_auth( cindy_id, authority( 1, cindy_active_pub, 1 ), authority( 1, cindy_owner_pub, 1 ) ); + set_auth( daisy_id, authority( 1, daisy_active_pub, 1 ), authority( 1, daisy_owner_pub, 1 ) ); + + set_auth( edwin_id, authority( 1, edwin_active_pub, 1, gavin_id, 1 ), authority( 1, edwin_owner_pub, 1, frank_id, 1 ) ); + + set_auth( frank_id, authority( 1, frank_active_pub, 1 ), authority( 1, frank_owner_pub, 1 ) ); + set_auth( gavin_id, authority( 1, gavin_active_pub, 1 ), authority( 1, gavin_owner_pub, 1 ) ); + + generate_block(); + + signed_transaction tx; + transfer_operation op; + op.from = bob_id; + op.to = alice_id; + op.amount = asset(1); + tx.operations.push_back( op ); + set_expiration( db, tx ); + + // https://github.com/bitshares/bitshares-core/issues/584 + // If not allow non-immediate owner to authorize + BOOST_CHECK( chk( tx, false, { alice_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { alice_active_pub }, { alice_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { alice_active_pub, alice_owner_pub }, { alice_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { bob_owner_pub }, { bob_owner_pub } ) ); + BOOST_CHECK( chk( tx, false, { bob_active_pub }, { bob_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { bob_active_pub, bob_owner_pub }, { bob_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { cindy_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { cindy_active_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { cindy_active_pub, cindy_owner_pub }, { } ) ); + + BOOST_CHECK( chk( tx, false, { daisy_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { daisy_active_pub }, { daisy_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { daisy_active_pub, daisy_owner_pub }, { daisy_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { edwin_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { edwin_active_pub }, { edwin_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { edwin_active_pub, edwin_owner_pub }, { edwin_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { frank_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { frank_active_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { frank_active_pub, frank_owner_pub }, { } ) ); + + BOOST_CHECK( chk( tx, false, { gavin_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { gavin_active_pub }, { gavin_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { gavin_active_pub, gavin_owner_pub }, { gavin_active_pub } ) ); + + // If allow non-immediate owner to authorize + BOOST_CHECK( chk( tx, true, { alice_owner_pub }, { alice_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { alice_active_pub }, { alice_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { alice_active_pub, alice_owner_pub }, { alice_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { bob_owner_pub }, { bob_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { bob_active_pub }, { bob_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { bob_active_pub, bob_owner_pub }, { bob_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { cindy_owner_pub }, { cindy_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { cindy_active_pub }, { cindy_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { cindy_active_pub, cindy_owner_pub }, { cindy_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { daisy_owner_pub }, { daisy_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { daisy_active_pub }, { daisy_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { daisy_active_pub, daisy_owner_pub }, { daisy_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { edwin_owner_pub }, { edwin_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { edwin_active_pub }, { edwin_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { edwin_active_pub, edwin_owner_pub }, { edwin_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { frank_owner_pub }, { frank_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { frank_active_pub }, { frank_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { frank_active_pub, frank_owner_pub }, { frank_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { gavin_owner_pub }, { gavin_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { gavin_active_pub }, { gavin_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { gavin_active_pub, gavin_owner_pub }, { gavin_active_pub } ) ); + + sign( tx, alice_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, alice_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, bob_owner_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, bob_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, cindy_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, cindy_active_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, daisy_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, daisy_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, edwin_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, edwin_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, frank_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, frank_active_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, gavin_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + sign( tx, gavin_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true); + tx.clear_signatures(); + + // proposal tests + auto new_proposal = [&]() -> proposal_id_type { + signed_transaction ptx; + + proposal_create_operation pop; + pop.proposed_ops.emplace_back(op); + pop.fee_paying_account = bob_id; + pop.expiration_time = db.head_block_time() + fc::days(1); + ptx.operations.push_back(pop); + set_expiration( db, ptx ); + sign( ptx, bob_active_key ); + + return PUSH_TX( db, ptx, database::skip_transaction_dupe_check ).operation_results[0].get(); + }; + + auto approve_proposal = [&]( + proposal_id_type proposal, + account_id_type account, + bool approve_with_owner, + fc::ecc::private_key key + ) + { + signed_transaction ptx; + + proposal_update_operation pup; + pup.fee_paying_account = account; + pup.proposal = proposal; + if( approve_with_owner ) + pup.owner_approvals_to_add.insert( account ); + else + pup.active_approvals_to_add.insert( account ); + ptx.operations.push_back(pup); + set_expiration( db, ptx ); + sign( ptx, key ); + PUSH_TX( db, ptx, database::skip_transaction_dupe_check ); + }; + + proposal_id_type pid; + + pid = new_proposal(); + approve_proposal( pid, alice_id, true, alice_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, alice_id, false, alice_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, true, bob_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, false, bob_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + // Cindy's approval doesn't work + pid = new_proposal(); + approve_proposal( pid, cindy_id, true, cindy_owner_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, cindy_id, false, cindy_active_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, true, daisy_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, false, daisy_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, true, edwin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, false, edwin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + // Frank's approval doesn't work + pid = new_proposal(); + approve_proposal( pid, frank_id, true, frank_owner_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, frank_id, false, frank_active_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, true, gavin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, false, gavin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + generate_block( database::skip_transaction_dupe_check ); + + // pass the hard fork time + generate_blocks( HARDFORK_1002_TIME, true, database::skip_transaction_dupe_check ); + set_expiration( db, tx ); + + // signing tests + sign( tx, alice_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, alice_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, bob_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, bob_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, cindy_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, cindy_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, daisy_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, daisy_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, edwin_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, edwin_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, frank_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, frank_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, gavin_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, gavin_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + // proposal tests + pid = new_proposal(); + approve_proposal( pid, alice_id, true, alice_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, alice_id, false, alice_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, true, bob_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, false, bob_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, cindy_id, true, cindy_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, cindy_id, false, cindy_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, true, daisy_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, false, daisy_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, true, edwin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, false, edwin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, frank_id, true, frank_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, frank_id, false, frank_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, true, gavin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, false, gavin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + generate_block( database::skip_transaction_dupe_check ); } catch(fc::exception& e) { diff --git a/tests/tests/database_api_tests.cpp b/tests/tests/database_api_tests.cpp index e2453176f..67a1dbfc3 100644 --- a/tests/tests/database_api_tests.cpp +++ b/tests/tests/database_api_tests.cpp @@ -25,6 +25,9 @@ #include #include +#include + +#include #include "../common/database_fixture.hpp" @@ -68,4 +71,682 @@ BOOST_FIXTURE_TEST_SUITE(database_api_tests, database_fixture) } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE( get_potential_signatures_owner_and_active ) +{ + try { + fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); + fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest("key2")); + public_key_type pub_key_active( nathan_key1.get_public_key() ); + public_key_type pub_key_owner( nathan_key2.get_public_key() ); + const account_object& nathan = create_account("nathan", nathan_key1.get_public_key() ); + + try { + account_update_operation op; + op.account = nathan.id; + op.active = authority(1, pub_key_active, 1); + op.owner = authority(1, pub_key_owner, 1); + trx.operations.push_back(op); + sign(trx, nathan_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + } FC_CAPTURE_AND_RETHROW ((nathan.active)) + + // this op requires active + transfer_operation op; + op.from = nathan.id; + op.to = account_id_type(); + trx.operations.push_back(op); + + graphene::app::database_api db_api(db); + set pub_keys = db_api.get_potential_signatures( trx ); + + BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + + trx.operations.clear(); + + // this op requires owner + account_update_operation auop; + auop.account = nathan.id; + auop.owner = authority(1, pub_key_owner, 1); + trx.operations.push_back(auop); + + pub_keys = db_api.get_potential_signatures( trx ); + + BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() ); // active key doesn't help in this case + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + + } FC_LOG_AND_RETHROW() +} + +/// Testing get_potential_signatures and get_required_signatures for non-immediate owner authority issue. +/// https://github.com/bitshares/bitshares-core/issues/584 +BOOST_AUTO_TEST_CASE( get_signatures_non_immediate_owner ) +{ + try { + fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); + fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest("key2")); + fc::ecc::private_key ashley_key1 = fc::ecc::private_key::regenerate(fc::digest("akey1")); + fc::ecc::private_key ashley_key2 = fc::ecc::private_key::regenerate(fc::digest("akey2")); + fc::ecc::private_key oliver_key1 = fc::ecc::private_key::regenerate(fc::digest("okey1")); + fc::ecc::private_key oliver_key2 = fc::ecc::private_key::regenerate(fc::digest("okey2")); + public_key_type pub_key_active( nathan_key1.get_public_key() ); + public_key_type pub_key_owner( nathan_key2.get_public_key() ); + public_key_type a_pub_key_active( ashley_key1.get_public_key() ); + public_key_type a_pub_key_owner( ashley_key2.get_public_key() ); + public_key_type o_pub_key_active( oliver_key1.get_public_key() ); + public_key_type o_pub_key_owner( oliver_key2.get_public_key() ); + const account_object& nathan = create_account("nathan", nathan_key1.get_public_key() ); + const account_object& ashley = create_account("ashley", ashley_key1.get_public_key() ); + const account_object& oliver = create_account("oliver", oliver_key1.get_public_key() ); + account_id_type nathan_id = nathan.id; + account_id_type ashley_id = ashley.id; + account_id_type oliver_id = oliver.id; + + try { + account_update_operation op; + op.account = nathan_id; + op.active = authority(1, pub_key_active, 1, ashley_id, 1); + op.owner = authority(1, pub_key_owner, 1, oliver_id, 1); + trx.operations.push_back(op); + sign(trx, nathan_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + + op.account = ashley_id; + op.active = authority(1, a_pub_key_active, 1); + op.owner = authority(1, a_pub_key_owner, 1); + trx.operations.push_back(op); + sign(trx, ashley_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + + op.account = oliver_id; + op.active = authority(1, o_pub_key_active, 1); + op.owner = authority(1, o_pub_key_owner, 1); + trx.operations.push_back(op); + sign(trx, oliver_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + } FC_CAPTURE_AND_RETHROW ((nathan.active)) + + // this transaction requires active + signed_transaction trx_a; + transfer_operation op; + op.from = nathan_id; + op.to = account_id_type(); + trx_a.operations.push_back(op); + + // get potential signatures + graphene::app::database_api db_api(db); + set pub_keys = db_api.get_potential_signatures( trx_a ); + + BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) != pub_keys.end() ); + // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584 + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584 + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) == pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_a, { a_pub_key_owner, o_pub_key_owner } ); + BOOST_CHECK( pub_keys.empty() ); + + // this op requires owner + signed_transaction trx_o; + account_update_operation auop; + auop.account = nathan_id; + auop.owner = authority(1, pub_key_owner, 1); + trx_o.operations.push_back(auop); + + // get potential signatures + pub_keys = db_api.get_potential_signatures( trx_o ); + + // active authorities doesn't help in this case + BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + + // owner authorities should be ok + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584 + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) == pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_o, { a_pub_key_owner, o_pub_key_owner } ); + BOOST_CHECK( pub_keys.empty() ); + + // go beyond hard fork + generate_blocks( HARDFORK_1002_TIME, true ); + + // for the transaction that requires active + // get potential signatures + pub_keys = db_api.get_potential_signatures( trx_a ); + + // all authorities should be ok + BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_a, { a_pub_key_owner } ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) != pub_keys.end() ); + pub_keys = db_api.get_required_signatures( trx_a, { o_pub_key_owner } ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + // for the transaction that requires owner + // get potential signatures + pub_keys = db_api.get_potential_signatures( trx_o ); + + // active authorities doesn't help in this case + BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + + // owner authorities should help + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_o, { a_pub_key_owner, o_pub_key_owner } ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( get_potential_signatures_other ) +{ + try { + fc::ecc::private_key priv_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); + public_key_type pub_key1( priv_key1.get_public_key() ); + + const account_object& nathan = create_account( "nathan" ); + + balance_claim_operation op; + op.deposit_to_account = nathan.id; + op.balance_owner_key = pub_key1; + trx.operations.push_back(op); + + graphene::app::database_api db_api(db); + set pub_keys = db_api.get_potential_signatures( trx ); + + BOOST_CHECK( pub_keys.find( pub_key1 ) != pub_keys.end() ); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( get_required_signatures_owner_or_active ) +{ + try { + fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); + fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest("key2")); + public_key_type pub_key_active( nathan_key1.get_public_key() ); + public_key_type pub_key_owner( nathan_key2.get_public_key() ); + const account_object& nathan = create_account("nathan", nathan_key1.get_public_key() ); + + try { + account_update_operation op; + op.account = nathan.id; + op.active = authority(1, pub_key_active, 1); + op.owner = authority(1, pub_key_owner, 1); + trx.operations.push_back(op); + sign(trx, nathan_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + } FC_CAPTURE_AND_RETHROW ((nathan.active)) + + graphene::app::database_api db_api(db); + + // prepare available keys sets + flat_set avail_keys1, avail_keys2, avail_keys3; + avail_keys1.insert( pub_key_active ); + avail_keys2.insert( pub_key_owner ); + avail_keys3.insert( pub_key_active ); + avail_keys3.insert( pub_key_owner ); + + set pub_keys; + + // this op requires active + transfer_operation op; + op.from = nathan.id; + op.to = account_id_type(); + trx.operations.push_back(op); + + // provides active, should be ok + pub_keys = db_api.get_required_signatures( trx, avail_keys1 ); + BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() ); + + // provides owner, should be ok + pub_keys = db_api.get_required_signatures( trx, avail_keys2 ); + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + + // provides both active and owner, should return one of them + pub_keys = db_api.get_required_signatures( trx, avail_keys3 ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() || pub_keys.find( pub_key_owner ) != pub_keys.end() ); + + trx.operations.clear(); + + // this op requires owner + account_update_operation auop; + auop.account = nathan.id; + auop.owner = authority(1, pub_key_owner, 1); + trx.operations.push_back(auop); + + // provides active, should return an empty set + pub_keys = db_api.get_required_signatures( trx, avail_keys1 ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides owner, should return it + pub_keys = db_api.get_required_signatures( trx, avail_keys2 ); + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + + // provides both active and owner, should return owner only + pub_keys = db_api.get_required_signatures( trx, avail_keys3 ); + BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( get_required_signatures_partially_signed_or_not ) +{ + try { + fc::ecc::private_key morgan_key = fc::ecc::private_key::regenerate(fc::digest("morgan_key")); + fc::ecc::private_key nathan_key = fc::ecc::private_key::regenerate(fc::digest("nathan_key")); + fc::ecc::private_key oliver_key = fc::ecc::private_key::regenerate(fc::digest("oliver_key")); + public_key_type pub_key_morgan( morgan_key.get_public_key() ); + public_key_type pub_key_nathan( nathan_key.get_public_key() ); + public_key_type pub_key_oliver( oliver_key.get_public_key() ); + const account_object& morgan = create_account("morgan", morgan_key.get_public_key() ); + const account_object& nathan = create_account("nathan", nathan_key.get_public_key() ); + const account_object& oliver = create_account("oliver", oliver_key.get_public_key() ); + + graphene::app::database_api db_api(db); + + // prepare available keys sets + flat_set avail_keys_empty, avail_keys_m, avail_keys_n, avail_keys_o; + flat_set avail_keys_mn, avail_keys_mo, avail_keys_no, avail_keys_mno; + avail_keys_m.insert( pub_key_morgan ); + avail_keys_mn.insert( pub_key_morgan ); + avail_keys_mo.insert( pub_key_morgan ); + avail_keys_mno.insert( pub_key_morgan ); + avail_keys_n.insert( pub_key_nathan ); + avail_keys_mn.insert( pub_key_nathan ); + avail_keys_no.insert( pub_key_nathan ); + avail_keys_mno.insert( pub_key_nathan ); + avail_keys_o.insert( pub_key_oliver ); + avail_keys_mo.insert( pub_key_oliver ); + avail_keys_no.insert( pub_key_oliver ); + avail_keys_mno.insert( pub_key_oliver ); + + // result set + set pub_keys; + + // make a transaction that require 1 signature (m) + transfer_operation op; + op.from = morgan.id; + op.to = oliver.id; + trx.operations.push_back(op); + + // provides [], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_empty ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m], should return [m] + pub_keys = db_api.get_required_signatures( trx, avail_keys_m ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + + // provides [n], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_n ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n], should return [m] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mn ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + + // sign with n, but actually need m + sign(trx, nathan_key); + + // provides [], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_empty ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m], should return [m] + pub_keys = db_api.get_required_signatures( trx, avail_keys_m ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + + // provides [n], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_n ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_o ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n], should return [m] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mn ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + + // provides [m,o], should return [m] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mo ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + + // provides [n,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_no ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n,o], should return [m] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mno ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + + // sign with m, should be enough + trx.clear_signatures(); + sign(trx, morgan_key); + + // provides [], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_empty ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_m ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_n ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mn ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // sign with m+n, although m only should be enough, this API won't complain + sign(trx, nathan_key); + + // provides [], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_empty ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_m ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_n ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_o ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mn ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mo ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_no ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mno ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // make a transaction that require 2 signatures (m+n) + trx.clear_signatures(); + op.from = nathan.id; + trx.operations.push_back(op); + + // provides [], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_empty ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m], should return [m] + pub_keys = db_api.get_required_signatures( trx, avail_keys_m ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + + // provides [n], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_n ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_o ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n], should return [m,n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mn ); + BOOST_CHECK( pub_keys.size() == 2 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [m,o], should return [m] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mo ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + + // provides [n,o], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_no ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [m,n,o], should return [m,n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mno ); + BOOST_CHECK( pub_keys.size() == 2 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // sign with o, but actually need m+n + sign(trx, oliver_key); + + // provides [], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_empty ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m], should return [m] + pub_keys = db_api.get_required_signatures( trx, avail_keys_m ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + + // provides [n], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_n ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_o ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n], should return [m,n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mn ); + BOOST_CHECK( pub_keys.size() == 2 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [m,o], should return [m] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mo ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + + // provides [n,o], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_no ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [m,n,o], should return [m,n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mno ); + BOOST_CHECK( pub_keys.size() == 2 ); + BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // sign with m+o, but actually need m+n + sign(trx, morgan_key); + + // provides [], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_empty ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_m ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_n ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_o ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mn ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [m,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mo ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n,o], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_no ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [m,n,o], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mno ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // sign with m, but actually need m+n + trx.clear_signatures(); + sign(trx, morgan_key); + + // provides [], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_empty ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_m ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_n ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_o ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mn ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [m,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mo ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n,o], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_no ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // provides [m,n,o], should return [n] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mno ); + BOOST_CHECK( pub_keys.size() == 1 ); + BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() ); + + // sign with m+n, should be enough + sign(trx, nathan_key); + + // provides [], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_empty ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_m ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_n ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_o ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mn ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mo ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_no ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mno ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // sign with m+n+o, should be enough as well + sign(trx, oliver_key); + + // provides [], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_empty ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_m ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_n ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_o ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mn ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mo ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [n,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_no ); + BOOST_CHECK( pub_keys.size() == 0 ); + + // provides [m,n,o], should return [] + pub_keys = db_api.get_required_signatures( trx, avail_keys_mno ); + BOOST_CHECK( pub_keys.size() == 0 ); + + } FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_SUITE_END()