Merge pull request #14 from peerplays-network/feature/duplicate-operation-check-hardfork

Feature/duplicate operation check hardfork
This commit is contained in:
Fabian Schuh 2019-02-07 15:34:35 +01:00 committed by GitHub
commit 89361e58af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 212 additions and 108 deletions

View file

@ -169,14 +169,14 @@ namespace graphene { namespace app {
void network_broadcast_api::broadcast_transaction(const signed_transaction& trx)
{
trx.validate();
_app.chain_database()->check_tansaction_for_duplicated_operations(trx);
database_api( *(_app.chain_database() ) ).check_transaction_for_duplicated_operations(trx);
_app.chain_database()->push_transaction(trx);
_app.p2p_node()->broadcast_transaction(trx);
}
fc::variant network_broadcast_api::broadcast_transaction_synchronous(const signed_transaction& trx)
{
_app.chain_database()->check_tansaction_for_duplicated_operations(trx);
database_api( *(_app.chain_database() ) ).check_transaction_for_duplicated_operations(trx);
fc::promise<fc::variant>::ptr prom( new fc::promise<fc::variant>() );
broadcast_transaction_with_callback( [=]( const fc::variant& v ){

View file

@ -31,6 +31,7 @@
#include <fc/smart_ref_impl.hpp>
#include <fc/crypto/hex.hpp>
#include <fc/crypto/digest.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/rational.hpp>
@ -45,6 +46,44 @@
typedef std::map< std::pair<graphene::chain::asset_id_type, graphene::chain::asset_id_type>, std::vector<fc::variant> > market_queue_type;
namespace {
struct proposed_operations_digest_accumulator
{
typedef void result_type;
void operator()(const graphene::chain::proposal_create_operation& proposal)
{
for (auto& operation: proposal.proposed_ops)
{
if( operation.op.which() != graphene::chain::operation::tag<graphene::chain::betting_market_group_create_operation>::value
&& operation.op.which() != graphene::chain::operation::tag<graphene::chain::betting_market_create_operation>::value )
proposed_operations_digests.push_back(fc::digest(operation.op));
}
}
//empty template method is needed for all other operation types
//we can ignore them, we are interested in only proposal_create_operation
template<class T>
void operator()(const T&)
{}
std::vector<fc::sha256> proposed_operations_digests;
};
std::vector<fc::sha256> gather_proposed_operations_digests(const graphene::chain::transaction& trx)
{
proposed_operations_digest_accumulator digest_accumulator;
for (auto& operation: trx.operations)
{
operation.visit(digest_accumulator);
}
return digest_accumulator.proposed_operations_digests;
}
}
namespace graphene { namespace app {
class database_api_impl;
@ -70,6 +109,7 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
map<uint32_t, optional<block_header>> get_block_header_batch(const vector<uint32_t> block_nums)const;
optional<signed_block> get_block(uint32_t block_num)const;
processed_transaction get_transaction( uint32_t block_num, uint32_t trx_in_block )const;
void check_transaction_for_duplicated_operations(const signed_transaction& trx);
// Globals
chain_property_object get_chain_properties()const;
@ -429,6 +469,39 @@ processed_transaction database_api_impl::get_transaction(uint32_t block_num, uin
return opt_block->transactions[trx_num];
}
void database_api::check_transaction_for_duplicated_operations(const signed_transaction& trx)
{
my->check_transaction_for_duplicated_operations(trx);
}
void database_api_impl::check_transaction_for_duplicated_operations(const signed_transaction& trx)
{
const auto& idx = _db.get_index_type<proposal_index>();
const auto& pidx = dynamic_cast<const primary_index<proposal_index>&>(idx);
const auto& raidx = pidx.get_secondary_index<graphene::chain::required_approval_index>();
auto acc_itr = raidx._account_to_proposals.find( GRAPHENE_WITNESS_ACCOUNT );
if( acc_itr != raidx._account_to_proposals.end() )
{
auto& p_set = acc_itr->second;
std::set<fc::sha256> existed_operations_digests;
for( auto p_itr = p_set.begin(); p_itr != p_set.end(); ++p_itr )
{
for( auto& operation : (*p_itr)(_db).proposed_transaction.operations )
{
existed_operations_digests.insert( fc::digest(operation) );
}
}
auto proposed_operations_digests = gather_proposed_operations_digests(trx);
for (auto& digest : proposed_operations_digests)
{
FC_ASSERT(existed_operations_digests.count(digest) == 0, "Proposed operation is already pending for apsproval.");
}
}
}
//////////////////////////////////////////////////////////////////////
// //
// Globals //

View file

@ -192,6 +192,12 @@ class database_api
*/
optional<signed_transaction> get_recent_transaction_by_id( const transaction_id_type& id )const;
/**
* TODO
*
*/
void check_transaction_for_duplicated_operations(const signed_transaction& trx);
/////////////
// Globals //
/////////////
@ -645,6 +651,7 @@ class database_api
*/
vector<tournament_id_type> get_registered_tournaments(account_id_type account_filter, uint32_t limit) const;
private:
std::shared_ptr< database_api_impl > my;
};

View file

@ -136,8 +136,6 @@ namespace graphene { namespace chain {
void add_checkpoints( const flat_map<uint32_t,block_id_type>& checkpts );
const flat_map<uint32_t,block_id_type> get_checkpoints()const { return _checkpoints; }
bool before_last_checkpoint()const;
void check_tansaction_for_duplicated_operations(const signed_transaction& trx);
bool push_block( const signed_block& b, uint32_t skip = skip_nothing );
processed_transaction push_transaction( const signed_transaction& trx, uint32_t skip = skip_nothing );

View file

@ -9,7 +9,9 @@
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/protocol/committee_member.hpp>
#include <graphene/chain/protocol/sport.hpp>
#include <fc/crypto/digest.hpp>
#include <graphene/app/database_api.hpp>
#include "../common/database_fixture.hpp"
@ -28,6 +30,27 @@ namespace
return transfer;
}
sport_create_operation make_sport_create_operation(std::string s1, std::string s2)
{
sport_create_operation op;
op.name = {{ s1, s2 }};
return op;
}
betting_market_group_create_operation make_betting_market_group_create(string s1, string s2)
{
betting_market_group_create_operation op;
op.description = {{ s1, s2 }};
return op;
}
betting_market_create_operation make_betting_market_operation(string s1, string s2)
{
betting_market_create_operation op;
op.description = {{ s1, s2 }};
return op;
}
committee_member_create_operation make_committee_member_create_operation(const asset& fee, const account_id_type& member, const string& url)
{
committee_member_create_operation member_create_operation;
@ -48,6 +71,7 @@ namespace
fixture.db.create<proposal_object>([&](proposal_object& proposal)
{
proposal.proposed_transaction = transaction;
proposal.required_active_approvals = { GRAPHENE_WITNESS_ACCOUNT };
});
}
@ -88,18 +112,16 @@ namespace
}
}
BOOST_FIXTURE_TEST_SUITE( check_tansaction_for_duplicated_operations, database_fixture )
BOOST_FIXTURE_TEST_SUITE( check_transaction_for_duplicated_operations, database_fixture )
BOOST_AUTO_TEST_CASE( test_exception_throwing_for_the_same_operation_proposed_twice )
BOOST_AUTO_TEST_CASE( test_exception_throwing_for_the_same_operation_proposed_for_witness_twice )
{
try
{
ACTORS((alice))
create_proposal(*this, {make_sport_create_operation("SPORT1", "S1")});
create_proposal(*this, {make_transfer_operation(account_id_type(), alice_id, asset(500))});
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_transfer_operation(account_id_type(), alice_id, asset(500))});
BOOST_CHECK_THROW(db.check_tansaction_for_duplicated_operations(trx), fc::exception);
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_sport_create_operation("SPORT1", "S1")} );
BOOST_CHECK_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx), fc::exception);
}
catch( const fc::exception& e )
{
@ -112,10 +134,8 @@ BOOST_AUTO_TEST_CASE( check_passes_without_duplication )
{
try
{
ACTORS((alice))
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_transfer_operation(account_id_type(), alice_id, asset(500))});
BOOST_CHECK_NO_THROW(db.check_tansaction_for_duplicated_operations(trx));
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_sport_create_operation("SPORT1", "S1")});
BOOST_CHECK_NO_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx));
}
catch( const fc::exception& e )
{
@ -124,16 +144,14 @@ BOOST_AUTO_TEST_CASE( check_passes_without_duplication )
}
}
BOOST_AUTO_TEST_CASE( check_passes_for_the_same_operation_with_different_assets )
BOOST_AUTO_TEST_CASE( check_passes_for_the_same_operation_with_different_names )
{
try
{
ACTORS((alice))
create_proposal(*this, {make_sport_create_operation("SPORT1", "S1")});
create_proposal(*this, {make_transfer_operation(account_id_type(), alice_id, asset(500))});
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_transfer_operation(account_id_type(), alice_id, asset(501))});
BOOST_CHECK_NO_THROW(db.check_tansaction_for_duplicated_operations(trx));
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_sport_create_operation("SPORT2", "S2")});
BOOST_CHECK_NO_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx));
}
catch( const fc::exception& e )
{
@ -146,13 +164,11 @@ BOOST_AUTO_TEST_CASE( check_fails_for_duplication_in_transaction_with_several_op
{
try
{
ACTORS((alice))
create_proposal(*this, {make_sport_create_operation("SPORT1", "S1")});
create_proposal(*this, {make_transfer_operation(account_id_type(), alice_id, asset(500))});
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_transfer_operation(account_id_type(), alice_id, asset(501)),
make_transfer_operation(account_id_type(), alice_id, asset(500))}); //duplicated one
BOOST_CHECK_THROW(db.check_tansaction_for_duplicated_operations(trx), fc::exception);
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_sport_create_operation("SPORT2", "S2"),
make_sport_create_operation("SPORT1", "S1") }); //duplicated one
BOOST_CHECK_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx), fc::exception);
}
catch( const fc::exception& e )
{
@ -165,14 +181,12 @@ BOOST_AUTO_TEST_CASE( check_fails_for_duplicated_operation_in_existed_proposal_w
{
try
{
ACTORS((alice))
create_proposal(*this, {make_sport_create_operation("SPORT1", "S1"),
make_sport_create_operation("SPORT2", "S2") }); //duplicated one
create_proposal(*this, {make_transfer_operation(account_id_type(), alice_id, asset(499)),
make_transfer_operation(account_id_type(), alice_id, asset(500))}); //duplicated one
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_transfer_operation(account_id_type(), alice_id, asset(501)),
make_transfer_operation(account_id_type(), alice_id, asset(500))}); //duplicated one
BOOST_CHECK_THROW(db.check_tansaction_for_duplicated_operations(trx), fc::exception);
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_sport_create_operation("SPORT3", "S3"),
make_sport_create_operation("SPORT2", "S2")}); //duplicated one
BOOST_CHECK_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx), fc::exception);
}
catch( const fc::exception& e )
{
@ -185,13 +199,11 @@ BOOST_AUTO_TEST_CASE( check_fails_for_duplicated_operation_in_existed_proposal_w
{
try
{
ACTORS((alice))
create_proposal(*this, {make_sport_create_operation("SPORT1", "S1"),
make_sport_create_operation("SPORT2", "S2")}); //duplicated one
create_proposal(*this, {make_transfer_operation(account_id_type(), alice_id, asset(499)),
make_transfer_operation(account_id_type(), alice_id, asset(500))}); //duplicated one
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_transfer_operation(account_id_type(), alice_id, asset(500))}); //duplicated one
BOOST_CHECK_THROW(db.check_tansaction_for_duplicated_operations(trx), fc::exception);
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_sport_create_operation("SPORT2", "S2")}); //duplicated one
BOOST_CHECK_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx), fc::exception);
}
catch( const fc::exception& e )
{
@ -204,12 +216,12 @@ BOOST_AUTO_TEST_CASE( check_passes_for_different_operations_types )
{
try
{
ACTORS((alice))
ACTOR( alice );
create_proposal(*this, {make_transfer_operation(account_id_type(), alice_id, asset(500))});
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_committee_member_create_operation(asset(1000), account_id_type(), "test url")});
BOOST_CHECK_NO_THROW(db.check_tansaction_for_duplicated_operations(trx));
BOOST_CHECK_NO_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx));
}
catch( const fc::exception& e )
{
@ -225,7 +237,7 @@ BOOST_AUTO_TEST_CASE( check_fails_for_same_member_create_operations )
create_proposal(*this, {make_committee_member_create_operation(asset(1000), account_id_type(), "test url")});
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_committee_member_create_operation(asset(1000), account_id_type(), "test url")});
BOOST_CHECK_THROW(db.check_tansaction_for_duplicated_operations(trx), fc::exception);
BOOST_CHECK_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx), fc::exception);
}
catch( const fc::exception& e )
{
@ -241,7 +253,7 @@ BOOST_AUTO_TEST_CASE( check_passes_for_different_member_create_operations )
create_proposal(*this, {make_committee_member_create_operation(asset(1000), account_id_type(), "test url")});
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_committee_member_create_operation(asset(1001), account_id_type(), "test url")});
BOOST_CHECK_NO_THROW(db.check_tansaction_for_duplicated_operations(trx));
BOOST_CHECK_NO_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx));
}
catch( const fc::exception& e )
{
@ -265,7 +277,7 @@ BOOST_AUTO_TEST_CASE( check_failes_for_several_operations_of_mixed_type )
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_transfer_operation(account_id_type(), alice_id, asset(501)), //duplicate
make_committee_member_create_operation(asset(1002), account_id_type(), "test url")});
BOOST_CHECK_THROW(db.check_tansaction_for_duplicated_operations(trx), fc::exception);
BOOST_CHECK_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx), fc::exception);
}
catch( const fc::exception& e )
{
@ -278,20 +290,15 @@ BOOST_AUTO_TEST_CASE( check_failes_for_duplicates_in_pending_transactions_list )
{
try
{
ACTORS((alice))
ACTOR( alice );
fc::ecc::private_key committee_key = init_account_priv_key;
auto duplicate = make_sport_create_operation("SPORT1", "S1");
const account_object& moneyman = create_account("moneyman", init_account_pub_key);
const asset_object& core = asset_id_type()(db);
push_proposal( *this, GRAPHENE_WITNESS_ACCOUNT(db), {duplicate} );
transfer(account_id_type()(db), moneyman, core.amount(1000000));
auto trx = make_signed_transaction_with_proposed_operation( *this, {duplicate} );
auto duplicate = make_transfer_operation(alice.id, moneyman.get_id(), asset(100));
push_proposal(*this, moneyman, {duplicate});
auto trx = make_signed_transaction_with_proposed_operation(*this, {duplicate});
BOOST_CHECK_THROW(db.check_tansaction_for_duplicated_operations(trx), fc::exception);
BOOST_CHECK_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx), fc::exception);
}
catch( const fc::exception& e )
{
@ -316,7 +323,7 @@ BOOST_AUTO_TEST_CASE( check_passes_for_no_duplicates_in_pending_transactions_lis
push_proposal(*this, moneyman, {make_transfer_operation(alice.id, moneyman.get_id(), asset(100))});
auto trx = make_signed_transaction_with_proposed_operation(*this, {make_transfer_operation(alice.id, moneyman.get_id(), asset(101))});
BOOST_CHECK_NO_THROW(db.check_tansaction_for_duplicated_operations(trx));
BOOST_CHECK_NO_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx));
}
catch( const fc::exception& e )
{
@ -338,13 +345,13 @@ BOOST_AUTO_TEST_CASE( check_fails_for_several_transactions_with_duplicates_in_pe
transfer(account_id_type()(db), moneyman, core.amount(1000000));
auto duplicate = make_transfer_operation(alice.id, moneyman.get_id(), asset(100));
push_proposal(*this, moneyman, {make_transfer_operation(alice.id, moneyman.get_id(), asset(101)),
duplicate});
auto duplicate = make_sport_create_operation("SPORT1", "S1");
push_proposal(*this, moneyman, {make_sport_create_operation("SPORT2", "S2"), duplicate} );
auto trx = make_signed_transaction_with_proposed_operation(*this, {duplicate,
make_transfer_operation(alice.id, moneyman.get_id(), asset(102))});
BOOST_CHECK_THROW(db.check_tansaction_for_duplicated_operations(trx), fc::exception);
auto trx = make_signed_transaction_with_proposed_operation(*this,
{duplicate, make_sport_create_operation("SPORT3", "S3")} );
BOOST_CHECK_THROW(graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx), fc::exception);
}
catch( const fc::exception& e )
{
@ -353,61 +360,80 @@ BOOST_AUTO_TEST_CASE( check_fails_for_several_transactions_with_duplicates_in_pe
}
}
BOOST_AUTO_TEST_CASE( check_passes_for_duplicated_betting_market_or_group )
BOOST_AUTO_TEST_CASE( check_passes_for_duplicated_betting_market_group_create )
{
generate_blocks( HARDFORK_1000_TIME + fc::seconds(300) );
try
{
auto duplicate = make_betting_market_group_create( "BMGROUP1", "BMG1" );
create_proposal(*this, {duplicate} );
auto trx = make_signed_transaction_with_proposed_operation(*this, {duplicate} );
BOOST_CHECK_NO_THROW( graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx) );
}
catch( const fc::exception &e )
{
edump( ( e.to_detail_string() ) );
throw;
}
}
BOOST_AUTO_TEST_CASE( check_passes_for_duplicated_betting_market_create )
{
try
{
const sport_id_type sport_id = create_sport( {{"SN","SPORT_NAME"}} ).id;
const event_group_id_type event_group_id = create_event_group( {{"EG", "EVENT_GROUP"}}, sport_id ).id;
const betting_market_rules_id_type betting_market_rules_id =
create_betting_market_rules( {{"EN", "Rules"}}, {{"EN", "Some rules"}} ).id;
event_create_operation evcop1;
evcop1.event_group_id = event_group_id;
evcop1.name = {{"NO", "NAME_ONE"}};
evcop1.season = {{"NO", "NAME_ONE"}};
auto duplicate = make_betting_market_operation( "BMARKET1", "BM1" );
event_create_operation evcop2;
evcop2.event_group_id = event_group_id;
evcop2.name = {{"NT", "NAME_TWO"}};
evcop2.season = {{"NT", "NAME_TWO"}};
create_proposal( *this, {duplicate} );
betting_market_group_create_operation bmgcop;
bmgcop.description = {{"NN", "NO_NAME"}};
bmgcop.event_id = object_id_type(relative_protocol_ids, 0, 0);
bmgcop.rules_id = betting_market_rules_id;
bmgcop.asset_id = asset_id_type();
auto trx = make_signed_transaction_with_proposed_operation(*this, {duplicate} );
betting_market_create_operation bmcop;
bmcop.group_id = object_id_type(relative_protocol_ids, 0, 1);
bmcop.payout_condition.insert( internationalized_string_type::value_type( "CN", "CONDI_NAME" ) );
proposal_create_operation pcop1 = proposal_create_operation::committee_proposal(
db.get_global_properties().parameters,
db.head_block_time()
);
pcop1.review_period_seconds.reset();
proposal_create_operation pcop2 = pcop1;
pcop1.proposed_ops.emplace_back( evcop1 );
pcop1.proposed_ops.emplace_back( bmgcop );
pcop1.proposed_ops.emplace_back( bmcop );
pcop2.proposed_ops.emplace_back( evcop2 );
pcop2.proposed_ops.emplace_back( bmgcop );
pcop2.proposed_ops.emplace_back( bmcop );
create_proposal(*this, { pcop1, pcop2 });
auto trx = make_signed_transaction_with_proposed_operation(*this, { pcop1, pcop2 });
BOOST_CHECK_NO_THROW( db.check_tansaction_for_duplicated_operations(trx) );
BOOST_CHECK_NO_THROW( graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx) );
}
catch( const fc::exception& e )
catch( const fc::exception &e )
{
edump((e.to_detail_string()));
edump( ( e.to_detail_string() ) );
throw;
}
}
BOOST_AUTO_TEST_CASE( check_passes_for_duplicated_betting_market_and_betting_market_group_create )
{
try
{
auto duplicate_market = make_betting_market_operation( "BMARKET1", "BM1" );
auto duplicate_group = make_betting_market_group_create( "BMGROUP1", "BMG1" );
create_proposal( *this, {duplicate_market, duplicate_group} );
auto trx = make_signed_transaction_with_proposed_operation(*this, {duplicate_market, duplicate_group} );
BOOST_CHECK_NO_THROW( graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx) );
}
catch( const fc::exception &e )
{
edump( ( e.to_detail_string() ) );
throw;
}
}
BOOST_AUTO_TEST_CASE( check_passes_for_duplicated_betting_market_in_one_operation )
{
try
{
auto duplicate = make_betting_market_operation( "BMARKET1", "BM1" );
create_proposal( *this, {duplicate, duplicate} );
auto trx = make_signed_transaction_with_proposed_operation(*this, {duplicate, duplicate} );
BOOST_CHECK_NO_THROW( graphene::app::database_api(db).check_transaction_for_duplicated_operations(trx) );
}
catch( const fc::exception &e )
{
edump( ( e.to_detail_string() ) );
throw;
}
}