Merge commit 'refs/pull/144/head' of https://github.com/peerplays-network/peerplays into feature/SON-84

This commit is contained in:
S 2019-09-24 21:44:44 +02:00
commit aa322f6ffd
22 changed files with 475 additions and 6 deletions

View file

@ -432,7 +432,12 @@ namespace graphene { namespace app {
} case balance_object_type:{
/** these are free from any accounts */
break;
}
} case son_object_type:{
const auto& aobj = dynamic_cast<const son_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->son_member_account );
break;
}
case sport_object_type:
case event_group_object_type:
case event_object_type:

View file

@ -136,6 +136,9 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
fc::optional<committee_member_object> get_committee_member_by_account(account_id_type account)const;
map<string, committee_member_id_type> lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const;
// SON members
fc::optional<son_object> get_son_member_by_account(account_id_type account)const;
// Votes
vector<variant> lookup_vote_ids( const vector<vote_id_type>& votes )const;
@ -1577,6 +1580,26 @@ map<string, committee_member_id_type> database_api_impl::lookup_committee_member
return committee_members_by_account_name;
}
//////////////////////////////////////////////////////////////////////
// //
// SON members //
// //
//////////////////////////////////////////////////////////////////////
fc::optional<son_object> database_api::get_son_member_by_account(account_id_type account)const
{
return my->get_son_member_by_account( account );
}
fc::optional<son_object> database_api_impl::get_son_member_by_account(account_id_type account) const
{
const auto& idx = _db.get_index_type<son_member_index>().indices().get<by_account>();
auto itr = idx.find(account);
if( itr != idx.end() )
return *itr;
return {};
}
//////////////////////////////////////////////////////////////////////
// //
// Votes //

View file

@ -282,6 +282,15 @@ struct get_impacted_account_visitor
_impacted.insert( op.affiliate );
}
void operator()( const affiliate_referral_payout_operation& op ) { }
void operator()( const son_create_operation& op ){
_impacted.insert( op.owner_account );
}
void operator()( const son_update_operation& op ){
_impacted.insert( op.owner_account );
}
void operator()( const son_delete_operation& op ){
_impacted.insert( op.owner_account );
}
};
void operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )

View file

@ -43,6 +43,7 @@
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/global_betting_statistics_object.hpp>
#include <graphene/chain/son_object.hpp>
#include <graphene/chain/worker_object.hpp>
#include <graphene/chain/witness_object.hpp>
@ -546,6 +547,17 @@ class database_api
*/
map<string, committee_member_id_type> lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const;
/////////////////
// SON members //
/////////////////
/**
* @brief Get the son_member owned by a given account
* @param account The ID of the account whose son_member should be retrieved
* @return The son_member object, or null if the account does not have a son_member
*/
fc::optional<son_object> get_son_member_by_account(account_id_type account)const;
/// WORKERS
@ -757,6 +769,9 @@ FC_API(graphene::app::database_api,
(get_committee_member_by_account)
(lookup_committee_member_accounts)
// SON members
(get_son_member_by_account)
// workers
(get_workers_by_account)
// Votes

View file

@ -111,6 +111,8 @@ add_library( graphene_chain
affiliate_payout.cpp
son_evaluator.cpp
${HEADERS}
${PROTOCOL_HEADERS}
"${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp"

View file

@ -49,13 +49,12 @@
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/sport_object.hpp>
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/global_betting_statistics_object.hpp>
#include <graphene/chain/son_object.hpp>
#include <graphene/chain/account_evaluator.hpp>
#include <graphene/chain/asset_evaluator.hpp>
@ -76,6 +75,7 @@
#include <graphene/chain/event_evaluator.hpp>
#include <graphene/chain/betting_market_evaluator.hpp>
#include <graphene/chain/tournament_evaluator.hpp>
#include <graphene/chain/son_evaluator.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
@ -237,6 +237,9 @@ void database::initialize_evaluators()
register_evaluator<tournament_join_evaluator>();
register_evaluator<game_move_evaluator>();
register_evaluator<tournament_leave_evaluator>();
register_evaluator<create_son_evaluator>();
register_evaluator<update_son_evaluator>();
register_evaluator<delete_son_evaluator>();
}
void database::initialize_indexes()
@ -301,6 +304,8 @@ void database::initialize_indexes()
//add_index< primary_index<distributed_dividend_balance_object_index > >();
add_index< primary_index<pending_dividend_payout_balance_for_holder_object_index > >();
add_index< primary_index<total_distributed_dividend_balance_object_index > >();
add_index< primary_index<son_member_index> >();
}
void database::init_genesis(const genesis_state_type& genesis_state)

View file

@ -269,6 +269,15 @@ struct get_impacted_account_visitor
_impacted.insert( op.affiliate );
}
void operator()( const affiliate_referral_payout_operation& op ) { }
void operator()( const son_create_operation& op ) {
_impacted.insert( op.owner_account );
}
void operator()( const son_update_operation& op ) {
_impacted.insert( op.owner_account );
}
void operator()( const son_delete_operation& op ) {
_impacted.insert( op.owner_account );
}
};
void operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )
@ -357,6 +366,11 @@ void get_relevant_accounts( const object* obj, flat_set<account_id_type>& accoun
} case balance_object_type:{
/** these are free from any accounts */
break;
} case son_object_type:{
const auto& aobj = dynamic_cast<const son_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->son_member_account );
break;
}
}
}

View file

@ -52,6 +52,8 @@ namespace graphene { namespace chain {
vector<committee_member_id_type> active_committee_members; // updated once per maintenance interval
flat_set<witness_id_type> active_witnesses; // updated once per maintenance interval
// n.b. witness scheduling is done by witness_schedule object
flat_set<son_id_type> active_son_members; // updated once per maintenance interval
};
/**

View file

@ -52,6 +52,9 @@ namespace graphene { namespace chain {
/// The number of active committee members this account votes the blockchain should appoint
/// Must not exceed the actual number of committee members voted for in @ref votes
uint16_t num_committee = 0;
/// The number of active son members this account votes the blockchain should appoint
/// Must not exceed the actual number of son members voted for in @ref votes
uint16_t num_son = 0;
/// This is the list of vote IDs this account votes for. The weight of these votes is determined by this
/// account's balance of core asset.
flat_set<vote_id_type> votes;

View file

@ -44,6 +44,7 @@
#include <graphene/chain/protocol/event.hpp>
#include <graphene/chain/protocol/betting_market.hpp>
#include <graphene/chain/protocol/tournament.hpp>
#include <graphene/chain/protocol/son.hpp>
namespace graphene { namespace chain {
@ -129,7 +130,10 @@ namespace graphene { namespace chain {
sport_delete_operation,
event_group_delete_operation,
affiliate_payout_operation, // VIRTUAL
affiliate_referral_payout_operation // VIRTUAL
affiliate_referral_payout_operation, // VIRTUAL
son_create_operation,
son_update_operation,
son_delete_operation
> operation;
/// @} // operations group

View file

@ -0,0 +1,52 @@
#pragma once
#include <graphene/chain/protocol/base.hpp>
namespace graphene { namespace chain {
struct son_create_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = 0; };
asset fee;
account_id_type owner_account;
std::string url;
account_id_type fee_payer()const { return owner_account; }
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
};
struct son_update_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = 0; };
asset fee;
son_id_type son_id;
account_id_type owner_account;
std::string new_url;
account_id_type fee_payer()const { return owner_account; }
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
};
struct son_delete_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = 0; };
asset fee;
son_id_type son_id;
account_id_type owner_account;
account_id_type fee_payer()const { return owner_account; }
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
};
} } // namespace graphene::chain
FC_REFLECT( graphene::chain::son_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::son_create_operation, (fee)(owner_account)(url) )
FC_REFLECT( graphene::chain::son_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::son_update_operation, (fee)(son_id)(owner_account)(new_url) )
FC_REFLECT( graphene::chain::son_delete_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::son_delete_operation, (fee)(son_id)(owner_account) )

View file

@ -145,6 +145,7 @@ namespace graphene { namespace chain {
betting_market_group_object_type,
betting_market_object_type,
bet_object_type,
son_object_type,
OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types
};
@ -202,6 +203,7 @@ namespace graphene { namespace chain {
class betting_market_group_object;
class betting_market_object;
class bet_object;
class son_object;
typedef object_id< protocol_ids, account_object_type, account_object> account_id_type;
typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type;
@ -228,6 +230,7 @@ namespace graphene { namespace chain {
typedef object_id< protocol_ids, betting_market_group_object_type, betting_market_group_object> betting_market_group_id_type;
typedef object_id< protocol_ids, betting_market_object_type, betting_market_object> betting_market_id_type;
typedef object_id< protocol_ids, bet_object_type, bet_object> bet_id_type;
typedef object_id< protocol_ids, son_object_type, son_object> son_id_type;
// implementation types
class global_property_object;
@ -402,6 +405,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type,
(betting_market_group_object_type)
(betting_market_object_type)
(bet_object_type)
(son_object_type)
(OBJECT_TYPE_COUNT)
)
FC_REFLECT_ENUM( graphene::chain::impl_object_type,
@ -469,6 +473,8 @@ FC_REFLECT_TYPENAME( graphene::chain::fba_accumulator_id_type )
FC_REFLECT_TYPENAME( graphene::chain::betting_market_position_id_type )
FC_REFLECT_TYPENAME( graphene::chain::global_betting_statistics_id_type )
FC_REFLECT_TYPENAME( graphene::chain::tournament_details_id_type )
FC_REFLECT_TYPENAME( graphene::chain::son_id_type )
FC_REFLECT( graphene::chain::void_t, )

View file

@ -64,6 +64,7 @@ struct vote_id_type
committee,
witness,
worker,
son,
VOTE_TYPE_COUNT
};
@ -148,5 +149,5 @@ void from_variant( const fc::variant& var, graphene::chain::vote_id_type& vo );
FC_REFLECT_TYPENAME( fc::flat_set<graphene::chain::vote_id_type> )
FC_REFLECT_ENUM( graphene::chain::vote_id_type::vote_type, (witness)(committee)(worker)(VOTE_TYPE_COUNT) )
FC_REFLECT_ENUM( graphene::chain::vote_id_type::vote_type, (witness)(committee)(worker)(son)(VOTE_TYPE_COUNT) )
FC_REFLECT( graphene::chain::vote_id_type, (content) )

View file

@ -0,0 +1,34 @@
#pragma once
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/protocol/son.hpp>
namespace graphene { namespace chain {
class create_son_evaluator : public evaluator<create_son_evaluator>
{
public:
typedef son_create_operation operation_type;
void_result do_evaluate(const son_create_operation& o);
object_id_type do_apply(const son_create_operation& o);
};
class update_son_evaluator : public evaluator<update_son_evaluator>
{
public:
typedef son_update_operation operation_type;
void_result do_evaluate(const son_update_operation& o);
object_id_type do_apply(const son_update_operation& o);
};
class delete_son_evaluator : public evaluator<delete_son_evaluator>
{
public:
typedef son_delete_operation operation_type;
void_result do_evaluate(const son_delete_operation& o);
void_result do_apply(const son_delete_operation& o);
};
} } // namespace graphene::chain

View file

@ -0,0 +1,46 @@
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
namespace graphene { namespace chain {
using namespace graphene::db;
/**
* @class son_object
* @brief tracks information about a son_member account.
* @ingroup object
*/
class son_object : public abstract_object<son_object>
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = son_object_type;
account_id_type son_member_account;
vote_id_type vote_id;
uint64_t total_votes = 0;
string url;
};
struct by_account;
struct by_vote_id;
using son_member_multi_index_type = multi_index_container<
son_object,
indexed_by<
ordered_unique< tag<by_id>,
member<object, object_id_type, &object::id>
>,
ordered_unique< tag<by_account>,
member<son_object, account_id_type, &son_object::son_member_account>
>,
ordered_unique< tag<by_vote_id>,
member<son_object, vote_id_type, &son_object::vote_id>
>
>
>;
using son_member_index = generic_index<son_object, son_member_multi_index_type>;
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::son_object, (graphene::db::object),
(son_member_account)(vote_id)(total_votes)(url) )

View file

@ -140,6 +140,18 @@ struct proposal_operation_hardfork_visitor
FC_ASSERT( vbco.balance_type == vesting_balance_type::unspecified, "balance_type in vesting create not allowed yet!" );
}
void operator()(const son_create_operation &v) const {
FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_create_operation not allowed yet!" );
}
void operator()(const son_update_operation &v) const {
FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_update_operation not allowed yet!" );
}
void operator()(const son_delete_operation &v) const {
FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_delete_operation not allowed yet!" );
}
// loop and self visit in proposals
void operator()(const proposal_create_operation &v) const {
for (const op_wrapper &op : v.proposed_ops)

View file

@ -0,0 +1,67 @@
#include <graphene/chain/son_evaluator.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/son_object.hpp>
#include <graphene/chain/hardfork.hpp>
namespace graphene { namespace chain {
void_result create_son_evaluator::do_evaluate(const son_create_operation& op)
{ try{
FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK");
FC_ASSERT(db().get(op.owner_account).is_lifetime_member(), "Only Lifetime members may register a SON.");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type create_son_evaluator::do_apply(const son_create_operation& op)
{ try {
vote_id_type vote_id;
db().modify(db().get_global_properties(), [&vote_id](global_property_object& p) {
vote_id = get_next_vote_id(p, vote_id_type::son);
});
const auto& new_son_object = db().create<son_object>( [&]( son_object& obj ){
obj.son_member_account = op.owner_account;
obj.vote_id = vote_id;
obj.url = op.url;
});
return new_son_object.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result update_son_evaluator::do_evaluate(const son_update_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass
const auto& idx = db().get_index_type<son_member_index>().indices().get<by_id>();
FC_ASSERT( idx.find(op.son_id) != idx.end() );
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type update_son_evaluator::do_apply(const son_update_operation& op)
{ try {
const auto& idx = db().get_index_type<son_member_index>().indices().get<by_id>();
auto itr = idx.find(op.son_id);
if(itr != idx.end())
{
db().modify(*itr, [&op](son_object &so) {
so.url = op.new_url;
});
}
return op.son_id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result delete_son_evaluator::do_evaluate(const son_delete_operation& op)
{ try {
FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON_HARDFORK"); // can be removed after HF date pass
const auto& idx = db().get_index_type<son_member_index>().indices().get<by_id>();
FC_ASSERT( idx.find(op.son_id) != idx.end() );
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result delete_son_evaluator::do_apply(const son_delete_operation& op)
{ try {
const auto& idx = db().get_index_type<son_member_index>().indices().get<by_id>();
db().remove(*idx.find(op.son_id));
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
} } // namespace graphene::chain

View file

@ -10,7 +10,7 @@ set(SOURCES node.cpp
add_library( graphene_net ${SOURCES} ${HEADERS} )
target_link_libraries( graphene_net
PUBLIC fc graphene_db )
PUBLIC graphene_chain )
target_include_directories( graphene_net
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include"
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../chain/include" "${CMAKE_CURRENT_BINARY_DIR}/../chain/include"

View file

@ -1378,6 +1378,28 @@ class wallet_api
bool approve,
bool broadcast = false);
/** Vote for a given son_member.
*
* An account can publish a list of all son_memberes they approve of. This
* command allows you to add or remove son_memberes from this list.
* Each account's vote is weighted according to the number of shares of the
* core asset owned by that account at the time the votes are tallied.
*
* @note you cannot vote against a son_member, you can only vote for the son_member
* or not vote for the son_member.
*
* @param voting_account the name or id of the account who is voting with their shares
* @param son_member the name or id of the son_member' owner account
* @param approve true if you wish to vote in favor of that son_member, false to
* remove your vote in favor of that son_member
* @param broadcast true if you wish to broadcast the transaction
* @return the signed transaction changing your vote for the given son_member
*/
signed_transaction vote_for_son_member(string voting_account,
string son_member,
bool approve,
bool broadcast = false);
/** Vote for a given witness.
*
* An account can publish a list of all witnesses they approve of. This
@ -1965,6 +1987,7 @@ FC_API( graphene::wallet::wallet_api,
(get_vesting_balances)
(withdraw_vesting)
(vote_for_committee_member)
(vote_for_son_member)
(vote_for_witness)
(update_witness_votes)
(set_voting_proxy)

View file

@ -2024,6 +2024,40 @@ public:
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (voting_account)(committee_member)(approve)(broadcast) ) }
signed_transaction vote_for_son_member(string voting_account,
string son_member,
bool approve,
bool broadcast /* = false */)
{ try {
account_object voting_account_object = get_account(voting_account);
account_id_type son_member_owner_account_id = get_account_id(son_member);
fc::optional<son_object> son_member_obj = _remote_db->get_son_member_by_account(son_member_owner_account_id);
if (!son_member_obj)
FC_THROW("Account ${son_member} is not registered as a son_member", ("son_member", son_member));
if (approve)
{
auto insert_result = voting_account_object.options.votes.insert(son_member_obj->vote_id);
if (!insert_result.second)
FC_THROW("Account ${account} was already voting for son_member ${son_member}", ("account", voting_account)("son_member", son_member));
}
else
{
unsigned votes_removed = voting_account_object.options.votes.erase(son_member_obj->vote_id);
if (!votes_removed)
FC_THROW("Account ${account} is already not voting for son_member ${son_member}", ("account", voting_account)("son_member", son_member));
}
account_update_operation account_update_op;
account_update_op.account = voting_account_object.id;
account_update_op.new_options = voting_account_object.options;
signed_transaction tx;
tx.operations.push_back( account_update_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (voting_account)(son_member)(approve)(broadcast) ) }
signed_transaction vote_for_witness(string voting_account,
string witness,
bool approve,
@ -4044,6 +4078,14 @@ signed_transaction wallet_api::vote_for_committee_member(string voting_account,
return my->vote_for_committee_member(voting_account, witness, approve, broadcast);
}
signed_transaction wallet_api::vote_for_son_member(string voting_account,
string son_member,
bool approve,
bool broadcast /* = false */)
{
return my->vote_for_son_member(voting_account, son_member, approve, broadcast);
}
signed_transaction wallet_api::vote_for_witness(string voting_account,
string witness,
bool approve,

View file

@ -43,6 +43,7 @@
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/son_object.hpp>
#include <fc/smart_ref_impl.hpp>
#include <iostream>

View file

@ -0,0 +1,103 @@
#include <boost/test/unit_test.hpp>
#include "../common/database_fixture.hpp"
#include <graphene/chain/son_object.hpp>
#include <graphene/chain/son_evaluator.hpp>
using namespace graphene::chain;
using namespace graphene::chain::test;
class test_create_son_member_evaluator: public create_son_evaluator {
public:
test_create_son_member_evaluator( database& link_db ):
ptr_trx_state( new transaction_evaluation_state( &link_db ) )
{
trx_state = ptr_trx_state.get();
}
std::unique_ptr<transaction_evaluation_state> ptr_trx_state;
};
class test_update_son_member_evaluator: public update_son_evaluator {
public:
test_update_son_member_evaluator( database& link_db ):
ptr_trx_state( new transaction_evaluation_state( &link_db ) )
{
trx_state = ptr_trx_state.get();
}
std::unique_ptr<transaction_evaluation_state> ptr_trx_state;
};
class test_delete_son_member_evaluator: public delete_son_evaluator {
public:
test_delete_son_member_evaluator( database& link_db ):
ptr_trx_state( new transaction_evaluation_state( &link_db ) )
{
trx_state = ptr_trx_state.get();
}
std::unique_ptr<transaction_evaluation_state> ptr_trx_state;
};
BOOST_FIXTURE_TEST_SUITE( son_operation_tests, database_fixture )
BOOST_AUTO_TEST_CASE( create_son_test ) {
generate_blocks( HARDFORK_SON_TIME );
while( db.head_block_time() <= HARDFORK_SON_TIME )
{
generate_block();
}
std::string test_url = "https://create_son_test";
test_create_son_member_evaluator test_eval( db );
son_create_operation op;
op.owner_account = account_id_type(1);
op.url = test_url;
BOOST_CHECK_NO_THROW( test_eval.do_evaluate( op ) );
auto id = test_eval.do_apply( op );
const auto& idx = db.get_index_type<son_member_index>().indices().get<by_account>();
BOOST_REQUIRE( idx.size() == 1 );
auto obj = idx.find( op.owner_account );
BOOST_REQUIRE( obj != idx.end() );
BOOST_CHECK( obj->url == test_url );
BOOST_CHECK( id == obj->id );
}
BOOST_AUTO_TEST_CASE( update_son_test ){
INVOKE(create_son_test);
std::string new_url = "https://anewurl.com";
test_update_son_member_evaluator test_eval( db );
son_update_operation op;
op.owner_account = account_id_type(1);
op.new_url = new_url;
op.son_id = son_id_type(0);
BOOST_CHECK_NO_THROW( test_eval.do_evaluate( op ) );
auto id = test_eval.do_apply( op );
const auto& idx = db.get_index_type<son_member_index>().indices().get<by_account>();
BOOST_REQUIRE( idx.size() == 1 );
auto obj = idx.find( op.owner_account );
BOOST_REQUIRE( obj != idx.end() );
BOOST_CHECK( obj->url == new_url );
BOOST_CHECK( id == obj->id );
}
BOOST_AUTO_TEST_CASE( delete_son_test ){
INVOKE(create_son_test);
test_delete_son_member_evaluator test_eval( db );
son_delete_operation delete_op;
delete_op.owner_account = account_id_type(1);
delete_op.son_id = son_id_type(0);
BOOST_CHECK_NO_THROW( test_eval.do_evaluate( delete_op ) );
test_eval.do_apply( delete_op );
const auto& idx = db.get_index_type<son_member_index>().indices().get<by_account>();
BOOST_REQUIRE( idx.empty() );
}
BOOST_AUTO_TEST_SUITE_END()