diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 2650d47c..8c31a333 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -325,6 +325,24 @@ processed_transaction database::validate_transaction( const signed_transaction& return _apply_transaction( trx ); } +class push_proposal_nesting_guard { +public: + push_proposal_nesting_guard( uint32_t& nesting_counter, const database& db ) + : orig_value(nesting_counter), counter(nesting_counter) + { + FC_ASSERT( counter < db.get_global_properties().active_witnesses.size() * 2, "Max proposal nesting depth exceeded!" ); + counter++; + } + ~push_proposal_nesting_guard() + { + if( --counter != orig_value ) + elog( "Unexpected proposal nesting count value: ${n} != ${o}", ("n",counter)("o",orig_value) ); + } +private: + const uint32_t orig_value; + uint32_t& counter; +}; + processed_transaction database::push_proposal(const proposal_object& proposal) { try { transaction_evaluation_state eval_state(this); @@ -336,6 +354,9 @@ processed_transaction database::push_proposal(const proposal_object& proposal) size_t old_applied_ops_size = _applied_ops.size(); try { + push_proposal_nesting_guard guard( _push_proposal_nesting_depth, *this ); + if( _undo_db.size() >= _undo_db.max_size() ) + _undo_db.set_max_size( _undo_db.size() + 1 ); auto session = _undo_db.start_undo_session(true); for( auto& op : proposal.proposed_transaction.operations ) eval_state.operation_results.emplace_back(apply_operation(eval_state, op)); diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 179fb2df..6e611be3 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -544,6 +544,8 @@ namespace graphene { namespace chain { node_property_object _node_property_object; fc::hash_ctr_rng _random_number_generator; bool _slow_replays = false; + // Counts nested proposal updates + uint32_t _push_proposal_nesting_depth = 0; }; namespace detail diff --git a/libraries/chain/protocol/proposal.cpp b/libraries/chain/protocol/proposal.cpp index 069824af..54fd728b 100644 --- a/libraries/chain/protocol/proposal.cpp +++ b/libraries/chain/protocol/proposal.cpp @@ -89,7 +89,8 @@ void proposal_update_operation::get_required_authorities( vector& o ) auth.key_auths[k] = 1; auth.weight_threshold = auth.key_auths.size(); - o.emplace_back( std::move(auth) ); + if( auth.weight_threshold > 0 ) + o.emplace_back( std::move(auth) ); } void proposal_update_operation::get_required_active_authorities( flat_set& a )const diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index c46e698f..ad9b7062 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -894,7 +894,6 @@ BOOST_FIXTURE_TEST_CASE( max_authority_membership, database_fixture ) }); transaction tx; - processed_transaction ptx; private_key_type committee_key = init_account_priv_key; // Sam is the creator of accounts @@ -1313,4 +1312,58 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) } } +BOOST_AUTO_TEST_CASE( nested_execution ) +{ try { + ACTORS( (alice)(bob) ); + fund( alice ); + + generate_blocks( fc::time_point::now() + fc::hours(1) ); + set_expiration( db, trx ); + + const auto& gpo = db.get_global_properties(); + + proposal_create_operation pco; + pco.expiration_time = db.head_block_time() + fc::minutes(1); + pco.fee_paying_account = alice_id; + proposal_id_type inner; + { + transfer_operation top; + top.from = alice_id; + top.to = bob_id; + top.amount = asset( 10 ); + pco.proposed_ops.emplace_back( top ); + trx.operations.push_back( pco ); + inner = PUSH_TX( db, trx, ~0 ).operation_results.front().get(); + trx.clear(); + pco.proposed_ops.clear(); + } + + std::vector nested; + nested.push_back( inner ); + for( size_t i = 0; i < gpo.active_witnesses.size() * 2; i++ ) + { + proposal_update_operation pup; + pup.fee_paying_account = alice_id; + pup.proposal = nested.back(); + pup.active_approvals_to_add.insert( alice_id ); + pco.proposed_ops.emplace_back( pup ); + trx.operations.push_back( pco ); + nested.push_back( PUSH_TX( db, trx, ~0 ).operation_results.front().get() ); + trx.clear(); + pco.proposed_ops.clear(); + } + + + proposal_update_operation pup; + pup.fee_paying_account = alice_id; + pup.proposal = nested.back(); + pup.active_approvals_to_add.insert( alice_id ); + trx.operations.push_back( pup ); + PUSH_TX( db, trx, ~0 ); + + for( size_t i = 1; i < nested.size(); i++ ) + BOOST_CHECK_THROW( db.get( nested[i] ), fc::assert_exception ); // executed successfully -> object removed + db.get( inner ); // asn't executed -> object exists, doesn't throw +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END()