Merge branch 'rock-paper-scissors' of https://bitbucket.org/peerplaysblockchain/peerplays-graphene into betting-merge

This commit is contained in:
Roman Olearski 2017-07-13 16:07:30 +02:00
commit d3482f3a08
82 changed files with 226632 additions and 122 deletions

2
.gitmodules vendored
View file

@ -4,5 +4,5 @@
ignore = dirty
[submodule "libraries/fc"]
path = libraries/fc
url = git@bitbucket.org:peerplaysblockchain/peerplays-fc.git
url = https://bitbucket.org/peerplaysblockchain/peerplays-fc
ignore = dirty

View file

@ -23,7 +23,7 @@ endif()
list( APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules" )
set(CMAKE_EXPORT_COMPILE_COMMANDS "ON")
set(GRAPHENE_EGENESIS_JSON "${CMAKE_CURRENT_SOURCE_DIR}/genesis.json" )
set(GRAPHENE_EGENESIS_JSON "${CMAKE_CURRENT_SOURCE_DIR}/genesis.json" CACHE PATH "location of the genesis.json to embed in the executable" )
#set (ENABLE_INSTALLER 1)
#set (USE_PCH 1)

File diff suppressed because one or more lines are too long

216158
genesis/genesis.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,8 @@ add_library( graphene_app
)
# need to link graphene_debug_witness because plugins aren't sufficiently isolated #246
target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_chain fc graphene_db graphene_net graphene_utilities graphene_debug_witness )
#target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_chain fc graphene_db graphene_net graphene_utilities graphene_debug_witness )
target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_accounts_list graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness )
target_include_directories( graphene_app
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/../egenesis/include" )

View file

@ -36,6 +36,7 @@
#include <graphene/chain/transaction_object.hpp>
#include <graphene/chain/withdraw_permission_object.hpp>
#include <graphene/chain/worker_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <fc/crypto/hex.hpp>
#include <fc/smart_ref_impl.hpp>
@ -268,6 +269,7 @@ namespace graphene { namespace app {
return *_debug_api;
}
#if 0
vector<account_id_type> get_relevant_accounts( const object* obj )
{
vector<account_id_type> result;
@ -361,6 +363,13 @@ namespace graphene { namespace app {
const auto& aobj = dynamic_cast<const bet_object*>(obj);
assert( aobj != nullptr );
result.push_back( aobj->bettor_id );
} case tournament_object_type:{
const tournament_object* tournament_obj = dynamic_cast<const tournament_object*>(obj);
assert(tournament_obj);
const tournament_details_object& details = tournament_obj->tournament_details_id(*_app.chain_database());
flat_set<account_id_type> impacted = details.registered_players;
impacted.insert(tournament_obj->creator);
std::copy(impacted.begin(), impacted.end(), std::back_inserter(result));
break;
}
}
@ -428,6 +437,7 @@ namespace graphene { namespace app {
}
return result;
} // end get_relevant_accounts( obj )
#endif
vector<order_history_object> history_api::get_fill_order_history( asset_id_type a, asset_id_type b, uint32_t limit )const
{
@ -546,6 +556,13 @@ namespace graphene { namespace app {
return result;
}
vector<account_balance_object> history_api::list_core_accounts()const
{
auto list = _app.get_plugin<accounts_list_plugin>( "accounts_list" );
FC_ASSERT( list );
return list->list_accounts();
}
flat_set<uint32_t> history_api::get_market_history_buckets()const
{
auto hist = _app.get_plugin<market_history_plugin>( "market_history" );

View file

@ -178,7 +178,8 @@ namespace detail {
"seed05.bts-nodes.net:1776", // Thom (USA)
"seed06.bts-nodes.net:1776", // Thom (USA)
"seed07.bts-nodes.net:1776", // Thom (Singapore)
"seeds.bitshares.eu:1776" // pc (http://seeds.quisquis.de/bitshares.html)
"seeds.bitshares.eu:1776", // pc (http://seeds.quisquis.de/bitshares.html)
"peerplays.blocktrades.info:2776"
};
for( const string& endpoint_string : seeds )
{

View file

@ -24,6 +24,7 @@
#include <graphene/app/database_api.hpp>
#include <graphene/chain/get_config.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/account_object.hpp>
#include <fc/bloom_filter.hpp>
@ -151,6 +152,13 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
// Blinded balances
vector<blinded_balance_object> get_blinded_balances( const flat_set<commitment_type>& commitments )const;
// Tournaments
vector<tournament_object> get_tournaments_in_state(tournament_state state, uint32_t limit) const;
vector<tournament_object> get_tournaments(tournament_id_type stop, unsigned limit, tournament_id_type start);
vector<tournament_object> get_tournaments_by_state(tournament_id_type stop, unsigned limit, tournament_id_type start, tournament_state state);
vector<tournament_id_type> get_registered_tournaments(account_id_type account_filter, uint32_t limit) const;
//private:
template<typename T>
void subscribe_to_item( const T& i )const
@ -1884,6 +1892,99 @@ vector<blinded_balance_object> database_api_impl::get_blinded_balances( const fl
return result;
}
//////////////////////////////////////////////////////////////////////
// //
// Tournament methods //
// //
//////////////////////////////////////////////////////////////////////
vector<tournament_object> database_api::get_tournaments_in_state(tournament_state state, uint32_t limit) const
{
return my->get_tournaments_in_state(state, limit);
}
vector<tournament_object> database_api_impl::get_tournaments_in_state(tournament_state state, uint32_t limit) const
{
vector<tournament_object> result;
const auto& registration_deadline_index = _db.get_index_type<tournament_index>().indices().get<by_registration_deadline>();
const auto range = registration_deadline_index.equal_range(boost::make_tuple(state));
for (const tournament_object& tournament_obj : boost::make_iterator_range(range.first, range.second))
{
result.emplace_back(tournament_obj);
subscribe_to_item( tournament_obj.id );
if (result.size() >= limit)
break;
}
return result;
}
vector<tournament_object> database_api::get_tournaments(tournament_id_type stop,
unsigned limit,
tournament_id_type start)
{
return my->get_tournaments(stop, limit, start);
}
vector<tournament_object> database_api_impl::get_tournaments(tournament_id_type stop,
unsigned limit,
tournament_id_type start)
{
vector<tournament_object> result;
const auto& tournament_idx = _db.get_index_type<tournament_index>().indices().get<by_id>();
for (auto elem: tournament_idx) {
if( result.size() >= limit ) break;
if( ( (elem.get_id().instance.value <= start.instance.value) || start == tournament_id_type()) &&
( (elem.get_id().instance.value >= stop.instance.value) || stop == tournament_id_type()))
result.push_back( elem );
}
return result;
}
vector<tournament_object> database_api::get_tournaments_by_state(tournament_id_type stop,
unsigned limit,
tournament_id_type start,
tournament_state state)
{
return my->get_tournaments_by_state(stop, limit, start, state);
}
vector<tournament_object> database_api_impl::get_tournaments_by_state(tournament_id_type stop,
unsigned limit,
tournament_id_type start,
tournament_state state)
{
vector<tournament_object> result;
const auto& tournament_idx = _db.get_index_type<tournament_index>().indices().get<by_id>();
for (auto elem: tournament_idx) {
if( result.size() >= limit ) break;
if( ( (elem.get_id().instance.value <= start.instance.value) || start == tournament_id_type()) &&
( (elem.get_id().instance.value >= stop.instance.value) || stop == tournament_id_type()) &&
elem.get_state() == state )
result.push_back( elem );
}
return result;
}
vector<tournament_id_type> database_api::get_registered_tournaments(account_id_type account_filter, uint32_t limit) const
{
return my->get_registered_tournaments(account_filter, limit);
}
vector<tournament_id_type> database_api_impl::get_registered_tournaments(account_id_type account_filter, uint32_t limit) const
{
const auto& tournament_details_idx = _db.get_index_type<tournament_details_index>();
const auto& tournament_details_primary_idx = dynamic_cast<const primary_index<tournament_details_index>&>(tournament_details_idx);
const auto& players_idx = tournament_details_primary_idx.get_secondary_index<graphene::chain::tournament_players_index>();
vector<tournament_id_type> tournament_ids = players_idx.get_registered_tournaments_for_account(account_filter);
if (tournament_ids.size() >= limit)
tournament_ids.resize(limit);
return tournament_ids;
}
//////////////////////////////////////////////////////////////////////
// //
// Private methods //
@ -1978,6 +2079,12 @@ void database_api_impl::handle_object_changed(bool force_notify, bool full_objec
if( _market_subscriptions.size() )
{
market_queue_type broadcast_queue;
/// pushing the future back / popping the prior future if it is complete.
/// if a connection hangs then this could get backed up and result in
/// a failure to exit cleanly.
//fc::async([capture_this,this,updates,market_broadcast_queue](){
//if( _subscribe_callback )
// _subscribe_callback( updates );
for(auto id : ids)
{

View file

@ -238,6 +238,31 @@ struct get_impacted_account_visitor
_impacted.insert( op.bettor_id );
}
void operator()( const tournament_create_operation& op )
{
_impacted.insert( op.creator );
_impacted.insert( op.options.whitelist.begin(), op.options.whitelist.end() );
}
void operator()( const tournament_join_operation& op )
{
_impacted.insert( op.payer_account_id );
_impacted.insert( op.player_account_id );
}
void operator()( const tournament_leave_operation& op )
{
//if account canceling registration is not the player, it must be the payer
if (op.canceling_account_id != op.player_account_id)
_impacted.erase( op.canceling_account_id );
_impacted.erase( op.player_account_id );
}
void operator()( const game_move_operation& op )
{
_impacted.insert( op.player_account_id );
}
void operator()( const tournament_payout_operation& op )
{
_impacted.insert( op.payout_account_id );
}
};
void operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )

View file

@ -29,6 +29,7 @@
#include <graphene/chain/protocol/confidential.hpp>
#include <graphene/market_history/market_history_plugin.hpp>
#include <graphene/accounts_list/accounts_list_plugin.hpp>
#include <graphene/debug_witness/debug_api.hpp>
@ -49,6 +50,7 @@
namespace graphene { namespace app {
using namespace graphene::chain;
using namespace graphene::market_history;
using namespace graphene::accounts_list;
using namespace fc::ecc;
using namespace std;
@ -141,6 +143,7 @@ namespace graphene { namespace app {
vector<order_history_object> get_fill_order_history( asset_id_type a, asset_id_type b, uint32_t limit )const;
vector<bucket_object> get_market_history( asset_id_type a, asset_id_type b, uint32_t bucket_seconds,
fc::time_point_sec start, fc::time_point_sec end )const;
vector<account_balance_object> list_core_accounts()const;
flat_set<uint32_t> get_market_history_buckets()const;
private:
application& _app;
@ -393,6 +396,7 @@ FC_API(graphene::app::history_api,
(get_fill_order_history)
(get_market_history)
(get_market_history_buckets)
(list_core_accounts)
)
FC_API(graphene::app::block_api,
(get_blocks)

View file

@ -45,6 +45,7 @@
#include <graphene/chain/worker_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/market_history/market_history_plugin.hpp>
@ -606,6 +607,28 @@ class database_api
*/
vector<blinded_balance_object> get_blinded_balances( const flat_set<commitment_type>& commitments )const;
/////////////////
// Tournaments //
/////////////////
/**
* @return the list of tournaments in the given state
*/
vector<tournament_object> get_tournaments_in_state(tournament_state state, uint32_t limit) const;
vector<tournament_object> get_tournaments(tournament_id_type stop = tournament_id_type(),
unsigned limit = 100,
tournament_id_type start = tournament_id_type());
vector<tournament_object> get_tournaments_by_state(tournament_id_type stop = tournament_id_type(),
unsigned limit = 100,
tournament_id_type start = tournament_id_type(),
tournament_state state = tournament_state::accepting_registrations);
/**
* @return the list of tournaments that a given account is registered to play in
*/
vector<tournament_id_type> get_registered_tournaments(account_id_type account_filter, uint32_t limit) const;
private:
std::shared_ptr< database_api_impl > my;
};
@ -717,4 +740,10 @@ FC_API(graphene::app::database_api,
// Blinded balances
(get_blinded_balances)
// Tournaments
(get_tournaments_in_state)
(get_tournaments_by_state)
(get_tournaments )
(get_registered_tournaments)
)

View file

@ -58,6 +58,7 @@ add_library( graphene_chain
protocol/fee_schedule.cpp
protocol/confidential.cpp
protocol/vote.cpp
protocol/tournament.cpp
genesis_state.cpp
get_config.cpp
@ -75,6 +76,10 @@ add_library( graphene_chain
proposal_evaluator.cpp
market_evaluator.cpp
vesting_balance_evaluator.cpp
tournament_evaluator.cpp
tournament_object.cpp
match_object.cpp
game_object.cpp
withdraw_permission_evaluator.cpp
worker_evaluator.cpp
confidential_evaluator.cpp

View file

@ -397,6 +397,22 @@ signed_block database::_generate_block(
pending_block.transaction_merkle_root = pending_block.calculate_merkle_root();
pending_block.witness = witness_id;
// Genesis witnesses start with a default initial secret
if( witness_obj.next_secret_hash == secret_hash_type::hash( secret_hash_type() ) )
pending_block.previous_secret = secret_hash_type();
else
{
secret_hash_type::encoder last_enc;
fc::raw::pack( last_enc, block_signing_private_key );
fc::raw::pack( last_enc, witness_obj.previous_secret );
pending_block.previous_secret = last_enc.result();
}
secret_hash_type::encoder next_enc;
fc::raw::pack( next_enc, block_signing_private_key );
fc::raw::pack( next_enc, pending_block.previous_secret );
pending_block.next_secret_hash = secret_hash_type::hash(next_enc.result());
if( !(skip & skip_witness_signature) )
pending_block.sign( block_signing_private_key );
@ -513,6 +529,8 @@ void database::_apply_block( const signed_block& next_block )
++_current_trx_in_block;
}
if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM)
update_witness_schedule(next_block);
update_global_dynamic_data(next_block);
update_signing_witness(signing_witness, next_block);
update_last_irreversible_block();
@ -527,6 +545,7 @@ void database::_apply_block( const signed_block& next_block )
clear_expired_orders();
update_expired_feeds();
update_withdraw_permissions();
update_tournaments();
// n.b., update_maintenance_flag() happens this late
// because get_slot_time() / get_slot_at_time() is needed above
@ -534,6 +553,7 @@ void database::_apply_block( const signed_block& next_block )
// update_global_dynamic_data() as perhaps these methods only need
// to be called for header validation?
update_maintenance_flag( maint_needed );
if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM)
update_witness_schedule();
if( !_node_property_object.debug_updates.empty() )
apply_debug_updates();
@ -543,6 +563,7 @@ void database::_apply_block( const signed_block& next_block )
_applied_ops.clear();
notify_changed_objects();
} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) ) }
@ -650,6 +671,9 @@ const witness_object& database::validate_block_header( uint32_t skip, const sign
FC_ASSERT( head_block_id() == next_block.previous, "", ("head_block_id",head_block_id())("next.prev",next_block.previous) );
FC_ASSERT( head_block_time() < next_block.timestamp, "", ("head_block_time",head_block_time())("next",next_block.timestamp)("blocknum",next_block.block_num()) );
const witness_object& witness = next_block.witness(*this);
//DLN: TODO: Temporarily commented out to test shuffle vs RNG scheduling algorithm for witnesses, this was causing shuffle agorithm to fail during create_witness test. This should be re-enabled for RNG, and maybe for shuffle too, don't really know for sure.
// FC_ASSERT( secret_hash_type::hash( next_block.previous_secret ) == witness.next_secret_hash, "",
// ("previous_secret", next_block.previous_secret)("next_secret_hash", witness.next_secret_hash)("null_secret_hash", secret_hash_type::hash( secret_hash_type())));
if( !(skip&skip_witness_signature) )
FC_ASSERT( next_block.validate_signee( witness.signing_key ) );

View file

@ -46,6 +46,9 @@
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/witness_schedule_object.hpp>
#include <graphene/chain/worker_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/sport_object.hpp>
@ -72,6 +75,7 @@
#include <graphene/chain/event_group_evaluator.hpp>
#include <graphene/chain/event_evaluator.hpp>
#include <graphene/chain/betting_market_evaluator.hpp>
#include <graphene/chain/tournament_evaluator.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
@ -218,6 +222,10 @@ void database::initialize_evaluators()
register_evaluator<betting_market_create_evaluator>();
register_evaluator<bet_place_evaluator>();
register_evaluator<betting_market_group_resolve_evaluator>();
register_evaluator<tournament_create_evaluator>();
register_evaluator<tournament_join_evaluator>();
register_evaluator<game_move_evaluator>();
register_evaluator<tournament_leave_evaluator>();
}
void database::initialize_indexes()
@ -254,6 +262,12 @@ void database::initialize_indexes()
add_index< primary_index<betting_market_object_index > >();
add_index< primary_index<bet_object_index > >();
add_index< primary_index<tournament_index> >();
auto tournament_details_idx = add_index< primary_index<tournament_details_index> >();
tournament_details_idx->add_secondary_index<tournament_players_index>();
add_index< primary_index<match_index> >();
add_index< primary_index<game_index> >();
//Implementation object indexes
add_index< primary_index<transaction_index > >();
add_index< primary_index<account_balance_index > >();
@ -372,6 +386,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
}).get_id() == GRAPHENE_PROXY_TO_SELF_ACCOUNT);
FC_ASSERT(create<account_object>([this](account_object& a) {
a.name = "default-dividend-distribution";
//a.name = "test-dividend-distribution";
a.statistics = create<account_statistics_object>([&](account_statistics_object& s){s.owner = a.id;}).id;
a.owner.weight_threshold = 1;
a.active.weight_threshold = 1;
@ -380,7 +395,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
a.network_fee_percentage = 0;
a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT;
}).get_id() == GRAPHENE_RAKE_FEE_ACCOUNT_ID);
//}).get_id() == TOURNAMENT_RAKE_FEE_ACCOUNT_ID);
// Create more special accounts
while( true )
{
@ -414,6 +429,9 @@ void database::init_genesis(const genesis_state_type& genesis_state)
a.options.next_payout_time = genesis_state.initial_timestamp + fc::hours(1);
a.options.payout_interval = 7*24*60*60;
a.dividend_distribution_account = GRAPHENE_RAKE_FEE_ACCOUNT_ID;
//a.options.next_payout_time = genesis_state.initial_timestamp + fc::days(1);
//a.options.payout_interval = 30*24*60*60;
//a.dividend_distribution_account = TOURNAMENT_RAKE_FEE_ACCOUNT_ID;
});
const asset_object& core_asset =
@ -434,6 +452,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
assert( asset_id_type(core_asset.id) == asset().asset_id );
assert( get_balance(account_id_type(), asset_id_type()) == asset(dyn_asset.current_supply) );
#ifdef _DEFAULT_DIVIDEND_ASSET
// Create default dividend asset
const asset_dynamic_data_object& dyn_asset1 =
create<asset_dynamic_data_object>([&](asset_dynamic_data_object& a) {
@ -446,17 +465,20 @@ void database::init_genesis(const genesis_state_type& genesis_state)
a.options.next_payout_time = genesis_state.initial_timestamp + fc::hours(1);
a.options.payout_interval = 7*24*60*60;
a.dividend_distribution_account = GRAPHENE_RAKE_FEE_ACCOUNT_ID;
//a.dividend_distribution_account = TOURNAMENT_RAKE_FEE_ACCOUNT_ID;
});
const asset_object& default_asset =
create<asset_object>( [&]( asset_object& a ) {
a.symbol = "DEF";
//a.symbol = "DEFAULT";
a.options.max_market_fee =
a.options.max_supply = genesis_state.max_core_supply;
a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS;
a.options.flags = 0;
a.options.issuer_permissions = 79;
a.issuer = GRAPHENE_RAKE_FEE_ACCOUNT_ID;
//a.issuer = TOURNAMENT_RAKE_FEE_ACCOUNT_ID;
a.options.core_exchange_rate.base.amount = 1;
a.options.core_exchange_rate.base.asset_id = asset_id_type(0);
a.options.core_exchange_rate.quote.amount = 1;
@ -465,6 +487,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
a.dividend_data_id = div_asset1.id;
});
assert( default_asset.id == asset_id_type(1) );
#endif
// Create more special assets
while( true )
@ -523,6 +546,19 @@ void database::init_genesis(const genesis_state_type& genesis_state)
} );
create<block_summary_object>([&](block_summary_object&) {});
// Create initial accounts from graphene-based chains
// graphene accounts can refer to other accounts in their authorities, so
// we first create all accounts with dummy authorities, then go back and
// set up the authorities once the accounts all have ids assigned.
for( const auto& account : genesis_state.initial_bts_accounts )
{
account_create_operation cop;
cop.name = account.name;
cop.registrar = GRAPHENE_TEMP_ACCOUNT;
cop.owner = authority(1, GRAPHENE_TEMP_ACCOUNT, 1);
account_id_type account_id(apply_operation(genesis_eval_state, cop).get<object_id_type>());
}
// Create initial accounts
for( const auto& account : genesis_state.initial_accounts )
{
@ -561,6 +597,32 @@ void database::init_genesis(const genesis_state_type& genesis_state)
return itr->get_id();
};
for( const auto& account : genesis_state.initial_bts_accounts )
{
account_update_operation op;
op.account = get_account_id(account.name);
authority owner_authority;
owner_authority.weight_threshold = account.owner_authority.weight_threshold;
for (const auto& value : account.owner_authority.account_auths)
owner_authority.account_auths.insert(std::make_pair(get_account_id(value.first), value.second));
owner_authority.key_auths = account.owner_authority.key_auths;
owner_authority.address_auths = account.owner_authority.address_auths;
op.owner = std::move(owner_authority);
authority active_authority;
active_authority.weight_threshold = account.active_authority.weight_threshold;
for (const auto& value : account.active_authority.account_auths)
active_authority.account_auths.insert(std::make_pair(get_account_id(value.first), value.second));
active_authority.key_auths = account.active_authority.key_auths;
active_authority.address_auths = account.active_authority.address_auths;
op.active = std::move(active_authority);
apply_operation(genesis_eval_state, op);
}
// Helper function to get asset ID by symbol
const auto& assets_by_symbol = get_index_type<asset_index>().indices().get<by_symbol>();
const auto get_asset_id = [&assets_by_symbol](const string& symbol) {
@ -646,6 +708,46 @@ void database::init_genesis(const genesis_state_type& genesis_state)
});
}
// Create balances for all bts accounts
for( const auto& account : genesis_state.initial_bts_accounts ) {
if (account.core_balance != share_type()) {
total_supplies[asset_id_type()] += account.core_balance;
create<account_balance_object>([&](account_balance_object& b) {
b.owner = get_account_id(account.name);
b.balance = account.core_balance;
});
}
// create any vesting balances for this account
if (account.vesting_balances)
for (const auto& vesting_balance : *account.vesting_balances) {
create<vesting_balance_object>([&](vesting_balance_object& vbo) {
vbo.owner = get_account_id(account.name);
vbo.balance = asset(vesting_balance.amount, get_asset_id(vesting_balance.asset_symbol));
if (vesting_balance.policy_type == "linear") {
auto initial_linear_vesting_policy = vesting_balance.policy.as<genesis_state_type::initial_bts_account_type::initial_linear_vesting_policy>();
linear_vesting_policy new_vesting_policy;
new_vesting_policy.begin_timestamp = initial_linear_vesting_policy.begin_timestamp;
new_vesting_policy.vesting_cliff_seconds = initial_linear_vesting_policy.vesting_cliff_seconds;
new_vesting_policy.vesting_duration_seconds = initial_linear_vesting_policy.vesting_duration_seconds;
new_vesting_policy.begin_balance = initial_linear_vesting_policy.begin_balance;
vbo.policy = new_vesting_policy;
} else if (vesting_balance.policy_type == "cdd") {
auto initial_cdd_vesting_policy = vesting_balance.policy.as<genesis_state_type::initial_bts_account_type::initial_cdd_vesting_policy>();
cdd_vesting_policy new_vesting_policy;
new_vesting_policy.vesting_seconds = initial_cdd_vesting_policy.vesting_seconds;
new_vesting_policy.coin_seconds_earned = initial_cdd_vesting_policy.coin_seconds_earned;
new_vesting_policy.start_claim = initial_cdd_vesting_policy.start_claim;
new_vesting_policy.coin_seconds_earned_last_update = initial_cdd_vesting_policy.coin_seconds_earned_last_update;
vbo.policy = new_vesting_policy;
}
total_supplies[get_asset_id(vesting_balance.asset_symbol)] += vesting_balance.amount;
});
}
}
// Create initial balances
share_type total_allocation;
for( const auto& handout : genesis_state.initial_balances )
@ -669,7 +771,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
linear_vesting_policy policy;
policy.begin_timestamp = vest.begin_timestamp;
policy.vesting_cliff_seconds = 0;
policy.vesting_cliff_seconds = vest.vesting_cliff_seconds ? *vest.vesting_cliff_seconds : 0;
policy.vesting_duration_seconds = vest.vesting_duration_seconds;
policy.begin_balance = vest.begin_balance;
@ -687,8 +789,11 @@ void database::init_genesis(const genesis_state_type& genesis_state)
{
total_supplies[ asset_id_type(0) ] = GRAPHENE_MAX_SHARE_SUPPLY;
}
#ifdef _DEFAULT_DIVIDEND_ASSET
total_debts[ asset_id_type(1) ] =
total_supplies[ asset_id_type(1) ] = 0;
// it is workaround, should be clarified
total_debts[ asset_id_type() ] = total_supplies[ asset_id_type() ];
const auto& idx = get_index_type<asset_index>().indices().get<by_symbol>();
auto it = idx.begin();
@ -740,6 +845,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
std::for_each(genesis_state.initial_witness_candidates.begin(), genesis_state.initial_witness_candidates.end(),
[&](const genesis_state_type::initial_witness_type& witness) {
witness_create_operation op;
op.initial_secret = secret_hash_type::hash(secret_hash_type());
op.witness_account = get_account_id(witness.owner_name);
op.block_signing_key = witness.block_signing_key;
apply_operation(genesis_eval_state, op);
@ -776,17 +882,47 @@ void database::init_genesis(const genesis_state_type& genesis_state)
}
});
// Initialize witness schedule
#ifndef NDEBUG
const witness_schedule_object& wso =
#endif
create<witness_schedule_object>([&](witness_schedule_object& _wso)
{
// for scheduled
memset(_wso.rng_seed.begin(), 0, _wso.rng_seed.size());
witness_scheduler_rng rng(_wso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV);
auto init_witnesses = get_global_properties().active_witnesses;
_wso.scheduler = witness_scheduler();
_wso.scheduler._min_token_count = std::max(int(init_witnesses.size()) / 2, 1);
_wso.scheduler.update(init_witnesses);
for( size_t i=0; i<init_witnesses.size(); ++i )
_wso.scheduler.produce_schedule(rng);
_wso.last_scheduling_block = 0;
_wso.recent_slots_filled = fc::uint128::max_value();
// for shuffled
for( const witness_id_type& wid : get_global_properties().active_witnesses )
_wso.current_shuffled_witnesses.push_back( wid );
});
assert( wso.id == witness_schedule_id_type() );
// Enable fees
modify(get_global_properties(), [&genesis_state](global_property_object& p) {
p.parameters.current_fees = genesis_state.initial_parameters.current_fees;
});
// Create witness scheduler
create<witness_schedule_object>([&]( witness_schedule_object& wso )
{
for( const witness_id_type& wid : get_global_properties().active_witnesses )
wso.current_shuffled_witnesses.push_back( wid );
});
//create<witness_schedule_object>([&]( witness_schedule_object& wso )
//{
// for( const witness_id_type& wid : get_global_properties().active_witnesses )
// wso.current_shuffled_witnesses.push_back( wid );
//});
// Create FBA counters
create<fba_accumulator_object>([&]( fba_accumulator_object& acc )

View file

@ -45,6 +45,7 @@
#include <graphene/chain/vesting_balance_object.hpp>
#include <graphene/chain/vote_count.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/witness_schedule_object.hpp>
#include <graphene/chain/worker_object.hpp>
#define USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX // vesting_balance_object by_asset_balance index needed
@ -242,6 +243,11 @@ void database::update_active_witnesses()
});
});
const witness_schedule_object& wso = witness_schedule_id_type()(*this);
modify(wso, [&](witness_schedule_object& _wso)
{
_wso.scheduler.update(gpo.active_witnesses);
});
} FC_CAPTURE_AND_RETHROW() }
void database::update_active_committee_members()
@ -934,7 +940,6 @@ void schedule_pending_dividend_balances(database& db,
auto itr = vesting_amounts.find(holder_balance_object.owner);
if (itr != vesting_amounts.end())
holder_balance += itr->second;
//if (holder_balance.value)
fc::uint128_t amount_to_credit(delta_balance.value);
amount_to_credit *= holder_balance.value;

View file

@ -35,7 +35,8 @@
namespace graphene { namespace chain {
database::database()
database::database() :
_random_number_generator(fc::ripemd160().data())
{
initialize_indexes();
initialize_evaluators();

View file

@ -33,6 +33,8 @@
#include <graphene/chain/transaction_object.hpp>
#include <graphene/chain/withdraw_permission_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
@ -42,11 +44,20 @@ namespace graphene { namespace chain {
void database::update_global_dynamic_data( const signed_block& b )
{
const dynamic_global_property_object& _dgp =
dynamic_global_property_id_type(0)(*this);
const dynamic_global_property_object& _dgp = dynamic_global_property_id_type(0)(*this);
const global_property_object& gpo = get_global_properties();
uint32_t missed_blocks = get_slot_at_time( b.timestamp );
//#define DIRTY_TRICK // problem with missed_blocks can occur when "maintenance_interval" set to few minutes
#ifdef DIRTY_TRICK
if (missed_blocks != 0) {
#else
assert( missed_blocks != 0 );
#endif
// bad if-condition, this code needs to execute for both shuffled and rng algorithms
// if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM)
// {
missed_blocks--;
for( uint32_t i = 0; i < missed_blocks; ++i ) {
const auto& witness_missed = get_scheduled_witness( i+1 )(*this);
@ -62,9 +73,19 @@ void database::update_global_dynamic_data( const signed_block& b )
});
}
}
// }
#ifdef DIRTY_TRICK
}
#endif
// dynamic global properties updating
modify( _dgp, [&]( dynamic_global_property_object& dgp ){
secret_hash_type::encoder enc;
fc::raw::pack( enc, dgp.random );
fc::raw::pack( enc, b.previous_secret );
dgp.random = enc.result();
_random_number_generator = fc::hash_ctr_rng<secret_hash_type, 20>(dgp.random.data());
if( BOOST_UNLIKELY( b.block_num() == 1 ) )
dgp.recently_missed_count = 0;
else if( _checkpoints.size() && _checkpoints.rbegin()->first >= b.block_num() )
@ -118,6 +139,8 @@ void database::update_signing_witness(const witness_object& signing_witness, con
{
_wit.last_aslot = new_block_aslot;
_wit.last_confirmed_block_num = new_block.block_num();
_wit.previous_secret = new_block.previous_secret;
_wit.next_secret_hash = new_block.next_secret_hash;
} );
}
@ -155,7 +178,6 @@ void database::update_last_irreversible_block()
} );
}
}
void database::clear_expired_transactions()
{ try {
//Look for expired transactions in the deduplication list, and remove them.
@ -474,4 +496,116 @@ void database::update_withdraw_permissions()
remove(*permit_index.begin());
}
uint64_t database::get_random_bits( uint64_t bound )
{
return _random_number_generator(bound);
}
void process_finished_games(database& db)
{
//auto& games_index = db.get_index_type<game_index>().indices().get<by_id>();
}
void process_finished_matches(database& db)
{
}
void process_in_progress_tournaments(database& db)
{
auto& start_time_index = db.get_index_type<tournament_index>().indices().get<by_start_time>();
auto start_iter = start_time_index.lower_bound(boost::make_tuple(tournament_state::in_progress));
while (start_iter != start_time_index.end() &&
start_iter->get_state() == tournament_state::in_progress)
{
auto next_iter = std::next(start_iter);
start_iter->check_for_new_matches_to_start(db);
start_iter = next_iter;
}
}
void cancel_expired_tournaments(database& db)
{
// First, cancel any tournaments that didn't get enough players
auto& registration_deadline_index = db.get_index_type<tournament_index>().indices().get<by_registration_deadline>();
// this index is sorted on state and deadline, so the tournaments awaiting registrations with the earliest
// deadlines will be at the beginning
while (!registration_deadline_index.empty() &&
registration_deadline_index.begin()->get_state() == tournament_state::accepting_registrations &&
registration_deadline_index.begin()->options.registration_deadline <= db.head_block_time())
{
const tournament_object& tournament_obj = *registration_deadline_index.begin();
fc_ilog(fc::logger::get("tournament"),
"Canceling tournament ${id} because its deadline expired",
("id", tournament_obj.id));
// cancel this tournament
db.modify(tournament_obj, [&](tournament_object& t) {
t.on_registration_deadline_passed(db);
});
}
}
void start_fully_registered_tournaments(database& db)
{
// Next, start any tournaments that have enough players and whose start time just arrived
auto& start_time_index = db.get_index_type<tournament_index>().indices().get<by_start_time>();
while (1)
{
// find the first tournament waiting to start; if its start time has arrived, start it
auto start_iter = start_time_index.lower_bound(boost::make_tuple(tournament_state::awaiting_start));
if (start_iter != start_time_index.end() &&
start_iter->get_state() == tournament_state::awaiting_start &&
*start_iter->start_time <= db.head_block_time())
{
db.modify(*start_iter, [&](tournament_object& t) {
t.on_start_time_arrived(db);
});
}
else
break;
}
}
void initiate_next_round_of_matches(database& db)
{
}
void initiate_next_games(database& db)
{
// Next, trigger timeouts on any games which have been waiting too long for commit or
// reveal moves
auto& next_timeout_index = db.get_index_type<game_index>().indices().get<by_next_timeout>();
while (1)
{
// empty time_points are sorted to the beginning, so upper_bound takes us to the first
// non-empty time_point
auto start_iter = next_timeout_index.upper_bound(boost::make_tuple(optional<time_point_sec>()));
if (start_iter != next_timeout_index.end() &&
*start_iter->next_timeout <= db.head_block_time())
{
db.modify(*start_iter, [&](game_object& game) {
game.on_timeout(db);
});
}
else
break;
}
}
void database::update_tournaments()
{
// Process as follows:
// - Process games
// - Process matches
// - Process tournaments
// - Process matches
// - Process games
process_finished_games(*this);
process_finished_matches(*this);
cancel_expired_tournaments(*this);
start_fully_registered_tournaments(*this);
process_in_progress_tournaments(*this);
initiate_next_round_of_matches(*this);
initiate_next_games(*this);
}
} }

View file

@ -32,12 +32,45 @@ namespace graphene { namespace chain {
using boost::container::flat_set;
witness_id_type database::get_scheduled_witness( uint32_t slot_num )const
{
witness_id_type wid;
const global_property_object& gpo = get_global_properties();
if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM)
{
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
const witness_schedule_object& wso = witness_schedule_id_type()(*this);
uint64_t current_aslot = dpo.current_aslot + slot_num;
return wso.current_shuffled_witnesses[ current_aslot % wso.current_shuffled_witnesses.size() ];
}
if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM &&
slot_num != 0 )
{
const witness_schedule_object& wso = witness_schedule_id_type()(*this);
// ask the near scheduler who goes in the given slot
bool slot_is_near = wso.scheduler.get_slot(slot_num-1, wid);
if(! slot_is_near)
{
// if the near scheduler doesn't know, we have to extend it to
// a far scheduler.
// n.b. instantiating it is slow, but block gaps long enough to
// need it are likely pretty rare.
witness_scheduler_rng far_rng(wso.rng_seed.begin(), GRAPHENE_FAR_SCHEDULE_CTR_IV);
far_future_witness_scheduler far_scheduler =
far_future_witness_scheduler(wso.scheduler, far_rng);
if(!far_scheduler.get_slot(slot_num-1, wid))
{
// no scheduled witness -- somebody set up us the bomb
// n.b. this code path is impossible, the present
// implementation of far_future_witness_scheduler
// returns true unconditionally
assert( false );
}
}
}
return wid;
}
fc::time_point_sec database::get_slot_time(uint32_t slot_num)const
{
@ -72,17 +105,12 @@ fc::time_point_sec database::get_slot_time(uint32_t slot_num)const
uint32_t database::get_slot_at_time(fc::time_point_sec when)const
{
fc::time_point_sec first_slot_time = get_slot_time( 1 );
//@ROL std::cout << "@get_slot_at_time " << when.to_iso_string() << " " << first_slot_time.to_iso_string() << "\n";
if( when < first_slot_time )
return 0;
return (when - first_slot_time).to_seconds() / block_interval() + 1;
}
uint32_t database::witness_participation_rate()const
{
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
return uint64_t(GRAPHENE_100_PERCENT) * dpo.recent_slots_filled.popcount() / 128;
}
void database::update_witness_schedule()
{
const witness_schedule_object& wso = witness_schedule_id_type()(*this);
@ -118,4 +146,100 @@ void database::update_witness_schedule()
}
}
vector<witness_id_type> database::get_near_witness_schedule()const
{
const witness_schedule_object& wso = witness_schedule_id_type()(*this);
vector<witness_id_type> result;
result.reserve(wso.scheduler.size());
uint32_t slot_num = 0;
witness_id_type wid;
while( wso.scheduler.get_slot(slot_num++, wid) )
result.emplace_back(wid);
return result;
}
void database::update_witness_schedule(const signed_block& next_block)
{
auto start = fc::time_point::now();
const global_property_object& gpo = get_global_properties();
const witness_schedule_object& wso = get(witness_schedule_id_type());
uint32_t schedule_needs_filled = gpo.active_witnesses.size();
uint32_t schedule_slot = get_slot_at_time(next_block.timestamp);
// We shouldn't be able to generate _pending_block with timestamp
// in the past, and incoming blocks from the network with timestamp
// in the past shouldn't be able to make it this far without
// triggering FC_ASSERT elsewhere
assert( schedule_slot > 0 );
witness_id_type first_witness;
bool slot_is_near = wso.scheduler.get_slot( schedule_slot-1, first_witness );
witness_id_type wit;
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
assert( dpo.random.data_size() == witness_scheduler_rng::seed_length );
assert( witness_scheduler_rng::seed_length == wso.rng_seed.size() );
modify(wso, [&](witness_schedule_object& _wso)
{
_wso.slots_since_genesis += schedule_slot;
witness_scheduler_rng rng(wso.rng_seed.data, _wso.slots_since_genesis);
_wso.scheduler._min_token_count = std::max(int(gpo.active_witnesses.size()) / 2, 1);
if( slot_is_near )
{
uint32_t drain = schedule_slot;
while( drain > 0 )
{
if( _wso.scheduler.size() == 0 )
break;
_wso.scheduler.consume_schedule();
--drain;
}
}
else
{
_wso.scheduler.reset_schedule( first_witness );
}
while( !_wso.scheduler.get_slot(schedule_needs_filled, wit) )
{
if( _wso.scheduler.produce_schedule(rng) & emit_turn )
memcpy(_wso.rng_seed.begin(), dpo.random.data(), dpo.random.data_size());
}
_wso.last_scheduling_block = next_block.block_num();
_wso.recent_slots_filled = (
(_wso.recent_slots_filled << 1)
+ 1) << (schedule_slot - 1);
});
auto end = fc::time_point::now();
static uint64_t total_time = 0;
static uint64_t calls = 0;
total_time += (end - start).count();
if( ++calls % 1000 == 0 )
idump( ( double(total_time/1000000.0)/calls) );
}
uint32_t database::witness_participation_rate()const
{
const global_property_object& gpo = get_global_properties();
if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM)
{
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
return uint64_t(GRAPHENE_100_PERCENT) * dpo.recent_slots_filled.popcount() / 128;
}
if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM)
{
const witness_schedule_object& wso = get(witness_schedule_id_type());
return uint64_t(GRAPHENE_100_PERCENT) * wso.recent_slots_filled.popcount() / 128;
}
return 0;
}
} }

View file

@ -0,0 +1,583 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/database.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/msm/back/tools.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <fc/crypto/hash_ctr_rng.hpp>
namespace graphene { namespace chain {
namespace msm = boost::msm;
namespace mpl = boost::mpl;
namespace
{
// Events
struct initiate_game
{
database& db;
vector<account_id_type> players;
initiate_game(database& db, const vector<account_id_type>& players) :
db(db), players(players)
{}
};
struct game_move
{
database& db;
const game_move_operation& move;
game_move(database& db, const game_move_operation& move) :
db(db), move(move)
{}
};
struct timeout
{
database& db;
timeout(database& db) :
db(db)
{}
};
struct game_state_machine_ : public msm::front::state_machine_def<game_state_machine_>
{
// disable a few state machine features we don't use for performance
typedef int no_exception_thrown;
typedef int no_message_queue;
// States
struct waiting_for_game_to_start : public msm::front::state<> {};
struct expecting_commit_moves : public msm::front::state<>
{
void set_next_timeout(database& db, game_object& game)
{
const match_object& match_obj = game.match_id(db);
const tournament_object& tournament_obj = match_obj.tournament_id(db);
const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get<rock_paper_scissors_game_options>();
game.next_timeout = db.head_block_time() + game_options.time_per_commit_move;
}
void on_entry(const initiate_game& event, game_state_machine_& fsm)
{
game_object& game = *fsm.game_obj;
fc_ilog(fc::logger::get("tournament"),
"game ${id} is now in progress, expecting commit moves",
("id", game.id));
fc_ilog(fc::logger::get("tournament"),
"game ${id} is associtated with match ${match_id}",
("id", game.id)
("match_id", game.match_id));
set_next_timeout(event.db, game);
}
void on_entry(const game_move& event, game_state_machine_& fsm)
{
game_object& game = *fsm.game_obj;
fc_ilog(fc::logger::get("tournament"),
"game ${id} received a commit move, still expecting another commit move",
("id", game.id));
}
};
struct expecting_reveal_moves : public msm::front::state<>
{
void set_next_timeout(database& db, game_object& game)
{
const match_object& match_obj = game.match_id(db);
const tournament_object& tournament_obj = match_obj.tournament_id(db);
const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get<rock_paper_scissors_game_options>();
game.next_timeout = db.head_block_time() + game_options.time_per_reveal_move;
}
void on_entry(const timeout& event, game_state_machine_& fsm)
{
game_object& game = *fsm.game_obj;
fc_ilog(fc::logger::get("tournament"),
"game ${id} timed out waiting for commit moves, now expecting reveal move",
("id", game.id));
set_next_timeout(event.db, game);
}
void on_entry(const game_move& event, game_state_machine_& fsm)
{
game_object& game = *fsm.game_obj;
if (event.move.move.which() == game_specific_moves::tag<rock_paper_scissors_throw_commit>::value)
{
fc_ilog(fc::logger::get("tournament"),
"game ${id} received a commit move, now expecting reveal moves",
("id", game.id));
set_next_timeout(event.db, game);
}
else
fc_ilog(fc::logger::get("tournament"),
"game ${id} received a reveal move, still expecting reveal moves",
("id", game.id));
}
};
struct game_complete : public msm::front::state<>
{
void clear_next_timeout(database& db, game_object& game)
{
//const match_object& match_obj = game.match_id(db);
//const tournament_object& tournament_obj = match_obj.tournament_id(db);
//const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get<rock_paper_scissors_game_options>();
game.next_timeout = fc::optional<fc::time_point_sec>();
}
void on_entry(const timeout& event, game_state_machine_& fsm)
{
game_object& game = *fsm.game_obj;
fc_ilog(fc::logger::get("tournament"),
"timed out waiting for commits or reveals, game ${id} is complete",
("id", game.id));
game.make_automatic_moves(event.db);
game.determine_winner(event.db);
clear_next_timeout(event.db, game);
}
void on_entry(const game_move& event, game_state_machine_& fsm)
{
game_object& game = *fsm.game_obj;
fc_ilog(fc::logger::get("tournament"),
"received a reveal move, game ${id} is complete",
("id", fsm.game_obj->id));
// if one player didn't commit a move we might need to make their "insurance" move now
game.make_automatic_moves(event.db);
game.determine_winner(event.db);
clear_next_timeout(event.db, game);
}
};
typedef waiting_for_game_to_start initial_state;
typedef game_state_machine_ x; // makes transition table cleaner
// Guards
bool already_have_other_commit(const game_move& event)
{
auto iter = std::find(game_obj->players.begin(), game_obj->players.end(),
event.move.player_account_id);
unsigned player_index = std::distance(game_obj->players.begin(), iter);
// hard-coded here for two-player games
unsigned other_player_index = player_index == 0 ? 1 : 0;
const rock_paper_scissors_game_details& game_details = game_obj->game_details.get<rock_paper_scissors_game_details>();
return game_details.commit_moves.at(other_player_index).valid();
}
bool now_have_reveals_for_all_commits(const game_move& event)
{
auto iter = std::find(game_obj->players.begin(), game_obj->players.end(),
event.move.player_account_id);
unsigned this_reveal_index = std::distance(game_obj->players.begin(), iter);
const rock_paper_scissors_game_details& game_details = game_obj->game_details.get<rock_paper_scissors_game_details>();
for (unsigned i = 0; i < game_details.commit_moves.size(); ++i)
if (game_details.commit_moves[i] && !game_details.reveal_moves[i] && i != this_reveal_index)
return false;
return true;
}
bool have_at_least_one_commit_move(const timeout& event)
{
const rock_paper_scissors_game_details& game_details = game_obj->game_details.get<rock_paper_scissors_game_details>();
return game_details.commit_moves[0] || game_details.commit_moves[1];
}
void apply_commit_move(const game_move& event)
{
auto iter = std::find(game_obj->players.begin(), game_obj->players.end(),
event.move.player_account_id);
unsigned player_index = std::distance(game_obj->players.begin(), iter);
rock_paper_scissors_game_details& details = game_obj->game_details.get<rock_paper_scissors_game_details>();
details.commit_moves[player_index] = event.move.move.get<rock_paper_scissors_throw_commit>();
}
void apply_reveal_move(const game_move& event)
{
auto iter = std::find(game_obj->players.begin(), game_obj->players.end(),
event.move.player_account_id);
unsigned player_index = std::distance(game_obj->players.begin(), iter);
rock_paper_scissors_game_details& details = game_obj->game_details.get<rock_paper_scissors_game_details>();
details.reveal_moves[player_index] = event.move.move.get<rock_paper_scissors_throw_reveal>();
}
void start_next_game(const game_complete& event)
{
fc_ilog(fc::logger::get("tournament"),
"In start_next_game action");
}
// Transition table for tournament
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+
_row < waiting_for_game_to_start, initiate_game, expecting_commit_moves >,
// +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+
a_row < expecting_commit_moves, game_move, expecting_commit_moves, &x::apply_commit_move >,
row < expecting_commit_moves, game_move, expecting_reveal_moves, &x::apply_commit_move, &x::already_have_other_commit >,
_row < expecting_commit_moves, timeout, game_complete >,
g_row < expecting_commit_moves, timeout, expecting_reveal_moves, &x::have_at_least_one_commit_move >,
// +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+
_row < expecting_reveal_moves, timeout, game_complete >,
a_row < expecting_reveal_moves, game_move, expecting_reveal_moves, &x::apply_reveal_move >,
row < expecting_reveal_moves, game_move, game_complete, &x::apply_reveal_move, &x::now_have_reveals_for_all_commits >
// +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+
//a_row < game_in_progress, game_complete, game_in_progress, &x::start_next_game >,
//g_row < game_in_progress, game_complete, game_complete, &x::was_final_game >
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
> {};
game_object* game_obj;
game_state_machine_(game_object* game_obj) : game_obj(game_obj) {}
};
typedef msm::back::state_machine<game_state_machine_> game_state_machine;
}
class game_object::impl {
public:
game_state_machine state_machine;
impl(game_object* self) : state_machine(self) {}
};
game_object::game_object() :
my(new impl(this))
{
}
game_object::game_object(const game_object& rhs) :
graphene::db::abstract_object<game_object>(rhs),
match_id(rhs.match_id),
players(rhs.players),
winners(rhs.winners),
game_details(rhs.game_details),
next_timeout(rhs.next_timeout),
my(new impl(this))
{
my->state_machine = rhs.my->state_machine;
my->state_machine.game_obj = this;
}
game_object& game_object::operator=(const game_object& rhs)
{
//graphene::db::abstract_object<game_object>::operator=(rhs);
id = rhs.id;
match_id = rhs.match_id;
players = rhs.players;
winners = rhs.winners;
game_details = rhs.game_details;
next_timeout = rhs.next_timeout;
my->state_machine = rhs.my->state_machine;
my->state_machine.game_obj = this;
return *this;
}
game_object::~game_object()
{
}
bool verify_game_state_constants()
{
unsigned error_count = 0;
typedef msm::back::generate_state_set<game_state_machine::stt>::type all_states;
static char const* filled_state_names[mpl::size<all_states>::value];
mpl::for_each<all_states,boost::msm::wrap<mpl::placeholders::_1> >
(msm::back::fill_state_names<game_state_machine::stt>(filled_state_names));
for (unsigned i = 0; i < mpl::size<all_states>::value; ++i)
{
try
{
// this is an approximate test, the state name provided by typeinfo will be mangled, but should
// at least contain the string we're looking for
const char* fc_reflected_value_name = fc::reflector<game_state>::to_string((game_state)i);
if (!strcmp(fc_reflected_value_name, filled_state_names[i]))
fc_elog(fc::logger::get("game"),
"Error, state string misgame between fc and boost::msm for int value ${int_value}: boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}",
("int_value", i)("boost_string", filled_state_names[i])("fc_string", fc_reflected_value_name));
}
catch (const fc::bad_cast_exception&)
{
fc_elog(fc::logger::get("game"),
"Error, no reflection for value ${int_value} in enum game_state",
("int_value", i));
++error_count;
}
}
return error_count == 0;
}
game_state game_object::get_state() const
{
static bool state_constants_are_correct = verify_game_state_constants();
(void)&state_constants_are_correct;
game_state state = (game_state)my->state_machine.current_state()[0];
return state;
}
void game_object::evaluate_move_operation(const database& db, const game_move_operation& op) const
{
//const match_object& match_obj = match_id(db);
if (game_details.which() == game_specific_details::tag<rock_paper_scissors_game_details>::value)
{
if (op.move.which() == game_specific_moves::tag<rock_paper_scissors_throw_commit>::value)
{
// Is this move made by a player in the match
auto iter = std::find(players.begin(), players.end(),
op.player_account_id);
if (iter == players.end())
FC_THROW("Player ${account_id} is not a player in game ${game}",
("account_id", op.player_account_id)
("game", id));
unsigned player_index = std::distance(players.begin(), iter);
//const rock_paper_scissors_throw_commit& commit = op.move.get<rock_paper_scissors_throw_commit>();
// are we expecting commits?
if (get_state() != game_state::expecting_commit_moves)
FC_THROW("Game ${game} is not accepting any commit moves", ("game", id));
// has this player committed already?
const rock_paper_scissors_game_details& details = game_details.get<rock_paper_scissors_game_details>();
if (details.commit_moves.at(player_index))
FC_THROW("Player ${account_id} has already committed their move for game ${game}",
("account_id", op.player_account_id)
("game", id));
// if all the above checks pass, then the move is accepted
}
else if (op.move.which() == game_specific_moves::tag<rock_paper_scissors_throw_reveal>::value)
{
// Is this move made by a player in the match
auto iter = std::find(players.begin(), players.end(),
op.player_account_id);
if (iter == players.end())
FC_THROW("Player ${account_id} is not a player in game ${game}",
("account_id", op.player_account_id)
("game", id));
unsigned player_index = std::distance(players.begin(), iter);
// has this player committed already?
const rock_paper_scissors_game_details& details = game_details.get<rock_paper_scissors_game_details>();
if (!details.commit_moves.at(player_index))
FC_THROW("Player ${account_id} cannot reveal a move which they did not commit in game ${game}",
("account_id", op.player_account_id)
("game", id));
// are we expecting reveals?
if (get_state() != game_state::expecting_reveal_moves)
FC_THROW("Game ${game} is not accepting any reveal moves", ("game", id));
const rock_paper_scissors_throw_commit& commit = *details.commit_moves.at(player_index);
const rock_paper_scissors_throw_reveal& reveal = op.move.get<rock_paper_scissors_throw_reveal>();
// does the reveal match the commit?
rock_paper_scissors_throw reconstructed_throw;
reconstructed_throw.nonce1 = commit.nonce1;
reconstructed_throw.nonce2 = reveal.nonce2;
reconstructed_throw.gesture = reveal.gesture;
fc::sha256 reconstructed_hash = reconstructed_throw.calculate_hash();
if (commit.throw_hash != reconstructed_hash)
FC_THROW("Reveal does not match commit's hash of ${commit_hash}",
("commit_hash", commit.throw_hash));
// is the throw valid for this game
const match_object& match_obj = match_id(db);
const tournament_object& tournament_obj = match_obj.tournament_id(db);
const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get<rock_paper_scissors_game_options>();
if ((unsigned)reveal.gesture >= game_options.number_of_gestures)
FC_THROW("Gesture ${gesture_int} is not valid for this game", ("gesture", (unsigned)reveal.gesture));
// if all the above checks pass, then the move is accepted
}
else
FC_THROW("The only valid moves in a rock-paper-scissors game are commit and reveal, not ${type}",
("type", op.move.which()));
}
else
FC_THROW("Game of type ${type} not supported", ("type", game_details.which()));
}
void game_object::make_automatic_moves(database& db)
{
rock_paper_scissors_game_details& rps_game_details = game_details.get<rock_paper_scissors_game_details>();
unsigned players_without_commit_moves = 0;
bool no_player_has_reveal_move = true;
for (unsigned i = 0; i < 2; ++i)
{
if (!rps_game_details.commit_moves[i])
++players_without_commit_moves;
if (rps_game_details.reveal_moves[i])
no_player_has_reveal_move = false;
}
if (players_without_commit_moves || no_player_has_reveal_move)
{
const match_object& match_obj = match_id(db);
const tournament_object& tournament_obj = match_obj.tournament_id(db);
const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get<rock_paper_scissors_game_options>();
if (game_options.insurance_enabled)
{
for (unsigned i = 0; i < 2; ++i)
{
if (!rps_game_details.commit_moves[i] ||
no_player_has_reveal_move)
{
struct rock_paper_scissors_throw_reveal reveal;
reveal.nonce2 = 0;
reveal.gesture = (rock_paper_scissors_gesture)db.get_random_bits(game_options.number_of_gestures);
rps_game_details.reveal_moves[i] = reveal;
ilog("Player ${player} failed to commit a move, generating a random move for him: ${gesture}",
("player", i)("gesture", reveal.gesture));
}
}
}
}
}
void game_object::determine_winner(database& db)
{
// we now know who played what, figure out if we have a winner
const rock_paper_scissors_game_details& rps_game_details = game_details.get<rock_paper_scissors_game_details>();
if (rps_game_details.reveal_moves[0] && rps_game_details.reveal_moves[1] &&
rps_game_details.reveal_moves[0]->gesture == rps_game_details.reveal_moves[1]->gesture)
ilog("The game was a tie, both players threw ${gesture}", ("gesture", rps_game_details.reveal_moves[0]->gesture));
else
{
const match_object& match_obj = match_id(db);
const tournament_object& tournament_obj = match_obj.tournament_id(db);
const rock_paper_scissors_game_options& game_options = tournament_obj.options.game_options.get<rock_paper_scissors_game_options>();
if (rps_game_details.reveal_moves[0] && rps_game_details.reveal_moves[1])
{
unsigned winner = ((((int)rps_game_details.reveal_moves[0]->gesture -
(int)rps_game_details.reveal_moves[1]->gesture +
game_options.number_of_gestures) % game_options.number_of_gestures) + 1) % 2;
ilog("${gesture1} vs ${gesture2}, ${winner} wins",
("gesture1", rps_game_details.reveal_moves[1]->gesture)
("gesture2", rps_game_details.reveal_moves[0]->gesture)
("winner", rps_game_details.reveal_moves[winner]->gesture));
winners.insert(players[winner]);
}
else if (rps_game_details.reveal_moves[0])
{
ilog("Player 1 didn't commit or reveal their move, player 0 wins");
winners.insert(players[0]);
}
else if (rps_game_details.reveal_moves[1])
{
ilog("Player 0 didn't commit or reveal their move, player 1 wins");
winners.insert(players[1]);
}
else
{
ilog("Neither player made a move, both players lose");
}
}
const match_object& match_obj = match_id(db);
db.modify(match_obj, [&](match_object& match) {
match.on_game_complete(db, *this);
});
}
void game_object::on_move(database& db, const game_move_operation& op)
{
my->state_machine.process_event(game_move(db, op));
}
void game_object::on_timeout(database& db)
{
my->state_machine.process_event(timeout(db));
}
void game_object::start_game(database& db, const std::vector<account_id_type>& players)
{
my->state_machine.process_event(initiate_game(db, players));
}
void game_object::pack_impl(std::ostream& stream) const
{
boost::archive::binary_oarchive oa(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
oa << my->state_machine;
}
void game_object::unpack_impl(std::istream& stream)
{
boost::archive::binary_iarchive ia(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
ia >> my->state_machine;
}
} } // graphene::chain
namespace fc {
// Manually reflect game_object to variant to properly reflect "state"
void to_variant(const graphene::chain::game_object& game_obj, fc::variant& v)
{
fc_elog(fc::logger::get("tournament"), "In game_obj to_variant");
elog("In game_obj to_variant");
fc::mutable_variant_object o;
o("id", game_obj.id)
("match_id", game_obj.match_id)
("players", game_obj.players)
("winners", game_obj.winners)
("game_details", game_obj.game_details)
("next_timeout", game_obj.next_timeout)
("state", game_obj.get_state());
v = o;
}
// Manually reflect game_object to variant to properly reflect "state"
void from_variant(const fc::variant& v, graphene::chain::game_object& game_obj)
{
fc_elog(fc::logger::get("tournament"), "In game_obj from_variant");
game_obj.id = v["id"].as<graphene::chain::game_id_type>();
game_obj.match_id = v["match_id"].as<graphene::chain::match_id_type>();
game_obj.players = v["players"].as<std::vector<graphene::chain::account_id_type> >();
game_obj.winners = v["winners"].as<flat_set<graphene::chain::account_id_type> >();
game_obj.game_details = v["game_details"].as<graphene::chain::game_specific_details>();
game_obj.next_timeout = v["next_timeout"].as<fc::optional<time_point_sec> >();
graphene::chain::game_state state = v["state"].as<graphene::chain::game_state>();
const_cast<int*>(game_obj.my->state_machine.current_state())[0] = (int)state;
}
} //end namespace fc

View file

@ -96,6 +96,7 @@ fc::variant_object get_config()
result[ "GRAPHENE_MAX_URL_LENGTH" ] = GRAPHENE_MAX_URL_LENGTH;
result[ "GRAPHENE_NEAR_SCHEDULE_CTR_IV" ] = GRAPHENE_NEAR_SCHEDULE_CTR_IV;
result[ "GRAPHENE_FAR_SCHEDULE_CTR_IV" ] = GRAPHENE_FAR_SCHEDULE_CTR_IV;
result[ "GRAPHENE_RNG_SEED_LENGTH" ] = GRAPHENE_RNG_SEED_LENGTH;
result[ "GRAPHENE_CORE_ASSET_CYCLE_RATE" ] = GRAPHENE_CORE_ASSET_CYCLE_RATE;
result[ "GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS" ] = GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS;
result[ "GRAPHENE_DEFAULT_WITNESS_PAY_PER_BLOCK" ] = GRAPHENE_DEFAULT_WITNESS_PAY_PER_BLOCK;

View file

@ -23,8 +23,8 @@
*/
#pragma once
#define GRAPHENE_SYMBOL "BTS"
#define GRAPHENE_ADDRESS_PREFIX "BTS"
#define GRAPHENE_SYMBOL "PPY"
#define GRAPHENE_ADDRESS_PREFIX "PPY"
#define GRAPHENE_MIN_ACCOUNT_NAME_LENGTH 1
#define GRAPHENE_MAX_ACCOUNT_NAME_LENGTH 63
@ -113,6 +113,9 @@
#define GRAPHENE_MAX_URL_LENGTH 127
#define GRAPHENE_WITNESS_SHUFFLED_ALGORITHM 0
#define GRAPHENE_WITNESS_SCHEDULED_ALGORITHM 1
// counter initialization values used to derive near and far future seeds for shuffling witnesses
// we use the fractional bits of sqrt(2) in hex
#define GRAPHENE_NEAR_SCHEDULE_CTR_IV ( (uint64_t( 0x6a09 ) << 0x30) \
@ -126,6 +129,10 @@
| (uint64_t( 0x84ca ) << 0x10) \
| (uint64_t( 0xa73b ) ) )
// counter used to determine bits of entropy
// must be less than or equal to secret_hash_type::data_length()
#define GRAPHENE_RNG_SEED_LENGTH (160 / 8)
/**
* every second, the fraction of burned core asset which cycles is
* GRAPHENE_CORE_ASSET_CYCLE_RATE / (1 << GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS)
@ -166,6 +173,7 @@
#define GRAPHENE_PROXY_TO_SELF_ACCOUNT (graphene::chain::account_id_type(5))
///
#define GRAPHENE_RAKE_FEE_ACCOUNT_ID (graphene::chain::account_id_type(6))
#define TOURNAMENT_RAKE_FEE_ACCOUNT_ID (graphene::chain::account_id_type(6))
/// Sentinel value used in the scheduler.
#define GRAPHENE_NULL_WITNESS (graphene::chain::witness_id_type(0))
///@}
@ -203,3 +211,18 @@
{ 1000000, 50000}, /* <= 100: 5.00 */ \
{ 10000000, 100000} } /* <= 1000: 10.00 */
#define GRAPHENE_DEFAULT_BETTING_PERCENT_FEE (2 * GRAPHENE_1_PERCENT)
#define TOURNAMENT_MIN_ROUND_DELAY 0
#define TOURNAMENT_MAX_ROUND_DELAY 600
#define TOURNAMENT_MIN_TIME_PER_COMMIT_MOVE 0
#define TOURNAMENT_MAN_TIME_PER_COMMIT_MOVE 600
#define TOURNAMENT_MIN_TIME_PER_REVEAL_MOVE 0
#define TOURNAMENT_MAX_TIME_PER_REVEAL_MOVE 600
#define TOURNAMENT_DEFAULT_RAKE_FEE_PERCENTAGE (3*GRAPHENE_1_PERCENT)
#define TOURNAMENT_MINIMAL_RAKE_FEE_PERCENTAGE (1*GRAPHENE_1_PERCENT)
#define TOURNAMENT_MAXIMAL_RAKE_FEE_PERCENTAGE (20*GRAPHENE_1_PERCENT)
#define TOURNAMENT_MAXIMAL_REGISTRATION_DEADLINE (60*60*24*30) // seconds, 30 days
#define TOURNAMENT_MAX_NUMBER_OF_WINS 100
#define TOURNAMENT_MAX_PLAYERS_NUMBER 256
#define TOURNAMENT_MAX_WHITELIST_LENGTH 1000
#define TOURNAMENT_MAX_START_TIME_IN_FUTURE (60*60*24*7*4) // 1 month
#define TOURNAMENT_MAX_START_DELAY (60*60*24*7) // 1 week

View file

@ -36,6 +36,8 @@
#include <graphene/db/simple_index.hpp>
#include <fc/signals.hpp>
#include <fc/crypto/hash_ctr_rng.hpp>
#include <graphene/chain/protocol/protocol.hpp>
#include <fc/log/logger.hpp>
@ -244,7 +246,9 @@ namespace graphene { namespace chain {
*/
uint32_t get_slot_at_time(fc::time_point_sec when)const;
vector<witness_id_type> get_near_witness_schedule()const;
void update_witness_schedule();
void update_witness_schedule(const signed_block& next_block);
//////////////////// db_getter.cpp ////////////////////
@ -256,6 +260,8 @@ namespace graphene { namespace chain {
const node_property_object& get_node_properties()const;
const fee_schedule& current_fee_schedule()const;
uint64_t get_random_bits( uint64_t bound );
time_point_sec head_block_time()const;
uint32_t head_block_num()const;
block_id_type head_block_id()const;
@ -461,6 +467,7 @@ namespace graphene { namespace chain {
void update_expired_feeds();
void update_maintenance_flag( bool new_maintenance_flag );
void update_withdraw_permissions();
void update_tournaments();
bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true );
///Steps performed only at maintenance intervals
@ -516,6 +523,7 @@ namespace graphene { namespace chain {
flat_map<uint32_t,block_id_type> _checkpoints;
node_property_object _node_property_object;
fc::hash_ctr_rng<secret_hash_type, 20> _random_number_generator;
};
namespace detail

View file

@ -0,0 +1,147 @@
#pragma once
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/rock_paper_scissors.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <graphene/db/flat_index.hpp>
#include <graphene/db/generic_index.hpp>
#include <fc/crypto/hex.hpp>
#include <sstream>
namespace graphene { namespace chain {
class game_object;
} }
namespace fc {
void to_variant(const graphene::chain::game_object& game_obj, fc::variant& v);
void from_variant(const fc::variant& v, graphene::chain::game_object& game_obj);
} //end namespace fc
namespace graphene { namespace chain {
class database;
using namespace graphene::db;
enum class game_state
{
game_in_progress,
expecting_commit_moves,
expecting_reveal_moves,
game_complete
};
class game_object : public graphene::db::abstract_object<game_object>
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = game_object_type;
match_id_type match_id;
vector<account_id_type> players;
flat_set<account_id_type> winners;
game_specific_details game_details;
fc::optional<time_point_sec> next_timeout;
game_state get_state() const;
game_object();
game_object(const game_object& rhs);
~game_object();
game_object& operator=(const game_object& rhs);
void evaluate_move_operation(const database& db, const game_move_operation& op) const;
void make_automatic_moves(database& db);
void determine_winner(database& db);
void on_move(database& db, const game_move_operation& op);
void on_timeout(database& db);
void start_game(database& db, const std::vector<account_id_type>& players);
// serialization functions:
// for serializing to raw, go through a temporary sstream object to avoid
// having to implement serialization in the header file
template<typename Stream>
friend Stream& operator<<( Stream& s, const game_object& game_obj );
template<typename Stream>
friend Stream& operator>>( Stream& s, game_object& game_obj );
friend void ::fc::to_variant(const graphene::chain::game_object& game_obj, fc::variant& v);
friend void ::fc::from_variant(const fc::variant& v, graphene::chain::game_object& game_obj);
void pack_impl(std::ostream& stream) const;
void unpack_impl(std::istream& stream);
class impl;
std::unique_ptr<impl> my;
};
struct by_next_timeout {};
typedef multi_index_container<
game_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_unique< tag<by_next_timeout>,
composite_key<game_object,
member<game_object, optional<time_point_sec>, &game_object::next_timeout>,
member<object, object_id_type, &object::id> > > >
> game_object_multi_index_type;
typedef generic_index<game_object, game_object_multi_index_type> game_index;
template<typename Stream>
inline Stream& operator<<( Stream& s, const game_object& game_obj )
{
// pack all fields exposed in the header in the usual way
// instead of calling the derived pack, just serialize the one field in the base class
// fc::raw::pack<Stream, const graphene::db::abstract_object<game_object> >(s, game_obj);
fc::raw::pack(s, game_obj.id);
fc::raw::pack(s, game_obj.match_id);
fc::raw::pack(s, game_obj.players);
fc::raw::pack(s, game_obj.winners);
fc::raw::pack(s, game_obj.game_details);
fc::raw::pack(s, game_obj.next_timeout);
// fc::raw::pack the contents hidden in the impl class
std::ostringstream stream;
game_obj.pack_impl(stream);
std::string stringified_stream(stream.str());
fc::raw::pack(s, stream.str());
return s;
}
template<typename Stream>
inline Stream& operator>>( Stream& s, game_object& game_obj )
{
// unpack all fields exposed in the header in the usual way
//fc::raw::unpack<Stream, graphene::db::abstract_object<game_object> >(s, game_obj);
fc::raw::unpack(s, game_obj.id);
fc::raw::unpack(s, game_obj.match_id);
fc::raw::unpack(s, game_obj.players);
fc::raw::unpack(s, game_obj.winners);
fc::raw::unpack(s, game_obj.game_details);
fc::raw::unpack(s, game_obj.next_timeout);
// fc::raw::unpack the contents hidden in the impl class
std::string stringified_stream;
fc::raw::unpack(s, stringified_stream);
std::istringstream stream(stringified_stream);
game_obj.unpack_impl(stream);
return s;
}
} }
FC_REFLECT_ENUM(graphene::chain::game_state,
(game_in_progress)
(expecting_commit_moves)
(expecting_reveal_moves)
(game_complete))
//FC_REFLECT_TYPENAME(graphene::chain::game_object) // manually serialized
FC_REFLECT(graphene::chain::game_object, (players))

View file

@ -52,6 +52,46 @@ struct genesis_state_type {
public_key_type active_key;
bool is_lifetime_member = false;
};
struct initial_bts_account_type {
struct initial_authority {
uint32_t weight_threshold;
flat_map<string, weight_type> account_auths; // uses account name instead of account id
flat_map<public_key_type, weight_type> key_auths;
flat_map<address, weight_type> address_auths;
};
struct initial_cdd_vesting_policy {
uint32_t vesting_seconds;
fc::uint128_t coin_seconds_earned;
fc::time_point_sec start_claim;
fc::time_point_sec coin_seconds_earned_last_update;
};
struct initial_linear_vesting_policy {
fc::time_point_sec begin_timestamp;
uint32_t vesting_cliff_seconds;
uint32_t vesting_duration_seconds;
share_type begin_balance;
};
struct initial_vesting_balance {
string asset_symbol;
share_type amount;
std::string policy_type; // either "linear" or "cdd"
fc::variant policy; // either an initial_cdd_vesting_policy or initial_linear_vesting_policy
};
initial_bts_account_type(const string& name = string(),
const initial_authority& owner_authority = initial_authority(),
const initial_authority& active_authority = initial_authority(),
const share_type& core_balance = share_type())
: name(name),
owner_authority(owner_authority),
active_authority(active_authority),
core_balance(core_balance)
{}
string name;
initial_authority owner_authority;
initial_authority active_authority;
share_type core_balance;
fc::optional<std::vector<initial_vesting_balance> > vesting_balances;
};
struct initial_asset_type {
struct initial_collateral_position {
address owner;
@ -81,6 +121,7 @@ struct genesis_state_type {
string asset_symbol;
share_type amount;
time_point_sec begin_timestamp;
fc::optional<uint32_t> vesting_cliff_seconds;
uint32_t vesting_duration_seconds = 0;
share_type begin_balance;
};
@ -103,6 +144,7 @@ struct genesis_state_type {
share_type max_core_supply = GRAPHENE_MAX_SHARE_SUPPLY;
chain_parameters initial_parameters;
immutable_chain_parameters immutable_parameters;
vector<initial_bts_account_type> initial_bts_accounts;
vector<initial_account_type> initial_accounts;
vector<initial_asset_type> initial_assets;
vector<initial_balance_type> initial_balances;
@ -139,7 +181,7 @@ FC_REFLECT(graphene::chain::genesis_state_type::initial_balance_type,
(owner)(asset_symbol)(amount))
FC_REFLECT(graphene::chain::genesis_state_type::initial_vesting_balance_type,
(owner)(asset_symbol)(amount)(begin_timestamp)(vesting_duration_seconds)(begin_balance))
(owner)(asset_symbol)(amount)(begin_timestamp)(vesting_cliff_seconds)(vesting_duration_seconds)(begin_balance))
FC_REFLECT(graphene::chain::genesis_state_type::initial_witness_type, (owner_name)(block_signing_key))
@ -147,8 +189,35 @@ FC_REFLECT(graphene::chain::genesis_state_type::initial_committee_member_type, (
FC_REFLECT(graphene::chain::genesis_state_type::initial_worker_type, (owner_name)(daily_pay))
FC_REFLECT(graphene::chain::genesis_state_type::initial_bts_account_type::initial_authority,
(weight_threshold)
(account_auths)
(key_auths)
(address_auths))
FC_REFLECT(graphene::chain::genesis_state_type::initial_bts_account_type::initial_cdd_vesting_policy,
(vesting_seconds)
(coin_seconds_earned)
(start_claim)
(coin_seconds_earned_last_update))
FC_REFLECT(graphene::chain::genesis_state_type::initial_bts_account_type::initial_linear_vesting_policy,
(begin_timestamp)
(vesting_cliff_seconds)
(vesting_duration_seconds)
(begin_balance))
FC_REFLECT(graphene::chain::genesis_state_type::initial_bts_account_type::initial_vesting_balance,
(asset_symbol)
(amount)
(policy_type)
(policy))
FC_REFLECT(graphene::chain::genesis_state_type::initial_bts_account_type,
(name)
(owner_authority)
(active_authority)
(core_balance)
(vesting_balances))
FC_REFLECT(graphene::chain::genesis_state_type,
(initial_timestamp)(max_core_supply)(initial_parameters)(initial_accounts)(initial_assets)(initial_balances)
(initial_timestamp)(max_core_supply)(initial_parameters)(initial_bts_accounts)(initial_accounts)(initial_assets)(initial_balances)
(initial_vesting_balances)(initial_active_witnesses)(initial_witness_candidates)
(initial_committee_candidates)(initial_worker_candidates)
(initial_chain_id)

View file

@ -69,6 +69,7 @@ namespace graphene { namespace chain {
static const uint8_t space_id = implementation_ids;
static const uint8_t type_id = impl_dynamic_global_property_object_type;
secret_hash_type random;
uint32_t head_block_number = 0;
block_id_type head_block_id;
time_point_sec time;
@ -125,6 +126,7 @@ namespace graphene { namespace chain {
}}
FC_REFLECT_DERIVED( graphene::chain::dynamic_global_property_object, (graphene::db::object),
(random)
(head_block_number)
(head_block_id)
(time)

View file

@ -0,0 +1,167 @@
#pragma once
#include <graphene/chain/protocol/tournament.hpp>
#include <graphene/chain/rock_paper_scissors.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <graphene/db/flat_index.hpp>
#include <graphene/db/generic_index.hpp>
#include <fc/crypto/hex.hpp>
#include <sstream>
namespace graphene { namespace chain {
class match_object;
} }
namespace fc {
void to_variant(const graphene::chain::match_object& match_obj, fc::variant& v);
void from_variant(const fc::variant& v, graphene::chain::match_object& match_obj);
} //end namespace fc
namespace graphene { namespace chain {
class database;
using namespace graphene::db;
enum class match_state
{
waiting_on_previous_matches,
match_in_progress,
match_complete
};
class match_object : public graphene::db::abstract_object<match_object>
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = match_object_type;
tournament_id_type tournament_id;
/// The players in the match
vector<account_id_type> players;
/// The list of games in the match
/// Unlike tournaments where the list of matches is known at the start,
/// the list of games will start with one game and grow until we have played
/// enough games to declare a winner for the match.
vector<game_id_type> games;
/// A list of the winners of each round of the game. This information is
/// also stored in the game object, but is duplicated here to allow displaying
/// information about a match without having to request all game objects
vector<flat_set<account_id_type> > game_winners;
/// A count of the number of wins for each player
vector<uint32_t> number_of_wins;
/// the total number of games that ended up in a tie/draw/stalemate
uint32_t number_of_ties;
// If the match is not yet complete, this will be empty
// If the match is in the "match_complete" state, it will contain the
// list of winners.
// For Rock-paper-scissors, there will be one winner, unless there is
// a stalemate (in that case, there are no winners)
flat_set<account_id_type> match_winners;
/// the time the match started
time_point_sec start_time;
/// If the match has ended, the time it ended
optional<time_point_sec> end_time;
match_object();
match_object(const match_object& rhs);
~match_object();
match_object& operator=(const match_object& rhs);
match_state get_state() const;
// serialization functions:
// for serializing to raw, go through a temporary sstream object to avoid
// having to implement serialization in the header file
template<typename Stream>
friend Stream& operator<<( Stream& s, const match_object& match_obj );
template<typename Stream>
friend Stream& operator>>( Stream& s, match_object& match_obj );
friend void ::fc::to_variant(const graphene::chain::match_object& match_obj, fc::variant& v);
friend void ::fc::from_variant(const fc::variant& v, graphene::chain::match_object& match_obj);
void pack_impl(std::ostream& stream) const;
void unpack_impl(std::istream& stream);
void on_initiate_match(database& db);
void on_game_complete(database& db, const game_object& game);
game_id_type start_next_game(database& db, match_id_type match_id);
class impl;
std::unique_ptr<impl> my;
};
typedef multi_index_container<
match_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > > >
> match_object_multi_index_type;
typedef generic_index<match_object, match_object_multi_index_type> match_index;
template<typename Stream>
inline Stream& operator<<( Stream& s, const match_object& match_obj )
{
// pack all fields exposed in the header in the usual way
// instead of calling the derived pack, just serialize the one field in the base class
// fc::raw::pack<Stream, const graphene::db::abstract_object<match_object> >(s, match_obj);
fc::raw::pack(s, match_obj.id);
fc::raw::pack(s, match_obj.tournament_id);
fc::raw::pack(s, match_obj.players);
fc::raw::pack(s, match_obj.games);
fc::raw::pack(s, match_obj.game_winners);
fc::raw::pack(s, match_obj.number_of_wins);
fc::raw::pack(s, match_obj.number_of_ties);
fc::raw::pack(s, match_obj.match_winners);
fc::raw::pack(s, match_obj.start_time);
fc::raw::pack(s, match_obj.end_time);
// fc::raw::pack the contents hidden in the impl class
std::ostringstream stream;
match_obj.pack_impl(stream);
std::string stringified_stream(stream.str());
fc::raw::pack(s, stream.str());
return s;
}
template<typename Stream>
inline Stream& operator>>( Stream& s, match_object& match_obj )
{
// unpack all fields exposed in the header in the usual way
//fc::raw::unpack<Stream, graphene::db::abstract_object<match_object> >(s, match_obj);
fc::raw::unpack(s, match_obj.id);
fc::raw::unpack(s, match_obj.tournament_id);
fc::raw::unpack(s, match_obj.players);
fc::raw::unpack(s, match_obj.games);
fc::raw::unpack(s, match_obj.game_winners);
fc::raw::unpack(s, match_obj.number_of_wins);
fc::raw::unpack(s, match_obj.number_of_ties);
fc::raw::unpack(s, match_obj.match_winners);
fc::raw::unpack(s, match_obj.start_time);
fc::raw::unpack(s, match_obj.end_time);
// fc::raw::unpack the contents hidden in the impl class
std::string stringified_stream;
fc::raw::unpack(s, stringified_stream);
std::istringstream stream(stringified_stream);
match_obj.unpack_impl(stream);
return s;
}
} }
FC_REFLECT_ENUM(graphene::chain::match_state,
(waiting_on_previous_matches)
(match_in_progress)
(match_complete))
//FC_REFLECT_TYPENAME(graphene::chain::match_object) // manually serialized
FC_REFLECT(graphene::chain::match_object, (players))

View file

@ -264,6 +264,7 @@ namespace graphene { namespace chain {
} } // graphene::chain
FC_REFLECT(graphene::chain::account_options, (memo_key)(voting_account)(num_witness)(num_committee)(votes)(extensions))
// FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listing)
FC_REFLECT_ENUM( graphene::chain::account_whitelist_operation::account_listing,
(no_listing)(white_listed)(black_listed)(white_and_black_listed))

View file

@ -132,4 +132,5 @@ void add_authority_accounts(
} } // namespace graphene::chain
FC_REFLECT( graphene::chain::authority, (weight_threshold)(account_auths)(key_auths)(address_auths) )
// FC_REFLECT_TYPENAME( graphene::chain::authority::classification )
FC_REFLECT_ENUM( graphene::chain::authority::classification, (owner)(active)(key) )

View file

@ -33,6 +33,8 @@ namespace graphene { namespace chain {
uint32_t block_num()const { return num_from_id(previous) + 1; }
fc::time_point_sec timestamp;
witness_id_type witness;
secret_hash_type next_secret_hash;
secret_hash_type previous_secret;
checksum_type transaction_merkle_root;
extensions_type extensions;
@ -57,6 +59,13 @@ namespace graphene { namespace chain {
} } // graphene::chain
FC_REFLECT( graphene::chain::block_header, (previous)(timestamp)(witness)(transaction_merkle_root)(extensions) )
FC_REFLECT( graphene::chain::block_header,
(previous)
(timestamp)
(witness)
(next_secret_hash)
(previous_secret)
(transaction_merkle_root)
(extensions) )
FC_REFLECT_DERIVED( graphene::chain::signed_block_header, (graphene::chain::block_header), (witness_signature) )
FC_REFLECT_DERIVED( graphene::chain::signed_block, (graphene::chain::signed_block_header), (transactions) )

View file

@ -73,7 +73,22 @@ namespace graphene { namespace chain {
bet_multiplier_type min_bet_multiplier = GRAPHENE_DEFAULT_MIN_BET_MULTIPLIER;
bet_multiplier_type max_bet_multiplier = GRAPHENE_DEFAULT_MAX_BET_MULTIPLIER;
flat_map<bet_multiplier_type, bet_multiplier_type> permitted_betting_odds_increments = GRAPHENE_DEFAULT_PERMITTED_BETTING_ODDS_INCREMENTS;
//uint8_t witness_schedule_algorithm = GRAPHENE_WITNESS_SHUFFLED_ALGORITHM; ///< 0 shuffled, 1 scheduled
uint8_t witness_schedule_algorithm = GRAPHENE_WITNESS_SCHEDULED_ALGORITHM; ///< 0 shuffled, 1 scheduled
/* rps tournament parameters constraints */
uint32_t min_round_delay = TOURNAMENT_MIN_ROUND_DELAY; ///< miniaml delay between games
uint32_t max_round_delay = TOURNAMENT_MAX_ROUND_DELAY; ///< maximal delay between games
uint32_t min_time_per_commit_move = TOURNAMENT_MIN_TIME_PER_COMMIT_MOVE; ///< minimal time to commit the next move
uint32_t max_time_per_commit_move = TOURNAMENT_MAN_TIME_PER_COMMIT_MOVE; ///< maximal time to commit the next move
uint32_t min_time_per_reveal_move = TOURNAMENT_MIN_TIME_PER_REVEAL_MOVE; ///< minimal time to reveal move
uint32_t max_time_per_reveal_move = TOURNAMENT_MAX_TIME_PER_REVEAL_MOVE; ///< maximal time to reveal move
uint16_t rake_fee_percentage = TOURNAMENT_DEFAULT_RAKE_FEE_PERCENTAGE; ///< part of prize paid into the dividend account for the core token holders
uint32_t maximum_registration_deadline = TOURNAMENT_MAXIMAL_REGISTRATION_DEADLINE; ///< value registration deadline must be before
uint16_t maximum_players_in_tournament = TOURNAMENT_MAX_PLAYERS_NUMBER; ///< maximal count of players in tournament
uint16_t maximum_tournament_whitelist_length = TOURNAMENT_MAX_WHITELIST_LENGTH; ///< maximal tournament whitelist length
uint32_t maximum_tournament_start_time_in_future = TOURNAMENT_MAX_START_TIME_IN_FUTURE;
uint32_t maximum_tournament_start_delay = TOURNAMENT_MAX_START_DELAY;
uint16_t maximum_tournament_number_of_wins = TOURNAMENT_MAX_NUMBER_OF_WINS;
extensions_type extensions;
/** defined in fee_schedule.cpp */
@ -115,5 +130,19 @@ FC_REFLECT( graphene::chain::chain_parameters,
(max_bet_multiplier)
(betting_rake_fee_percentage)
(permitted_betting_odds_increments)
(witness_schedule_algorithm)
(min_round_delay)
(max_round_delay)
(min_time_per_commit_move)
(max_time_per_commit_move)
(min_time_per_reveal_move)
(max_time_per_reveal_move)
(rake_fee_percentage)
(maximum_registration_deadline)
(maximum_players_in_tournament)
(maximum_tournament_whitelist_length)
(maximum_tournament_start_time_in_future)
(maximum_tournament_start_delay)
(maximum_tournament_number_of_wins)
(extensions)
)

View file

@ -42,6 +42,7 @@
#include <graphene/chain/protocol/event_group.hpp>
#include <graphene/chain/protocol/event.hpp>
#include <graphene/chain/protocol/betting_market.hpp>
#include <graphene/chain/protocol/tournament.hpp>
namespace graphene { namespace chain {
@ -110,7 +111,14 @@ namespace graphene { namespace chain {
betting_market_group_freeze_operation,
bet_matched_operation, // VIRTUAL
bet_cancel_operation,
bet_canceled_operation // VIRTUAL
bet_canceled_operation, // VIRTUAL
tournament_create_operation,
tournament_join_operation,
game_move_operation,
asset_update_dividend_operation,
asset_dividend_distribution_operation, // VIRTUAL
tournament_payout_operation, // VIRTUAL
tournament_leave_operation
> operation;
/// @} // operations group

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <cassert>
#include <cstdint>
#include <string>
#include <fc/container/flat.hpp>
#include <fc/reflect/reflect.hpp>
namespace graphene { namespace chain {
struct rock_paper_scissors_game_options
{
/// If true and a user fails to commit their move before the time_per_commit_move expires,
/// the blockchain will randomly choose a move for the user
bool insurance_enabled;
/// The number of seconds users are given to commit their next move, counted from the beginning
/// of the hand (during the game, a hand begins immediately on the block containing the
/// second player's reveal or where the time_per_reveal move has expired).
/// Note, if these times aren't an even multiple of the block interval, they will be rounded
/// up.
uint32_t time_per_commit_move;
/// The number of seconds users are given to reveal their move, counted from the time of the
/// block containing the second commit or the where the time_per_commit_move expired
uint32_t time_per_reveal_move;
/// The number of allowed gestures, must be either 3 or 5. If 3, the game is
/// standard rock-paper-scissors, if 5, it's
/// rock-paper-scissors-lizard-spock.
uint8_t number_of_gestures;
};
enum class rock_paper_scissors_gesture
{
rock,
paper,
scissors,
spock,
lizard
};
struct rock_paper_scissors_throw
{
uint64_t nonce1;
uint64_t nonce2;
rock_paper_scissors_gesture gesture;
fc::sha256 calculate_hash() const;
};
struct rock_paper_scissors_throw_commit
{
uint64_t nonce1;
fc::sha256 throw_hash;
bool operator<(const graphene::chain::rock_paper_scissors_throw_commit& rhs) const
{
return std::tie(nonce1, throw_hash) < std::tie(rhs.nonce1, rhs.throw_hash);
}
};
struct rock_paper_scissors_throw_reveal
{
uint64_t nonce2;
rock_paper_scissors_gesture gesture;
};
} }
FC_REFLECT( graphene::chain::rock_paper_scissors_game_options, (insurance_enabled)(time_per_commit_move)(time_per_reveal_move)(number_of_gestures) )
// FC_REFLECT_TYPENAME( graphene::chain::rock_paper_scissors_gesture)
FC_REFLECT_ENUM( graphene::chain::rock_paper_scissors_gesture,
(rock)
(paper)
(scissors)
(spock)
(lizard))
FC_REFLECT( graphene::chain::rock_paper_scissors_throw,
(nonce1)
(nonce2)
(gesture) )
FC_REFLECT( graphene::chain::rock_paper_scissors_throw_commit,
(nonce1)
(throw_hash) )
FC_REFLECT( graphene::chain::rock_paper_scissors_throw_reveal,
(nonce2)(gesture) )

View file

@ -0,0 +1,279 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <cassert>
#include <cstdint>
#include <string>
#include <fc/container/flat.hpp>
#include <fc/reflect/reflect.hpp>
#include <graphene/chain/protocol/asset.hpp>
#include <graphene/chain/protocol/rock_paper_scissors.hpp>
#include <graphene/chain/protocol/base.hpp>
namespace graphene { namespace chain {
enum class payout_type
{
prize_award,
buyin_refund,
rake_fee
};
typedef fc::static_variant<rock_paper_scissors_game_options> game_specific_options;
/**
* @brief Options specified when creating a new tournament
*/
struct tournament_options
{
/// If there aren't enough players registered for the tournament before this time,
/// the tournament is canceled
fc::time_point_sec registration_deadline;
/// Number of players in the tournament. Must be greater than 1. Currently max is 255, should it be committe-settable?
uint32_t number_of_players;
/// Each player must pay this much to join the tournament. This can be
/// in any asset supported by the blockchain. If the tournament is canceled,
/// the buy-in will be returned.
asset buy_in;
/// A list of all accounts allowed to register for this tournament. If empty,
/// anyone can register for the tournament
flat_set<account_id_type> whitelist;
/// If specified, this is the time the tourament will start (must not be before the registration
/// deadline). If this is not specified, the creator must specify `start_delay` instead.
optional<fc::time_point_sec> start_time;
/// If specified, this is the number of seconds after the final player registers before the
/// tournament begins. If this is not specified, the creator must specify an absolute `start_time`
optional<uint32_t> start_delay;
/// The delay, in seconds, between the end of the last game in one round of the tournament and the
/// start of all the games in the next round
uint32_t round_delay;
/// The winner of a round in the tournament is the first to reach this number of wins
uint32_t number_of_wins;
/// Metadata about this tournament. This can be empty or it can contain any keys the creator desires.
/// The GUI will standardize on displaying a few keys, likely:
/// "name"
/// "description"
/// "url"
fc::variant_object meta;
/// Parameters that are specific to the type_of_game in this tournament
/// The type stored in this static_variant field determines what type of game is being
/// played, so each different supported game must have a unique game_options data type
game_specific_options game_options;
void validate() const;
};
struct tournament_create_operation : public base_operation
{
struct fee_parameters_type {
share_type fee = GRAPHENE_BLOCKCHAIN_PRECISION;
uint32_t price_per_kbyte = 10;
};
asset fee;
/// The account that created the tournament
account_id_type creator;
/// Options for the tournament
tournament_options options;
extensions_type extensions;
account_id_type fee_payer()const { return creator; }
share_type calculate_fee(const fee_parameters_type& k)const;
void validate()const;
};
struct tournament_join_operation : public base_operation
{
struct fee_parameters_type {
share_type fee = GRAPHENE_BLOCKCHAIN_PRECISION;
};
asset fee;
/// The account that is paying the buy-in for the tournament, if the tournament is
/// canceled, will be refunded the buy-in.
account_id_type payer_account_id;
/// The account that will play in the tournament, will receive any winnings.
account_id_type player_account_id;
/// The tournament `player_account_id` is joining
tournament_id_type tournament_id;
/// The buy-in paid by the `payer_account_id`
asset buy_in;
extensions_type extensions;
account_id_type fee_payer()const { return payer_account_id; }
share_type calculate_fee(const fee_parameters_type& k)const;
void validate()const;
};
struct tournament_leave_operation : public base_operation
{
struct fee_parameters_type {
share_type fee = GRAPHENE_BLOCKCHAIN_PRECISION;
};
asset fee;
/// The account that is unregistering the player from tournament (must be payer or player)
account_id_type canceling_account_id;
/// The account that would play in the tournament, would receive any winnings.
account_id_type player_account_id;
/// The tournament `player_account_id` is leaving
tournament_id_type tournament_id;
extensions_type extensions;
account_id_type fee_payer()const { return canceling_account_id; }
share_type calculate_fee(const fee_parameters_type& k)const;
void validate()const;
};
typedef fc::static_variant<rock_paper_scissors_throw_commit, rock_paper_scissors_throw_reveal> game_specific_moves;
struct game_move_operation : public base_operation
{
struct fee_parameters_type {
share_type fee = GRAPHENE_BLOCKCHAIN_PRECISION;
};
asset fee;
/// the id of the game
game_id_type game_id;
/// The account of the player making this move
account_id_type player_account_id;
/// the move itself
game_specific_moves move;
extensions_type extensions;
account_id_type fee_payer()const { return player_account_id; }
share_type calculate_fee(const fee_parameters_type& k)const;
void validate()const;
};
struct tournament_payout_operation : public base_operation
{
struct fee_parameters_type {};
asset fee;
/// The account received payout
account_id_type payout_account_id;
/// The tournament generated payout
tournament_id_type tournament_id;
/// The payout amount
asset payout_amount;
payout_type type;
extensions_type extensions;
account_id_type fee_payer()const { return payout_account_id; }
share_type calculate_fee(const fee_parameters_type&)const { return 0; }
void validate()const {}
};
} }
FC_REFLECT_ENUM(graphene::chain::payout_type,
(prize_award)
(buyin_refund)
(rake_fee)
)
FC_REFLECT_TYPENAME( graphene::chain::game_specific_options )
FC_REFLECT_TYPENAME( graphene::chain::game_specific_moves )
FC_REFLECT( graphene::chain::tournament_options,
(registration_deadline)
(number_of_players)
(buy_in)
(whitelist)
(start_time)
(start_delay)
(round_delay)
(number_of_wins)
(meta)
(game_options))
FC_REFLECT( graphene::chain::tournament_create_operation,
(fee)
(creator)
(options)
(extensions))
FC_REFLECT( graphene::chain::tournament_join_operation,
(fee)
(payer_account_id)
(player_account_id)
(tournament_id)
(buy_in)
(extensions))
FC_REFLECT( graphene::chain::tournament_leave_operation,
(fee)
(canceling_account_id)
(player_account_id)
(tournament_id)
(extensions))
FC_REFLECT( graphene::chain::game_move_operation,
(fee)
(game_id)
(player_account_id)
(move)
(extensions))
FC_REFLECT( graphene::chain::tournament_payout_operation,
(fee)
(payout_account_id)
(tournament_id)
(payout_amount)
(type)
(extensions))
FC_REFLECT( graphene::chain::tournament_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::tournament_join_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::tournament_leave_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::game_move_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::tournament_payout_operation::fee_parameters_type, )

View file

@ -141,6 +141,10 @@ namespace graphene { namespace chain {
betting_market_group_object_type,
betting_market_object_type,
bet_object_type,
tournament_object_type,
tournament_details_object_type,
match_object_type,
game_object_type,
OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types
};
@ -194,6 +198,10 @@ namespace graphene { namespace chain {
class betting_market_group_object;
class betting_market_object;
class bet_object;
class tournament_object;
class tournament_details_object;
class match_object;
class game_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;
@ -216,6 +224,10 @@ 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, tournament_object_type, tournament_object> tournament_id_type;
typedef object_id< protocol_ids, tournament_details_object_type, tournament_details_object> tournament_details_id_type;
typedef object_id< protocol_ids, match_object_type, match_object> match_id_type;
typedef object_id< protocol_ids, game_object_type, game_object> game_id_type;
// implementation types
class global_property_object;
@ -235,6 +247,7 @@ namespace graphene { namespace chain {
class fba_accumulator_object;
class betting_market_position_object;
class global_betting_statistics_object;
class tournament_details_object;
class asset_dividend_data_object;
class pending_dividend_payout_balance_for_holder_object;
@ -269,6 +282,7 @@ namespace graphene { namespace chain {
typedef fc::sha256 digest_type;
typedef fc::ecc::compact_signature signature_type;
typedef safe<int64_t> share_type;
typedef fc::ripemd160 secret_hash_type;
typedef uint16_t weight_type;
struct public_key_type
@ -385,6 +399,10 @@ FC_REFLECT_ENUM( graphene::chain::object_type,
(betting_market_group_object_type)
(betting_market_object_type)
(bet_object_type)
(tournament_object_type)
(tournament_details_object_type)
(match_object_type)
(game_object_type)
(OBJECT_TYPE_COUNT)
)
FC_REFLECT_ENUM( graphene::chain::impl_object_type,
@ -435,6 +453,7 @@ FC_REFLECT_TYPENAME( graphene::chain::betting_market_rules_id_type )
FC_REFLECT_TYPENAME( graphene::chain::betting_market_group_id_type )
FC_REFLECT_TYPENAME( graphene::chain::betting_market_id_type )
FC_REFLECT_TYPENAME( graphene::chain::bet_id_type )
FC_REFLECT_TYPENAME( graphene::chain::tournament_id_type )
FC_REFLECT_TYPENAME( graphene::chain::global_property_id_type )
FC_REFLECT_TYPENAME( graphene::chain::dynamic_global_property_id_type )
FC_REFLECT_TYPENAME( graphene::chain::asset_dynamic_data_id_type )
@ -450,6 +469,7 @@ FC_REFLECT_TYPENAME( graphene::chain::buyback_id_type )
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( graphene::chain::void_t, )

View file

@ -42,6 +42,7 @@ namespace graphene { namespace chain {
account_id_type witness_account;
string url;
public_key_type block_signing_key;
secret_hash_type initial_secret;
account_id_type fee_payer()const { return witness_account; }
void validate()const;
@ -67,6 +68,8 @@ namespace graphene { namespace chain {
optional< string > new_url;
/// The new block signing key.
optional< public_key_type > new_signing_key;
/// The new secreat hash.
optional<secret_hash_type> new_initial_secret;
account_id_type fee_payer()const { return witness_account; }
void validate()const;
@ -77,7 +80,7 @@ namespace graphene { namespace chain {
} } // graphene::chain
FC_REFLECT( graphene::chain::witness_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::witness_create_operation, (fee)(witness_account)(url)(block_signing_key) )
FC_REFLECT( graphene::chain::witness_create_operation, (fee)(witness_account)(url)(block_signing_key)(initial_secret) )
FC_REFLECT( graphene::chain::witness_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::witness_update_operation, (fee)(witness)(witness_account)(new_url)(new_signing_key) )
FC_REFLECT( graphene::chain::witness_update_operation, (fee)(witness)(witness_account)(new_url)(new_signing_key)(new_initial_secret) )

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <string>
#include <fc/crypto/sha256.hpp>
#include <fc/reflect/reflect.hpp>
#include <fc/optional.hpp>
#include <fc/static_variant.hpp>
#include <fc/array.hpp>
namespace graphene { namespace chain {
struct rock_paper_scissors_game_details
{
// note: I wanted to declare these as fixed arrays, but they don't serialize properly
//fc::array<fc::optional<rock_paper_scissors_throw_commit>, 2> commit_moves;
//fc::array<fc::optional<rock_paper_scissors_throw_reveal>, 2> reveal_moves;
std::vector<fc::optional<rock_paper_scissors_throw_commit> > commit_moves;
std::vector<fc::optional<rock_paper_scissors_throw_reveal> > reveal_moves;
rock_paper_scissors_game_details() :
commit_moves(2),
reveal_moves(2)
{
}
};
typedef fc::static_variant<rock_paper_scissors_game_details> game_specific_details;
} }
FC_REFLECT( graphene::chain::rock_paper_scissors_game_details,
(commit_moves)(reveal_moves) )
FC_REFLECT_TYPENAME( graphene::chain::game_specific_details )

View file

@ -0,0 +1,55 @@
#pragma once
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/database.hpp>
namespace graphene { namespace chain {
class tournament_create_evaluator : public evaluator<tournament_create_evaluator>
{
public:
typedef tournament_create_operation operation_type;
void_result do_evaluate( const tournament_create_operation& o );
object_id_type do_apply( const tournament_create_operation& o );
};
class tournament_join_evaluator : public evaluator<tournament_join_evaluator>
{
private:
const tournament_object* _tournament_obj = nullptr;
const tournament_details_object* _tournament_details_obj = nullptr;
const account_object* _payer_account = nullptr;
const asset_object* _buy_in_asset_type = nullptr;
public:
typedef tournament_join_operation operation_type;
void_result do_evaluate( const tournament_join_operation& o );
void_result do_apply( const tournament_join_operation& o );
};
class tournament_leave_evaluator : public evaluator<tournament_leave_evaluator>
{
private:
const tournament_object* _tournament_obj = nullptr;
const tournament_details_object* _tournament_details_obj = nullptr;
public:
typedef tournament_leave_operation operation_type;
void_result do_evaluate( const tournament_leave_operation& o );
void_result do_apply( const tournament_leave_operation& o );
};
class game_move_evaluator : public evaluator<game_move_evaluator>
{
private:
const game_object* _game_obj = nullptr;
public:
typedef game_move_operation operation_type;
void_result do_evaluate( const game_move_operation& o );
void_result do_apply( const game_move_operation& o );
};
} }

View file

@ -0,0 +1,249 @@
#pragma once
#include <graphene/chain/protocol/tournament.hpp>
#include <graphene/chain/rock_paper_scissors.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <graphene/db/flat_index.hpp>
#include <graphene/db/generic_index.hpp>
#include <fc/crypto/hex.hpp>
#include <sstream>
namespace graphene { namespace chain {
class tournament_object;
} }
namespace fc {
void to_variant(const graphene::chain::tournament_object& tournament_obj, fc::variant& v);
void from_variant(const fc::variant& v, graphene::chain::tournament_object& tournament_obj);
} //end namespace fc
namespace graphene { namespace chain {
class database;
using namespace graphene::db;
/// The tournament object has a lot of details, most of which are only of interest to anyone
/// involved in the tournament. The main `tournament_object` contains all of the information
/// needed to display an overview of the tournament, this object contains the rest.
class tournament_details_object : public graphene::db::abstract_object<tournament_details_object>
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = tournament_details_object_type;
/// the tournament object for which this is the details
tournament_id_type tournament_id;
/// List of players registered for this tournament
flat_set<account_id_type> registered_players;
/// List of payers who have contributed to the prize pool
flat_map<account_id_type, share_type> payers;
/// List of player payer pairs needed by torunament leave operation
flat_map<account_id_type, account_id_type> players_payers;
/// List of all matches in this tournament. When the tournament starts, all matches
/// are created. Matches in the first round will have players, matches in later
/// rounds will not be populated.
vector<match_id_type> matches;
};
enum class tournament_state
{
accepting_registrations,
awaiting_start,
in_progress,
registration_period_expired,
concluded
};
class tournament_object : public graphene::db::abstract_object<tournament_object>
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = tournament_object_type;
tournament_object();
tournament_object(const tournament_object& rhs);
~tournament_object();
tournament_object& operator=(const tournament_object& rhs);
tournament_id_type get_id() const { return id; };
/// the account that created this tournament
account_id_type creator;
/// the options set when creating the tournament
tournament_options options;
/// If the tournament has started, the time it started
optional<time_point_sec> start_time;
/// If the tournament has ended, the time it ended
optional<time_point_sec> end_time;
/// Total prize pool accumulated
/// This is the sum of all payers in the details object, and will be
/// registered_players.size() * buy_in_amount
share_type prize_pool;
/// The number of players registered for the tournament
/// (same as the details object's registered_players.size(), here to avoid
/// the GUI having to get the details object)
uint32_t registered_players = 0;
/// The current high-level status of the tournament (whether it is currently running or has been canceled, etc)
//tournament_state state;
/// Detailed information on this tournament
tournament_details_id_type tournament_details_id;
tournament_state get_state() const;
time_point_sec get_registration_deadline() const { return options.registration_deadline; }
// serialization functions:
// for serializing to raw, go through a temporary sstream object to avoid
// having to implement serialization in the header file
template<typename Stream>
friend Stream& operator<<( Stream& s, const tournament_object& tournament_obj );
template<typename Stream>
friend Stream& operator>>( Stream& s, tournament_object& tournament_obj );
friend void ::fc::to_variant(const graphene::chain::tournament_object& tournament_obj, fc::variant& v);
friend void ::fc::from_variant(const fc::variant& v, graphene::chain::tournament_object& tournament_obj);
void pack_impl(std::ostream& stream) const;
void unpack_impl(std::istream& stream);
/// called by database maintenance code when registration for this contest has expired
void on_registration_deadline_passed(database& db);
void on_player_registered(database& db, account_id_type payer_id, account_id_type player_id);
void on_player_unregistered(database& db, account_id_type player_id);
void on_start_time_arrived(database& db);
void on_match_completed(database& db, const match_object& match);
void check_for_new_matches_to_start(database& db) const;
private:
class impl;
std::unique_ptr<impl> my;
};
struct by_registration_deadline {};
struct by_start_time {};
typedef multi_index_container<
tournament_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_non_unique< tag<by_registration_deadline>,
composite_key<tournament_object,
const_mem_fun<tournament_object, tournament_state, &tournament_object::get_state>,
const_mem_fun<tournament_object, time_point_sec, &tournament_object::get_registration_deadline> > >,
ordered_non_unique< tag<by_start_time>,
composite_key<tournament_object,
const_mem_fun<tournament_object, tournament_state, &tournament_object::get_state>,
member<tournament_object, optional<time_point_sec>, &tournament_object::start_time> > >
>
> tournament_object_multi_index_type;
typedef generic_index<tournament_object, tournament_object_multi_index_type> tournament_index;
typedef multi_index_container<
tournament_details_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > > >
> tournament_details_object_multi_index_type;
typedef generic_index<tournament_details_object, tournament_details_object_multi_index_type> tournament_details_index;
template<typename Stream>
inline Stream& operator<<( Stream& s, const tournament_object& tournament_obj )
{
fc_elog(fc::logger::get("tournament"), "In tournament_obj to_raw");
// pack all fields exposed in the header in the usual way
// instead of calling the derived pack, just serialize the one field in the base class
// fc::raw::pack<Stream, const graphene::db::abstract_object<tournament_object> >(s, tournament_obj);
fc::raw::pack(s, tournament_obj.id);
fc::raw::pack(s, tournament_obj.creator);
fc::raw::pack(s, tournament_obj.options);
fc::raw::pack(s, tournament_obj.start_time);
fc::raw::pack(s, tournament_obj.end_time);
fc::raw::pack(s, tournament_obj.prize_pool);
fc::raw::pack(s, tournament_obj.registered_players);
fc::raw::pack(s, tournament_obj.tournament_details_id);
// fc::raw::pack the contents hidden in the impl class
std::ostringstream stream;
tournament_obj.pack_impl(stream);
std::string stringified_stream(stream.str());
fc_elog(fc::logger::get("tournament"), "Serialized state ${state} to bytes ${bytes}",
("state", tournament_obj.get_state())("bytes", fc::to_hex(stringified_stream.c_str(), stringified_stream.size())));
fc::raw::pack(s, stream.str());
return s;
}
template<typename Stream>
inline Stream& operator>>( Stream& s, tournament_object& tournament_obj )
{
fc_elog(fc::logger::get("tournament"), "In tournament_obj from_raw");
// unpack all fields exposed in the header in the usual way
//fc::raw::unpack<Stream, graphene::db::abstract_object<tournament_object> >(s, tournament_obj);
fc::raw::unpack(s, tournament_obj.id);
fc::raw::unpack(s, tournament_obj.creator);
fc::raw::unpack(s, tournament_obj.options);
fc::raw::unpack(s, tournament_obj.start_time);
fc::raw::unpack(s, tournament_obj.end_time);
fc::raw::unpack(s, tournament_obj.prize_pool);
fc::raw::unpack(s, tournament_obj.registered_players);
fc::raw::unpack(s, tournament_obj.tournament_details_id);
// fc::raw::unpack the contents hidden in the impl class
std::string stringified_stream;
fc::raw::unpack(s, stringified_stream);
std::istringstream stream(stringified_stream);
tournament_obj.unpack_impl(stream);
fc_elog(fc::logger::get("tournament"), "Deserialized state ${state} from bytes ${bytes}",
("state", tournament_obj.get_state())("bytes", fc::to_hex(stringified_stream.c_str(), stringified_stream.size())));
return s;
}
/**
* @brief This secondary index will allow a reverse lookup of all tournaments
* a particular account has registered for. This will be attached
* to the tournament details index because the registrations are contained
* in the tournament details object, but it will index the tournament ids
* since that is most useful to the GUI.
*/
class tournament_players_index : public secondary_index
{
public:
virtual void object_inserted( const object& obj ) override;
virtual void object_removed( const object& obj ) override;
virtual void about_to_modify( const object& before ) override;
virtual void object_modified( const object& after ) override;
/** given an account, map it to the set of tournaments in which that account is registered as a player */
map< account_id_type, flat_set<tournament_id_type> > account_to_joined_tournaments;
vector<tournament_id_type> get_registered_tournaments_for_account( const account_id_type& a )const;
protected:
flat_set<account_id_type> before_account_ids;
};
} }
FC_REFLECT_DERIVED(graphene::chain::tournament_details_object, (graphene::db::object),
(tournament_id)
(registered_players)
(payers)
(players_payers)
(matches))
//FC_REFLECT_TYPENAME(graphene::chain::tournament_object) // manually serialized
FC_REFLECT(graphene::chain::tournament_object, (creator))
FC_REFLECT_ENUM(graphene::chain::tournament_state,
(accepting_registrations)
(awaiting_start)
(in_progress)
(registration_period_expired)
(concluded))

View file

@ -181,7 +181,8 @@ namespace graphene { namespace chain {
ordered_non_unique< tag<by_account>,
member<vesting_balance_object, account_id_type, &vesting_balance_object::owner>
>,
ordered_unique< tag<by_asset_balance>,
//ordered_unique< tag<by_asset_balance>,
ordered_non_unique< tag<by_asset_balance>,
composite_key<
vesting_balance_object,
member_offset<vesting_balance_object, asset_id_type, (size_t) (offset_s(vesting_balance_object,balance) + offset_s(asset,asset_id))>,

View file

@ -40,6 +40,8 @@ namespace graphene { namespace chain {
account_id_type witness_account;
uint64_t last_aslot = 0;
public_key_type signing_key;
secret_hash_type next_secret_hash;
secret_hash_type previous_secret;
optional< vesting_balance_id_type > pay_vb;
vote_id_type vote_id;
uint64_t total_votes = 0;
@ -74,6 +76,8 @@ FC_REFLECT_DERIVED( graphene::chain::witness_object, (graphene::db::object),
(witness_account)
(last_aslot)
(signing_key)
(next_secret_hash)
(previous_secret)
(pay_vb)
(vote_id)
(total_votes)

View file

@ -25,11 +25,34 @@
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
#include <graphene/chain/witness_scheduler.hpp>
#include <graphene/chain/witness_scheduler_rng.hpp>
namespace graphene { namespace chain {
class witness_schedule_object;
typedef hash_ctr_rng<
/* HashClass = */ fc::sha256,
/* SeedLength = */ GRAPHENE_RNG_SEED_LENGTH
> witness_scheduler_rng;
typedef generic_witness_scheduler<
/* WitnessID = */ witness_id_type,
/* RNG = */ witness_scheduler_rng,
/* CountType = */ decltype( chain_parameters::maximum_witness_count ),
/* OffsetType = */ uint32_t,
/* debug = */ true
> witness_scheduler;
typedef generic_far_future_witness_scheduler<
/* WitnessID = */ witness_id_type,
/* RNG = */ witness_scheduler_rng,
/* CountType = */ decltype( chain_parameters::maximum_witness_count ),
/* OffsetType = */ uint32_t,
/* debug = */ true
> far_future_witness_scheduler;
class witness_schedule_object : public graphene::db::abstract_object<witness_schedule_object>
{
public:
@ -37,12 +60,39 @@ class witness_schedule_object : public graphene::db::abstract_object<witness_sch
static const uint8_t type_id = impl_witness_schedule_object_type;
vector< witness_id_type > current_shuffled_witnesses;
witness_scheduler scheduler;
uint32_t last_scheduling_block;
uint64_t slots_since_genesis = 0;
fc::array< char, sizeof(secret_hash_type) > rng_seed;
/**
* Not necessary for consensus, but used for figuring out the participation rate.
* The nth bit is 0 if the nth slot was unfilled, else it is 1.
*/
fc::uint128 recent_slots_filled;
};
} }
FC_REFLECT( graphene::chain::witness_scheduler,
(_turns)
(_tokens)
(_min_token_count)
(_ineligible_waiting_for_token)
(_ineligible_no_turn)
(_eligible)
(_schedule)
(_lame_duck)
)
FC_REFLECT_DERIVED(
graphene::chain::witness_schedule_object,
(graphene::db::object),
(scheduler)
(last_scheduling_block)
(slots_since_genesis)
(rng_seed)
(recent_slots_filled)
(current_shuffled_witnesses)
)

View file

@ -0,0 +1,440 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <algorithm>
#include <deque>
#include <memory>
#include <vector>
#include <boost/container/flat_set.hpp>
// @ROL helpfull dumps when debugging
//#define ROL_DUMP
namespace graphene { namespace chain {
//using boost::container::flat_set;
enum witness_scheduler_relax_flags
{
emit_turn = 0x01,
emit_token = 0x02
};
template< typename WitnessID, typename RNG, typename CountType, typename OffsetType, bool debug = true >
class generic_witness_scheduler
{
public:
void check_invariant() const
{
#ifndef NDEBUG
CountType tokens = _ineligible_no_turn.size() + _eligible.size();
CountType turns = _eligible.size();
for( const std::pair< WitnessID, bool >& item : _ineligible_waiting_for_token )
turns += (item.second ? 1 : 0 );
assert( _tokens == tokens );
assert( _turns == turns );
#endif
set< WitnessID > witness_set;
// make sure each witness_id occurs only once among the three states
auto process_id = [&]( WitnessID item )
{
assert( witness_set.find( item ) == witness_set.end() );
witness_set.insert( item );
} ;
for( const std::pair< WitnessID, bool >& item : _ineligible_waiting_for_token )
process_id( item.first );
for( const WitnessID& item : _ineligible_no_turn )
process_id( item );
for( const WitnessID& item : _eligible )
process_id( item );
return;
}
/**
* Deterministically evolve over time
*/
uint32_t relax()
{
uint32_t relax_flags = 0;
if( debug ) check_invariant();
assert( _min_token_count > 0 );
// turn distribution
if( _turns == 0 )
{
relax_flags |= emit_turn;
for( const WitnessID& item : _ineligible_no_turn )
_eligible.push_back( item );
_turns += _ineligible_no_turn.size();
_ineligible_no_turn.clear();
if( debug ) check_invariant();
for( std::pair< WitnessID, bool >& item : _ineligible_waiting_for_token )
{
assert( item.second == false );
item.second = true;
}
_turns += _ineligible_waiting_for_token.size();
if( debug ) check_invariant();
}
// token distribution
while( true )
{
if( _ineligible_waiting_for_token.empty() )
{
// eligible must be non-empty
assert( !_eligible.empty() );
return relax_flags;
}
if( _tokens >= _min_token_count )
{
if( !_eligible.empty() )
return relax_flags;
}
const std::pair< WitnessID, bool >& item = _ineligible_waiting_for_token.front();
if( item.second )
_eligible.push_back( item.first );
else
_ineligible_no_turn.push_back( item.first );
_ineligible_waiting_for_token.pop_front();
relax_flags |= emit_token;
_tokens++;
if( debug ) check_invariant();
}
return relax_flags;
}
/**
* Add another element to _schedule
*/
uint32_t produce_schedule( RNG& rng )
{
uint32_t relax_flags = relax();
if( debug ) check_invariant();
if( _eligible.empty() )
return relax_flags;
decltype( rng( _eligible.size() ) ) pos = rng( _eligible.size() );
assert( (pos >= 0) && (pos < _eligible.size()) );
auto it = _eligible.begin() + pos;
_schedule.push_back( *it );
_ineligible_waiting_for_token.emplace_back( *it, false );
_eligible.erase( it );
_turns--;
_tokens--;
if( debug ) check_invariant();
return relax_flags;
}
/**
* Pull an element from _schedule
*/
WitnessID consume_schedule()
{
assert( _schedule.size() > 0 );
WitnessID result = _schedule.front();
_schedule.pop_front();
auto it = _lame_duck.find( result );
if( it != _lame_duck.end() )
_lame_duck.erase( it );
if( debug ) check_invariant();
return result;
}
/**
* Remove all witnesses in the removal_set from
* future scheduling (but not from the current schedule).
*/
template< typename T >
void remove_all( const T& removal_set )
{
if( debug ) check_invariant();
_ineligible_waiting_for_token.erase(
std::remove_if(
_ineligible_waiting_for_token.begin(),
_ineligible_waiting_for_token.end(),
[&]( const std::pair< WitnessID, bool >& item ) -> bool
{
bool found = removal_set.find( item.first ) != removal_set.end();
_turns -= (found & item.second) ? 1 : 0;
return found;
} ),
_ineligible_waiting_for_token.end() );
if( debug ) check_invariant();
_ineligible_no_turn.erase(
std::remove_if(
_ineligible_no_turn.begin(),
_ineligible_no_turn.end(),
[&]( WitnessID item ) -> bool
{
bool found = (removal_set.find( item ) != removal_set.end());
_tokens -= (found ? 1 : 0);
return found;
} ),
_ineligible_no_turn.end() );
if( debug ) check_invariant();
_eligible.erase(
std::remove_if(
_eligible.begin(),
_eligible.end(),
[&]( WitnessID item ) -> bool
{
bool found = (removal_set.find( item ) != removal_set.end());
_tokens -= (found ? 1 : 0);
_turns -= (found ? 1 : 0);
return found;
} ),
_eligible.end() );
if( debug ) check_invariant();
return;
}
/**
* Convenience function to call insert_all() and remove_all()
* as needed to update to the given revised_set.
*/
template< typename T >
void insert_all( const T& insertion_set )
{
if( debug ) check_invariant();
for( const WitnessID wid : insertion_set )
{
_eligible.push_back( wid );
}
_turns += insertion_set.size();
_tokens += insertion_set.size();
if( debug ) check_invariant();
return;
}
/**
* Convenience function to call insert_all() and remove_all()
* as needed to update to the given revised_set.
*
* This function calls find() on revised_set for all current
* witnesses. Running time is O(n*log(n)) if the revised_set
* implementation of find() is O(log(n)).
*
* TODO: Rewriting to use std::set_difference may marginally
* increase efficiency, but a benchmark is needed to justify this.
*/
template< typename T >
void update( const T& revised_set )
{
#ifdef ROL_DUMP
wdump((revised_set));
#endif
set< WitnessID > current_set;
set< WitnessID > schedule_set;
/* current_set.reserve(
_ineligible_waiting_for_token.size()
+ _ineligible_no_turn.size()
+ _eligible.size()
+ _schedule.size() );
*/
for( const auto& item : _ineligible_waiting_for_token )
current_set.insert( item.first );
for( const WitnessID& item : _ineligible_no_turn )
current_set.insert( item );
for( const WitnessID& item : _eligible )
current_set.insert( item );
for( const WitnessID& item : _schedule )
{
current_set.insert( item );
schedule_set.insert( item );
}
set< WitnessID > insertion_set;
//insertion_set.reserve( revised_set.size() );
for( const WitnessID& item : revised_set )
{
if( current_set.find( item ) == current_set.end() )
insertion_set.insert( item );
}
set< WitnessID > removal_set;
//removal_set.reserve( current_set.size() );
for( const WitnessID& item : current_set )
{
if( revised_set.find( item ) == revised_set.end() )
{
if( schedule_set.find( item ) == schedule_set.end() )
removal_set.insert( item );
else
_lame_duck.insert( item );
}
}
insert_all( insertion_set );
remove_all( removal_set );
#ifdef ROL_DUMP
wdump((_eligible));
wdump((_schedule));
#endif
return;
}
/**
* Get the number of scheduled witnesses
*/
size_t size( )const
{
return _schedule.size();
}
bool get_slot( OffsetType offset, WitnessID& wit )const
{
#ifdef ROL_DUMP
wdump((_schedule)(_schedule.size())(offset));
#endif
if (_schedule.empty())
return false; //@ROL wit is 1.6.0!
if( offset >= _schedule.size() )
{
wit = _schedule[ 0 ];
return false;
}
wit = _schedule[ offset ];
return true;
}
/**
* Reset the schedule, then re-schedule the given witness as the
* first witness.
*/
void reset_schedule( WitnessID first_witness )
{
#ifdef ROL_DUMP
wdump((first_witness));
#endif
_schedule.clear();
for( const WitnessID& wid : _ineligible_no_turn )
{
_eligible.push_back( wid );
}
_turns += _ineligible_no_turn.size();
_ineligible_no_turn.clear();
for( const auto& item : _ineligible_waiting_for_token )
{
_eligible.push_back( item.first );
_turns += (item.second ? 0 : 1);
}
_tokens += _ineligible_waiting_for_token.size();
_ineligible_waiting_for_token.clear();
if( debug ) check_invariant();
#ifdef ROL_DUMP
wdump((_eligible));
#endif
auto it = std::find( _eligible.begin(), _eligible.end(), first_witness );
//@ROL: maybe good idea, when wit is 1.6.0 if ( it == _eligible.end() ) return;
assert( it != _eligible.end() );
_schedule.push_back( *it );
_ineligible_waiting_for_token.emplace_back( *it, false );
_eligible.erase( it );
_turns--;
_tokens--;
if( debug ) check_invariant();
return;
}
// keep track of total turns / tokens in existence
CountType _turns = 0;
CountType _tokens = 0;
// new tokens handed out when _tokens < _min_token_count
CountType _min_token_count;
// WitnessID appears in exactly one of the following:
// has no token; second indicates whether we have a turn or not:
std::deque < std::pair< WitnessID, bool > > _ineligible_waiting_for_token; // ".." | "T."
// has token, but no turn
std::vector< WitnessID > _ineligible_no_turn; // ".t"
// has token and turn
std::vector< WitnessID > _eligible; // "Tt"
// scheduled
std::deque < WitnessID > _schedule;
// in _schedule, but not to be replaced
set< WitnessID > _lame_duck;
};
template< typename WitnessID, typename RNG, typename CountType, typename OffsetType, bool debug = true >
class generic_far_future_witness_scheduler
{
public:
generic_far_future_witness_scheduler(
const generic_witness_scheduler< WitnessID, RNG, CountType, OffsetType, debug >& base_scheduler,
RNG rng
)
{
generic_witness_scheduler< WitnessID, RNG, CountType, OffsetType, debug > extended_scheduler = base_scheduler;
_begin_offset = base_scheduler.size()+1;
while( (extended_scheduler.produce_schedule( rng ) & emit_turn) == 0 )
_begin_offset++;
assert( _begin_offset == extended_scheduler.size() );
_end_offset = _begin_offset;
while( (extended_scheduler.produce_schedule( rng ) & emit_turn) == 0 )
_end_offset++;
assert( _end_offset == extended_scheduler.size()-1 );
_schedule.resize( extended_scheduler._schedule.size() );
std::copy( extended_scheduler._schedule.begin(),
extended_scheduler._schedule.end(),
_schedule.begin() );
return;
}
bool get_slot( OffsetType offset, WitnessID& wit )const
{
if( offset <= _end_offset )
wit = _schedule[ offset ];
else
wit = _schedule[ _begin_offset +
(
(offset - _begin_offset) %
(_end_offset + 1 - _begin_offset)
) ];
return true;
}
std::vector< WitnessID > _schedule;
OffsetType _begin_offset;
OffsetType _end_offset;
};
} }

View file

@ -0,0 +1,124 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <boost/multiprecision/integer.hpp>
namespace graphene { namespace chain {
/**
* Always returns 0. Useful for testing.
*/
class nullary_rng
{
public:
nullary_rng() {}
virtual ~nullary_rng() {}
template< typename T > T operator()( T max )
{ return T(0); }
} ;
/**
* The sha256_ctr_rng generates bits using SHA256 in counter (CTR)
* mode.
*/
template< class HashClass, int SeedLength=sizeof(secret_hash_type) >
class hash_ctr_rng
{
public:
hash_ctr_rng( const char* seed, uint64_t counter = 0 )
: _counter( counter ), _current_offset( 0 )
{
memcpy( _seed, seed, SeedLength );
_reset_current_value();
return;
}
virtual ~hash_ctr_rng() {}
uint64_t get_bits( uint8_t count )
{
uint64_t result = 0;
uint64_t mask = 1;
// grab the requested number of bits
while( count > 0 )
{
result |=
(
(
(
_current_value.data()[ (_current_offset >> 3) & 0x1F ]
& ( 1 << (_current_offset & 0x07) )
)
!= 0
) ? mask : 0
);
mask += mask;
--count;
++_current_offset;
if( _current_offset == (_current_value.data_size() << 3) )
{
_counter++;
_current_offset = 0;
_reset_current_value();
}
}
return result;
}
uint64_t operator()( uint64_t bound )
{
if( bound <= 1 )
return 0;
uint8_t bitcount = boost::multiprecision::detail::find_msb( bound ) + 1;
// probability of loop exiting is >= 1/2, so probability of
// running N times is bounded above by (1/2)^N
while( true )
{
uint64_t result = get_bits( bitcount );
if( result < bound )
return result;
}
}
// convenience method which does casting for types other than uint64_t
template< typename T > T operator()( T bound )
{ return (T) ( (*this)(uint64_t( bound )) ); }
void _reset_current_value()
{
// internal implementation detail, called to update
// _current_value when _counter changes
typename HashClass::encoder enc;
enc.write( _seed , SeedLength );
enc.write( (char *) &_counter, 8 );
_current_value = enc.result();
return;
}
uint64_t _counter;
char _seed[ SeedLength ];
HashClass _current_value;
uint16_t _current_offset;
static const int seed_length = SeedLength;
} ;
} }

View file

@ -53,6 +53,11 @@ void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_o
if( _sell_asset->options.blacklist_markets.size() )
FC_ASSERT( _sell_asset->options.blacklist_markets.find(_receive_asset->id) == _sell_asset->options.blacklist_markets.end() );
// $$$ I. DEX Task The Peerplays DEX should only allow UIA and sidechain assets to be paired (traded) with the core token (PPY)
FC_ASSERT(_receive_asset->id == asset_id_type() || _sell_asset->id == asset_id_type(),
"No asset in the trade is CORE.");
FC_ASSERT( is_authorized_asset( d, *_seller, *_sell_asset ) );
FC_ASSERT( is_authorized_asset( d, *_seller, *_receive_asset ) );

View file

@ -0,0 +1,406 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/database.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/msm/back/tools.hpp>
#include <boost/range/algorithm/copy.hpp>
namespace graphene { namespace chain {
namespace msm = boost::msm;
namespace mpl = boost::mpl;
namespace
{
// Events
struct initiate_match
{
database& db;
initiate_match(database& db) :
db(db)
{}
};
struct game_complete
{
database& db;
const game_object& game;
game_complete(database& db, const game_object& game) : db(db), game(game) {};
};
struct match_state_machine_ : public msm::front::state_machine_def<match_state_machine_>
{
// disable a few state machine features we don't use for performance
typedef int no_exception_thrown;
typedef int no_message_queue;
// States
struct waiting_on_previous_matches : public msm::front::state<>{};
struct match_in_progress : public msm::front::state<>
{
void on_entry(const game_complete& event, match_state_machine_& fsm)
{
fc_ilog(fc::logger::get("tournament"),
"Game ${game_id} in match ${id} is complete",
("game_id", event.game.id)("id", fsm.match_obj->id));
}
void on_entry(const initiate_match& event, match_state_machine_& fsm)
{
match_object& match = *fsm.match_obj;
fc_ilog(fc::logger::get("tournament"),
"Match ${id} is now in progress",
("id", match.id));
match.number_of_wins.resize(match.players.size());
match.start_time = event.db.head_block_time();
fsm.start_next_game(event.db);
}
};
struct match_complete : public msm::front::state<>
{
void on_entry(const game_complete& event, match_state_machine_& fsm)
{
match_object& match = *fsm.match_obj;
//wdump((match));
fc_ilog(fc::logger::get("tournament"),
"Match ${id} is complete",
("id", match.id));
optional<account_id_type> last_game_winner;
std::map<account_id_type, unsigned> scores_by_player;
for (const flat_set<account_id_type>& game_winners : match.game_winners)
for (const account_id_type& account_id : game_winners)
{
++scores_by_player[account_id];
last_game_winner = account_id;
}
bool all_scores_same = true;
optional<account_id_type> high_scoring_account;
unsigned high_score = 0;
for (const auto& value : scores_by_player)
if (value.second > high_score)
{
if (high_scoring_account)
all_scores_same = false;
high_score = value.second;
high_scoring_account = value.first;
}
if (high_scoring_account)
{
if (all_scores_same && last_game_winner)
match.match_winners.insert(*last_game_winner);
else
match.match_winners.insert(*high_scoring_account);
}
else
{
// III. Rock Paper Scissors Game Need to review how Ties are dealt with.
short i = match.number_of_ties == match.games.size() ? 0 : event.db.get_random_bits(match.players.size()) ;
match.match_winners.insert(match.players[i]);
++match.number_of_wins[i];
if (match.number_of_ties == match.games.size())
match.game_winners[match.game_winners.size()-1].insert(match.players[i]);
}
match.end_time = event.db.head_block_time();
const tournament_object& tournament_obj = match.tournament_id(event.db);
event.db.modify(tournament_obj, [&](tournament_object& tournament) {
tournament.on_match_completed(event.db, match);
});
}
void on_entry(const initiate_match& event, match_state_machine_& fsm)
{
match_object& match = *fsm.match_obj;
fc_ilog(fc::logger::get("tournament"),
"Match ${id} is complete, it was a buy",
("id", match));
match.number_of_wins.resize(match.players.size());
boost::copy(match.players, std::inserter(match.match_winners, match.match_winners.end()));
match.start_time = event.db.head_block_time();
match.end_time = event.db.head_block_time();
// NOTE: when the match is a buy, we don't send a match completed event to
// the tournament_obj, because it is already in the middle of handling
// an event; it will figure out that the match has completed on its own.
}
};
typedef waiting_on_previous_matches initial_state;
typedef match_state_machine_ x; // makes transition table cleaner
bool was_final_game(const game_complete& event)
{
const tournament_object& tournament_obj = match_obj->tournament_id(event.db);
if (match_obj->games.size() >= tournament_obj.options.number_of_wins * 4)
{
wdump((match_obj->games.size()));
return true;
}
for (unsigned i = 0; i < match_obj->players.size(); ++i)
{
// this guard is called before the winner of the current game factored in to our running totals,
// so we must add the current game to our count
uint32_t win_for_this_game = event.game.winners.find(match_obj->players[i]) != event.game.winners.end() ? 1 : 0;
if (match_obj->number_of_wins[i] + win_for_this_game >= tournament_obj.options.number_of_wins)
return true;
}
return false;
}
bool match_is_a_buy(const initiate_match& event)
{
return match_obj->players.size() < 2;
}
void record_completed_game(const game_complete& event)
{
if (event.game.winners.empty())
++match_obj->number_of_ties;
else
for (unsigned i = 0; i < match_obj->players.size(); ++i)
if (event.game.winners.find(match_obj->players[i]) != event.game.winners.end())
++match_obj->number_of_wins[i];
match_obj->game_winners.emplace_back(event.game.winners);
}
void start_next_game(database& db)
{
fc_ilog(fc::logger::get("tournament"),
"In start_next_game");
const game_object& game =
db.create<game_object>( [&]( game_object& game ) {
game.match_id = match_obj->id;
game.players = match_obj->players;
game.game_details = rock_paper_scissors_game_details();
game.start_game(db, game.players);
});
match_obj->games.push_back(game.id);
}
void record_and_start_next_game(const game_complete& event)
{
record_completed_game(event);
start_next_game(event.db);
}
// Transition table for tournament
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+
_row < waiting_on_previous_matches, initiate_match, match_in_progress >,
g_row < waiting_on_previous_matches, initiate_match, match_complete, &x::match_is_a_buy >,
// +-------------------------------+-------------------------+----------------------------+---------------------+----------------------+
a_row < match_in_progress, game_complete, match_in_progress, &x::record_and_start_next_game >,
row < match_in_progress, game_complete, match_complete, &x::record_completed_game, &x::was_final_game >
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
> {};
match_object* match_obj;
match_state_machine_(match_object* match_obj) : match_obj(match_obj) {}
};
typedef msm::back::state_machine<match_state_machine_> match_state_machine;
}
class match_object::impl {
public:
match_state_machine state_machine;
impl(match_object* self) : state_machine(self) {}
};
match_object::match_object() :
number_of_ties(0),
my(new impl(this))
{
}
match_object::match_object(const match_object& rhs) :
graphene::db::abstract_object<match_object>(rhs),
tournament_id(rhs.tournament_id),
players(rhs.players),
games(rhs.games),
game_winners(rhs.game_winners),
number_of_wins(rhs.number_of_wins),
number_of_ties(rhs.number_of_ties),
match_winners(rhs.match_winners),
start_time(rhs.start_time),
end_time(rhs.end_time),
my(new impl(this))
{
my->state_machine = rhs.my->state_machine;
my->state_machine.match_obj = this;
}
match_object& match_object::operator=(const match_object& rhs)
{
//graphene::db::abstract_object<match_object>::operator=(rhs);
id = rhs.id;
tournament_id = rhs.tournament_id;
players = rhs.players;
games = rhs.games;
game_winners = rhs.game_winners;
number_of_wins = rhs.number_of_wins;
number_of_ties = rhs.number_of_ties;
match_winners = rhs.match_winners;
start_time = rhs.start_time;
end_time = rhs.end_time;
my->state_machine = rhs.my->state_machine;
my->state_machine.match_obj = this;
return *this;
}
match_object::~match_object()
{
}
bool verify_match_state_constants()
{
unsigned error_count = 0;
typedef msm::back::generate_state_set<match_state_machine::stt>::type all_states;
static char const* filled_state_names[mpl::size<all_states>::value];
mpl::for_each<all_states,boost::msm::wrap<mpl::placeholders::_1> >
(msm::back::fill_state_names<match_state_machine::stt>(filled_state_names));
for (unsigned i = 0; i < mpl::size<all_states>::value; ++i)
{
try
{
// this is an approximate test, the state name provided by typeinfo will be mangled, but should
// at least contain the string we're looking for
const char* fc_reflected_value_name = fc::reflector<match_state>::to_string((match_state)i);
if (!strcmp(fc_reflected_value_name, filled_state_names[i]))
fc_elog(fc::logger::get("match"),
"Error, state string mismatch between fc and boost::msm for int value ${int_value}: boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}",
("int_value", i)("boost_string", filled_state_names[i])("fc_string", fc_reflected_value_name));
}
catch (const fc::bad_cast_exception&)
{
fc_elog(fc::logger::get("match"),
"Error, no reflection for value ${int_value} in enum match_state",
("int_value", i));
++error_count;
}
}
return error_count == 0;
}
match_state match_object::get_state() const
{
static bool state_constants_are_correct = verify_match_state_constants();
(void)&state_constants_are_correct;
match_state state = (match_state)my->state_machine.current_state()[0];
return state;
}
void match_object::pack_impl(std::ostream& stream) const
{
boost::archive::binary_oarchive oa(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
oa << my->state_machine;
}
void match_object::unpack_impl(std::istream& stream)
{
boost::archive::binary_iarchive ia(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
ia >> my->state_machine;
}
void match_object::on_initiate_match(database& db)
{
my->state_machine.process_event(initiate_match(db));
}
void match_object::on_game_complete(database& db, const game_object& game)
{
my->state_machine.process_event(game_complete(db, game));
}
#if 0
game_id_type match_object::start_next_game(database& db, match_id_type match_id)
{
const game_object& game =
db.create<game_object>( [&]( game_object& game ) {
game.match_id = match_id;
game.players = players;
});
return game.id;
}
#endif
} } // graphene::chain
namespace fc {
// Manually reflect match_object to variant to properly reflect "state"
void to_variant(const graphene::chain::match_object& match_obj, fc::variant& v)
{ try {
fc_elog(fc::logger::get("tournament"), "In match_obj to_variant");
elog("In match_obj to_variant");
fc::mutable_variant_object o;
o("id", match_obj.id)
("tournament_id", match_obj.tournament_id)
("players", match_obj.players)
("games", match_obj.games)
("game_winners", match_obj.game_winners)
("number_of_wins", match_obj.number_of_wins)
("number_of_ties", match_obj.number_of_ties)
("match_winners", match_obj.match_winners)
("start_time", match_obj.start_time)
("end_time", match_obj.end_time)
("state", match_obj.get_state());
v = o;
} FC_RETHROW_EXCEPTIONS(warn, "") }
// Manually reflect match_object to variant to properly reflect "state"
void from_variant(const fc::variant& v, graphene::chain::match_object& match_obj)
{ try {
fc_elog(fc::logger::get("tournament"), "In match_obj from_variant");
match_obj.id = v["id"].as<graphene::chain::match_id_type>();
match_obj.tournament_id = v["tournament_id"].as<graphene::chain::tournament_id_type>();
match_obj.players = v["players"].as<std::vector<graphene::chain::account_id_type> >();
match_obj.games = v["games"].as<std::vector<graphene::chain::game_id_type> >();
match_obj.game_winners = v["game_winners"].as<std::vector<flat_set<graphene::chain::account_id_type> > >();
match_obj.number_of_wins = v["number_of_wins"].as<std::vector<uint32_t> >();
match_obj.number_of_ties = v["number_of_ties"].as<uint32_t>();
match_obj.match_winners = v["match_winners"].as<flat_set<graphene::chain::account_id_type> >();
match_obj.start_time = v["start_time"].as<time_point_sec>();
match_obj.end_time = v["end_time"].as<optional<time_point_sec> >();
graphene::chain::match_state state = v["state"].as<graphene::chain::match_state>();
const_cast<int*>(match_obj.my->state_machine.current_state())[0] = (int)state;
} FC_RETHROW_EXCEPTIONS(warn, "") }
} //end namespace fc

View file

@ -194,6 +194,11 @@ namespace graphene { namespace chain {
FC_ASSERT( max_bet_multiplier >= GRAPHENE_BETTING_MIN_MULTIPLIER &&
max_bet_multiplier <= GRAPHENE_BETTING_MAX_MULTIPLIER );
FC_ASSERT( min_bet_multiplier < max_bet_multiplier );
FC_ASSERT( rake_fee_percentage >= TOURNAMENT_MINIMAL_RAKE_FEE_PERCENTAGE,
"Rake fee percentage must not be less than ${min}", ("min",TOURNAMENT_MINIMAL_RAKE_FEE_PERCENTAGE));
FC_ASSERT( rake_fee_percentage <= TOURNAMENT_MAXIMAL_RAKE_FEE_PERCENTAGE,
"Rake fee percentage must not be greater than ${max}", ("max", TOURNAMENT_MAXIMAL_RAKE_FEE_PERCENTAGE));
}
} } // graphene::chain

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/protocol/tournament.hpp>
namespace graphene { namespace chain {
void tournament_options::validate() const
{
//FC_ASSERT( number_of_players >= 2 && (number_of_players & (number_of_players - 1)) == 0,
// "Number of players must be a power of two" );
}
share_type tournament_create_operation::calculate_fee(const fee_parameters_type& k)const
{
return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte );
}
void tournament_create_operation::validate()const
{
FC_ASSERT( fee.amount >= 0 );
options.validate();
}
share_type tournament_join_operation::calculate_fee(const fee_parameters_type& k)const
{
return k.fee;
}
void tournament_join_operation::validate()const
{
FC_ASSERT( fee.amount >= 0 );
}
share_type tournament_leave_operation::calculate_fee(const fee_parameters_type& k)const
{
return k.fee;
}
void tournament_leave_operation::validate()const
{
// todo FC_ASSERT( fee.amount >= 0 );
}
share_type game_move_operation::calculate_fee(const fee_parameters_type& k)const
{
return k.fee;
}
void game_move_operation::validate()const
{
}
} } // namespace graphene::chain

View file

@ -0,0 +1,238 @@
#include <graphene/chain/protocol/tournament.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/tournament_evaluator.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/is_authorized_asset.hpp>
namespace graphene { namespace chain {
void_result tournament_create_evaluator::do_evaluate( const tournament_create_operation& op )
{ try {
database& d = db();
FC_ASSERT(op.options.registration_deadline >= d.head_block_time(), "Registration deadline has already passed");
const fc::time_point_sec maximum_registration_deadline = d.head_block_time() + d.get_global_properties().parameters.maximum_registration_deadline;
FC_ASSERT(op.options.registration_deadline <= maximum_registration_deadline,
"Registration deadline must be before ${maximum_registration_deadline}",
("maximum_registration_deadline", maximum_registration_deadline));
FC_ASSERT(op.options.buy_in.amount >= 0, "Tournament buy-in may not be negative");
FC_ASSERT(op.options.number_of_players > 1, "If you're going to play with yourself, do it off-chain");
const uint32_t maximum_players_in_tournament = d.get_global_properties().parameters.maximum_players_in_tournament;
FC_ASSERT(op.options.number_of_players <= maximum_players_in_tournament,
"Tournaments may not have more than ${maximum_players_in_tournament} players",
("maximum_players_in_tournament", maximum_players_in_tournament));
FC_ASSERT(op.options.whitelist.empty() ||
op.options.whitelist.size() >= op.options.number_of_players,
"Whitelist must allow enough players to fill the tournament");
const uint32_t maximum_tournament_whitelist_length = d.get_global_properties().parameters.maximum_tournament_whitelist_length;
FC_ASSERT(op.options.whitelist.size() < maximum_tournament_whitelist_length,
"Whitelist must not be longer than ${maximum_tournament_whitelist_length}",
("maximum_tournament_whitelist_length", maximum_tournament_whitelist_length));
for (const account_id_type& account_id : op.options.whitelist)
{
account_id(d);
}
if (op.options.start_time)
{
FC_ASSERT(!op.options.start_delay, "Cannot specify both a fixed start time and a delay");
FC_ASSERT(*op.options.start_time >= op.options.registration_deadline,
"Cannot start before registration deadline expires");
const uint32_t maximum_start_time_in_future = d.get_global_properties().parameters.maximum_tournament_start_time_in_future;
FC_ASSERT((*op.options.start_time - d.head_block_time()).to_seconds() <= maximum_start_time_in_future,
"Start time is too far in the future");
}
else if (op.options.start_delay)
{
FC_ASSERT(!op.options.start_time, "Cannot specify both a fixed start time and a delay");
const uint32_t maximum_start_delay = d.get_global_properties().parameters.maximum_tournament_start_delay;
FC_ASSERT(*op.options.start_delay < maximum_start_delay,
"Start delay is too long");
}
else
FC_THROW("Must specify either a fixed start time or a delay");
const uint32_t maximum_tournament_number_of_wins = d.get_global_properties().parameters.maximum_tournament_number_of_wins;
FC_ASSERT(op.options.number_of_wins > 0,
"Matches require positive number of wins");
FC_ASSERT(op.options.number_of_wins <= maximum_tournament_number_of_wins,
"Matches may not require more than ${number_of_wins} wins",
("number_of_wins", maximum_tournament_number_of_wins));
// round_delay constraints
const uint32_t minimum_round_delay = d.get_global_properties().parameters.min_round_delay;
FC_ASSERT(op.options.round_delay >= minimum_round_delay,
"Delay between games must not be less then ${min}",
("min", minimum_round_delay));
const uint32_t maximum_round_delay = d.get_global_properties().parameters.max_round_delay;
FC_ASSERT(op.options.round_delay <= maximum_round_delay,
"Delay between games must not be greater then ${max}",
("max", maximum_round_delay));
const rock_paper_scissors_game_options& game_options = op.options.game_options.get<rock_paper_scissors_game_options>();
// time_per_commit_move constraints
const uint32_t minimum_time_per_commit_move = d.get_global_properties().parameters.min_time_per_commit_move;
FC_ASSERT(game_options.time_per_commit_move >= minimum_time_per_commit_move,
"Time to commit the next move must not be less than ${min}",
("min", minimum_time_per_commit_move));
const uint32_t maximum_time_per_commit_move = d.get_global_properties().parameters.max_time_per_commit_move;
FC_ASSERT(game_options.time_per_commit_move <= maximum_time_per_commit_move,
"Time to commit the next move must not be greater than ${max}",
("max", maximum_time_per_commit_move));
// time_per_commit_reveal constraints
const uint32_t minimum_time_per_reveal_move = d.get_global_properties().parameters.min_time_per_reveal_move;
FC_ASSERT(game_options.time_per_reveal_move >= minimum_time_per_reveal_move,
"Time to reveal the move must not be less than ${min}",
("min", minimum_time_per_reveal_move));
const uint32_t maximum_time_per_reveal_move = d.get_global_properties().parameters.max_time_per_reveal_move;
FC_ASSERT(game_options.time_per_reveal_move <= maximum_time_per_reveal_move,
"Time to reveal the move must not be greater than ${max}",
("max", maximum_time_per_reveal_move));
//cli-wallet supports 5 gesture games as well, but limit to 3 now as GUI wallet only supports 3 gesture games currently
FC_ASSERT(game_options.number_of_gestures == 3,
"GUI Wallet only supports 3 gestures currently");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type tournament_create_evaluator::do_apply( const tournament_create_operation& op )
{ try {
const tournament_details_object& tournament_details =
db().create<tournament_details_object>( [&]( tournament_details_object& a ) {
});
const tournament_object& new_tournament =
db().create<tournament_object>( [&]( tournament_object& t ) {
t.options = op.options;
t.creator = op.creator;
t.tournament_details_id = tournament_details.id;
});
// TODO: look up how to do this in the initial create
db().modify(tournament_details, [&]( tournament_details_object& a ) {
a.tournament_id = new_tournament.id;
});
fc_ilog(fc::logger::get("tournament"),
"Created tournament ${id} with details id ${details_id}",
("id", new_tournament.id)("details_id", tournament_details.id));
return new_tournament.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result tournament_join_evaluator::do_evaluate( const tournament_join_operation& op )
{ try {
const database& d = db();
_tournament_obj = &op.tournament_id(d);
fc_ilog(fc::logger::get("tournament"), "details_id = ${id}",("id", _tournament_obj->tournament_details_id));
_tournament_details_obj = &_tournament_obj->tournament_details_id(d);
_payer_account = &op.payer_account_id(d);
const account_object& player_account = op.player_account_id(d);
//const account_object& player_account = op.player_account_id(d);
_buy_in_asset_type = &op.buy_in.asset_id(d);
FC_ASSERT(_tournament_obj->get_state() == tournament_state::accepting_registrations,
"Can only join a tournament during registration period");
FC_ASSERT(_tournament_details_obj->registered_players.size() < _tournament_obj->options.number_of_players,
"Tournament is already full");
FC_ASSERT(d.head_block_time() <= _tournament_obj->options.registration_deadline,
"Registration deadline has already passed");
FC_ASSERT(_tournament_obj->options.whitelist.empty() ||
_tournament_obj->options.whitelist.find(op.player_account_id) != _tournament_obj->options.whitelist.end(),
"Player is not on the whitelist for this tournament");
FC_ASSERT(_tournament_details_obj->registered_players.find(op.player_account_id) == _tournament_details_obj->registered_players.end(),
"Player is already registered for this tournament");
FC_ASSERT(op.buy_in == _tournament_obj->options.buy_in, "Buy-in is incorrect");
GRAPHENE_ASSERT(!_buy_in_asset_type->is_transfer_restricted(),
transfer_restricted_transfer_asset,
"Asset {asset} has transfer_restricted flag enabled",
("asset", op.buy_in.asset_id));
GRAPHENE_ASSERT(is_authorized_asset(d, player_account, *_buy_in_asset_type),
transfer_from_account_not_whitelisted,
"player account ${player} is not whitelisted for asset ${asset}",
("player", op.player_account_id)
("asset", op.buy_in.asset_id));
GRAPHENE_ASSERT(is_authorized_asset(d, *_payer_account, *_buy_in_asset_type),
transfer_from_account_not_whitelisted,
"payer account ${payer} is not whitelisted for asset ${asset}",
("payer", op.payer_account_id)
("asset", op.buy_in.asset_id));
bool sufficient_balance = d.get_balance(*_payer_account, *_buy_in_asset_type).amount >= op.buy_in.amount;
FC_ASSERT(sufficient_balance,
"Insufficient Balance: paying account '${payer}' has insufficient balance to pay buy-in of ${buy_in} (balance is ${balance})",
("payer", _payer_account->name)
("buy_in", d.to_pretty_string(op.buy_in))
("balance",d.to_pretty_string(d.get_balance(*_payer_account, *_buy_in_asset_type))));
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result tournament_join_evaluator::do_apply( const tournament_join_operation& op )
{ try {
db().modify(*_tournament_obj, [&](tournament_object& tournament_obj){
tournament_obj.on_player_registered(db(), op.payer_account_id, op.player_account_id);
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result tournament_leave_evaluator::do_evaluate( const tournament_leave_operation& op )
{
try {
const database& d = db();
_tournament_obj = &op.tournament_id(d);
fc_ilog(fc::logger::get("tournament"), "details_id = ${id}",("id", _tournament_obj->tournament_details_id));
_tournament_details_obj = &_tournament_obj->tournament_details_id(d);
FC_ASSERT(_tournament_details_obj->registered_players.find(op.player_account_id) != _tournament_details_obj->registered_players.end(),
"Player is not registered for this tournament");
FC_ASSERT(op.canceling_account_id == op.player_account_id ||
op.canceling_account_id == _tournament_details_obj->players_payers.at(op.player_account_id),
"Only player or payer can unregister the player from a tournament");
FC_ASSERT(_tournament_obj->get_state() == tournament_state::accepting_registrations,
"Can only leave a tournament during registration period");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result tournament_leave_evaluator::do_apply( const tournament_leave_operation& op )
{ try {
db().modify(*_tournament_obj, [&](tournament_object& tournament_obj){
tournament_obj.on_player_unregistered(db(), op.player_account_id);
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result game_move_evaluator::do_evaluate( const game_move_operation& o )
{ try {
const database& d = db();
_game_obj = &o.game_id(d);
_game_obj->evaluate_move_operation(d, o);
return void_result();
} FC_CAPTURE_AND_RETHROW( (o) ) }
void_result game_move_evaluator::do_apply( const game_move_operation& o )
{ try {
db().modify(*_game_obj, [&](game_object& game_obj){
game_obj.on_move(db(), o);
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (o) ) }
} }

View file

@ -0,0 +1,755 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/database.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/msm/back/tools.hpp>
namespace graphene { namespace chain {
namespace msm = boost::msm;
namespace mpl = boost::mpl;
namespace
{
// Events
struct player_registered
{
database& db;
account_id_type payer_id;
account_id_type player_id;
player_registered(database& db, account_id_type payer_id, account_id_type player_id) :
db(db), payer_id(payer_id), player_id(player_id)
{}
};
struct player_unregistered
{
database& db;
account_id_type player_id;
player_unregistered(database& db, account_id_type player_id) :
db(db), player_id(player_id)
{}
};
struct registration_deadline_passed
{
database& db;
registration_deadline_passed(database& db) : db(db) {};
};
struct start_time_arrived
{
database& db;
start_time_arrived(database& db) : db(db) {};
};
struct match_completed
{
database& db;
const match_object& match;
match_completed(database& db, const match_object& match) : db(db), match(match) {}
};
struct tournament_state_machine_ : public msm::front::state_machine_def<tournament_state_machine_>
{
// disable a few state machine features we don't use for performance
typedef int no_exception_thrown;
typedef int no_message_queue;
// States
struct accepting_registrations : public msm::front::state<>{};
struct awaiting_start : public msm::front::state<>
{
void on_entry(const player_registered& event, tournament_state_machine_& fsm)
{
fc_ilog(fc::logger::get("tournament"),
"Tournament ${id} now has enough players registered to begin",
("id", fsm.tournament_obj->id));
if (fsm.tournament_obj->options.start_time)
fsm.tournament_obj->start_time = fsm.tournament_obj->options.start_time;
else
fsm.tournament_obj->start_time = event.db.head_block_time() + fc::seconds(*fsm.tournament_obj->options.start_delay);
}
};
struct in_progress : public msm::front::state<>
{
// reverse the bits in an integer
static uint32_t reverse_bits(uint32_t x)
{
x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
return ((x >> 16) | (x << 16));
}
match_id_type create_match(database& db, tournament_id_type tournament_id,
const vector<account_id_type>& players)
{
const match_object& match =
db.create<match_object>( [&]( match_object& match ) {
match.tournament_id = tournament_id;
match.players = players;
match.number_of_wins.resize(match.players.size());
match.start_time = db.head_block_time();
if (match.players.size() == 1)
{
// this is a bye
match.end_time = db.head_block_time();
}
});
return match.id;
}
void on_entry(const start_time_arrived& event, tournament_state_machine_& fsm)
{
fc_ilog(fc::logger::get("tournament"),
"Tournament ${id} is beginning",
("id", fsm.tournament_obj->id));
const tournament_details_object& tournament_details_obj = fsm.tournament_obj->tournament_details_id(event.db);
// Create the "seeding" order for the tournament as a random shuffle of the players.
//
// If this were a game of skill where players were ranked, this algorithm expects the
// most skilled players to the front of the list
vector<account_id_type> seeded_players(tournament_details_obj.registered_players.begin(),
tournament_details_obj.registered_players.end());
for (unsigned i = seeded_players.size() - 1; i >= 1; --i)
{
unsigned j = (unsigned)event.db.get_random_bits(i + 1);
std::swap(seeded_players[i], seeded_players[j]);
}
// Create all matches in the tournament now.
// If the number of players isn't a power of two, we will compensate with byes
// in the first round.
const uint32_t num_players = fsm.tournament_obj->options.number_of_players;
uint32_t num_rounds = boost::multiprecision::detail::find_msb(num_players - 1) + 1;
uint32_t num_matches = (1 << num_rounds) - 1;
uint32_t num_matches_in_first_round = 1 << (num_rounds - 1);
// First, assign players to their first round of matches in the paired_players
// array, where the first two play against each other, the second two play against
// each other, etc.
// Anyone with account_id_type() as their opponent gets a bye
vector<account_id_type> paired_players;
paired_players.resize(num_matches_in_first_round * 2);
for (uint32_t player_num = 0; player_num < num_players; ++player_num)
{
uint32_t player_position = reverse_bits(player_num ^ (player_num >> 1)) >> (32 - num_rounds);
paired_players[player_position] = seeded_players[player_num];
}
// now create the match objects for this first round
vector<match_id_type> matches;
matches.reserve(num_matches);
// create a bunch of empty matches
for (unsigned i = 0; i < num_matches; ++i)
matches.emplace_back(create_match(event.db, fsm.tournament_obj->id, vector<account_id_type>()));
// then walk through our paired players by twos, starting the first matches
for (unsigned i = 0; i < num_matches_in_first_round; ++i)
{
vector<account_id_type> players;
players.emplace_back(paired_players[2 * i]);
if (paired_players[2 * i + 1] != account_id_type())
players.emplace_back(paired_players[2 * i + 1]);
event.db.modify(matches[i](event.db), [&](match_object& match) {
match.players = players;
match.on_initiate_match(event.db);
});
}
event.db.modify(tournament_details_obj, [&](tournament_details_object& tournament_details_obj){
tournament_details_obj.matches = matches;
});
// find "bye" matches, complete missing player in the next match
for (unsigned i = 0; i < num_matches_in_first_round; ++i)
{
const match_object& match = matches[i](event.db);
if (match.players.size() == 1) // is "bye"
{
unsigned tournament_num_matches = tournament_details_obj.matches.size();
unsigned next_round_match_index = (i + tournament_num_matches + 1) / 2;
assert(next_round_match_index < tournament_num_matches);
const match_object& next_round_match = tournament_details_obj.matches[next_round_match_index](event.db);
event.db.modify(next_round_match, [&](match_object& next_match) {
next_match.players.emplace_back(match.players[0]);
if (next_match.players.size() > 1) // both previous matches were "bye"
next_match.on_initiate_match(event.db);
});
}
}
}
void on_entry(const match_completed& event, tournament_state_machine_& fsm)
{
tournament_object& tournament = *fsm.tournament_obj;
fc_ilog(fc::logger::get("tournament"),
"Match ${match_id} in tournament tournament ${tournament_id} is still in progress",
("match_id", event.match.id)("tournament_id", tournament.id));
// this wasn't the final match that just finished, so figure out if we can start the next match.
// The next match can start if both this match and the previous match have completed
const tournament_details_object& tournament_details_obj = fsm.tournament_obj->tournament_details_id(event.db);
unsigned num_matches = tournament_details_obj.matches.size();
auto this_match_iter = std::find(tournament_details_obj.matches.begin(), tournament_details_obj.matches.end(), event.match.id);
assert(this_match_iter != tournament_details_obj.matches.end());
unsigned this_match_index = std::distance(tournament_details_obj.matches.begin(), this_match_iter);
// TODO: we currently create all matches at startup, so they are numbered sequentially. We could get the index
// by subtracting match.id as long as this behavior doesn't change
unsigned next_round_match_index = (this_match_index + num_matches + 1) / 2;
assert(next_round_match_index < num_matches);
const match_object& next_round_match = tournament_details_obj.matches[next_round_match_index](event.db);
// each match will have two players, match.players[0] and match.players[1].
// for consistency, we want to feed the winner of this match into the correct
// slot in the next match
unsigned winner_index_in_next_match = (this_match_index + num_matches + 1) % 2;
unsigned other_match_index = num_matches - ((num_matches - next_round_match_index) * 2 + winner_index_in_next_match);
const match_object& other_match = tournament_details_obj.matches[other_match_index](event.db);
// the winners of the matches event.match and other_match will play in next_round_match
assert(event.match.match_winners.size() <= 1);
event.db.modify(next_round_match, [&](match_object& next_match_obj) {
if (!event.match.match_winners.empty()) // if there is a winner
{
if (winner_index_in_next_match == 0)
next_match_obj.players.insert(next_match_obj.players.begin(), *event.match.match_winners.begin());
else
next_match_obj.players.push_back(*event.match.match_winners.begin());
}
if (other_match.get_state() == match_state::match_complete)
{
next_match_obj.on_initiate_match(event.db);
}
});
}
};
struct registration_period_expired : public msm::front::state<>
{
void on_entry(const registration_deadline_passed& event, tournament_state_machine_& fsm)
{
fc_ilog(fc::logger::get("tournament"),
"Tournament ${id} is canceled",
("id", fsm.tournament_obj->id));
// repay everyone who paid into the prize pool
const tournament_details_object& details = fsm.tournament_obj->tournament_details_id(event.db);
for (const auto& payer_pair : details.payers)
{
// TODO: create a virtual operation to record the refund
// we'll think of this as just releasing an asset that the user had locked up
// for a period of time, not as a transfer back to the user; it doesn't matter
// if they are currently authorized to transfer this asset, they never really
// transferred it in the first place
asset amount(payer_pair.second, fsm.tournament_obj->options.buy_in.asset_id);
event.db.adjust_balance(payer_pair.first, amount);
// Generating a virtual operation that shows the payment
tournament_payout_operation op;
op.tournament_id = fsm.tournament_obj->id;
op.payout_amount = amount;
op.payout_account_id = payer_pair.first;
op.type = payout_type::buyin_refund;
event.db.push_applied_operation(op);
}
}
};
struct concluded : public msm::front::state<>
{
void on_entry(const match_completed& event, tournament_state_machine_& fsm)
{
tournament_object& tournament_obj = *fsm.tournament_obj;
fc_ilog(fc::logger::get("tournament"),
"Tournament ${id} is complete",
("id", tournament_obj.id));
tournament_obj.end_time = event.db.head_block_time();
// Distribute prize money when a tournament ends
#ifndef NDEBUG
const tournament_details_object& details = tournament_obj.tournament_details_id(event.db);
share_type total_prize = 0;
for (const auto& payer_pair : details.payers)
{
total_prize += payer_pair.second;
}
assert(total_prize == tournament_obj.prize_pool);
#endif
assert(event.match.match_winners.size() == 1);
const account_id_type& winner = *event.match.match_winners.begin();
uint16_t rake_fee_percentage = event.db.get_global_properties().parameters.rake_fee_percentage;
share_type rake_amount = 0;
const asset_object & asset_obj = tournament_obj.options.buy_in.asset_id(event.db);
optional<asset_dividend_data_id_type> dividend_id = asset_obj.dividend_data_id;
if (dividend_id.valid())
{
rake_amount = (fc::uint128_t(tournament_obj.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64();
}
asset won_prize(tournament_obj.prize_pool - rake_amount, tournament_obj.options.buy_in.asset_id);
tournament_payout_operation op;
if (won_prize.amount.value)
{
// Adjusting balance of winner
event.db.adjust_balance(winner, won_prize);
// Generating a virtual operation that shows the payment
op.tournament_id = tournament_obj.id;
op.payout_amount = won_prize;
op.payout_account_id = winner;
op.type = payout_type::prize_award;
event.db.push_applied_operation(op);
}
if (dividend_id.valid() && rake_amount.value)
{
// Adjusting balance of dividend_distribution_account
const asset_dividend_data_id_type& asset_dividend_data_id_= *dividend_id;
const asset_dividend_data_object& dividend_obj = asset_dividend_data_id_(event.db);
const account_id_type& rake_account_id = dividend_obj.dividend_distribution_account;
asset rake(rake_amount, tournament_obj.options.buy_in.asset_id);
event.db.adjust_balance(rake_account_id, rake);
// Generating a virtual operation that shows the payment
op.payout_amount = rake;
op.payout_account_id = rake_account_id;
op.type = payout_type::rake_fee;
event.db.push_applied_operation(op);
}
}
};
typedef accepting_registrations initial_state;
typedef tournament_state_machine_ x; // makes transition table cleaner
// Guards
bool will_be_fully_registered(const player_registered& event)
{
fc_ilog(fc::logger::get("tournament"),
"In will_be_fully_registered guard, returning ${value}",
("value", tournament_obj->registered_players == tournament_obj->options.number_of_players - 1));
return tournament_obj->registered_players == tournament_obj->options.number_of_players - 1;
}
bool was_final_match(const match_completed& event)
{
const tournament_details_object& tournament_details_obj = tournament_obj->tournament_details_id(event.db);
auto final_match_id = tournament_details_obj.matches[tournament_details_obj.matches.size() - 1];
bool was_final = event.match.id == final_match_id;
fc_ilog(fc::logger::get("tournament"),
"In was_final_match guard, returning ${value}",
("value", was_final));
return was_final;
}
void register_player(const player_registered& event)
{
fc_ilog(fc::logger::get("tournament"),
"In register_player action, player_id is ${player_id}, payer_id is ${payer_id}",
("player_id", event.player_id)("payer_id", event.payer_id));
event.db.adjust_balance(event.payer_id, -tournament_obj->options.buy_in);
const tournament_details_object& tournament_details_obj = tournament_obj->tournament_details_id(event.db);
event.db.modify(tournament_details_obj, [&](tournament_details_object& tournament_details_obj){
tournament_details_obj.payers[event.payer_id] += tournament_obj->options.buy_in.amount;
tournament_details_obj.registered_players.insert(event.player_id);
tournament_details_obj.players_payers[event.player_id] = event.payer_id;
});
++tournament_obj->registered_players;
tournament_obj->prize_pool += tournament_obj->options.buy_in.amount;
}
void unregister_player(const player_unregistered& event)
{
fc_ilog(fc::logger::get("tournament"),
"In unregister_player action, player_id is ${player_id}",
("player_id", event.player_id));
const tournament_details_object& tournament_details_obj = tournament_obj->tournament_details_id(event.db);
account_id_type payer_id = tournament_details_obj.players_payers.at(event.player_id);
event.db.adjust_balance(payer_id, tournament_obj->options.buy_in);
event.db.modify(tournament_details_obj, [&](tournament_details_object& tournament_details_obj){
tournament_details_obj.payers[payer_id] -= tournament_obj->options.buy_in.amount;
if (tournament_details_obj.payers[payer_id] <= 0)
tournament_details_obj.payers.erase(payer_id);
tournament_details_obj.registered_players.erase(event.player_id);
tournament_details_obj.players_payers.erase(event.player_id);
});
--tournament_obj->registered_players;
tournament_obj->prize_pool -= tournament_obj->options.buy_in.amount;
}
// Transition table for tournament
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
a_row < accepting_registrations, player_registered, accepting_registrations, &x::register_player >,
a_row < accepting_registrations, player_unregistered, accepting_registrations, &x::unregister_player >,
row < accepting_registrations, player_registered, awaiting_start, &x::register_player, &x::will_be_fully_registered >,
_row < accepting_registrations, registration_deadline_passed, registration_period_expired >,
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
a_row < awaiting_start, player_unregistered, accepting_registrations, &x::unregister_player >,
_row < awaiting_start, start_time_arrived, in_progress >,
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
_row < in_progress, match_completed, in_progress >,
g_row < in_progress, match_completed, concluded, &x::was_final_match >
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
> {};
tournament_object* tournament_obj;
tournament_state_machine_(tournament_object* tournament_obj) : tournament_obj(tournament_obj) {}
};
typedef msm::back::state_machine<tournament_state_machine_> tournament_state_machine;
}
class tournament_object::impl {
public:
tournament_state_machine state_machine;
impl(tournament_object* self) : state_machine(self) {}
};
tournament_object::tournament_object() :
my(new impl(this))
{
}
tournament_object::tournament_object(const tournament_object& rhs) :
graphene::db::abstract_object<tournament_object>(rhs),
creator(rhs.creator),
options(rhs.options),
start_time(rhs.start_time),
end_time(rhs.end_time),
prize_pool(rhs.prize_pool),
registered_players(rhs.registered_players),
tournament_details_id(rhs.tournament_details_id),
my(new impl(this))
{
my->state_machine = rhs.my->state_machine;
my->state_machine.tournament_obj = this;
}
tournament_object& tournament_object::operator=(const tournament_object& rhs)
{
//graphene::db::abstract_object<tournament_object>::operator=(rhs);
id = rhs.id;
creator = rhs.creator;
options = rhs.options;
start_time = rhs.start_time;
end_time = rhs.end_time;
prize_pool = rhs.prize_pool;
registered_players = rhs.registered_players;
tournament_details_id = rhs.tournament_details_id;
my->state_machine = rhs.my->state_machine;
my->state_machine.tournament_obj = this;
return *this;
}
tournament_object::~tournament_object()
{
}
bool verify_tournament_state_constants()
{
unsigned error_count = 0;
typedef msm::back::generate_state_set<tournament_state_machine::stt>::type all_states;
static char const* filled_state_names[mpl::size<all_states>::value];
mpl::for_each<all_states,boost::msm::wrap<mpl::placeholders::_1> >
(msm::back::fill_state_names<tournament_state_machine::stt>(filled_state_names));
for (unsigned i = 0; i < mpl::size<all_states>::value; ++i)
{
try
{
// this is an approximate test, the state name provided by typeinfo will be mangled, but should
// at least contain the string we're looking for
const char* fc_reflected_value_name = fc::reflector<tournament_state>::to_string((tournament_state)i);
if (!strcmp(fc_reflected_value_name, filled_state_names[i]))
fc_elog(fc::logger::get("tournament"),
"Error, state string mismatch between fc and boost::msm for int value ${int_value}: boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}",
("int_value", i)("boost_string", filled_state_names[i])("fc_string", fc_reflected_value_name));
}
catch (const fc::bad_cast_exception&)
{
fc_elog(fc::logger::get("tournament"),
"Error, no reflection for value ${int_value} in enum tournament_state",
("int_value", i));
++error_count;
}
}
return error_count == 0;
}
tournament_state tournament_object::get_state() const
{
static bool state_constants_are_correct = verify_tournament_state_constants();
(void)&state_constants_are_correct;
tournament_state state = (tournament_state)my->state_machine.current_state()[0];
return state;
}
void tournament_object::pack_impl(std::ostream& stream) const
{
boost::archive::binary_oarchive oa(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
oa << my->state_machine;
}
void tournament_object::unpack_impl(std::istream& stream)
{
boost::archive::binary_iarchive ia(stream, boost::archive::no_header|boost::archive::no_codecvt|boost::archive::no_xml_tag_checking);
ia >> my->state_machine;
}
void tournament_object::on_registration_deadline_passed(database& db)
{
my->state_machine.process_event(registration_deadline_passed(db));
}
void tournament_object::on_player_registered(database& db, account_id_type payer_id, account_id_type player_id)
{
my->state_machine.process_event(player_registered(db, payer_id, player_id));
}
void tournament_object::on_player_unregistered(database& db, account_id_type player_id)
{
my->state_machine.process_event(player_unregistered(db, player_id));
}
void tournament_object::on_start_time_arrived(database& db)
{
my->state_machine.process_event(start_time_arrived(db));
}
void tournament_object::on_match_completed(database& db, const match_object& match)
{
my->state_machine.process_event(match_completed(db, match));
}
void tournament_object::check_for_new_matches_to_start(database& db) const
{
const tournament_details_object& tournament_details_obj = tournament_details_id(db);
unsigned num_matches = tournament_details_obj.matches.size();
uint32_t num_rounds = boost::multiprecision::detail::find_msb(num_matches + 1);
// Scan the matches by round to find the last round where all matches are complete
int last_complete_round = -1;
bool first_incomplete_match_was_waiting = false;
for (unsigned round_num = 0; round_num < num_rounds; ++round_num)
{
uint32_t num_matches_in_this_round = 1 << (num_rounds - round_num - 1);
uint32_t first_match_in_round = (num_matches - (num_matches >> round_num));
bool all_matches_in_round_complete = true;
for (uint32_t match_num = first_match_in_round; match_num < first_match_in_round + num_matches_in_this_round; ++match_num)
{
const match_object& match = tournament_details_obj.matches[match_num](db);
if (match.get_state() != match_state::match_complete)
{
first_incomplete_match_was_waiting = match.get_state() == match_state::waiting_on_previous_matches;
all_matches_in_round_complete = false;
break;
}
}
if (all_matches_in_round_complete)
last_complete_round = round_num;
else
break;
}
if (last_complete_round == -1)
return;
// We shouldn't be here if the final match is complete
assert(last_complete_round != num_rounds - 1);
if (last_complete_round == num_rounds - 1)
return;
if (first_incomplete_match_was_waiting)
{
// all previous matches have completed, and the first match in this round hasn't been
// started (which means none of the matches in this round should have started)
unsigned first_incomplete_round = last_complete_round + 1;
uint32_t num_matches_in_incomplete_round = 1 << (num_rounds - first_incomplete_round - 1);
uint32_t first_match_in_incomplete_round = num_matches - (num_matches >> first_incomplete_round);
for (uint32_t match_num = first_match_in_incomplete_round;
match_num < first_match_in_incomplete_round + num_matches_in_incomplete_round;
++match_num)
{
int left_child_index = (num_matches - 1) - ((num_matches - 1 - match_num) * 2 + 2);
int right_child_index = left_child_index + 1;
const match_object& match_to_start = tournament_details_obj.matches[left_child_index](db);
const match_object& left_match = tournament_details_obj.matches[left_child_index](db);
const match_object& right_match = tournament_details_obj.matches[right_child_index](db);
std::vector<account_id_type> winners;
if (!left_match.match_winners.empty())
{
assert(left_match.match_winners.size() == 1);
winners.emplace_back(*left_match.match_winners.begin());
}
if (!right_match.match_winners.empty())
{
assert(right_match.match_winners.size() == 1);
winners.emplace_back(*right_match.match_winners.begin());
}
db.modify(match_to_start, [&](match_object& match) {
match.players = winners;
//match.state = ready_to_begin;
});
}
}
}
fc::sha256 rock_paper_scissors_throw::calculate_hash() const
{
std::vector<char> full_throw_packed(fc::raw::pack(*this));
return fc::sha256::hash(full_throw_packed.data(), full_throw_packed.size());
}
vector<tournament_id_type> tournament_players_index::get_registered_tournaments_for_account( const account_id_type& a )const
{
auto iter = account_to_joined_tournaments.find(a);
if (iter != account_to_joined_tournaments.end())
return vector<tournament_id_type>(iter->second.begin(), iter->second.end());
return vector<tournament_id_type>();
}
void tournament_players_index::object_inserted(const object& obj)
{
assert( dynamic_cast<const tournament_details_object*>(&obj) ); // for debug only
const tournament_details_object& details = static_cast<const tournament_details_object&>(obj);
for (const account_id_type& account_id : details.registered_players)
account_to_joined_tournaments[account_id].insert(details.tournament_id);
}
void tournament_players_index::object_removed(const object& obj)
{
assert( dynamic_cast<const tournament_details_object*>(&obj) ); // for debug only
const tournament_details_object& details = static_cast<const tournament_details_object&>(obj);
for (const account_id_type& account_id : details.registered_players)
{
auto iter = account_to_joined_tournaments.find(account_id);
if (iter != account_to_joined_tournaments.end())
iter->second.erase(details.tournament_id);
}
}
void tournament_players_index::about_to_modify(const object& before)
{
assert( dynamic_cast<const tournament_details_object*>(&before) ); // for debug only
const tournament_details_object& details = static_cast<const tournament_details_object&>(before);
before_account_ids = details.registered_players;
}
void tournament_players_index::object_modified(const object& after)
{
assert( dynamic_cast<const tournament_details_object*>(&after) ); // for debug only
const tournament_details_object& details = static_cast<const tournament_details_object&>(after);
{
vector<account_id_type> newly_registered_players(details.registered_players.size());
auto end_iter = std::set_difference(details.registered_players.begin(), details.registered_players.end(),
before_account_ids.begin(), before_account_ids.end(),
newly_registered_players.begin());
newly_registered_players.resize(end_iter - newly_registered_players.begin());
for (const account_id_type& account_id : newly_registered_players)
account_to_joined_tournaments[account_id].insert(details.tournament_id);
}
{
vector<account_id_type> newly_unregistered_players(before_account_ids.size());
auto end_iter = std::set_difference(before_account_ids.begin(), before_account_ids.end(),
details.registered_players.begin(), details.registered_players.end(),
newly_unregistered_players.begin());
newly_unregistered_players.resize(end_iter - newly_unregistered_players.begin());
for (const account_id_type& account_id : newly_unregistered_players)
{
auto iter = account_to_joined_tournaments.find(account_id);
if (iter != account_to_joined_tournaments.end())
iter->second.erase(details.tournament_id);
}
}
}
} } // graphene::chain
namespace fc {
// Manually reflect tournament_object to variant to properly reflect "state"
void to_variant(const graphene::chain::tournament_object& tournament_obj, fc::variant& v)
{
fc_elog(fc::logger::get("tournament"), "In tournament_obj to_variant");
elog("In tournament_obj to_variant");
fc::mutable_variant_object o;
o("id", tournament_obj.id)
("creator", tournament_obj.creator)
("options", tournament_obj.options)
("start_time", tournament_obj.start_time)
("end_time", tournament_obj.end_time)
("prize_pool", tournament_obj.prize_pool)
("registered_players", tournament_obj.registered_players)
("tournament_details_id", tournament_obj.tournament_details_id)
("state", tournament_obj.get_state());
v = o;
}
// Manually reflect tournament_object to variant to properly reflect "state"
void from_variant(const fc::variant& v, graphene::chain::tournament_object& tournament_obj)
{
fc_elog(fc::logger::get("tournament"), "In tournament_obj from_variant");
tournament_obj.id = v["id"].as<graphene::chain::tournament_id_type>();
tournament_obj.creator = v["creator"].as<graphene::chain::account_id_type>();
tournament_obj.options = v["options"].as<graphene::chain::tournament_options>();
tournament_obj.start_time = v["start_time"].as<optional<time_point_sec> >();
tournament_obj.end_time = v["end_time"].as<optional<time_point_sec> >();
tournament_obj.prize_pool = v["prize_pool"].as<graphene::chain::share_type>();
tournament_obj.registered_players = v["registered_players"].as<uint32_t>();
tournament_obj.tournament_details_id = v["tournament_details_id"].as<graphene::chain::tournament_details_id_type>();
graphene::chain::tournament_state state = v["state"].as<graphene::chain::tournament_state>();
const_cast<int*>(tournament_obj.my->state_machine.current_state())[0] = (int)state;
}
} //end namespace fc

View file

@ -46,6 +46,7 @@ object_id_type witness_create_evaluator::do_apply( const witness_create_operatio
const auto& new_witness_object = db().create<witness_object>( [&]( witness_object& obj ){
obj.witness_account = op.witness_account;
obj.signing_key = op.block_signing_key;
obj.next_secret_hash = op.initial_secret;
obj.vote_id = vote_id;
obj.url = op.url;
});
@ -69,6 +70,8 @@ void_result witness_update_evaluator::do_apply( const witness_update_operation&
wit.url = *op.new_url;
if( op.new_signing_key.valid() )
wit.signing_key = *op.new_signing_key;
if( op.new_initial_secret.valid() )
wit.next_secret_hash = *op.new_initial_secret;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }

View file

@ -166,8 +166,16 @@ struct egenesis_info
else if( genesis_json.valid() )
{
// If genesis not exist, generate from genesis_json
try
{
genesis = fc::json::from_string( *genesis_json ).as< genesis_state_type >();
}
catch (const fc::exception& e)
{
edump((e));
throw;
}
}
else
{
// Neither genesis nor genesis_json exists, crippled

View file

@ -54,7 +54,7 @@
#define GRAPHENE_NET_TEST_SEED_IP "104.236.44.210" // autogenerated
#define GRAPHENE_NET_TEST_P2P_PORT 1700
#define GRAPHENE_NET_DEFAULT_P2P_PORT 1776
#define GRAPHENE_NET_DEFAULT_P2P_PORT 2776
#define GRAPHENE_NET_DEFAULT_DESIRED_CONNECTIONS 20
#define GRAPHENE_NET_DEFAULT_MAX_CONNECTIONS 200

View file

@ -1,6 +1,9 @@
add_subdirectory( witness )
add_subdirectory( account_history )
add_subdirectory( accounts_list )
add_subdirectory( market_history )
add_subdirectory( delayed_node )
add_subdirectory( bookie )
add_subdirectory( generate_genesis )
add_subdirectory( generate_uia_sharedrop_genesis )
add_subdirectory( debug_witness )

View file

@ -0,0 +1,21 @@
file(GLOB HEADERS "include/graphene/accouns_list/*.hpp")
add_library( graphene_accounts_list
accounts_list_plugin.cpp
)
target_link_libraries( graphene_accounts_list graphene_chain graphene_app )
target_include_directories( graphene_accounts_list
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
if(MSVC)
set_source_files_properties( accounts_list_plugin.cpp PROPERTIES COMPILE_FLAGS "/bigobj" )
endif(MSVC)
install( TARGETS
graphene_accounts_list
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/accounts_list/accounts_list_plugin.hpp>
#include <graphene/app/impacted.hpp>
#include <graphene/chain/account_evaluator.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/config.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/operation_history_object.hpp>
#include <graphene/chain/transaction_evaluation_state.hpp>
#include <fc/smart_ref_impl.hpp>
#include <fc/thread/thread.hpp>
namespace graphene { namespace accounts_list {
namespace detail
{
class accounts_list_plugin_impl
{
public:
accounts_list_plugin_impl(accounts_list_plugin& _plugin)
: _self( _plugin )
{ }
virtual ~accounts_list_plugin_impl();
/**
*/
void list_accounts();
graphene::chain::database& database()
{
return _self.database();
}
accounts_list_plugin& _self;
vector<account_balance_object> _listed_balances;
};
accounts_list_plugin_impl::~accounts_list_plugin_impl()
{
return;
}
void accounts_list_plugin_impl::list_accounts()
{
graphene::chain::database& db = database();
_listed_balances.clear();
auto& balance_index = db.get_index_type<graphene::chain::account_balance_index>().indices().get<graphene::chain::by_asset_balance>();
for (auto balance_iter = balance_index.begin();
balance_iter != balance_index.end() &&
balance_iter->asset_type == graphene::chain::asset_id_type() &&
balance_iter->balance > 0; ++balance_iter)
{
//idump((balance_iter->owner(db) .name)(*balance_iter));
_listed_balances.emplace_back(*balance_iter);
}
}
} // end namespace detail
accounts_list_plugin::accounts_list_plugin() :
my( new detail::accounts_list_plugin_impl(*this) )
{
}
accounts_list_plugin::~accounts_list_plugin()
{
}
std::string accounts_list_plugin::plugin_name()const
{
return "accounts_list";
}
void accounts_list_plugin::plugin_set_program_options(
boost::program_options::options_description& /*cli*/,
boost::program_options::options_description& /*cfg*/
)
{
// cli.add_options()
// ("list-account", boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(), "Account ID to list (may specify multiple times)")
// ;
// cfg.add(cli);
}
void accounts_list_plugin::plugin_initialize(const boost::program_options::variables_map& /*options*/)
{
//ilog("accounts list plugin: plugin_initialize()");
list_accounts();
}
void accounts_list_plugin::plugin_startup()
{
//ilog("accounts list plugin: plugin_startup()");
}
vector<account_balance_object> accounts_list_plugin::list_accounts() const
{
ilog("accounts list plugin: list_accounts()");
my->list_accounts();
//idump((my->_listed_balances));
return my->_listed_balances;
}
} }

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/app/plugin.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/account_object.hpp>
#include <fc/thread/future.hpp>
namespace graphene { namespace accounts_list {
using namespace chain;
namespace detail
{
class accounts_list_plugin_impl;
}
class accounts_list_plugin : public graphene::app::plugin
{
public:
accounts_list_plugin();
virtual ~accounts_list_plugin();
std::string plugin_name()const override;
virtual void plugin_set_program_options(
boost::program_options::options_description& cli,
boost::program_options::options_description& cfg) override;
virtual void plugin_initialize(const boost::program_options::variables_map& options) override;
virtual void plugin_startup() override;
vector<account_balance_object>list_accounts()const;
friend class detail::accounts_list_plugin_impl;
std::unique_ptr<detail::accounts_list_plugin_impl> my;
};
} } //graphene::accounts_list

View file

@ -0,0 +1,17 @@
file(GLOB HEADERS "include/graphene/generate_genesis/*.hpp")
add_library( graphene_generate_genesis
generate_genesis.cpp
)
target_link_libraries( graphene_generate_genesis graphene_chain graphene_app graphene_time )
target_include_directories( graphene_generate_genesis
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
install( TARGETS
graphene_generate_genesis
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)

View file

@ -0,0 +1,399 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/generate_genesis/generate_genesis_plugin.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/genesis_state.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/utilities/key_conversion.hpp>
#include <fc/smart_ref_impl.hpp>
#include <fc/thread/thread.hpp>
#include <graphene/chain/market_object.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
#include <iostream>
#include <fstream>
using namespace graphene::generate_genesis_plugin;
using std::string;
using std::vector;
namespace bpo = boost::program_options;
void generate_genesis_plugin::plugin_set_program_options(
boost::program_options::options_description& command_line_options,
boost::program_options::options_description& config_file_options)
{
command_line_options.add_options()
("output-genesis-file,o", bpo::value<std::string>()->default_value("genesis.json"), "Genesis file to create")
("output-csvlog-file,o", bpo::value<std::string>()->default_value("log.csv"), "CSV log file to create")
("snapshot-block-number", bpo::value<uint32_t>(), "Block number at which to snapshot balances")
;
config_file_options.add(command_line_options);
}
std::string generate_genesis_plugin::plugin_name()const
{
return "generate_genesis";
}
void generate_genesis_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{ try {
ilog("generate genesis plugin: plugin_initialize() begin");
_options = &options;
_genesis_filename = options["output-genesis-file"].as<std::string>();
_csvlog_filename = options["output-csvlog-file"].as<std::string>();
if (options.count("snapshot-block-number"))
_block_to_snapshot = options["snapshot-block-number"].as<uint32_t>();
database().applied_block.connect([this](const graphene::chain::signed_block& b){ block_applied(b); });
ilog("generate genesis plugin: plugin_initialize() end");
} FC_LOG_AND_RETHROW() }
void generate_genesis_plugin::plugin_startup()
{ try {
ilog("generate genesis plugin: plugin_startup() begin");
if (_block_to_snapshot)
{
chain::database& d = database();
if (d.head_block_num() == *_block_to_snapshot)
{
ilog("generate genesis plugin: already at snapshot block");
generate_snapshot();
}
else if (d.head_block_num() > *_block_to_snapshot)
elog("generate genesis plugin: already passed snapshot block, you must reindex to return to the snapshot state");
else
elog("generate genesis plugin: waiting for block ${snapshot_block} to generate snapshot, current head is ${head}",
("snapshot_block", _block_to_snapshot)("head", d.head_block_num()));
}
else
ilog("generate genesis plugin: no snapshot block number provided, plugin is disabled");
ilog("generate genesis plugin: plugin_startup() end");
} FC_CAPTURE_AND_RETHROW() }
void generate_genesis_plugin::block_applied(const graphene::chain::signed_block& b)
{
if (_block_to_snapshot && b.block_num() == *_block_to_snapshot)
{
ilog("generate genesis plugin: snapshot block has arrived");
generate_snapshot();
}
}
std::string modify_account_name(const std::string& name)
{
return std::string("bts-") + name;
}
bool is_special_account(const graphene::chain::account_id_type& account_id)
{
return account_id.instance < 100;
}
bool is_scam(const std::string& account_name)
{
static std::set<std::string> scam_accounts{
"polonie-wallet",
"polonie-xwallet",
"poloniewallet",
"poloniex-deposit",
"poloniex-wallet",
"poloniexwall-et",
"poloniexwallett",
"poloniexwall-t",
"poloniexwalle",
"poloniex",
"poloneix",
"poloniex1",
"bittrex-deopsit",
"bittrex-deposi",
"bittrex-depositt",
"bittrex-dposit",
"bittrex",
"bittrex-deposits",
"coinbase",
"blocktrade",
"locktrades",
"yun.bts",
"transwiser-walle",
"transwiser-wallets",
"ranswiser-wallet",
"yun.btc",
"pay.coinbase.com",
"pay.bts.com",
"btc38.com",
"yunbi.com",
"coinbase.com",
"ripple.com"
};
return scam_accounts.find(account_name) != scam_accounts.end();
}
bool is_exchange(const std::string& account_name)
{
static std::set<std::string> exchange_accounts{
"poloniexcoldstorage",
"btc38-public-for-bts-cold",
"poloniexwallet",
"btercom",
"yunbi-cold-wallet",
"btc38-btsx-octo-72722",
"bittrex-deposit",
"btc38btsxwithdrawal"
};
return exchange_accounts.find(account_name) != exchange_accounts.end();
}
bool exclude_account_from_sharedrop(graphene::chain::database& d, const graphene::chain::account_id_type& account_id)
{
if (is_special_account(account_id))
return true;
const std::string& account_name = account_id(d).name;
return is_exchange(account_name) || is_scam(account_name);
}
void generate_genesis_plugin::generate_snapshot()
{ try {
ilog("generate genesis plugin: generating snapshot now");
graphene::chain::genesis_state_type new_genesis_state;
chain::database& d = database();
// we'll distribute 5% of (some amount of tokens), so:
graphene::chain::share_type total_amount_to_distribute(27302662972);
my_account_balance_object_index_type db_balances;
graphene::chain::share_type total_bts_balance;
auto& balance_index = d.get_index_type<graphene::chain::account_balance_index>().indices().get<graphene::chain::by_asset_balance>();
for (auto balance_iter = balance_index.begin(); balance_iter != balance_index.end() && balance_iter->asset_type == graphene::chain::asset_id_type(); ++balance_iter)
{
if (balance_iter->balance > 0 && !exclude_account_from_sharedrop(d, balance_iter->owner))
{
total_bts_balance += balance_iter->balance;
my_account_balance_object new_balance_object;
new_balance_object.account_id = balance_iter->owner;
new_balance_object.balance = balance_iter->balance;
db_balances.insert(new_balance_object);
}
}
// account for BTS tied up in market orders
auto limit_order_index = d.get_index_type<graphene::chain::limit_order_index>().indices();
for (const graphene::chain::limit_order_object& limit_order : limit_order_index)
if (limit_order.amount_for_sale().asset_id == graphene::chain::asset_id_type())
{
graphene::chain::share_type limit_order_amount = limit_order.amount_for_sale().amount;
if (limit_order_amount > 0 && !exclude_account_from_sharedrop(d, limit_order.seller))
{
total_bts_balance += limit_order_amount;
auto my_balance_iter = db_balances.find(limit_order.seller);
if (my_balance_iter == db_balances.end())
{
my_account_balance_object balance_object;
balance_object.account_id = limit_order.seller;
balance_object.orders = limit_order_amount;
db_balances.insert(balance_object);
}
else
{
db_balances.modify(my_balance_iter, [&](my_account_balance_object& balance_object) {
balance_object.orders += limit_order_amount;
});
}
}
}
// account for BTS tied up in collateral for SmartCoins
auto call_order_index = d.get_index_type<graphene::chain::call_order_index>().indices();
for (const graphene::chain::call_order_object& call_order : call_order_index)
if (call_order.get_collateral().asset_id == graphene::chain::asset_id_type())
{
graphene::chain::share_type call_order_amount = call_order.get_collateral().amount;
if (call_order_amount > 0 && !exclude_account_from_sharedrop(d, call_order.borrower))
{
total_bts_balance += call_order_amount;
auto my_balance_iter = db_balances.find(call_order.borrower);
if (my_balance_iter == db_balances.end())
{
my_account_balance_object balance_object;
balance_object.account_id = call_order.borrower;
balance_object.collateral = call_order_amount;
db_balances.insert(balance_object);
}
else
{
db_balances.modify(my_balance_iter, [&](my_account_balance_object& balance_object) {
balance_object.collateral += call_order_amount;
});
}
}
}
// account available-but-unclaimed BTS in vesting balances
auto vesting_balance_index = d.get_index_type<graphene::chain::vesting_balance_index>().indices();
for (const graphene::chain::vesting_balance_object& vesting_balance : vesting_balance_index)
if (vesting_balance.balance.asset_id == graphene::chain::asset_id_type())
{
graphene::chain::share_type vesting_balance_amount = vesting_balance.get_allowed_withdraw(d.head_block_time()).amount;
if (vesting_balance_amount > 0 && !exclude_account_from_sharedrop(d, vesting_balance.owner))
{
total_bts_balance += vesting_balance_amount;
auto my_balance_iter = db_balances.find(vesting_balance.owner);
if (my_balance_iter == db_balances.end())
{
my_account_balance_object balance_object;
balance_object.account_id = vesting_balance.owner;
balance_object.vesting = vesting_balance_amount;
db_balances.insert(balance_object);
}
else
{
db_balances.modify(my_balance_iter, [&](my_account_balance_object& balance_object) {
balance_object.vesting += vesting_balance_amount;
});
}
}
}
graphene::chain::share_type total_shares_dropped;
// Now, we assume we're distributing balances to all BTS holders proportionally, figure
// the smallest balance we can distribute and still assign the user a satoshi of the share drop
graphene::chain::share_type effective_total_bts_balance;
auto& by_effective_balance_index = db_balances.get<by_effective_balance>();
auto balance_iter = by_effective_balance_index.begin();
for (; balance_iter != by_effective_balance_index.end(); ++balance_iter)
{
fc::uint128 share_drop_amount = total_amount_to_distribute.value;
share_drop_amount *= balance_iter->get_effective_balance().value;
share_drop_amount /= total_bts_balance.value;
if (!share_drop_amount.to_uint64())
break; // balances are decreasing, so every balance after will also round to zero
total_shares_dropped += share_drop_amount.to_uint64();
effective_total_bts_balance += balance_iter->get_effective_balance();
}
// our iterator is just after the smallest balance we will process,
// walk it backwards towards the larger balances, distributing the sharedrop as we go
graphene::chain::share_type remaining_amount_to_distribute = total_amount_to_distribute;
graphene::chain::share_type bts_balance_remaining = effective_total_bts_balance;
do {
--balance_iter;
fc::uint128 share_drop_amount = remaining_amount_to_distribute.value;
share_drop_amount *= balance_iter->get_effective_balance().value;
share_drop_amount /= bts_balance_remaining.value;
graphene::chain::share_type amount_distributed = share_drop_amount.to_uint64();
by_effective_balance_index.modify(balance_iter, [&](my_account_balance_object& balance_object) {
balance_object.sharedrop += amount_distributed;
});
remaining_amount_to_distribute -= amount_distributed;
bts_balance_remaining -= balance_iter->get_effective_balance();
} while (balance_iter != by_effective_balance_index.begin());
assert(remaining_amount_to_distribute == 0);
std::ofstream logfile;
logfile.open(_csvlog_filename);
assert(logfile.is_open());
logfile << "name,balance+orders+collateral+vesting,balance,orders,collateral,vesting,sharedrop\n";
for (const my_account_balance_object& balance : by_effective_balance_index)
logfile << balance.account_id(d).name << "," <<
balance.get_effective_balance().value << "," <<
balance.balance.value << "," <<
balance.orders.value << "," <<
balance.collateral.value << "," <<
balance.vesting.value << "," <<
balance.sharedrop.value << "\n";
ilog("CSV log written to file ${filename}", ("filename", _csvlog_filename));
logfile.close();
// remove all balance objects with zero sharedrops
by_effective_balance_index.erase(by_effective_balance_index.lower_bound(0),
by_effective_balance_index.end());
// inefficient way of crawling the graph, but we only do it once
std::set<graphene::chain::account_id_type> already_generated;
for (;;)
{
unsigned accounts_generated_this_round = 0;
for (const my_account_balance_object& balance : by_effective_balance_index)
{
const graphene::chain::account_object& account_obj = balance.account_id(d);
if (already_generated.find(balance.account_id) == already_generated.end())
{
graphene::chain::genesis_state_type::initial_bts_account_type::initial_authority owner;
owner.weight_threshold = account_obj.owner.weight_threshold;
owner.key_auths = account_obj.owner.key_auths;
for (const auto& value : account_obj.owner.account_auths)
{
owner.account_auths.insert(std::make_pair(modify_account_name(value.first(d).name), value.second));
db_balances.insert(my_account_balance_object{value.first}); // make sure the account is generated, even if it has a zero balance
}
owner.key_auths = account_obj.owner.key_auths;
owner.address_auths = account_obj.owner.address_auths;
graphene::chain::genesis_state_type::initial_bts_account_type::initial_authority active;
active.weight_threshold = account_obj.active.weight_threshold;
active.key_auths = account_obj.active.key_auths;
for (const auto& value : account_obj.active.account_auths)
{
active.account_auths.insert(std::make_pair(modify_account_name(value.first(d).name), value.second));
db_balances.insert(my_account_balance_object{value.first}); // make sure the account is generated, even if it has a zero balance
}
active.key_auths = account_obj.active.key_auths;
active.address_auths = account_obj.active.address_auths;
new_genesis_state.initial_bts_accounts.emplace_back(
graphene::chain::genesis_state_type::initial_bts_account_type(modify_account_name(account_obj.name),
owner, active,
balance.sharedrop));
already_generated.insert(balance.account_id);
++accounts_generated_this_round;
}
}
if (accounts_generated_this_round == 0)
break;
}
fc::json::save_to_file(new_genesis_state, _genesis_filename);
ilog("New genesis state written to file ${filename}", ("filename", _genesis_filename));
} FC_LOG_AND_RETHROW() }
void generate_genesis_plugin::plugin_shutdown()
{
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/app/plugin.hpp>
#include <graphene/chain/database.hpp>
#include <fc/thread/future.hpp>
namespace graphene { namespace generate_genesis_plugin {
class generate_genesis_plugin : public graphene::app::plugin {
public:
~generate_genesis_plugin() {
}
std::string plugin_name()const override;
virtual void plugin_set_program_options(
boost::program_options::options_description &command_line_options,
boost::program_options::options_description &config_file_options
) override;
virtual void plugin_initialize( const boost::program_options::variables_map& options ) override;
virtual void plugin_startup() override;
virtual void plugin_shutdown() override;
private:
void block_applied(const graphene::chain::signed_block& b);
void generate_snapshot();
boost::program_options::variables_map _options;
fc::optional<uint32_t> _block_to_snapshot;
std::string _genesis_filename;
std::string _csvlog_filename;
};
class my_account_balance_object
{
public:
// constructor copying from base class
//my_account_balance_object(const graphene::chain::account_balance_object& abo) : graphene::chain::account_balance_object(abo) {}
graphene::chain::account_id_type account_id;
graphene::chain::share_type balance;
graphene::chain::share_type orders;
graphene::chain::share_type collateral;
graphene::chain::share_type vesting;
graphene::chain::share_type sharedrop;
graphene::chain::share_type get_effective_balance() const { return balance + orders + collateral + vesting; }
};
using namespace boost::multi_index;
struct by_account{};
struct by_effective_balance{};
typedef multi_index_container<my_account_balance_object,
indexed_by<ordered_unique<tag<by_account>,
member<my_account_balance_object, graphene::chain::account_id_type, &my_account_balance_object::account_id> >,
ordered_non_unique<tag<by_effective_balance>,
const_mem_fun<my_account_balance_object, graphene::chain::share_type, &my_account_balance_object::get_effective_balance>,
std::greater<graphene::chain::share_type> > > > my_account_balance_object_index_type;
} } //graphene::generate_genesis_plugin

View file

@ -0,0 +1,17 @@
file(GLOB HEADERS "include/graphene/generate_uia_sharedrop_genesis/*.hpp")
add_library( graphene_generate_uia_sharedrop_genesis
generate_uia_sharedrop_genesis.cpp
)
target_link_libraries( graphene_generate_uia_sharedrop_genesis graphene_chain graphene_app graphene_time )
target_include_directories( graphene_generate_uia_sharedrop_genesis
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
install( TARGETS
graphene_generate_uia_sharedrop_genesis
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)

View file

@ -0,0 +1,362 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/generate_uia_sharedrop_genesis/generate_uia_sharedrop_genesis.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/genesis_state.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/utilities/key_conversion.hpp>
#include <fc/smart_ref_impl.hpp>
#include <fc/thread/thread.hpp>
#include <graphene/chain/market_object.hpp>
#include <iostream>
#include <fstream>
using namespace graphene::generate_uia_sharedrop_genesis;
using std::string;
using std::vector;
namespace bpo = boost::program_options;
void generate_uia_sharedrop_genesis_plugin::plugin_set_program_options(boost::program_options::options_description& command_line_options,
boost::program_options::options_description& config_file_options)
{
command_line_options.add_options()
("input-uia-sharedrop-genesis-file", bpo::value<std::string>()->default_value("genesis.json"), "Genesis file to read")
("output-uia-sharedrop-genesis-file", bpo::value<std::string>()->default_value("genesis.json"), "Genesis file to create")
("output-uia-sharedrop-csvlog-file", bpo::value<std::string>()->default_value("log.csv"), "CSV log file to create")
("uia-sharedrop-snapshot-block-number", bpo::value<uint32_t>(), "Block number at which to snapshot balances")
;
config_file_options.add(command_line_options);
}
std::string generate_uia_sharedrop_genesis_plugin::plugin_name()const
{
return "generate_uia_sharedrop_genesis";
}
void generate_uia_sharedrop_genesis_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{ try {
ilog("generate uia sharedrop genesis plugin: plugin_initialize() begin");
_options = &options;
_output_genesis_filename = options["output-uia-sharedrop-genesis-file"].as<std::string>();
_input_genesis_filename = options["input-uia-sharedrop-genesis-file"].as<std::string>();
_csvlog_filename = options["output-uia-sharedrop-csvlog-file"].as<std::string>();
if (options.count("uia-sharedrop-snapshot-block-number"))
_block_to_snapshot = options["uia-sharedrop-snapshot-block-number"].as<uint32_t>();
database().applied_block.connect([this](const graphene::chain::signed_block& b){ block_applied(b); });
ilog("generate uia sharedrop genesis plugin: plugin_initialize() end");
} FC_LOG_AND_RETHROW() }
void generate_uia_sharedrop_genesis_plugin::plugin_startup()
{ try {
ilog("generate uia sharedrop genesis plugin: plugin_startup() begin");
if (_block_to_snapshot)
{
chain::database& d = database();
if (d.head_block_num() == *_block_to_snapshot)
{
ilog("generate uia sharedrop genesis plugin: already at snapshot block");
generate_snapshot();
}
else if (d.head_block_num() > *_block_to_snapshot)
elog("generate uia sharedrop genesis plugin: already passed snapshot block, you must reindex to return to the snapshot state");
else
elog("generate uia sharedrop genesis plugin: waiting for block ${snapshot_block} to generate snapshot, current head is ${head}",
("snapshot_block", _block_to_snapshot)("head", d.head_block_num()));
}
else
ilog("generate uia sharedrop genesis plugin: no snapshot block number provided, plugin is disabled");
ilog("generate uia sharedrop genesis plugin: plugin_startup() end");
} FC_CAPTURE_AND_RETHROW() }
void generate_uia_sharedrop_genesis_plugin::block_applied(const graphene::chain::signed_block& b)
{
if (_block_to_snapshot && b.block_num() == *_block_to_snapshot)
{
ilog("generate uia sharedrop genesis plugin: snapshot block has arrived");
generate_snapshot();
}
}
namespace
{
// anonymous namespace for file-scoped helper functions
std::string modify_account_name(const std::string& name)
{
return std::string("bts-") + name;
}
std::string unmodify_account_name(const std::string& name)
{
FC_ASSERT(name.substr(0, 4) == "bts-");
return name.substr(4);
}
bool is_special_account(const graphene::chain::account_id_type& account_id)
{
return account_id.instance < 100;
}
bool is_scam(const std::string& account_name)
{
static std::set<std::string> scam_accounts{
"polonie-wallet",
"polonie-xwallet",
"poloniewallet",
"poloniex-deposit",
"poloniex-wallet",
"poloniexwall-et",
"poloniexwallett",
"poloniexwall-t",
"poloniexwalle",
"poloniex",
"poloneix",
"poloniex1",
"bittrex-deopsit",
"bittrex-deposi",
"bittrex-depositt",
"bittrex-dposit",
"bittrex",
"bittrex-deposits",
"coinbase",
"blocktrade",
"locktrades",
"yun.bts",
"transwiser-walle",
"transwiser-wallets",
"ranswiser-wallet",
"yun.btc",
"pay.coinbase.com",
"pay.bts.com",
"btc38.com",
"yunbi.com",
"coinbase.com",
"ripple.com"
};
return scam_accounts.find(account_name) != scam_accounts.end();
}
bool is_exchange(const std::string& account_name)
{
static std::set<std::string> exchange_accounts{
"poloniexcoldstorage",
"btc38-public-for-bts-cold",
"poloniexwallet",
"btercom",
"yunbi-cold-wallet",
"btc38-btsx-octo-72722",
"bittrex-deposit",
"btc38btsxwithdrawal"
};
return exchange_accounts.find(account_name) != exchange_accounts.end();
}
}
void generate_uia_sharedrop_genesis_plugin::generate_snapshot()
{
ilog("generate genesis plugin: generating snapshot now");
chain::database& d = database();
// Lookup the ID of the UIA we will be sharedropping on
std::string uia_symbol("PEERPLAYS");
const auto& assets_by_symbol = d.get_index_type<graphene::chain::asset_index>().indices().get<graphene::chain::by_symbol>();
auto itr = assets_by_symbol.find(uia_symbol);
FC_ASSERT(itr != assets_by_symbol.end(), "Unable to find asset named ${uia_symbol}", ("uia_symbol", uia_symbol));
graphene::chain::asset_id_type uia_id = itr->get_id();
ilog("Scanning for all balances of asset ${uia_symbol} (${uia_id})", ("uia_symbol", uia_symbol)("uia_id", uia_id));
uia_sharedrop_balance_object_index_type sharedrop_balances;
// load the balances from the input genesis file, if any
graphene::chain::genesis_state_type new_genesis_state;
if (!_input_genesis_filename.empty())
{
new_genesis_state = fc::json::from_file<graphene::chain::genesis_state_type>(_input_genesis_filename);
for (const graphene::chain::genesis_state_type::initial_bts_account_type& initial_bts_account : new_genesis_state.initial_bts_accounts)
{
std::string account_name = unmodify_account_name(initial_bts_account.name);
auto& account_by_name_index = d.get_index_type<graphene::chain::account_index>().indices().get<graphene::chain::by_name>();
auto account_iter = account_by_name_index.find(account_name);
FC_ASSERT(account_iter != account_by_name_index.end(), "No account ${name}", ("name", account_name));
uia_sharedrop_balance_object balance_object;
balance_object.account_id = account_iter->id;
balance_object.genesis = initial_bts_account.core_balance;
sharedrop_balances.insert(balance_object);
ilog("Loaded genesis balance for ${name}: ${balance}", ("name", account_name)("balance", initial_bts_account.core_balance));
}
}
new_genesis_state.initial_bts_accounts.clear();
auto& balance_index = d.get_index_type<graphene::chain::account_balance_index>().indices().get<graphene::chain::by_asset_balance>();
for (auto balance_iter = balance_index.begin(); balance_iter != balance_index.end(); ++balance_iter)
if (balance_iter->asset_type == uia_id && balance_iter->balance != graphene::chain::share_type())
{
if (is_special_account(balance_iter->owner) || is_exchange(balance_iter->owner(d).name) || is_scam(balance_iter->owner(d).name))
{
ilog("skipping balance in ${account_id} because special or exchange", ("account_id", balance_iter->owner));
}
else
{
auto sharedrop_balance_iter = sharedrop_balances.find(balance_iter->owner);
if (sharedrop_balance_iter == sharedrop_balances.end())
{
uia_sharedrop_balance_object balance_object;
balance_object.account_id = balance_iter->owner;
balance_object.balance = balance_iter->balance;
sharedrop_balances.insert(balance_object);
}
else
{
sharedrop_balances.modify(sharedrop_balance_iter, [&](uia_sharedrop_balance_object& balance_object) {
balance_object.balance = balance_iter->balance;
});
}
}
}
// scan for PEERPLAYS tied up in market orders
auto& limit_order_index = d.get_index_type<graphene::chain::limit_order_index>().indices().get<graphene::chain::by_account>();
for (auto limit_order_iter = limit_order_index.begin(); limit_order_iter != limit_order_index.end(); ++limit_order_iter)
{
if (limit_order_iter->sell_price.base.asset_id == uia_id)
{
if (is_special_account(limit_order_iter->seller) || is_exchange(limit_order_iter->seller(d).name) || is_scam(limit_order_iter->seller(d).name))
ilog("Skipping account ${name} because special/scam/exchange", ("name", limit_order_iter->seller(d).name));
else
{
auto sharedrop_balance_iter = sharedrop_balances.find(limit_order_iter->seller);
if (sharedrop_balance_iter == sharedrop_balances.end())
{
//ilog("found order for new account ${account_id}", ("account_id", limit_order_iter->seller));
uia_sharedrop_balance_object balance_object;
balance_object.account_id = limit_order_iter->seller;
balance_object.orders = limit_order_iter->for_sale;
sharedrop_balances.insert(balance_object);
}
else
{
//ilog("found order for existing account ${account_id}", ("account_id", limit_order_iter->seller));
sharedrop_balances.modify(sharedrop_balance_iter, [&](uia_sharedrop_balance_object& balance_object) {
balance_object.orders += limit_order_iter->for_sale;
});
}
}
}
}
// compute the sharedrop
for (auto sharedrop_balance_iter = sharedrop_balances.begin(); sharedrop_balance_iter != sharedrop_balances.end();)
{
auto this_iter = sharedrop_balance_iter;
++sharedrop_balance_iter;
sharedrop_balances.modify(this_iter, [&](uia_sharedrop_balance_object& balance_object) {
balance_object.sharedrop = balance_object.genesis + (balance_object.balance + balance_object.orders) * 10;
});
}
// Generate CSV file of all sharedrops and the balances we used to calculate them
std::ofstream csv_log_file;
csv_log_file.open(_csvlog_filename);
assert(csv_log_file.is_open());
csv_log_file << "name,genesis,balance,orders,sharedrop\n";
for (const uia_sharedrop_balance_object& balance_object : sharedrop_balances)
csv_log_file << balance_object.account_id(d).name << "," << balance_object.genesis.value << "," << balance_object.balance.value << "," << balance_object.orders.value << "," << balance_object.sharedrop.value << "\n";
ilog("CSV log written to file ${filename}", ("filename", _csvlog_filename));
csv_log_file.close();
//auto& account_index = d.get_index_type<graphene::chain::account_index>();
//auto& account_by_id_index = account_index.indices().get<graphene::chain::by_id>();
// inefficient way of crawling the graph, but we only do it once
std::set<graphene::chain::account_id_type> already_generated;
for (;;)
{
unsigned accounts_generated_this_round = 0;
for (const uia_sharedrop_balance_object& balance_object : sharedrop_balances)
{
const graphene::chain::account_id_type& account_id = balance_object.account_id;
const graphene::chain::share_type& sharedrop_amount = balance_object.sharedrop;
const graphene::chain::account_object& account_obj = account_id(d);
if (already_generated.find(account_id) == already_generated.end())
{
graphene::chain::genesis_state_type::initial_bts_account_type::initial_authority owner;
owner.weight_threshold = account_obj.owner.weight_threshold;
owner.key_auths = account_obj.owner.key_auths;
for (const auto& value : account_obj.owner.account_auths)
{
owner.account_auths.insert(std::make_pair(modify_account_name(value.first(d).name), value.second));
auto owner_balance_iter = sharedrop_balances.find(value.first);
if (owner_balance_iter == sharedrop_balances.end())
{
uia_sharedrop_balance_object balance_object;
balance_object.account_id = value.first;
sharedrop_balances.insert(balance_object);
}
}
owner.key_auths = account_obj.owner.key_auths;
owner.address_auths = account_obj.owner.address_auths;
graphene::chain::genesis_state_type::initial_bts_account_type::initial_authority active;
active.weight_threshold = account_obj.active.weight_threshold;
active.key_auths = account_obj.active.key_auths;
for (const auto& value : account_obj.active.account_auths)
{
active.account_auths.insert(std::make_pair(modify_account_name(value.first(d).name), value.second));
auto active_balance_iter = sharedrop_balances.find(value.first);
if (active_balance_iter == sharedrop_balances.end())
{
uia_sharedrop_balance_object balance_object;
balance_object.account_id = value.first;
sharedrop_balances.insert(balance_object);
}
}
active.key_auths = account_obj.active.key_auths;
active.address_auths = account_obj.active.address_auths;
new_genesis_state.initial_bts_accounts.emplace_back(
graphene::chain::genesis_state_type::initial_bts_account_type(modify_account_name(account_obj.name),
owner, active,
sharedrop_amount));
already_generated.insert(account_id);
++accounts_generated_this_round;
}
}
if (accounts_generated_this_round == 0)
break;
}
fc::json::save_to_file(new_genesis_state, _output_genesis_filename);
ilog("New genesis state written to file ${filename}", ("filename", _output_genesis_filename));
}
void generate_uia_sharedrop_genesis_plugin::plugin_shutdown()
{
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/app/plugin.hpp>
#include <graphene/chain/database.hpp>
#include <fc/thread/future.hpp>
namespace graphene { namespace generate_uia_sharedrop_genesis {
class generate_uia_sharedrop_genesis_plugin : public graphene::app::plugin {
public:
~generate_uia_sharedrop_genesis_plugin() {
}
std::string plugin_name()const override;
virtual void plugin_set_program_options(
boost::program_options::options_description &command_line_options,
boost::program_options::options_description &config_file_options
) override;
virtual void plugin_initialize( const boost::program_options::variables_map& options ) override;
virtual void plugin_startup() override;
virtual void plugin_shutdown() override;
private:
void block_applied(const graphene::chain::signed_block& b);
void generate_snapshot();
boost::program_options::variables_map _options;
fc::optional<uint32_t> _block_to_snapshot;
std::string _input_genesis_filename;
std::string _output_genesis_filename;
std::string _csvlog_filename;
};
class uia_sharedrop_balance_object
{
public:
graphene::chain::account_id_type account_id;
graphene::chain::share_type genesis;
graphene::chain::share_type balance;
graphene::chain::share_type orders;
graphene::chain::share_type sharedrop;
};
using namespace boost::multi_index;
struct by_account{};
typedef multi_index_container<uia_sharedrop_balance_object,
indexed_by<ordered_unique<tag<by_account>,
member<uia_sharedrop_balance_object, graphene::chain::account_id_type, &uia_sharedrop_balance_object::account_id> > > > uia_sharedrop_balance_object_index_type;
} } //graphene::generate_uia_sharedrop_genesis_plugin

View file

@ -28,6 +28,8 @@
#include <graphene/utilities/key_conversion.hpp>
#include <boost/range/algorithm_ext/insert.hpp>
#include <fc/smart_ref_impl.hpp>
#include <fc/thread/thread.hpp>
@ -66,11 +68,14 @@ void witness_plugin::plugin_set_program_options(
{
auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan")));
string witness_id_example = fc::json::to_string(chain::witness_id_type(5));
string witness_id_example2 = fc::json::to_string(chain::witness_id_type(6));
command_line_options.add_options()
("enable-stale-production", bpo::bool_switch()->notifier([this](bool e){_production_enabled = e;}), "Enable block production, even if the chain is stale.")
("required-participation", bpo::bool_switch()->notifier([this](int e){_required_witness_participation = uint32_t(e*GRAPHENE_1_PERCENT);}), "Percent of witnesses (0-99) that must be participating in order to produce blocks")
("witness-id,w", bpo::value<vector<string>>()->composing()->multitoken(),
("ID of witness controlled by this node (e.g. " + witness_id_example + ", quotes are required, may specify multiple times)").c_str())
("witness-ids,W", bpo::value<string>(),
("IDs of multiple witnesses controlled by this node (e.g. [" + witness_id_example + ", " + witness_id_example2 + "], quotes are required)").c_str())
("private-key", bpo::value<vector<string>>()->composing()->multitoken()->
DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()), graphene::utilities::key_to_wif(default_priv_key))),
"Tuple of [PublicKey, WIF private key] (may specify multiple times)")
@ -88,6 +93,8 @@ void witness_plugin::plugin_initialize(const boost::program_options::variables_m
ilog("witness plugin: plugin_initialize() begin");
_options = &options;
LOAD_VALUE_SET(options, "witness-id", _witnesses, chain::witness_id_type)
if (options.count("witness-ids"))
boost::insert(_witnesses, fc::json::from_string(options.at("witness-ids").as<string>()).as<vector<chain::witness_id_type>>());
if( options.count("private-key") )
{
@ -191,6 +198,7 @@ block_production_condition::block_production_condition_enum witness_plugin::bloc
break;
case block_production_condition::not_time_yet:
//ilog("Not producing block because slot has not yet arrived");
dlog("Not producing block because slot has not yet arrived");
break;
case block_production_condition::no_private_key:
ilog("Not producing block because I don't have the private key for ${scheduled_key}", (capture) );
@ -247,6 +255,7 @@ block_production_condition::block_production_condition_enum witness_plugin::mayb
assert( now > db.head_block_time() );
graphene::chain::witness_id_type scheduled_witness = db.get_scheduled_witness( slot );
// we must control the witness scheduled to produce the next block.
if( _witnesses.find( scheduled_witness ) == _witnesses.end() )
{
@ -255,6 +264,7 @@ block_production_condition::block_production_condition_enum witness_plugin::mayb
}
fc::time_point_sec scheduled_time = db.get_slot_time( slot );
wdump((slot)(scheduled_witness)(scheduled_time)(now));
graphene::chain::public_key_type scheduled_key = scheduled_witness( db ).signing_key;
auto private_key_itr = _private_keys.find( scheduled_key );
@ -271,18 +281,28 @@ block_production_condition::block_production_condition_enum witness_plugin::mayb
return block_production_condition::low_participation;
}
// the local clock must be at least 1 second ahead of head_block_time.
//if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM)
//if( (now - db.head_block_time()).to_seconds() < GRAPHENE_MIN_BLOCK_INTERVAL ) {
// return block_production_condition::local_clock; //Not producing block because head block is less than a second old.
//}
if( llabs((scheduled_time - now).count()) > fc::milliseconds( 500 ).count() )
{
capture("scheduled_time", scheduled_time)("now", now);
return block_production_condition::lag;
}
//if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM)
ilog("Witness ${id} production slot has arrived; generating a block now...", ("id", scheduled_witness));
auto block = db.generate_block(
scheduled_time,
scheduled_witness,
private_key_itr->second,
_production_skip_flags
);
capture("n", block.block_num())("t", block.timestamp)("c", now);
fc::async( [this,block](){ p2p_node().broadcast(net::block_message(block)); } );

View file

@ -62,7 +62,7 @@ struct static_variant_map_visitor
template< typename T >
result_type operator()( const T& dummy )
{
assert( which == static_cast<int>(m.which_to_name.size()) );
//assert( which == (int)m.which_to_name.size() );
std::string name = clean_name( fc::get_typename<T>::name() );
m.name_to_which[ name ] = which;
m.which_to_name.push_back( name );

View file

@ -191,6 +191,8 @@ struct wallet_data
key_label_index_type labeled_keys;
blind_receipt_index_type blind_receipts;
std::map<rock_paper_scissors_throw_commit, rock_paper_scissors_throw_reveal> committed_game_moves;
string ws_server = "ws://localhost:8090";
string ws_user;
string ws_password;
@ -366,6 +368,8 @@ class wallet_api
*/
vector<operation_detail> get_relative_account_history(string name, uint32_t stop, int limit, uint32_t start)const;
vector<account_balance_object> list_core_accounts()const;
vector<bucket_object> get_market_history(string symbol, string symbol2, uint32_t bucket, fc::time_point_sec start, fc::time_point_sec end)const;
vector<limit_order_object> get_limit_orders(string a, string b, uint32_t limit)const;
vector<call_order_object> get_call_orders(string a, uint32_t limit)const;
@ -627,6 +631,10 @@ class wallet_api
* @return Whether a public key is known
*/
bool is_public_key_registered(string public_key) const;
/**
* @param role - active | owner | memo
*/
pair<public_key_type,string> get_private_key_from_password( string account, string role, string password )const;
/** Converts a signed_transaction in JSON form to its binary representation.
*
@ -1392,6 +1400,38 @@ class wallet_api
bool approve,
bool broadcast = false);
/** Change your witness votes.
*
* An account can publish a list of all witnesses they approve of.
* 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.
* This command allows you to add or remove one or more witnesses from this list
* in one call. When you are changing your vote on several witnesses, this
* may be easier than multiple `vote_for_witness` and
* `set_desired_witness_and_committee_member_count` calls.
*
* @note you cannot vote against a witness, you can only vote for the witness
* or not vote for the witness.
*
* @param voting_account the name or id of the account who is voting with their shares
* @param witnesses_to_approve the names or ids of the witnesses owner accounts you wish
* to approve (these will be added to the list of witnesses
* you currently approve). This list can be empty.
* @param witnesses_to_reject the names or ids of the witnesses owner accounts you wish
* to reject (these will be removed fromthe list of witnesses
* you currently approve). This list can be empty.
* @param desired_number_of_witnesses the number of witnesses you believe the network
* should have. You must vote for at least this many
* witnesses. You can set this to 0 to abstain from
* voting on the number of witnesses.
* @param broadcast true if you wish to broadcast the transaction
* @return the signed transaction changing your vote for the given witnesses
*/
signed_transaction update_witness_votes(string voting_account,
std::vector<std::string> witnesses_to_approve,
std::vector<std::string> witnesses_to_reject,
uint16_t desired_number_of_witnesses,
bool broadcast = false);
/** Set the voting proxy for an account.
*
* If a user does not wish to take an active part in voting, they can choose
@ -1587,6 +1627,63 @@ class wallet_api
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions,
bool broadcast = false);
/** Creates a new tournament
* @param creator the accout that is paying the fee to create the tournament
* @param options the options detailing the specifics of the tournament
* @return the signed version of the transaction
*/
signed_transaction tournament_create( string creator, tournament_options options, bool broadcast = false );
/** Join an existing tournament
* @param payer_account the account that is paying the buy-in and the fee to join the tournament
* @param player_account the account that will be playing in the tournament
* @param buy_in_amount buy_in to pay
* @param buy_in_asset_symbol buy_in asset
* @param tournament_id the tournament the user wishes to join
* @param broadcast true if you wish to broadcast the transaction
* @return the signed version of the transaction
*/
signed_transaction tournament_join( string payer_account, string player_account, tournament_id_type tournament_id, string buy_in_amount, string buy_in_asset_symbol, bool broadcast = false );
/** Leave an existing tournament
* @param payer_account the account that is paying the fee
* @param player_account the account that would be playing in the tournament
* @param tournament_id the tournament the user wishes to leave
* @param broadcast true if you wish to broadcast the transaction
* @return the signed version of the transaction
*/
signed_transaction tournament_leave(string payer_account, string player_account, tournament_id_type tournament_id, bool broadcast = false);
/** Get a list of upcoming tournaments
* @param limit the number of tournaments to return
*/
vector<tournament_object> get_upcoming_tournaments(uint32_t limit);
vector<tournament_object> get_tournaments(tournament_id_type stop,
unsigned limit,
tournament_id_type start);
vector<tournament_object> get_tournaments_by_state(tournament_id_type stop,
unsigned limit,
tournament_id_type start,
tournament_state state);
/** Get specific information about a tournament
* @param tournament_id the ID of the tournament
*/
tournament_object get_tournament(tournament_id_type id);
/** Play a move in the rock-paper-scissors game
* @param game_id the id of the game
* @param player_account the name of the player
* @param gesture rock, paper, or scissors
* @return the signed version of the transaction
*/
signed_transaction rps_throw(game_id_type game_id,
string player_account,
rock_paper_scissors_gesture gesture,
bool broadcast);
void dbg_make_uia(string creator, string symbol);
void dbg_make_mia(string creator, string symbol);
void dbg_push_blocks( std::string src_filename, uint32_t count );
@ -1634,6 +1731,7 @@ FC_REFLECT( graphene::wallet::wallet_data,
(pending_account_registrations)(pending_witness_registrations)
(labeled_keys)
(blind_receipts)
(committed_game_moves)
(ws_server)
(ws_user)
(ws_password)
@ -1704,6 +1802,7 @@ FC_API( graphene::wallet::wallet_api,
(import_balance)
(suggest_brain_key)
(derive_owner_keys_from_brain_key)
(get_private_key_from_password)
(register_account)
(upgrade_account)
(create_account_with_brain_key)
@ -1742,6 +1841,7 @@ FC_API( graphene::wallet::wallet_api,
(withdraw_vesting)
(vote_for_committee_member)
(vote_for_witness)
(update_witness_votes)
(set_voting_proxy)
(set_desired_witness_and_committee_member_count)
(get_account)
@ -1751,6 +1851,7 @@ FC_API( graphene::wallet::wallet_api,
(get_account_history)
(get_relative_account_history)
(is_public_key_registered)
(list_core_accounts)
(get_market_history)
(get_global_properties)
(get_dynamic_global_properties)
@ -1802,5 +1903,13 @@ FC_API( graphene::wallet::wallet_api,
(propose_create_betting_market)
(place_bet)
(propose_resolve_betting_market_group)
(tournament_create)
(tournament_join)
(tournament_leave)
(rps_throw)
(get_upcoming_tournaments)
(get_tournaments)
(get_tournaments_by_state)
(get_tournament)
(get_order_book)
)

View file

@ -1,4 +1,4 @@
/*
u/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
@ -33,6 +33,7 @@
#include <boost/version.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/multiprecision/integer.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/map.hpp>
@ -60,9 +61,17 @@
#include <fc/crypto/hex.hpp>
#include <fc/thread/mutex.hpp>
#include <fc/thread/scoped_lock.hpp>
#include <fc/crypto/rand.hpp>
#include <graphene/app/api.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/protocol/rock_paper_scissors.hpp>
#include <graphene/chain/rock_paper_scissors.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
#include <graphene/utilities/git_revision.hpp>
#include <graphene/utilities/key_conversion.hpp>
@ -125,6 +134,7 @@ public:
std::string operator()(const account_update_operation& op)const;
std::string operator()(const asset_create_operation& op)const;
std::string operator()(const asset_dividend_distribution_operation& op)const;
std::string operator()(const tournament_payout_operation& op)const;
};
template<class T>
@ -339,6 +349,126 @@ private:
}
}
// return true if any of my_accounts are players in this tournament
bool tournament_is_relevant_to_my_accounts(const tournament_object& tournament_obj)
{
tournament_details_object tournament_details = get_object<tournament_details_object>(tournament_obj.tournament_details_id);
for (const account_object& account_obj : _wallet.my_accounts)
if (tournament_details.registered_players.find(account_obj.id) != tournament_details.registered_players.end())
return true;
return false;
}
fc::mutex _subscribed_object_changed_mutex;
void subscribed_object_changed(const variant& changed_objects_variant)
{
fc::scoped_lock<fc::mutex> lock(_resync_mutex);
fc::variants changed_objects = changed_objects_variant.get_array();
for (const variant& changed_object_variant : changed_objects)
{
// changed_object_variant is either the object, or just the id if the object was removed
if (changed_object_variant.is_object())
{
try
{
object_id_type id = changed_object_variant["id"].as<tournament_id_type>();
tournament_object current_tournament_obj = changed_object_variant.as<tournament_object>();
auto tournament_cache_iter = tournament_cache.find(id);
if (tournament_cache_iter != tournament_cache.end())
{
const tournament_object& cached_tournament_obj = *tournament_cache_iter;
if (cached_tournament_obj.get_state() != current_tournament_obj.get_state())
{
ilog("Tournament ${id} changed state from ${old} to ${new}",
("id", id)
("old", cached_tournament_obj.get_state())
("new", current_tournament_obj.get_state()));
if (current_tournament_obj.get_state() == tournament_state::in_progress)
monitor_matches_in_tournament(current_tournament_obj);
}
tournament_cache.modify(tournament_cache_iter, [&](tournament_object& obj) { obj = current_tournament_obj; });
}
else if (tournament_is_relevant_to_my_accounts(current_tournament_obj))
{
ilog ("We were just notified about an in-progress tournament ${id} relevant to our accounts",
("id", current_tournament_obj.id));
tournament_cache.insert(current_tournament_obj);
if (current_tournament_obj.get_state() == tournament_state::in_progress)
monitor_matches_in_tournament(current_tournament_obj);
}
continue;
}
catch (const fc::exception& e)
{
// idump((e));
}
try
{
object_id_type id = changed_object_variant["id"].as<match_id_type>();
match_object current_match_obj = changed_object_variant.as<match_object>();
auto match_cache_iter = match_cache.find(id);
if (match_cache_iter != match_cache.end())
{
const match_object& cached_match_obj = *match_cache_iter;
if (cached_match_obj.get_state() != current_match_obj.get_state() ||
cached_match_obj.games.size() != current_match_obj.games.size())
{
ilog("match ${id} changed state from ${old} to ${new}",
("id", id)
("old", cached_match_obj.get_state())
("new", current_match_obj.get_state()));
match_in_new_state(current_match_obj);
}
match_cache.modify(match_cache_iter, [&](match_object& obj) { obj = current_match_obj; });
}
continue;
}
catch (const fc::exception& e)
{
// idump((e));
}
try
{
object_id_type id = changed_object_variant["id"].as<game_id_type>();
game_object current_game_obj = changed_object_variant.as<game_object>();
auto game_cache_iter = game_cache.find(id);
if (game_cache_iter != game_cache.end())
{
const game_object& cached_game_obj = *game_cache_iter;
if (cached_game_obj.get_state() != current_game_obj.get_state())
{
ilog("game ${id} changed state from ${old} to ${new}",
("id", id)
("old", cached_game_obj.get_state())
("new", current_game_obj.get_state()));
game_in_new_state(current_game_obj);
}
game_cache.modify(game_cache_iter, [&](game_object& obj) { obj = current_game_obj; });
}
continue;
}
catch (const fc::exception& e)
{
// idump((e));
}
try
{
object_id_type id = changed_object_variant["id"].as<account_id_type>();
if (_wallet.my_accounts.find(id) != _wallet.my_accounts.end())
{
account_object account = changed_object_variant.as<account_object>();
_wallet.update_account(account);
}
continue;
}
catch (const fc::exception& e)
{
// idump((e));
}
}
}
}
void enable_umask_protection()
{
#ifdef __unix__
@ -411,10 +541,16 @@ public:
on_block_applied( block_id );
} );
_remote_db->set_subscribe_callback( [this](const variant& object )
{
on_subscribe_callback( object );
}, false );
_wallet.chain_id = _chain_id;
_wallet.ws_server = initial_data.ws_server;
_wallet.ws_user = initial_data.ws_user;
_wallet.ws_password = initial_data.ws_password;
}
virtual ~wallet_api_impl()
{
@ -449,6 +585,12 @@ public:
fc::async([this]{resync();}, "Resync after block");
}
void on_subscribe_callback( const variant& object )
{
//idump((object));
fc::async([this, object]{subscribed_object_changed(object);}, "Object changed");
}
bool copy_wallet_file( string destination_filename )
{
fc::path src_path = get_wallet_filename();
@ -516,6 +658,7 @@ public:
result["participation"] = (100*dynamic_props.recent_slots_filled.popcount()) / 128.0;
result["active_witnesses"] = global_props.active_witnesses;
result["active_committee_members"] = global_props.active_committee_members;
result["entropy"] = dynamic_props.random;
return result;
}
@ -711,6 +854,147 @@ public:
vector< signed_transaction > import_balance( string name_or_id, const vector<string>& wif_keys, bool broadcast );
void game_in_new_state(const game_object& game_obj)
{ try {
if (game_obj.get_state() == game_state::expecting_commit_moves)
{
if (game_obj.players.size() != 2) // we only support RPS, a 2 player game
return;
const rock_paper_scissors_game_details& rps_details = game_obj.game_details.get<rock_paper_scissors_game_details>();
for (unsigned i = 0; i < 2; ++i)
{
if (!rps_details.commit_moves.at(i)) // if this player hasn't committed their move
{
const account_id_type& account_id = game_obj.players[i];
if (_wallet.my_accounts.find(account_id) != _wallet.my_accounts.end()) // and they're us
{
ilog("Game ${game_id}: it is ${account_name}'s turn to commit their move",
("game_id", game_obj.id)
("account_name", get_account(account_id).name));
}
}
}
}
else if (game_obj.get_state() == game_state::expecting_reveal_moves)
{
if (game_obj.players.size() != 2) // we only support RPS, a 2 player game
return;
const rock_paper_scissors_game_details& rps_details = game_obj.game_details.get<rock_paper_scissors_game_details>();
for (unsigned i = 0; i < 2; ++i)
{
if (rps_details.commit_moves.at(i) &&
!rps_details.reveal_moves.at(i)) // if this player has committed but not revealed
{
const account_id_type& account_id = game_obj.players[i];
if (_wallet.my_accounts.find(account_id) != _wallet.my_accounts.end()) // and they're us
{
if (self.is_locked())
ilog("Game ${game_id}: unable to broadcast ${account_name}'s reveal because the wallet is locked",
("game_id", game_obj.id)
("account_name", get_account(account_id).name));
else
{
ilog("Game ${game_id}: it is ${account_name}'s turn to reveal their move",
("game_id", game_obj.id)
("account_name", get_account(account_id).name));
auto iter = _wallet.committed_game_moves.find(*rps_details.commit_moves.at(i));
if (iter != _wallet.committed_game_moves.end())
{
const rock_paper_scissors_throw_reveal& reveal = iter->second;
game_move_operation move_operation;
move_operation.game_id = game_obj.id;
move_operation.player_account_id = account_id;
move_operation.move = reveal;
signed_transaction trx;
trx.operations = {move_operation};
set_operation_fees( trx, _remote_db->get_global_properties().parameters.current_fees);
trx.validate();
ilog("Broadcasting reveal...");
trx = sign_transaction(trx, true);
ilog("Reveal broadcast, transaction id is ${id}", ("id", trx.id()));
}
}
}
}
}
}
} FC_RETHROW_EXCEPTIONS(warn, "") }
void match_in_new_state(const match_object& match_obj)
{ try {
if (match_obj.get_state() == match_state::match_in_progress)
{
for (const account_id_type& account_id : match_obj.players)
{
if (_wallet.my_accounts.find(account_id) != _wallet.my_accounts.end())
{
ilog("Match ${match} is now in progress for player ${account}",
("match", match_obj.id)("account", get_account(account_id).name));
for (const game_id_type& game_id : match_obj.games)
{
game_object game_obj = get_object<game_object>(game_id);
auto insert_result = game_cache.insert(game_obj);
if (insert_result.second)
game_in_new_state(game_obj);
}
}
}
}
} FC_RETHROW_EXCEPTIONS(warn, "") }
// Cache all matches in the tournament, which will also register us for
// updates on those matches
void monitor_matches_in_tournament(const tournament_object& tournament_obj)
{ try {
tournament_details_object tournament_details = get_object<tournament_details_object>(tournament_obj.tournament_details_id);
for (const match_id_type& match_id : tournament_details.matches)
{
match_object match_obj = get_object<match_object>(match_id);
auto insert_result = match_cache.insert(match_obj);
if (insert_result.second)
match_in_new_state(match_obj);
}
} FC_RETHROW_EXCEPTIONS(warn, "") }
void resync_active_tournaments()
{
// check to see if any of our accounts are registered for tournaments
// the real purpose of this is to ensure that we are subscribed for callbacks on these tournaments
ilog("Checking my accounts for active tournaments",);
tournament_cache.clear();
match_cache.clear();
game_cache.clear();
for (const account_object& my_account : _wallet.my_accounts)
{
std::vector<tournament_id_type> tournament_ids = _remote_db->get_registered_tournaments(my_account.id, 100);
for (const tournament_id_type& tournament_id : tournament_ids)
{
try
{
tournament_object tournament = get_object<tournament_object>(tournament_id);
auto insert_result = tournament_cache.insert(tournament);
if (insert_result.second)
{
// then this is the first time we've seen this tournament
monitor_matches_in_tournament(tournament);
}
tournament_ids.push_back(tournament.id);
}
catch (const fc::exception& e)
{
edump((e)(tournament_id));
}
}
if (!tournament_ids.empty())
ilog("Account ${my_account} is registered for tournaments: ${tournaments}", ("my_account", my_account.name)("tournaments", tournament_ids));
else
ilog("Account ${my_account} is not registered for any tournaments", ("my_account", my_account.name));
}
}
bool load_wallet_file(string wallet_filename = "")
{
// TODO: Merge imported wallet with existing wallet,
@ -771,6 +1055,8 @@ public:
}
}
resync_active_tournaments();
return true;
}
void save_wallet_file(string wallet_filename = "")
@ -1476,6 +1762,11 @@ public:
witness_create_op.witness_account = witness_account.id;
witness_create_op.block_signing_key = witness_public_key;
witness_create_op.url = url;
secret_hash_type::encoder enc;
fc::raw::pack(enc, witness_private_key);
fc::raw::pack(enc, secret_hash_type());
witness_create_op.initial_secret = secret_hash_type::hash(enc.result());
if (_remote_db->get_witness_by_account(witness_create_op.witness_account))
FC_THROW("Account ${owner_account} is already a witness", ("owner_account", owner_account));
@ -1504,8 +1795,10 @@ public:
witness_update_op.witness_account = witness_account.id;
if( url != "" )
witness_update_op.new_url = url;
if( block_signing_key != "" )
if( block_signing_key != "" ) {
witness_update_op.new_signing_key = public_key_type( block_signing_key );
witness_update_op.new_initial_secret = secret_hash_type::hash(secret_hash_type());
}
signed_transaction tx;
tx.operations.push_back( witness_update_op );
@ -1758,6 +2051,47 @@ public:
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (voting_account)(witness)(approve)(broadcast) ) }
signed_transaction update_witness_votes(string voting_account,
std::vector<std::string> witnesses_to_approve,
std::vector<std::string> witnesses_to_reject,
uint16_t desired_number_of_witnesses,
bool broadcast /* = false */)
{ try {
account_object voting_account_object = get_account(voting_account);
for (const std::string& witness : witnesses_to_approve)
{
account_id_type witness_owner_account_id = get_account_id(witness);
fc::optional<witness_object> witness_obj = _remote_db->get_witness_by_account(witness_owner_account_id);
if (!witness_obj)
FC_THROW("Account ${witness} is not registered as a witness", ("witness", witness));
auto insert_result = voting_account_object.options.votes.insert(witness_obj->vote_id);
if (!insert_result.second)
FC_THROW("Account ${account} was already voting for witness ${witness}", ("account", voting_account)("witness", witness));
}
for (const std::string& witness : witnesses_to_reject)
{
account_id_type witness_owner_account_id = get_account_id(witness);
fc::optional<witness_object> witness_obj = _remote_db->get_witness_by_account(witness_owner_account_id);
if (!witness_obj)
FC_THROW("Account ${witness} is not registered as a witness", ("witness", witness));
unsigned votes_removed = voting_account_object.options.votes.erase(witness_obj->vote_id);
if (!votes_removed)
FC_THROW("Account ${account} is already not voting for witness ${witness}", ("account", voting_account)("witness", witness));
}
voting_account_object.options.num_witness = desired_number_of_witnesses;
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)(witnesses_to_approve)(witnesses_to_reject)(desired_number_of_witnesses)(broadcast) ) }
signed_transaction set_voting_proxy(string account_to_modify,
optional<string> voting_account,
bool broadcast /* = false */)
@ -2144,6 +2478,21 @@ public:
return ss.str();
};
m["list_core_accounts"] = [this](variant result, const fc::variants& a)
{
std::stringstream ss;
auto balances = result.as<vector<account_balance_object>>();
for (const account_balance_object& balance: balances)
{
const account_object& account = get_account(balance.owner);
//ss << account.name << " " << std::string(balance.id) << " " << balance.balance.value << "\n";
ss << account.name << " " << std::string(balance.id) << " " << get_asset(balance.asset_type).amount_to_pretty_string(balance.balance) << "\n";
}
return ss.str();
};
m["get_blind_balances"] = [this](variant result, const fc::variants& a)
{
auto r = result.as<vector<asset>>();
@ -2207,6 +2556,155 @@ public:
}
return ss.str();
};
m["get_upcoming_tournaments"] = m["get_tournaments"] = m["get_tournaments_by_state"] = [this](variant result, const fc::variants& a)
{
const vector<tournament_object> tournaments = result.as<vector<tournament_object> >();
std::stringstream ss;
ss << "ID GAME BUY IN PLAYERS\n";
ss << "====================================================================================\n";
for( const tournament_object& tournament_obj : tournaments )
{
asset_object buy_in_asset = get_asset(tournament_obj.options.buy_in.asset_id);
ss << fc::variant(tournament_obj.id).as<std::string>() << " "
<< buy_in_asset.amount_to_pretty_string(tournament_obj.options.buy_in.amount) << " "
<< tournament_obj.options.number_of_players << " players\n";
switch (tournament_obj.get_state())
{
case tournament_state::accepting_registrations:
{
ss << " Waiting for players, " << tournament_obj.registered_players << " of " << tournament_obj.options.number_of_players << " have registered\n";
ss << " If enough players register, the game will start ";
if (tournament_obj.options.start_time)
ss << "at " << tournament_obj.options.start_time->to_iso_string() << "\n";
else
ss << *tournament_obj.options.start_delay << " seconds after the last player registers\n";
break;
}
case tournament_state::awaiting_start:
{
ss << " All players have registered, tournament will start at " << tournament_obj.start_time->to_iso_string() << "\n";
break;
}
case tournament_state::in_progress:
{
ss << " Tournament started at " << tournament_obj.start_time->to_iso_string() << "\n";
break;
}
case tournament_state::registration_period_expired:
{
ss << " Tournament was canceled at " << tournament_obj.options.registration_deadline.to_iso_string() << ", not enough players registered\n";
break;
}
case tournament_state::concluded:
{
ss << " Tournament finished at " << tournament_obj.end_time->to_iso_string() << "\n";
break;
}
}
}
return ss.str();
};
m["get_tournament"] = [this](variant result, const fc::variants& a)
{
std::stringstream ss;
tournament_object tournament = result.as<tournament_object>();
tournament_details_object tournament_details = _remote_db->get_objects({result["tournament_details_id"].as<object_id_type>()})[0].as<tournament_details_object>();
tournament_state state = tournament.get_state();
if (state == tournament_state::accepting_registrations)
{
ss << "Tournament is accepting registrations\n";
ss << "Players " << tournament.registered_players << "/" << tournament.options.number_of_players << ":\n";
for (const account_id_type& player : tournament_details.registered_players)
ss << "\t" << get_account(player).name << "\n";
}
else if (state == tournament_state::registration_period_expired)
{
ss << "Tournament registration period expired\n";
ss << "Players " << tournament.registered_players << "/" << tournament.options.number_of_players << ":\n";
for (const account_id_type& player : tournament_details.registered_players)
ss << "\t" << get_account(player).name << "\n";
}
else if (state == tournament_state::awaiting_start)
{
ss << "Tournament starts at " << tournament.start_time->to_iso_string() << "\n";
ss << "Players:\n";
for (const account_id_type& player : tournament_details.registered_players)
ss << "\t" << get_account(player).name << "\n";
}
else if (state == tournament_state::in_progress ||
state == tournament_state::concluded)
{
unsigned num_matches = tournament_details.matches.size();
uint32_t num_rounds = boost::multiprecision::detail::find_msb(tournament_details.matches.size() + 1);
unsigned num_rows = (num_matches + 1) * 2 - 1;
for (unsigned row = 0; row < num_rows; ++row)
{
for (unsigned round = 0; round <= num_rounds; ++round)
{
unsigned row_offset = (1 << round) - 1;
unsigned row_vertical_spacing = 1 << (round + 1);
if (row >= row_offset &&
(row - row_offset) % row_vertical_spacing == 0)
{
unsigned player_number_in_round = (row - row_offset) / row_vertical_spacing;
unsigned first_player_in_round = (num_matches - (num_matches >> round)) * 2;
unsigned player_number = first_player_in_round + player_number_in_round;
unsigned match_number = player_number / 2;
unsigned player_in_match = player_number % 2;
std::string player_name;
if (round == num_rounds)
{
match_object match = get_object<match_object>(tournament_details.matches[num_matches - 1]);
if (match.get_state() == match_state::match_complete &&
!match.match_winners.empty())
{
assert(match.match_winners.size() == 1);
player_name = get_account(*match.match_winners.begin()).name;
}
}
else
{
match_object match = get_object<match_object>(tournament_details.matches[match_number]);
if (!match.players.empty())
{
if (player_in_match < match.players.size())
player_name = get_account(match.players[player_in_match]).name;
else
player_name = "[bye]";
}
}
ss << "__";
ss << std::setfill('_') << std::setw(10) << player_name.substr(0,10);
ss << "__";
}
else
ss << " ";
if (round != num_rounds)
{
unsigned round_horizontal_spacing = 1 << round;
unsigned next_row_vertical_spacing = 1 << (round + 2);
for (unsigned i = 0; i < round_horizontal_spacing; ++i)
{
if ((row - 1 - i - row_offset) % next_row_vertical_spacing == 0)
ss << "\\";
else if ((row - row_vertical_spacing + i - row_offset) % next_row_vertical_spacing == 0)
ss << "/";
else
ss << " ";
}
}
}
ss << "\n";
}
}
return ss.str();
};
m["get_order_book"] = [this](variant result, const fc::variants& a)
{
auto orders = result.as<order_book>();
@ -2668,6 +3166,24 @@ public:
static_variant_map _operation_which_map = create_static_variant_map< operation >();
typedef multi_index_container<
tournament_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > > > > tournament_index_type;
tournament_index_type tournament_cache;
typedef multi_index_container<
match_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > > > > match_index_type;
match_index_type match_cache;
typedef multi_index_container<
game_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > > > > game_index_type;
game_index_type game_cache;
#ifdef __unix__
mode_t _old_umask;
#endif
@ -2794,6 +3310,18 @@ std::string operation_printer::operator()(const asset_dividend_distribution_oper
return "";
}
std::string operation_printer::operator()(const tournament_payout_operation& op)const
{
asset_object payout_asset = wallet.get_asset(op.payout_amount.asset_id);
out << "Tournament #" << std::string(object_id_type(op.tournament_id)) << " Payout : "
<< "Account '" << wallet.get_account(op.payout_account_id).name
<< "', Amount " << payout_asset.amount_to_pretty_string(op.payout_amount) << ", Type "
<< (op.type == payout_type::buyin_refund ? "buyin refund" : (op.type == payout_type::rake_fee ? "rake fee" : "prize award"))
<< ".";
return "";
}
std::string operation_result_printer::operator()(const void_result& x) const
{
return "";
@ -2936,6 +3464,10 @@ vector<operation_detail> wallet_api::get_relative_account_history(string name, u
}
return result;
}
vector<account_balance_object> wallet_api::list_core_accounts()const
{
return my->_remote_hist->list_core_accounts();
}
vector<bucket_object> wallet_api::get_market_history( string symbol1, string symbol2, uint32_t bucket , fc::time_point_sec start, fc::time_point_sec end )const
{
@ -2998,6 +3530,13 @@ bool wallet_api::is_public_key_registered(string public_key) const
return is_known;
}
pair<public_key_type,string> wallet_api::get_private_key_from_password( string account, string role, string password )const {
auto seed = password + account + role;
FC_ASSERT( seed.size() );
auto secret = fc::sha256::hash( seed.c_str(), seed.size() );
auto priv = fc::ecc::private_key::regenerate( secret );
return std::make_pair( public_key_type( priv.get_public_key() ), key_to_wif( priv ) );
}
string wallet_api::serialize_transaction( signed_transaction tx )const
{
@ -3465,6 +4004,15 @@ signed_transaction wallet_api::vote_for_witness(string voting_account,
return my->vote_for_witness(voting_account, witness, approve, broadcast);
}
signed_transaction wallet_api::update_witness_votes(string voting_account,
std::vector<std::string> witnesses_to_approve,
std::vector<std::string> witnesses_to_reject,
uint16_t desired_number_of_witnesses,
bool broadcast /* = false */)
{
return my->update_witness_votes(voting_account, witnesses_to_approve, witnesses_to_reject, desired_number_of_witnesses, broadcast);
}
signed_transaction wallet_api::set_voting_proxy(string account_to_modify,
optional<string> voting_account,
bool broadcast /* = false */)
@ -3724,6 +4272,7 @@ void wallet_api::unlock(string password)
my->_keys = std::move(pk.keys);
my->_checksum = pk.checksum;
my->self.lock_changed(false);
my->resync_active_tournaments();
} FC_CAPTURE_AND_RETHROW() }
void wallet_api::set_password( string password )
@ -4673,6 +5222,150 @@ signed_transaction wallet_api::propose_resolve_betting_market_group(
return my->sign_transaction(tx, broadcast);
}
signed_transaction wallet_api::tournament_create( string creator, tournament_options options, bool broadcast )
{
FC_ASSERT( !is_locked() );
account_object creator_account_obj = get_account(creator);
signed_transaction tx;
tournament_create_operation op;
op.creator = creator_account_obj.get_id();
op.options = options;
tx.operations = {op};
my->set_operation_fees( tx, my->_remote_db->get_global_properties().parameters.current_fees );
tx.validate();
return my->sign_transaction( tx, broadcast );
}
signed_transaction wallet_api::tournament_join( string payer_account,
string player_account,
tournament_id_type tournament_id,
string buy_in_amount,
string buy_in_asset_symbol,
bool broadcast )
{
FC_ASSERT( !is_locked() );
account_object payer_account_obj = get_account(payer_account);
account_object player_account_obj = get_account(player_account);
//graphene::chain::tournament_object tournament_obj = my->get_object<graphene::chain::tournament_object>(tournament_id);
fc::optional<asset_object> buy_in_asset_obj = get_asset(buy_in_asset_symbol);
FC_ASSERT(buy_in_asset_obj, "Could not find asset matching ${asset}", ("asset", buy_in_asset_symbol));
signed_transaction tx;
tournament_join_operation op;
op.payer_account_id = payer_account_obj.get_id();
op.player_account_id = player_account_obj.get_id();
op.tournament_id = tournament_id;
op.buy_in = buy_in_asset_obj->amount_from_string(buy_in_amount);
tx.operations = {op};
my->set_operation_fees( tx, my->_remote_db->get_global_properties().parameters.current_fees );
tx.validate();
return my->sign_transaction( tx, broadcast );
}
signed_transaction wallet_api::tournament_leave( string canceling_account,
string player_account,
tournament_id_type tournament_id,
bool broadcast)
{
FC_ASSERT( !is_locked() );
account_object player_account_obj = get_account(player_account);
account_object canceling_account_obj = get_account(canceling_account);
//graphene::chain::tournament_object tournament_obj = my->get_object<graphene::chain::tournament_object>(tournament_id);
signed_transaction tx;
tournament_leave_operation op;
op.canceling_account_id = canceling_account_obj.get_id();
op.player_account_id = player_account_obj.get_id();
op.tournament_id = tournament_id;
tx.operations = {op};
my->set_operation_fees( tx, my->_remote_db->get_global_properties().parameters.current_fees );
tx.validate();
return my->sign_transaction( tx, broadcast );
}
vector<tournament_object> wallet_api::get_upcoming_tournaments(uint32_t limit)
{
return my->_remote_db->get_tournaments_in_state(tournament_state::accepting_registrations, limit);
}
vector<tournament_object> wallet_api::get_tournaments(tournament_id_type stop,
unsigned limit,
tournament_id_type start) {
return my->_remote_db->get_tournaments(stop, limit, start);
}
vector<tournament_object> wallet_api::get_tournaments_by_state(tournament_id_type stop,
unsigned limit,
tournament_id_type start,
tournament_state state) {
return my->_remote_db->get_tournaments_by_state(stop, limit, start, state);
}
tournament_object wallet_api::get_tournament(tournament_id_type id)
{
return my->_remote_db->get_objects({id})[0].as<tournament_object>();
}
signed_transaction wallet_api::rps_throw(game_id_type game_id,
string player_account,
rock_paper_scissors_gesture gesture,
bool broadcast)
{
FC_ASSERT( !is_locked() );
// check whether the gesture is appropriate for the game we're playing
graphene::chain::game_object game_obj = my->get_object<graphene::chain::game_object>(game_id);
graphene::chain::match_object match_obj = my->get_object<graphene::chain::match_object>(game_obj.match_id);
graphene::chain::tournament_object tournament_obj = my->get_object<graphene::chain::tournament_object>(match_obj.tournament_id);
graphene::chain::rock_paper_scissors_game_options game_options =
tournament_obj.options.game_options.get<graphene::chain::rock_paper_scissors_game_options>();
if ((int)gesture >= game_options.number_of_gestures)
FC_THROW("Gesture ${gesture} not supported in this game", ("gesture", gesture));
account_object player_account_obj = get_account(player_account);
// construct the complete throw, the commit, and reveal
rock_paper_scissors_throw full_throw;
fc::rand_bytes((char*)&full_throw.nonce1, sizeof(full_throw.nonce1));
fc::rand_bytes((char*)&full_throw.nonce2, sizeof(full_throw.nonce2));
full_throw.gesture = gesture;
rock_paper_scissors_throw_commit commit_throw;
commit_throw.nonce1 = full_throw.nonce1;
std::vector<char> full_throw_packed(fc::raw::pack(full_throw));
commit_throw.throw_hash = fc::sha256::hash(full_throw_packed.data(), full_throw_packed.size());
rock_paper_scissors_throw_reveal reveal_throw;
reveal_throw.nonce2 = full_throw.nonce2;
reveal_throw.gesture = full_throw.gesture;
// store off the reveal for transmitting after both players commit
my->_wallet.committed_game_moves[commit_throw] = reveal_throw;
// broadcast the commit
signed_transaction tx;
game_move_operation move_operation;
move_operation.game_id = game_id;
move_operation.player_account_id = player_account_obj.id;
move_operation.move = commit_throw;
tx.operations = {move_operation};
my->set_operation_fees( tx, my->_remote_db->get_global_properties().parameters.current_fees );
tx.validate();
return my->sign_transaction( tx, broadcast );
}
// default ctor necessary for FC_REFLECT
signed_block_with_info::signed_block_with_info()
{
}
order_book wallet_api::get_order_book( const string& base, const string& quote, unsigned limit )
{
return( my->_remote_db->get_order_book( base, quote, limit ) );

View file

@ -70,6 +70,8 @@ int main( int argc, char** argv )
("dev-account-count", bpo::value<uint32_t>()->default_value(0), "Prefix for dev accounts")
("dev-balance-count", bpo::value<uint32_t>()->default_value(0), "Prefix for dev balances")
("dev-balance-amount", bpo::value<uint64_t>()->default_value(uint64_t(1000)*uint64_t(1000)*uint64_t(100000)), "Amount in each dev balance")
("nop", "just write the genesis file out after reading it in, do not alter any keys or add accounts or balances. used to pretty-print a genesis file")
("replace-all-keys", bpo::value<boost::filesystem::path>(), "Replace all keys/addresses in the genesis files with dev keys based on dev-key-prefix and dump the new keys to this filename.")
;
bpo::variables_map options;
@ -116,13 +118,91 @@ int main( int argc, char** argv )
genesis = graphene::app::detail::create_example_genesis();
}
if (!options.count("nop"))
{
std::string dev_key_prefix = options["dev-key-prefix"].as<std::string>();
auto get_dev_key = [&]( std::string prefix, uint32_t i ) -> public_key_type
{
return fc::ecc::private_key::regenerate( fc::sha256::hash( dev_key_prefix + prefix + std::to_string(i) ) ).get_public_key();
};
if (options.count("replace-all-keys"))
{
unsigned dev_keys_used = 0;
std::map<std::string, fc::ecc::private_key> replacement_keys;
auto get_replacement_key = [&](const std::string& original_key) -> fc::ecc::private_key {
auto iter = replacement_keys.find(original_key);
if (iter != replacement_keys.end())
return iter->second;
fc::ecc::private_key new_private_key = fc::ecc::private_key::regenerate(fc::sha256::hash(dev_key_prefix + std::to_string(dev_keys_used++)));
replacement_keys[original_key] = new_private_key;
return new_private_key;
};
for (genesis_state_type::initial_balance_type& initial_balance : genesis.initial_balances)
{
std::string address_string = (std::string)initial_balance.owner;
initial_balance.owner = address(get_replacement_key(address_string).get_public_key());
}
for (genesis_state_type::initial_vesting_balance_type& initial_balance : genesis.initial_vesting_balances)
{
std::string address_string = (std::string)initial_balance.owner;
initial_balance.owner = address(get_replacement_key(address_string).get_public_key());
}
for (genesis_state_type::initial_witness_type& initial_witness : genesis.initial_witness_candidates)
{
std::string public_key_string = (std::string)initial_witness.block_signing_key;
initial_witness.block_signing_key = get_replacement_key(public_key_string).get_public_key();
}
for (genesis_state_type::initial_account_type& initial_account : genesis.initial_accounts)
{
std::string public_key_string = (std::string)initial_account.owner_key;
initial_account.owner_key = get_replacement_key(public_key_string).get_public_key();
public_key_string = (std::string)initial_account.active_key;
initial_account.active_key = get_replacement_key(public_key_string).get_public_key();
}
for (genesis_state_type::initial_bts_account_type& initial_account : genesis.initial_bts_accounts)
{
for (auto iter = initial_account.owner_authority.key_auths.begin();
iter != initial_account.owner_authority.key_auths.end(); ++iter)
{
std::string public_key_string = (std::string)iter->first;
iter->first = get_replacement_key(public_key_string).get_public_key();
}
for (auto iter = initial_account.active_authority.key_auths.begin();
iter != initial_account.active_authority.key_auths.end(); ++iter)
{
std::string public_key_string = (std::string)iter->first;
iter->first = get_replacement_key(public_key_string).get_public_key();
}
for (auto iter = initial_account.owner_authority.address_auths.begin();
iter != initial_account.owner_authority.address_auths.end(); ++iter)
{
std::string address_string = (std::string)iter->first;
iter->first = address(get_replacement_key(address_string).get_public_key());
}
for (auto iter = initial_account.active_authority.address_auths.begin();
iter != initial_account.active_authority.address_auths.end(); ++iter)
{
std::string address_string = (std::string)iter->first;
iter->first = address(get_replacement_key(address_string).get_public_key());
}
}
fc::path keys_csv_path = options["replace-all-keys"].as<boost::filesystem::path>();
std::ofstream keys_csv(keys_csv_path.string());
keys_csv << "wif_private_key,public_key,address\n";
for (const auto& value : replacement_keys)
keys_csv << graphene::utilities::key_to_wif(value.second) << "," << std::string(public_key_type(value.second.get_public_key()))
<< "," << std::string(address(value.second.get_public_key())) << "\n";
}
else
{
uint32_t dev_account_count = options["dev-account-count"].as<uint32_t>();
std::string dev_account_prefix = options["dev-account-prefix"].as<std::string>();
for(uint32_t i=0;i<dev_account_count;i++)
@ -165,6 +245,8 @@ int main( int argc, char** argv )
wit_acct.owner_key = get_dev_key( "wit-owner-", i );
wit_acct.active_key = get_dev_key( "wit-active-", i );
}
}
}
fc::path output_filename = options["out"].as<boost::filesystem::path>();
fc::json::save_to_file( genesis, output_filename );

View file

@ -40,6 +40,9 @@
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <fc/smart_ref_impl.hpp>
#include <iostream>
@ -383,6 +386,7 @@ template<typename T, bool reflected>
struct serializer
{
static_assert( fc::reflector<T>::is_defined::value == reflected, "invalid template arguments" );
static void init()
{
serializer_init_helper< T, typename fc::reflector<T>::is_enum >::init();

View file

@ -12,6 +12,7 @@ endif()
# We have to link against graphene_debug_witness because deficiency in our API infrastructure doesn't allow plugins to be fully abstracted #246
target_link_libraries( witness_node
PRIVATE graphene_app graphene_account_history graphene_market_history graphene_witness graphene_chain graphene_debug_witness graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )
# also add dependencies to graphene_generate_genesis graphene_generate_uia_sharedrop_genesis if you want those plugins
install( TARGETS
witness_node

View file

@ -25,7 +25,10 @@
#include <graphene/witness/witness.hpp>
#include <graphene/account_history/account_history_plugin.hpp>
#include <graphene/accounts_list/accounts_list_plugin.hpp>
#include <graphene/market_history/market_history_plugin.hpp>
//#include <graphene/generate_genesis/generate_genesis_plugin.hpp>
//#include <graphene/generate_uia_sharedrop_genesis/generate_uia_sharedrop_genesis.hpp>
#include <fc/exception/exception.hpp>
#include <fc/thread/thread.hpp>
@ -74,6 +77,9 @@ int main(int argc, char** argv) {
auto witness_plug = node->register_plugin<witness_plugin::witness_plugin>();
auto history_plug = node->register_plugin<account_history::account_history_plugin>();
auto market_history_plug = node->register_plugin<market_history::market_history_plugin>();
//auto generate_genesis_plug = node->register_plugin<generate_genesis_plugin::generate_genesis_plugin>();
//auto generate_uia_sharedrop_genesis_plug = node->register_plugin<generate_uia_sharedrop_genesis::generate_uia_sharedrop_genesis_plugin>();
auto list_plug = node->register_plugin<accounts_list::accounts_list_plugin>();
try
{

View file

@ -33,4 +33,12 @@ file(GLOB BETTING_TESTS "betting/*.cpp")
add_executable( betting_test ${BETTING_TESTS} ${COMMON_SOURCES} )
target_link_libraries( betting_test graphene_chain graphene_app graphene_account_history graphene_bookie graphene_egenesis_none fc graphene_wallet ${PLATFORM_SPECIFIC_LIBS} )
file(GLOB TOURNAMENT_TESTS "tournament/*.cpp")
add_executable( tournament_test ${TOURNAMENT_TESTS} ${COMMON_SOURCES} )
target_link_libraries( tournament_test graphene_chain graphene_app graphene_account_history graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} )
file(GLOB RANDOM_SOURCES "random/*.cpp")
add_executable( random_test ${RANDOM_SOURCES} ${COMMON_SOURCES} )
target_link_libraries( random_test graphene_chain graphene_app graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} )
add_subdirectory( generate_empty_blocks )

View file

@ -42,6 +42,7 @@
#include <graphene/chain/sport_object.hpp>
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/utilities/tempdir.hpp>
@ -77,6 +78,7 @@ database_fixture::database_fixture()
if( arg == "--show-test-names" )
std::cout << "running test " << boost::unit_test::framework::current_test_case().p_name << std::endl;
}
auto ahplugin = app.register_plugin<graphene::account_history::account_history_plugin>();
auto mhplugin = app.register_plugin<graphene::market_history::market_history_plugin>();
//auto bookieplugin = app.register_plugin<graphene::bookie::bookie_plugin>();
@ -85,9 +87,11 @@ database_fixture::database_fixture()
boost::program_options::variables_map options;
genesis_state.initial_timestamp = time_point_sec( GRAPHENE_TESTING_GENESIS_TIMESTAMP );
genesis_state.initial_timestamp = time_point_sec( (fc::time_point::now().sec_since_epoch() / GRAPHENE_DEFAULT_BLOCK_INTERVAL) * GRAPHENE_DEFAULT_BLOCK_INTERVAL );
// genesis_state.initial_parameters.witness_schedule_algorithm = GRAPHENE_WITNESS_SHUFFLED_ALGORITHM;
genesis_state.initial_active_witnesses = 10;
for( int i = 0; i < genesis_state.initial_active_witnesses; ++i )
for( unsigned i = 0; i < genesis_state.initial_active_witnesses; ++i )
{
auto name = "init"+fc::to_string(i);
genesis_state.initial_accounts.emplace_back(name,
@ -164,11 +168,17 @@ void database_fixture::verify_asset_supplies( const database& db )
const simple_index<account_statistics_object>& statistics_index = db.get_index_type<simple_index<account_statistics_object>>();
const auto& balance_index = db.get_index_type<account_balance_index>().indices();
const auto& settle_index = db.get_index_type<force_settlement_index>().indices();
const auto& tournaments_index = db.get_index_type<tournament_index>().indices();
map<asset_id_type,share_type> total_balances;
map<asset_id_type,share_type> total_debts;
share_type core_in_orders;
share_type reported_core_in_orders;
for( const tournament_object& t : tournaments_index )
if (t.get_state() != tournament_state::concluded && t.get_state() != tournament_state::registration_period_expired)
total_balances[t.options.buy_in.asset_id] += t.prize_pool;
for( const account_balance_object& b : balance_index )
total_balances[b.asset_type] += b.balance;
for( const force_settlement_object& s : settle_index )
@ -682,6 +692,10 @@ const witness_object& database_fixture::create_witness( const account_object& ow
witness_create_operation op;
op.witness_account = owner.id;
op.block_signing_key = signing_private_key.get_public_key();
secret_hash_type::encoder enc;
fc::raw::pack(enc, signing_private_key);
fc::raw::pack(enc, secret_hash_type());
op.initial_secret = secret_hash_type::hash(enc.result());
trx.operations.push_back(op);
trx.validate();
processed_transaction ptx = db.push_transaction(trx, ~0);

View file

@ -31,6 +31,7 @@
#include <graphene/chain/protocol/protocol.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/witness_schedule_object.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
#include <fc/crypto/digest.hpp>

20
tests/random.sh Executable file
View file

@ -0,0 +1,20 @@
#!/bin/bash -e
i=1
while [ 0 ]; do
echo "*** $i `date`"
if [ -f random-2 ]; then
mv random-2 random-2-last
fi
./random_test --log_level=message 2> random-2
echo "*** $i `date`"
echo
if [ "$1" = "-c" ]; then
sleep 2
else
break
fi
i=$[i + 1]
done

View file

@ -0,0 +1,154 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <boost/test/unit_test.hpp>
//#include <fc/crypto/openssl.hpp>
//#include <openssl/rand.h>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include "../common/database_fixture.hpp"
#include <graphene/utilities/tempdir.hpp>
#include <signal.h>
using namespace graphene::chain;
bool test_standard_rand = false;
bool all_tests = false;
bool game_is_over = false;
void sig_handler(int signo)
{
std::cout << "." << std::endl;
game_is_over = true;
}
BOOST_AUTO_TEST_SUITE(tournament_tests)
bool one_test(database_fixture& df, int test_nr = 0, int tsamples = 0, int psamples = 0, std::string options = "")
{
game_is_over = false;
std::string command = "dieharder -g 200 -d " + std::to_string(test_nr) ;
if (tsamples)
command += " -t " + std::to_string(tsamples);
if (psamples)
command += " -p " + std::to_string(psamples);
if (!options.empty())
command += " " + options;
FILE *io = popen(command.c_str(), "w");
BOOST_CHECK(io);
if(!io)
return false;
int r;
void *binary_data;
size_t binary_data_length = sizeof(r);
int m = 0; // 0 not generate blocks, > 0 generate block every m generated numbers 100 - 1000 are reasonable
int i = 0;
while ( !game_is_over && !feof(io) && !ferror(io) )
{
if (test_standard_rand) {
r = rand();
} else {
if (i) {
--i;
df.generate_block();
} else {
i = m;
}
r = df.db.get_random_bits((uint64_t)INT_MAX+1);
}
binary_data = (void *) &r;
size_t l =
fwrite(binary_data, 1, binary_data_length, io);
if (l != binary_data_length) break;
//fflush(io);
}
pclose(io);
return true;
}
BOOST_FIXTURE_TEST_CASE( basic, database_fixture )
{
try
{
std::string o(" dieharder ");
o.append(all_tests ? "all" : "selected").
append(" tests of ").
append(test_standard_rand ? "rand" : "get_random_bits");
BOOST_TEST_MESSAGE("Hello" + o);
std::vector<int> selected = {0, 1, 3, 5, 6, 15};
#if 1
// trying to randomize starting point
int r = std::rand() % 100;
for(int i = 0; i < r ; ++i)
db.get_random_bits(INT_MAX);
#endif
for (int i = 0; i < 18; ++i)
{
if (!all_tests && std::find(selected.begin(), selected.end(), i) == selected.end())
continue;
BOOST_TEST_MESSAGE("#" + std::to_string(i));
if (!one_test(*this, i))
break;
}
BOOST_TEST_MESSAGE("Bye" + o);
}
catch (fc::exception& e)
{
edump((e.to_detail_string()));
throw;
}
}
BOOST_AUTO_TEST_SUITE_END()
//#define BOOST_TEST_MODULE "C++ Unit Tests for Graphene Blockchain Database"
#include <cstdlib>
#include <iostream>
#include <boost/test/included/unit_test.hpp>
boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
for (int i=1; i<argc; i++)
{
const std::string arg = argv[i];
std::cout << "#" << i << " " << arg << std::endl;
if(arg == "-R")
test_standard_rand = true;
else if(arg == "-A")
all_tests = true;
}
std::srand(time(NULL));
std::cout << "Random number generator seeded to " << time(NULL) << std::endl;
if (signal(SIGPIPE, sig_handler) == SIG_ERR)
std::cout << "Can't catch SIGPIPE signal!" << std::endl;
return nullptr;
}

10
tests/random/readme Normal file
View file

@ -0,0 +1,10 @@
options:
-R test standard 'rand' instead of 'get_random_bits';
-A run all dieharder tests, default is to run only selected tests.
conclusion:
quality of randomness of 'get_random_bits' is comparable with randomness standard 'rand' function,
however about an order of magnitude slower than 'rand';
and distinctly weaker compared to e.g. /dev/urandom that is assessed by dieharder as almost perfect.

View file

@ -29,12 +29,14 @@
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/witness_scheduler_rng.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/db/simple_index.hpp>
#include <fc/crypto/digest.hpp>
#include <fc/crypto/hex.hpp>
#include <fc/crypto/hash_ctr_rng.hpp>
#include "../common/database_fixture.hpp"
#include <algorithm>
@ -197,6 +199,134 @@ BOOST_AUTO_TEST_CASE( memo_test )
BOOST_CHECK_EQUAL(m.get_message(receiver, sender.get_public_key()), "Hello, world!");
} FC_LOG_AND_RETHROW() }
BOOST_AUTO_TEST_CASE( witness_rng_test_bits )
{
try
{
const uint64_t COUNT = 131072;
const uint64_t HASH_SIZE = 32;
string ref_bits = "";
ref_bits.reserve( COUNT * HASH_SIZE );
static const char seed_data[] = "\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24\x27\xae\x41\xe4\x64\x9b\x93\x4c\xa4\x95\x99\x1b\x78\x52\xb8\x55";
for( uint64_t i=0; i<COUNT; i++ )
{
// grab the bits
fc::sha256::encoder enc;
enc.write( seed_data, HASH_SIZE );
enc.put( char((i ) & 0xFF) );
enc.put( char((i >> 0x08) & 0xFF) );
enc.put( char((i >> 0x10) & 0xFF) );
enc.put( char((i >> 0x18) & 0xFF) );
enc.put( char((i >> 0x20) & 0xFF) );
enc.put( char((i >> 0x28) & 0xFF) );
enc.put( char((i >> 0x30) & 0xFF) );
enc.put( char((i >> 0x38) & 0xFF) );
fc::sha256 result = enc.result();
auto result_data = result.data();
std::copy( result_data, result_data+HASH_SIZE, std::back_inserter( ref_bits ) );
}
fc::sha256 seed = fc::sha256::hash( string("") );
// seed = sha256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
BOOST_CHECK( memcmp( seed.data(), seed_data, HASH_SIZE ) == 0 );
fc::hash_ctr_rng<fc::sha256, 32> test_rng(seed.data(), 0);
// python2 -c 'import hashlib; import struct; h = lambda x : hashlib.sha256(x).digest(); i = lambda x : struct.pack("<Q", x); print( h( h("") + i(0) ) )' | hd
string ref_bits_hex =
"5c5d42dcf39f71c0226ca720d8d518db615b5773f038e5e491963f6f47621bbd" // h( h("") + i(0) )
"43fd6dae047c400060be262e6d443200eacd1fafcb77828638085c2e2341fd8d" // h( h("") + i(1) )
"d666330a7441dc7279b786e65aba32817275989cfc691b3901f000fb0f14cd05" // h( h("") + i(2) )
"34bd93f83d7bac4a667d62fee39bd5eb1991fbadc29a5f216ea746772ca31544" // h( h("") + i(3) )
"d3b41a093eab01cd25f987a909b2f4812b0f38475e0fe40f6f42a12c6e018aa7" // ...
"c8db17b946c5a6bceaa7b903c93e6ccb8cc6c09b0cfd2108d930de1a79c3a68e"
"cc1945b36c82e356b6d127057d036a150cb03b760e9c9e706c560f32a749e80d"
"872b28fe97e289d4f6f361f3427d454113e3b513892d129398dac4daf8a0e43e"
"8d7a5a2f3cbb245fa471e87e30a38d9c775c985c28db6e521e34cf1e88507c26"
"c662f230eed0f10899c3a74a2d1bfb88d732909b206a2aed3ae0bda728fac8fe"
"38eface8b1d473e45cbb40603bcef8bf2219e55669c7a2cfb5f8d52610689f14"
"3b1d1734273b069a7de7cc6dd2e80db09d1feff200c9bdaf033cd553ea40e05d"
"16653ca7aa7f790a95c6a8d41e5694b0c6bff806c3ce3e0e320253d408fb6f27"
"b55df71d265de0b86a1cdf45d1d9c53da8ebf0ceec136affa12228d0d372e698"
"37e9305ce57d386d587039b49b67104fd4d8467e87546237afc9a90cf8c677f9"
"fc26784c94f754cf7aeacb6189e705e2f1873ea112940560f11dbbebb22a8922"
;
char* ref_bits_chars = new char[ ref_bits_hex.length() / 2 ];
fc::from_hex( ref_bits_hex, ref_bits_chars, ref_bits_hex.length() / 2 );
string ref_bits_str( ref_bits_chars, ref_bits_hex.length() / 2 );
delete[] ref_bits_chars;
ref_bits_chars = nullptr;
BOOST_CHECK( ref_bits_str.length() < ref_bits.length() );
BOOST_CHECK( ref_bits_str == ref_bits.substr( 0, ref_bits_str.length() ) );
//std::cout << "ref_bits_str: " << fc::to_hex( ref_bits_str.c_str(), std::min( ref_bits_str.length(), size_t(256) ) ) << "\n";
//std::cout << "ref_bits : " << fc::to_hex( ref_bits .c_str(), std::min( ref_bits.length(), size_t(256) ) ) << "\n";
// when we get to this point, our code to generate the RNG byte output is working.
// now let's test get_bits() as follows:
uint64_t ref_get_bits_offset = 0;
auto ref_get_bits = [&]( uint8_t count ) -> uint64_t
{
uint64_t result = 0;
uint64_t i = ref_get_bits_offset;
uint64_t mask = 1;
while( count > 0 )
{
if( ref_bits[ i >> 3 ] & (1 << (i & 7)) )
result |= mask;
mask += mask;
i++;
count--;
}
ref_get_bits_offset = i;
return result;
};
// use PRNG to decide between 0-64 bits
std::minstd_rand rng;
rng.seed( 9999 );
std::uniform_int_distribution< uint16_t > bit_dist( 0, 64 );
for( int i=0; i<10000; i++ )
{
uint8_t bit_count = bit_dist( rng );
uint64_t ref_bits = ref_get_bits( bit_count );
uint64_t test_bits = test_rng.get_bits( bit_count );
//std::cout << i << ": get(" << int(bit_count) << ") -> " << test_bits << " (expect " << ref_bits << ")\n";
if( bit_count < 64 )
{
BOOST_CHECK( ref_bits < (uint64_t( 1 ) << bit_count ) );
BOOST_CHECK( test_bits < (uint64_t( 1 ) << bit_count ) );
}
BOOST_CHECK( ref_bits == test_bits );
if( ref_bits != test_bits )
break;
}
std::uniform_int_distribution< uint64_t > whole_dist(
0, std::numeric_limits<uint64_t>::max() );
for( int i=0; i<10000; i++ )
{
uint8_t bit_count = bit_dist( rng );
uint64_t bound = whole_dist( rng ) & ((uint64_t(1) << bit_count) - 1);
//std::cout << "bound:" << bound << "\n";
uint64_t rnum = test_rng( bound );
//std::cout << "rnum:" << rnum << "\n";
if( bound > 1 )
{
BOOST_CHECK( rnum < bound );
}
else
{
BOOST_CHECK( rnum == 0 );
}
}
} FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE( exceptions )
{
GRAPHENE_CHECK_THROW(FC_THROW_EXCEPTION(balance_claim_invalid_claim_amount, "Etc"), balance_claim_invalid_claim_amount);

View file

@ -32,6 +32,7 @@
#include <graphene/chain/committee_member_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/market_object.hpp>
#include <graphene/chain/witness_schedule_object.hpp>
#include <graphene/utilities/tempdir.hpp>
@ -916,6 +917,45 @@ BOOST_FIXTURE_TEST_CASE( pop_block_twice, database_fixture )
}
}
BOOST_FIXTURE_TEST_CASE( witness_scheduler_missed_blocks, database_fixture )
{ try {
uint8_t witness_schedule_algorithm = db.get_global_properties().parameters.witness_schedule_algorithm;
if (witness_schedule_algorithm != GRAPHENE_WITNESS_SCHEDULED_ALGORITHM)
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.witness_schedule_algorithm = GRAPHENE_WITNESS_SCHEDULED_ALGORITHM;
});
db.get_near_witness_schedule();
generate_block();
auto near_schedule = db.get_near_witness_schedule();
std::for_each(near_schedule.begin(), near_schedule.end(), [&](witness_id_type id) {
generate_block(0);
BOOST_CHECK(db.get_dynamic_global_properties().current_witness == id);
});
near_schedule = db.get_near_witness_schedule();
generate_block(0, init_account_priv_key, 2);
BOOST_CHECK(db.get_dynamic_global_properties().current_witness == near_schedule[2]);
near_schedule.erase(near_schedule.begin(), near_schedule.begin() + 3);
auto new_schedule = db.get_near_witness_schedule();
new_schedule.erase(new_schedule.end() - 3, new_schedule.end());
BOOST_CHECK(new_schedule == near_schedule);
std::for_each(near_schedule.begin(), near_schedule.end(), [&](witness_id_type id) {
generate_block(0);
BOOST_CHECK(db.get_dynamic_global_properties().current_witness == id);
if (db.get_global_properties().parameters.witness_schedule_algorithm != witness_schedule_algorithm)
db.modify(db.get_global_properties(), [&witness_schedule_algorithm](global_property_object& p) {
p.parameters.witness_schedule_algorithm = witness_schedule_algorithm;
});
});
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE( rsf_missed_blocks, database_fixture )
{
try
@ -924,7 +964,11 @@ BOOST_FIXTURE_TEST_CASE( rsf_missed_blocks, database_fixture )
auto rsf = [&]() -> string
{
fc::uint128 rsf = db.get_dynamic_global_properties().recent_slots_filled;
fc::uint128 rsf;
if (db.get_global_properties().parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM)
rsf = db.get(witness_schedule_id_type()).recent_slots_filled;
else
rsf = db.get_dynamic_global_properties().recent_slots_filled;
string result = "";
result.reserve(128);
for( int i=0; i<128; i++ )

View file

@ -457,6 +457,42 @@ BOOST_AUTO_TEST_CASE( witness_create )
while( ((db.get_dynamic_global_properties().current_aslot + 1) % witnesses.size()) != 0 )
generate_block();
if (db.get_global_properties().parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM)
{
generate_blocks(witnesses.size());
// make sure we're scheduled to produce
vector<witness_id_type> near_witnesses = db.get_near_witness_schedule();
BOOST_CHECK( std::find( near_witnesses.begin(), near_witnesses.end(), nathan_witness_id )
!= near_witnesses.end() );
struct generator_helper {
database_fixture& f;
witness_id_type nathan_id;
fc::ecc::private_key nathan_key;
bool nathan_generated_block;
void operator()(witness_id_type id) {
if( id == nathan_id )
{
nathan_generated_block = true;
f.generate_block(0, nathan_key);
} else
f.generate_block(0);
BOOST_CHECK_EQUAL(f.db.get_dynamic_global_properties().current_witness.instance.value, id.instance.value);
f.db.get_near_witness_schedule();
}
};
generator_helper h = std::for_each(near_witnesses.begin(), near_witnesses.end(),
generator_helper{*this, nathan_witness_id, nathan_private_key, false});
BOOST_CHECK(h.nathan_generated_block);
BOOST_CHECK_EQUAL( db.witness_participation_rate(), GRAPHENE_100_PERCENT );
}
if (db.get_global_properties().parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM)
{
int produced = 0;
// Make sure we get scheduled at least once in witnesses.size()*2 blocks
// may take this many unless we measure where in the scheduling round we are
@ -468,7 +504,10 @@ BOOST_AUTO_TEST_CASE( witness_create )
produced++;
}
BOOST_CHECK_GE( produced, 1 );
} FC_LOG_AND_RETHROW() }
}
} FC_LOG_AND_RETHROW()
}
/**
* This test should verify that the asset_global_settle operation works as expected,

File diff suppressed because it is too large Load diff

24
tests/tournaments.sh Executable file
View file

@ -0,0 +1,24 @@
#!/bin/bash -e
i=1
file='tournament-2'
file2=$file
while [ 0 ]; do
echo "*** $i `date`"
if [ "$1" = "-c" ]; then
file2=$file-`date +%Y-%m-%d:%H:%M:%S`
elif [ -f $file2 ]; then
mv $file2 $file2-last
fi
./tournament_test --log_level=message 2> $file2
echo
if [ "$1" = "-c" ]; then
sleep 2
else
break
fi
i=$[i + 1]
done