Change the behavior of the betting markets from being controlled by

a collection of flags (is_live, is_closed) to a single status field.
The status changes in an event can trickle down to the market groups,
and the status changes in market groups can bubble up to events.
This commit is contained in:
Eric Frias 2018-02-07 10:12:58 -05:00
parent 9fcebf8bc0
commit 3b3a0905ff
21 changed files with 3530 additions and 825 deletions

View file

@ -1,5 +1,5 @@
{
"initial_timestamp": "2017-07-18T18:33:30",
"initial_timestamp": "2018-02-01T00:00:00",
"max_core_supply": "1000000000000000",
"initial_parameters": {
"current_fees": {
@ -243,7 +243,7 @@
}
],[
57,{
"fee": 100000
"fee": 1
}
],[
58,{
@ -261,7 +261,7 @@
62,{}
],[
63,{
"fee": 100000
"fee": 0
}
],[
64,{}
@ -295,7 +295,7 @@
],
"scale": 10000
},
"block_interval": 5,
"block_interval": 3,
"maintenance_interval": 600,
"maintenance_skip_slots": 3,
"committee_proposal_review_period": 900,
@ -536,4 +536,4 @@
"num_special_accounts": 0,
"num_special_assets": 0
}
}
}

View file

@ -102,10 +102,11 @@ add_library( graphene_chain
event_group_evaluator.cpp
protocol/event.cpp
event_evaluator.cpp
event_object.cpp
protocol/betting_market.cpp
betting_market_evaluator.cpp
betting_market_object.cpp
db_bet.cpp
betting_market_group_object.cpp
${HEADERS}
${PROTOCOL_HEADERS}

View file

@ -21,6 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#define DEFAULT_LOGGER "betting"
#include <fc/smart_ref_impl.hpp>
#include <graphene/chain/betting_market_evaluator.hpp>
@ -109,8 +110,8 @@ object_id_type betting_market_group_create_evaluator::do_apply(const betting_mar
betting_market_group_obj.rules_id = _rules_id;
betting_market_group_obj.description = op.description;
betting_market_group_obj.asset_id = op.asset_id;
betting_market_group_obj.frozen = false;
betting_market_group_obj.delay_bets = false;
betting_market_group_obj.never_in_play = op.never_in_play;
betting_market_group_obj.delay_before_settling = op.delay_before_settling;
});
return new_betting_market_group.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -121,12 +122,9 @@ void_result betting_market_group_update_evaluator::do_evaluate(const betting_mar
FC_ASSERT(trx_state->_is_proposed_trx);
_betting_market_group = &op.betting_market_group_id(d);
FC_ASSERT(op.new_description.valid() ||
op.new_rules_id.valid() ||
op.freeze.valid() ||
op.delay_bets.valid(), "nothing to change");
FC_ASSERT(op.new_description || op.new_rules_id || op.status, "nothing to change");
if (op.new_rules_id.valid())
if (op.new_rules_id)
{
// the rules_id in the operation can be a relative id. If it is,
// resolve it and verify that it is truly rules
@ -141,11 +139,9 @@ void_result betting_market_group_update_evaluator::do_evaluate(const betting_mar
FC_ASSERT(d.find_object(_rules_id), "invalid rules specified");
}
if (op.freeze.valid())
FC_ASSERT(_betting_market_group->frozen != *op.freeze, "freeze would not change the state of the betting market group");
if (op.status)
FC_ASSERT(_betting_market_group->get_status() != *op.status, "status would not change the state of the betting market group");
if (op.delay_bets.valid())
FC_ASSERT(_betting_market_group->delay_bets != *op.delay_bets, "delay_bets would not change the state of the betting market group");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -153,39 +149,40 @@ void_result betting_market_group_update_evaluator::do_apply(const betting_market
{ try {
database& d = db();
d.modify(*_betting_market_group, [&](betting_market_group_object& betting_market_group) {
if (op.new_description.valid())
if (op.new_description)
betting_market_group.description = *op.new_description;
if (op.new_rules_id.valid())
if (op.new_rules_id)
betting_market_group.rules_id = _rules_id;
if (op.freeze.valid())
betting_market_group.frozen = *op.freeze;
if (op.delay_bets.valid())
bool bets_were_delayed = betting_market_group.bets_are_delayed();
if (op.status)
betting_market_group.dispatch_new_status(d, *op.status);
bool bets_are_delayed = betting_market_group.bets_are_delayed();
// if we have transitioned from in-play to not-in-play-but-still-accepting-bets,
// place all delayed bets now
if (betting_market_group.bets_are_allowed() &&
bets_were_delayed && !bets_are_delayed)
{
assert(_betting_market_group->delay_bets != *op.delay_bets); // we checked this in evaluate
betting_market_group.delay_bets = *op.delay_bets;
if (!*op.delay_bets)
const auto& bet_odds_idx = d.get_index_type<bet_object_index>().indices().get<by_odds>();
auto bet_iter = bet_odds_idx.begin();
bool last = bet_iter == bet_odds_idx.end() || !bet_iter->end_of_delay;
while (!last)
{
// we have switched from delayed to not-delayed. if there are any delayed bets,
// push them through now.
const auto& bet_odds_idx = d.get_index_type<bet_object_index>().indices().get<by_odds>();
auto bet_iter = bet_odds_idx.begin();
bool last = bet_iter == bet_odds_idx.end() || !bet_iter->end_of_delay;
while (!last)
const bet_object& delayed_bet = *bet_iter;
++bet_iter;
last = bet_iter == bet_odds_idx.end() || !bet_iter->end_of_delay;
const betting_market_object& betting_market = delayed_bet.betting_market_id(d);
if (betting_market.group_id == op.betting_market_group_id)
{
const bet_object& delayed_bet = *bet_iter;
++bet_iter;
last = bet_iter == bet_odds_idx.end() || !bet_iter->end_of_delay;
d.modify(delayed_bet, [](bet_object& bet_obj) {
// clear the end_of_delay, which will re-sort the bet into its place in the book
bet_obj.end_of_delay.reset();
});
const betting_market_object& betting_market = delayed_bet.betting_market_id(d);
if (betting_market.group_id == op.betting_market_group_id && !_betting_market_group->frozen)
{
d.modify(delayed_bet, [](bet_object& bet_obj) {
// clear the end_of_delay, which will re-sort the bet into its place in the book
bet_obj.end_of_delay.reset();
});
d.place_bet(delayed_bet);
}
d.place_bet(delayed_bet);
}
}
}
@ -271,7 +268,9 @@ void_result bet_place_evaluator::do_evaluate(const bet_place_operation& op)
FC_ASSERT( op.amount_to_bet.asset_id == _betting_market_group->asset_id,
"Asset type bet does not match the market's asset type" );
FC_ASSERT( !_betting_market_group->frozen, "Unable to place bets while the market is frozen" );
ddump((_betting_market_group->get_status()));
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::frozen,
"Unable to place bets while the market is frozen" );
_asset = &_betting_market_group->asset_id(d);
FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *_asset ) );
@ -312,15 +311,19 @@ object_id_type bet_place_evaluator::do_apply(const bet_place_operation& op)
bet_obj.amount_to_bet = op.amount_to_bet;
bet_obj.backer_multiplier = op.backer_multiplier;
bet_obj.back_or_lay = op.back_or_lay;
if (_betting_market_group->delay_bets)
bet_obj.end_of_delay = d.head_block_time() + _current_params->live_betting_delay_time;
if (_betting_market_group->bets_are_delayed()) {
// the bet will be included in the block at time `head_block_time() + block_interval`, so make the delay relative
// to the time it's included in a block
bet_obj.end_of_delay = d.head_block_time() + _current_params->block_interval + _current_params->live_betting_delay_time;
}
});
bet_id_type new_bet_id = new_bet.id; // save the bet id here, new_bet may be deleted during place_bet()
d.adjust_balance(fee_paying_account->id, -op.amount_to_bet);
if (!_betting_market_group->delay_bets || _current_params->live_betting_delay_time <= 0)
ddump((_betting_market_group->bets_are_delayed())(_current_params->live_betting_delay_time));
if (!_betting_market_group->bets_are_delayed() || _current_params->live_betting_delay_time <= 0)
d.place_bet(new_bet);
return new_bet_id;

View file

@ -0,0 +1,541 @@
#define DEFAULT_LOGGER "betting"
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/database.hpp>
#include <boost/math/common_factor_rt.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 {
enum class betting_market_group_state {
upcoming,
frozen_upcoming,
in_play,
frozen_in_play,
closed,
graded,
canceled,
settled
};
} }
FC_REFLECT_ENUM(graphene::chain::betting_market_group_state,
(upcoming)
(frozen_upcoming)
(in_play)
(frozen_in_play)
(closed)
(graded)
(canceled)
(settled))
namespace graphene { namespace chain {
namespace msm = boost::msm;
namespace mpl = boost::mpl;
// betting market object implementation
namespace
{
// Events -- most events happen when the witnesses publish an update operation with a new
// status, so if they publish an event with the status set to `frozen`, we'll generate a `frozen_event`
struct upcoming_event
{
database& db;
upcoming_event(database& db) : db(db) {}
};
struct frozen_event
{
database& db;
frozen_event(database& db) : db(db) {}
};
struct in_play_event
{
database& db;
in_play_event(database& db) : db(db) {}
};
struct closed_event
{
database& db;
bool closed_by_event;
closed_event(database& db, bool closed_by_event) : db(db), closed_by_event(closed_by_event) {}
};
struct graded_event
{
database& db;
graded_event(database& db) : db(db) {}
};
struct re_grading_event
{
database& db;
re_grading_event(database& db) : db(db) {}
};
struct settled_event
{
database& db;
settled_event(database& db) : db(db) {}
};
struct canceled_event
{
database& db;
// true if this was triggered by setting event to canceled state,
// false if this was triggered directly on this betting market group
bool canceled_by_event;
canceled_event(database& db, bool canceled_by_event = false) : db(db), canceled_by_event(canceled_by_event) {}
};
// Events
struct betting_market_group_state_machine_ : public msm::front::state_machine_def<betting_market_group_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 upcoming : public msm::front::state<>
{
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> upcoming", ("id", fsm.betting_market_group_obj->id));
// when a betting market group goes from frozen -> upcoming, transition the markets from frozen -> unresolved
auto& betting_market_index = event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
try
{
event.db.modify(betting_market, [&event](betting_market_object& betting_market) {
betting_market.on_unresolved_event(event.db);
});
}
catch (const graphene::chain::no_transition&)
{
}
}
};
struct frozen_upcoming : public msm::front::state<>
{
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> frozen_upcoming", ("id", fsm.betting_market_group_obj->id));
auto& betting_market_index = event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
event.db.modify(betting_market, [&event](betting_market_object& betting_market) {
betting_market.on_frozen_event(event.db);
});
}
};
struct in_play : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> in_play", ("id", fsm.betting_market_group_obj->id));
// when an event goes in-play, cancel all unmatched bets in its betting markets
auto& betting_market_index = event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
event.db.modify(betting_market, [&event](betting_market_object& betting_market) {
betting_market.cancel_all_unmatched_bets(event.db);
try {
event.db.modify(betting_market, [&event](betting_market_object& betting_market) {
betting_market.on_unresolved_event(event.db);
});
} catch (const graphene::chain::no_transition&) {
// if this wasn't a transition from frozen state, this wasn't necessary
}
});
}
};
struct frozen_in_play : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> frozen_in_play", ("id", fsm.betting_market_group_obj->id));
auto& betting_market_index = event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
event.db.modify(betting_market, [&event](betting_market_object& betting_market) {
betting_market.on_frozen_event(event.db);
});
}
};
struct closed : public msm::front::state<> {
template <class Event>
void on_entry(const Event& fsm_event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> closed", ("id", fsm.betting_market_group_obj->id));
auto& betting_market_index = fsm_event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
fsm_event.db.modify(betting_market, [&fsm_event](betting_market_object& betting_market) {
betting_market.cancel_all_unmatched_bets(fsm_event.db);
betting_market.on_closed_event(fsm_event.db);
});
// then notify the event that this betting market is now closed so it can change its status accordingly
if (!fsm_event.closed_by_event) {
const event_object& event = fsm.betting_market_group_obj->event_id(fsm_event.db);
fsm_event.db.modify(event, [&fsm_event,&fsm](event_object& event_obj) {
event_obj.on_betting_market_group_closed(fsm_event.db, fsm.betting_market_group_obj->id);
});
}
}
};
struct graded : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> graded", ("id", fsm.betting_market_group_obj->id));
fsm.betting_market_group_obj->settling_time = event.db.head_block_time() + fsm.betting_market_group_obj->delay_before_settling;
dlog("grading complete, setting settling time to ${settling_time}", ("settling_time", fsm.betting_market_group_obj->settling_time));
}
};
struct re_grading : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> re_grading", ("id", fsm.betting_market_group_obj->id));
}
};
struct settled : public msm::front::state<> {
template <class Event>
void on_entry(const Event& fsm_event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> settled", ("id", fsm.betting_market_group_obj->id));
// TODO: what triggers this? I guess it will be the blockchain when its settling delay expires. So in that case, it should
// trigger the payout in the betting markets
auto& betting_market_index = fsm_event.db.template get_index_type<betting_market_object_index>().indices().template get<by_betting_market_group_id>();
for (const betting_market_object& betting_market :
boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id)))
fsm_event.db.modify(betting_market, [&fsm_event](betting_market_object& betting_market) {
betting_market.on_settled_event(fsm_event.db);
});
// then notify the event that this betting market is now resolved so it can change its status accordingly
const event_object& event = fsm.betting_market_group_obj->event_id(fsm_event.db);
fsm_event.db.modify(event, [&fsm_event,&fsm](event_object& event_obj) {
event_obj.on_betting_market_group_resolved(fsm_event.db, fsm.betting_market_group_obj->id, false);
});
}
};
struct canceled : public msm::front::state<>{
void on_entry(const canceled_event& fsm_event, betting_market_group_state_machine_& fsm) {
dlog("betting market group ${id} -> canceled", ("id", fsm.betting_market_group_obj->id));
auto& betting_market_index = fsm_event.db.get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
auto betting_markets_in_group = boost::make_iterator_range(betting_market_index.equal_range(fsm.betting_market_group_obj->id));
for (const betting_market_object& betting_market : betting_markets_in_group)
fsm_event.db.modify(betting_market, [&fsm_event](betting_market_object& betting_market_obj) {
betting_market_obj.on_canceled_event(fsm_event.db);
});
if (!fsm_event.canceled_by_event) {
const event_object& event = fsm.betting_market_group_obj->event_id(fsm_event.db);
fsm_event.db.modify(event, [&fsm_event,&fsm](event_object& event_obj) {
event_obj.on_betting_market_group_resolved(fsm_event.db, fsm.betting_market_group_obj->id, true);
});
}
fsm.betting_market_group_obj->settling_time = fsm_event.db.head_block_time();
dlog("cancel complete, setting settling time to ${settling_time}", ("settling_time", fsm.betting_market_group_obj->settling_time));
}
};
typedef upcoming initial_state;
// actions
void cancel_all_unmatched_bets(const in_play_event& event) {
event.db.cancel_all_unmatched_bets_on_betting_market_group(*betting_market_group_obj);
}
// guards
bool in_play_is_allowed(const in_play_event& event) {
return !betting_market_group_obj->never_in_play;
}
typedef betting_market_group_state_machine_ x; // makes transition table cleaner
// Transition table for betting market
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +-------------------+------------------+---------------------+------------------------------+----------------------+
_row < upcoming, frozen_event, frozen_upcoming >,
row < upcoming, in_play_event, in_play, &x::cancel_all_unmatched_bets, &x::in_play_is_allowed >,
_row < upcoming, closed_event, closed >,
_row < upcoming, canceled_event, canceled >,
// +-------------------+------------------+---------------------+------------------------------+----------------------+
_row < frozen_upcoming, upcoming_event, upcoming >,
_row < frozen_upcoming, in_play_event, upcoming >,
row < frozen_upcoming, in_play_event, in_play, &x::cancel_all_unmatched_bets, &x::in_play_is_allowed >,
_row < frozen_upcoming, closed_event, closed >,
_row < frozen_upcoming, canceled_event, canceled >,
// +-------------------+------------------+---------------------+------------------------------+----------------------+
_row < in_play, frozen_event, frozen_in_play >,
_row < in_play, closed_event, closed >,
_row < in_play, canceled_event, canceled >,
// +-------------------+------------------+---------------------+------------------------------+----------------------+
_row < frozen_in_play, in_play_event, in_play >,
_row < frozen_in_play, closed_event, closed >,
_row < frozen_in_play, canceled_event, canceled >,
// +-------------------+------------------+---------------------+------------------------------+----------------------+
_row < closed, graded_event, graded >,
_row < closed, canceled_event, canceled >,
// +-------------------+------------------+---------------------+------------------------------+----------------------+
//_row < graded re_grading_event, re_grading >,
_row < graded, settled_event, settled >,
_row < graded, canceled_event, canceled >
// +-------------------+------------------+---------------------+------------------------------+----------------------+
//_row < re_grading, graded_event, graded >,
//_row < re_grading, canceled_event, canceled >
// +-------------------+------------------+---------------------+------------------------------+----------------------+
> {};
template <class Fsm,class Event>
void no_transition(Event const& e, Fsm& ,int state)
{
FC_THROW_EXCEPTION(graphene::chain::no_transition, "No transition");
}
betting_market_group_object* betting_market_group_obj;
betting_market_group_state_machine_(betting_market_group_object* betting_market_group_obj) : betting_market_group_obj(betting_market_group_obj) {}
};
typedef msm::back::state_machine<betting_market_group_state_machine_> betting_market_group_state_machine;
} // end anonymous namespace
class betting_market_group_object::impl {
public:
betting_market_group_state_machine state_machine;
impl(betting_market_group_object* self) : state_machine(self) {}
};
betting_market_group_object::betting_market_group_object() :
my(new impl(this))
{
dlog("betting_market_group_object ctor");
}
betting_market_group_object::betting_market_group_object(const betting_market_group_object& rhs) :
graphene::db::abstract_object<betting_market_group_object>(rhs),
description(rhs.description),
event_id(rhs.event_id),
rules_id(rhs.rules_id),
asset_id(rhs.asset_id),
total_matched_bets_amount(rhs.total_matched_bets_amount),
never_in_play(rhs.never_in_play),
delay_before_settling(rhs.delay_before_settling),
settling_time(rhs.settling_time),
my(new impl(this))
{
my->state_machine = rhs.my->state_machine;
my->state_machine.betting_market_group_obj = this;
}
betting_market_group_object& betting_market_group_object::operator=(const betting_market_group_object& rhs)
{
//graphene::db::abstract_object<betting_market_group_object>::operator=(rhs);
id = rhs.id;
description = rhs.description;
event_id = rhs.event_id;
rules_id = rhs.rules_id;
asset_id = rhs.asset_id;
total_matched_bets_amount = rhs.total_matched_bets_amount;
never_in_play = rhs.never_in_play;
delay_before_settling = rhs.delay_before_settling;
settling_time = rhs.settling_time;
my->state_machine = rhs.my->state_machine;
my->state_machine.betting_market_group_obj = this;
return *this;
}
betting_market_group_object::~betting_market_group_object()
{
}
namespace {
bool verify_betting_market_group_status_constants()
{
unsigned error_count = 0;
typedef msm::back::generate_state_set<betting_market_group_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<betting_market_group_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<betting_market_group_state>::to_string((betting_market_group_state)i);
if (!strcmp(fc_reflected_value_name, filled_state_names[i]))
fc_elog(fc::logger::get("default"),
"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("default"),
"Error, no reflection for value ${int_value} in enum betting_market_group_status",
("int_value", i));
++error_count;
}
}
dlog("Done checking constants");
return error_count == 0;
}
} // end anonymous namespace
betting_market_group_status betting_market_group_object::get_status() const
{
static bool state_constants_are_correct = verify_betting_market_group_status_constants();
(void)&state_constants_are_correct;
betting_market_group_state state = (betting_market_group_state)my->state_machine.current_state()[0];
ddump((state));
switch (state)
{
case betting_market_group_state::upcoming:
return betting_market_group_status::upcoming;
case betting_market_group_state::frozen_upcoming:
return betting_market_group_status::frozen;
case betting_market_group_state::in_play:
return betting_market_group_status::in_play;
case betting_market_group_state::frozen_in_play:
return betting_market_group_status::frozen;
case betting_market_group_state::closed:
return betting_market_group_status::closed;
case betting_market_group_state::graded:
return betting_market_group_status::graded;
case betting_market_group_state::canceled:
return betting_market_group_status::canceled;
case betting_market_group_state::settled:
return betting_market_group_status::settled;
default:
FC_THROW("Unexpected betting market group state");
};
}
void betting_market_group_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 betting_market_group_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 betting_market_group_object::on_upcoming_event(database& db)
{
my->state_machine.process_event(upcoming_event(db));
}
void betting_market_group_object::on_in_play_event(database& db)
{
my->state_machine.process_event(in_play_event(db));
}
void betting_market_group_object::on_frozen_event(database& db)
{
my->state_machine.process_event(frozen_event(db));
}
void betting_market_group_object::on_closed_event(database& db, bool closed_by_event)
{
my->state_machine.process_event(closed_event(db, closed_by_event));
}
void betting_market_group_object::on_graded_event(database& db)
{
my->state_machine.process_event(graded_event(db));
}
void betting_market_group_object::on_re_grading_event(database& db)
{
my->state_machine.process_event(re_grading_event(db));
}
void betting_market_group_object::on_settled_event(database& db)
{
my->state_machine.process_event(settled_event(db));
}
void betting_market_group_object::on_canceled_event(database& db, bool canceled_by_event)
{
my->state_machine.process_event(canceled_event(db, canceled_by_event));
}
// These are the only statuses that can be explicitly set by witness operations.
// Other states can only be reached indirectly (i.e., settling happens a fixed
// delay after grading)
void betting_market_group_object::dispatch_new_status(database& db, betting_market_group_status new_status)
{
switch (new_status) {
case betting_market_group_status::upcoming: // by witnesses to unfreeze a bmg
on_upcoming_event(db);
break;
case betting_market_group_status::in_play: // by witnesses to make a bmg in-play
on_in_play_event(db);
break;
case betting_market_group_status::closed: // by witnesses to close a bmg
on_closed_event(db, false);
break;
case betting_market_group_status::frozen: // by witnesses to freeze a bmg
on_frozen_event(db);
break;
case betting_market_group_status::canceled: // by witnesses to cancel a bmg
on_canceled_event(db, false);
break;
default:
FC_THROW("The status ${new_status} cannot be set directly", ("new_status", new_status));
}
}
} } // graphene::chain
namespace fc {
// Manually reflect betting_market_group_object to variant to properly reflect "state"
void to_variant(const graphene::chain::betting_market_group_object& betting_market_group_obj, fc::variant& v)
{
fc::mutable_variant_object o;
o("id", betting_market_group_obj.id)
("description", betting_market_group_obj.description)
("event_id", betting_market_group_obj.event_id)
("rules_id", betting_market_group_obj.rules_id)
("asset_id", betting_market_group_obj.asset_id)
("total_matched_bets_amount", betting_market_group_obj.total_matched_bets_amount)
("never_in_play", betting_market_group_obj.never_in_play)
("delay_before_settling", betting_market_group_obj.delay_before_settling)
("settling_time", betting_market_group_obj.settling_time)
("status", betting_market_group_obj.get_status());
v = o;
}
// Manually reflect betting_market_group_object to variant to properly reflect "state"
void from_variant(const fc::variant& v, graphene::chain::betting_market_group_object& betting_market_group_obj)
{
betting_market_group_obj.id = v["id"].as<graphene::chain::betting_market_group_id_type>();
betting_market_group_obj.description = v["description"].as<graphene::chain::internationalized_string_type>();
betting_market_group_obj.event_id = v["event_id"].as<graphene::chain::event_id_type>();
betting_market_group_obj.asset_id = v["asset_id"].as<graphene::chain::asset_id_type>();
betting_market_group_obj.total_matched_bets_amount = v["total_matched_bets_amount"].as<graphene::chain::share_type>();
betting_market_group_obj.never_in_play = v["never_in_play"].as<bool>();
betting_market_group_obj.delay_before_settling = v["delay_before_settling"].as<uint32_t>();
betting_market_group_obj.settling_time = v["settling_time"].as<fc::optional<fc::time_point_sec>>();
graphene::chain::betting_market_group_status status = v["status"].as<graphene::chain::betting_market_group_status>();
const_cast<int*>(betting_market_group_obj.my->state_machine.current_state())[0] = (int)status;
}
} //end namespace fc

View file

@ -1,7 +1,37 @@
#define DEFAULT_LOGGER "betting"
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/database.hpp>
#include <boost/math/common_factor_rt.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 {
enum class betting_market_state {
unresolved,
frozen,
closed,
canceled,
graded,
settled
};
} }
FC_REFLECT_ENUM(graphene::chain::betting_market_state,
(unresolved)
(frozen)
(closed)
(canceled)
(graded)
(settled))
namespace graphene { namespace chain {
namespace msm = boost::msm;
namespace mpl = boost::mpl;
/* static */ share_type bet_object::get_approximate_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay, bool round_up /* = false */)
{
@ -86,5 +116,326 @@ share_type betting_market_position_object::reduce()
return immediate_winnings;
}
// betting market object implementation
namespace
{
// Events -- most events happen when the witnesses publish an update operation with a new
// status, so if they publish an event with the status set to `frozen`, we'll generate a `frozen_event`
struct unresolved_event
{
database& db;
unresolved_event(database& db) : db(db) {}
};
struct frozen_event
{
database& db;
frozen_event(database& db) : db(db) {}
};
struct closed_event
{
database& db;
closed_event(database& db) : db(db) {}
};
struct graded_event
{
database& db;
betting_market_resolution_type new_grading;
graded_event(database& db, betting_market_resolution_type new_grading) : db(db), new_grading(new_grading) {}
};
struct settled_event
{
database& db;
settled_event(database& db) : db(db) {}
};
struct canceled_event
{
database& db;
canceled_event(database& db) : db(db) {}
};
// Events
struct betting_market_state_machine_ : public msm::front::state_machine_def<betting_market_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 unresolved : public msm::front::state<>{
template <class Event>
void on_entry(const Event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> unresolved", ("id", fsm.betting_market_obj->id));
}
};
struct frozen : public msm::front::state<>{
template <class Event>
void on_entry(const Event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> frozen", ("id", fsm.betting_market_obj->id));
}
};
struct closed : public msm::front::state<>{
template <class Event>
void on_entry(const Event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> closed", ("id", fsm.betting_market_obj->id));
}
};
struct graded : public msm::front::state<>{
void on_entry(const graded_event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> graded", ("id", fsm.betting_market_obj->id));
fsm.betting_market_obj->resolution = event.new_grading;
}
};
struct settled : public msm::front::state<>{
template <class Event>
void on_entry(const Event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> settled", ("id", fsm.betting_market_obj->id));
}
};
struct canceled : public msm::front::state<>{
void on_entry(const canceled_event& event, betting_market_state_machine_& fsm) {
dlog("betting market ${id} -> canceled", ("id", fsm.betting_market_obj->id));
fsm.betting_market_obj->resolution = betting_market_resolution_type::cancel;
}
};
typedef unresolved initial_state;
typedef betting_market_state_machine_ x; // makes transition table cleaner
// Transition table for betting market
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
_row < unresolved, frozen_event, frozen >,
_row < unresolved, closed_event, closed >,
_row < unresolved, canceled_event, canceled >,
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
_row < frozen, unresolved_event, unresolved >,
_row < frozen, closed_event, closed >,
_row < frozen, canceled_event, canceled >,
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
_row < closed, graded_event, graded >,
_row < closed, canceled_event, canceled >,
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
_row < graded, settled_event, settled >,
_row < graded, canceled_event, canceled >
// +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+
> {};
template <class Fsm,class Event>
void no_transition(Event const& e, Fsm& ,int state)
{
FC_THROW_EXCEPTION(graphene::chain::no_transition, "No transition");
}
betting_market_object* betting_market_obj;
betting_market_state_machine_(betting_market_object* betting_market_obj) : betting_market_obj(betting_market_obj) {}
};
typedef msm::back::state_machine<betting_market_state_machine_> betting_market_state_machine;
} // end anonymous namespace
class betting_market_object::impl {
public:
betting_market_state_machine state_machine;
impl(betting_market_object* self) : state_machine(self) {}
};
betting_market_object::betting_market_object() :
my(new impl(this))
{
}
betting_market_object::betting_market_object(const betting_market_object& rhs) :
graphene::db::abstract_object<betting_market_object>(rhs),
group_id(rhs.group_id),
description(rhs.description),
payout_condition(rhs.payout_condition),
my(new impl(this))
{
my->state_machine = rhs.my->state_machine;
my->state_machine.betting_market_obj = this;
}
betting_market_object& betting_market_object::operator=(const betting_market_object& rhs)
{
//graphene::db::abstract_object<betting_market_object>::operator=(rhs);
id = rhs.id;
group_id = rhs.group_id;
description = rhs.description;
payout_condition = rhs.payout_condition;
my->state_machine = rhs.my->state_machine;
my->state_machine.betting_market_obj = this;
return *this;
}
betting_market_object::~betting_market_object()
{
}
namespace {
bool verify_betting_market_status_constants()
{
unsigned error_count = 0;
typedef msm::back::generate_state_set<betting_market_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<betting_market_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<betting_market_state>::to_string((betting_market_state)i);
if (!strcmp(fc_reflected_value_name, filled_state_names[i]))
fc_elog(fc::logger::get("default"),
"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("default"),
"Error, no reflection for value ${int_value} in enum betting_market_status",
("int_value", i));
++error_count;
}
}
dlog("Done checking constants");
return error_count == 0;
}
} // end anonymous namespace
betting_market_status betting_market_object::get_status() const
{
static bool state_constants_are_correct = verify_betting_market_status_constants();
(void)&state_constants_are_correct;
betting_market_state state = (betting_market_state)my->state_machine.current_state()[0];
edump((state));
switch (state)
{
case betting_market_state::unresolved:
return betting_market_status::unresolved;
case betting_market_state::frozen:
return betting_market_status::frozen;
case betting_market_state::closed:
return betting_market_status::unresolved;
case betting_market_state::canceled:
return betting_market_status::canceled;
case betting_market_state::graded:
return betting_market_status::graded;
case betting_market_state::settled:
return betting_market_status::settled;
default:
FC_THROW("Unexpected betting market state");
};
}
void betting_market_object::cancel_all_unmatched_bets(database& db) const
{
const auto& bet_odds_idx = db.get_index_type<bet_object_index>().indices().get<by_odds>();
// first, cancel all bets on the active books
auto book_itr = bet_odds_idx.lower_bound(std::make_tuple(id));
auto book_end = bet_odds_idx.upper_bound(std::make_tuple(id));
while (book_itr != book_end)
{
auto old_book_itr = book_itr;
++book_itr;
db.cancel_bet(*old_book_itr, true);
}
// then, cancel any delayed bets on that market. We don't have an index for
// that, so walk through all delayed bets
book_itr = bet_odds_idx.begin();
while (book_itr != bet_odds_idx.end() &&
book_itr->end_of_delay)
{
auto old_book_itr = book_itr;
++book_itr;
if (old_book_itr->betting_market_id == id)
db.cancel_bet(*old_book_itr, true);
}
}
void betting_market_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 betting_market_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 betting_market_object::on_unresolved_event(database& db)
{
my->state_machine.process_event(unresolved_event(db));
}
void betting_market_object::on_frozen_event(database& db)
{
my->state_machine.process_event(frozen_event(db));
}
void betting_market_object::on_closed_event(database& db)
{
my->state_machine.process_event(closed_event(db));
}
void betting_market_object::on_graded_event(database& db, betting_market_resolution_type new_grading)
{
my->state_machine.process_event(graded_event(db, new_grading));
}
void betting_market_object::on_settled_event(database& db)
{
my->state_machine.process_event(settled_event(db));
}
void betting_market_object::on_canceled_event(database& db)
{
my->state_machine.process_event(canceled_event(db));
}
} } // graphene::chain
namespace fc {
// Manually reflect betting_market_object to variant to properly reflect "state"
void to_variant(const graphene::chain::betting_market_object& event_obj, fc::variant& v)
{
fc::mutable_variant_object o;
o("id", event_obj.id)
("group_id", event_obj.group_id)
("description", event_obj.description)
("payout_condition", event_obj.payout_condition)
("resolution", event_obj.resolution)
("status", event_obj.get_status());
v = o;
}
// Manually reflect betting_market_object to variant to properly reflect "state"
void from_variant(const fc::variant& v, graphene::chain::betting_market_object& event_obj)
{
event_obj.id = v["id"].as<graphene::chain::betting_market_id_type>();
event_obj.group_id = v["name"].as<graphene::chain::betting_market_group_id_type>();
event_obj.description = v["description"].as<graphene::chain::internationalized_string_type>();
event_obj.payout_condition = v["payout_condition"].as<graphene::chain::internationalized_string_type>();
event_obj.resolution = v["resolution"].as<fc::optional<graphene::chain::betting_market_resolution_type>>();
graphene::chain::betting_market_status status = v["status"].as<graphene::chain::betting_market_status>();
const_cast<int*>(event_obj.my->state_machine.current_state())[0] = (int)status;
}
} //end namespace fc

View file

@ -1,3 +1,4 @@
#define DEFAULT_LOGGER "betting"
#include <graphene/chain/database.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
@ -5,6 +6,8 @@
#include <graphene/chain/event_object.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/combine.hpp>
#include <boost/tuple/tuple.hpp>
namespace graphene { namespace chain {
@ -50,30 +53,36 @@ void database::cancel_all_unmatched_bets_on_betting_market(const betting_market_
}
}
void database::cancel_all_betting_markets_for_event(const event_object& event_obj)
{
auto& betting_market_group_index = get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
auto& betting_market_index = get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
//for each betting market group of event
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj.id)))
resolve_betting_market_group(betting_market_group, {});
}
void database::validate_betting_market_group_resolutions(const betting_market_group_object& betting_market_group,
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions)
{
auto& betting_market_index = get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
auto betting_market_itr = betting_market_index.lower_bound(betting_market_group.id);
while (betting_market_itr != betting_market_index.end() && betting_market_itr->group_id == betting_market_group.id)
{
const betting_market_object& betting_market = *betting_market_itr;
// every betting market in the group tied with resolution
//idump((betting_market.id)(resolutions));
assert(resolutions.count(betting_market.id));
++betting_market_itr;
}
auto& betting_market_index = get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
auto betting_markets_in_group = boost::make_iterator_range(betting_market_index.equal_range(betting_market_group.id));
// we must have one resolution for each betting market
FC_ASSERT(resolutions.size() == boost::size(betting_markets_in_group),
"You must publish resolutions for all ${size} markets in the group, you published ${published}", ("size", boost::size(betting_markets_in_group))("published", resolutions.size()));
// both are sorted by id, we can walk through both and verify that they match
unsigned number_of_wins = 0;
unsigned number_of_cancels = 0;
for (const auto& zipped : boost::combine(resolutions, betting_markets_in_group))
{
const auto& resolution = boost::get<0>(zipped);
const auto& betting_market = boost::get<1>(zipped);
FC_ASSERT(resolution.first == betting_market.id, "Missing resolution for betting market ${id}", ("id", betting_market.id));
if (resolution.second == betting_market_resolution_type::cancel)
++number_of_cancels;
else if (resolution.second == betting_market_resolution_type::win)
++number_of_wins;
else
FC_ASSERT(resolution.second == betting_market_resolution_type::not_win);
}
if (number_of_cancels != 0)
FC_ASSERT(number_of_cancels == resolutions.size(), "You must cancel all betting markets or none of the betting markets in the group");
else
FC_ASSERT(number_of_wins == 1, "There must be exactly one winning market");
}
void database::cancel_all_unmatched_bets_on_betting_market_group(const betting_market_group_object& betting_market_group)
@ -90,11 +99,40 @@ void database::cancel_all_unmatched_bets_on_betting_market_group(const betting_m
}
void database::resolve_betting_market_group(const betting_market_group_object& betting_market_group,
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions,
bool do_not_remove)
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions)
{
bool cancel = resolutions.size() == 0;
auto& betting_market_index = get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
auto betting_markets_in_group = boost::make_iterator_range(betting_market_index.equal_range(betting_market_group.id));
bool group_was_canceled = resolutions.begin()->second == betting_market_resolution_type::cancel;
if (group_was_canceled)
modify(betting_market_group, [group_was_canceled,this](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_canceled_event(*this, false); // this cancels the betting markets
});
else {
// TODO: this should be pushed into the bmg's on_graded_event
// both are sorted by id, we can walk through both and verify that they match
for (const auto& zipped : boost::combine(resolutions, betting_markets_in_group))
{
const auto& resolution = boost::get<0>(zipped);
const auto& betting_market = boost::get<1>(zipped);
modify(betting_market, [this,&resolution](betting_market_object& betting_market_obj) {
betting_market_obj.on_graded_event(*this, resolution.second);
});
}
modify(betting_market_group, [group_was_canceled,this](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_graded_event(*this);
});
}
}
void database::settle_betting_market_group(const betting_market_group_object& betting_market_group)
{
ilog("Settling betting market group ${id}", ("id", betting_market_group.id));
// we pay the rake fee to the dividend distribution account for the core asset, go ahead
// and look up that account now
fc::optional<account_id_type> rake_account_id;
@ -105,6 +143,10 @@ void database::resolve_betting_market_group(const betting_market_group_object& b
rake_account_id = core_asset_dividend_data_obj.dividend_distribution_account;
}
// collect the resolutions of all markets in the BMG: they were previously published and
// stored in the individual betting markets
std::map<betting_market_id_type, betting_market_resolution_type> resolutions_by_market_id;
// collecting bettors and their positions
std::map<account_id_type, std::vector<const betting_market_position_object*> > bettor_positions_map;
@ -116,11 +158,13 @@ void database::resolve_betting_market_group(const betting_market_group_object& b
while (betting_market_itr != betting_market_index.end() && betting_market_itr->group_id == betting_market_group.id)
{
const betting_market_object& betting_market = *betting_market_itr;
FC_ASSERT(betting_market_itr->resolution, "Unexpected error settling betting market ${market_id}: no published resolution",
("market_id", betting_market_itr->id));
resolutions_by_market_id.emplace(betting_market.id, *betting_market_itr->resolution);
++betting_market_itr;
cancel_all_unmatched_bets_on_betting_market(betting_market);
// [ROL] why tuple
//auto position_itr = position_index.lower_bound(std::make_tuple(betting_market.id));
auto position_itr = position_index.lower_bound(betting_market.id);
while (position_itr != position_index.end() && position_itr->betting_market_id == betting_market.id)
@ -144,14 +188,24 @@ void database::resolve_betting_market_group(const betting_market_group_object& b
for (const betting_market_position_object* position : bettor_positions)
{
betting_market_resolution_type resolution;
if (cancel)
resolution = betting_market_resolution_type::cancel;
else
try
{
// checked in evaluator, should never happen, see above
assert(resolutions.count(position->betting_market_id));
resolution = resolutions.at(position->betting_market_id);
resolution = resolutions_by_market_id.at(position->betting_market_id);
}
catch (std::out_of_range&)
{
FC_THROW_EXCEPTION(fc::key_not_found_exception, "Unexpected betting market ID, shouldn't happen");
}
///if (cancel)
/// resolution = betting_market_resolution_type::cancel;
///else
///{
/// // checked in evaluator, should never happen, see above
/// assert(resolutions.count(position->betting_market_id));
/// resolution = resolutions.at(position->betting_market_id);
///}
switch (resolution)
{
@ -194,29 +248,41 @@ void database::resolve_betting_market_group(const betting_market_group_object& b
push_applied_operation(betting_market_group_resolved_operation(bettor_id,
betting_market_group.id,
resolutions,
resolutions_by_market_id,
payout_amounts,
rake_amount));
}
betting_market_itr = betting_market_index.lower_bound(betting_market_group.id);
while (betting_market_itr != betting_market_index.end() && betting_market_itr->group_id == betting_market_group.id)
{
const betting_market_object& betting_market = *betting_market_itr;
++betting_market_itr;
if (!do_not_remove)
remove(betting_market);
}
if (!do_not_remove)
remove(betting_market_group);
}
// At this point, the betting market group will either be in the "graded" or "canceled" state,
// if it was graded, mark it as settled. if it's canceled, let it remain canceled.
#if 0
void database::get_required_deposit_for_bet(const betting_market_object& betting_market,
betting_market_resolution_type resolution)
{
bool was_canceled = betting_market_group.get_status() == betting_market_group_status::canceled;
if (!was_canceled)
modify(betting_market_group, [&](betting_market_group_object& group) {
group.on_settled_event(*this);
});
betting_market_itr = betting_market_index.lower_bound(betting_market_group.id);
while (betting_market_itr != betting_market_index.end() && betting_market_itr->group_id == betting_market_group.id) {
const betting_market_object& betting_market = *betting_market_itr;
++betting_market_itr;
dlog("removing betting market ${id}", ("id", betting_market.id));
remove(betting_market);
}
const event_object& event = betting_market_group.event_id(*this);
dlog("removing betting market group ${id}", ("id", betting_market_group.id));
remove(betting_market_group);
if (event.get_status() == event_status::canceled ||
event.get_status() == event_status::settled) {
dlog("removing event ${id}", ("id", event.id));
remove(event);
}
}
#endif
share_type adjust_betting_position(database& db, account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, share_type bet_amount, share_type matched_amount)
{ try {

View file

@ -551,6 +551,7 @@ void database::_apply_block( const signed_block& next_block )
update_expired_feeds();
update_withdraw_permissions();
update_tournaments();
update_betting_markets(next_block.timestamp);
// n.b., update_maintenance_flag() happens this late
// because get_slot_time() / get_slot_at_time() is needed above
@ -568,7 +569,6 @@ void database::_apply_block( const signed_block& next_block )
_applied_ops.clear();
notify_changed_objects();
} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) ) }

View file

@ -35,6 +35,7 @@
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
@ -222,8 +223,7 @@ void database::place_delayed_bets()
// Our current understanding is that the witnesses will typically cancel all unmatched
// bets on frozen markets to avoid this.
const betting_market_object& betting_market = bet_to_place.betting_market_id(*this);
const betting_market_group_object& betting_market_group = betting_market.group_id(*this);
if (!betting_market_group.frozen)
if (betting_market.get_status() == betting_market_status::unresolved)
{
modify(bet_to_place, [](bet_object& bet_obj) {
// clear the end_of_delay, which will re-sort the bet into its place in the book
@ -655,4 +655,28 @@ void database::update_tournaments()
initiate_next_games(*this);
}
void process_settled_betting_markets(database& db, fc::time_point_sec current_block_time)
{
// after a betting market is graded, it goes through a delay period in which it
// can be flagged for re-grading. If it isn't flagged during this interval,
// it is automatically settled (paid). Process these now.
const auto& betting_market_group_index = db.get_index_type<betting_market_group_object_index>().indices().get<by_settling_time>();
// this index will be sorted with all bmgs with no settling time set first, followed by
// ones with the settling time set by increasing time. Start at the first bmg with a time set
auto betting_market_group_iter = betting_market_group_index.upper_bound(fc::optional<fc::time_point_sec>());
while (betting_market_group_iter != betting_market_group_index.end() &&
*betting_market_group_iter->settling_time <= current_block_time)
{
auto next_iter = std::next(betting_market_group_iter);
db.settle_betting_market_group(*betting_market_group_iter);
betting_market_group_iter = next_iter;
}
}
void database::update_betting_markets(fc::time_point_sec current_block_time)
{
process_settled_betting_markets(*this, current_block_time);
}
} }

View file

@ -58,7 +58,6 @@ object_id_type event_create_evaluator::do_apply(const event_create_operation& op
const event_object& new_event =
d.create<event_object>( [&]( event_object& event_obj ) {
event_obj.name = op.name;
event_obj.status = event_status::upcoming;
event_obj.season = op.season;
event_obj.start_time = op.start_time;
event_obj.event_group_id = event_group_id;
@ -74,28 +73,19 @@ object_id_type event_create_evaluator::do_apply(const event_create_operation& op
void_result event_update_evaluator::do_evaluate(const event_update_operation& op)
{ try {
FC_ASSERT(trx_state->_is_proposed_trx);
FC_ASSERT(op.new_event_group_id.valid() ||
op.new_name.valid() ||
op.new_season.valid() ||
op.new_start_time.valid() ||
op.is_live_market.valid(), "nothing to change");
FC_ASSERT(op.new_event_group_id || op.new_name || op.new_season ||
op.new_start_time || op.new_status, "nothing to change");
if (op.new_event_group_id.valid())
if (op.new_event_group_id)
{
object_id_type resolved_event_group_id = *op.new_event_group_id;
if (is_relative(*op.new_event_group_id))
resolved_event_group_id = get_relative_id(*op.new_event_group_id);
object_id_type resolved_event_group_id = *op.new_event_group_id;
if (is_relative(*op.new_event_group_id))
resolved_event_group_id = get_relative_id(*op.new_event_group_id);
FC_ASSERT(resolved_event_group_id.space() == event_group_id_type::space_id &&
resolved_event_group_id.type() == event_group_id_type::type_id,
"event_group_id must refer to a event_group_id_type");
event_group_id = resolved_event_group_id;
}
if (op.is_live_market.valid())
{
const auto& _event_object = &op.event_id(db());
FC_ASSERT(_event_object->is_live_market != *op.is_live_market, "is_live_market would not change the state of the event");
FC_ASSERT(resolved_event_group_id.space() == event_group_id_type::space_id &&
resolved_event_group_id.type() == event_group_id_type::type_id,
"event_group_id must refer to a event_group_id_type");
event_group_id = resolved_event_group_id;
}
return void_result();
@ -103,25 +93,21 @@ void_result event_update_evaluator::do_evaluate(const event_update_operation& op
void_result event_update_evaluator::do_apply(const event_update_operation& op)
{ try {
database& _db = db();
_db.modify(
_db.get(op.event_id),
[&]( event_object& eo )
{
if (eo.status > event_status::STATUS_COUNT)
eo.status = event_status::upcoming;
if( op.new_name.valid() )
eo.name = *op.new_name;
if( op.new_season.valid() )
eo.season = *op.new_season;
if( op.new_start_time.valid() )
eo.start_time = *op.new_start_time;
if( op.new_event_group_id.valid() )
eo.event_group_id = event_group_id;
if( op.is_live_market.valid() )
eo.is_live_market = *op.is_live_market;
});
return void_result();
database& _db = db();
_db.modify(_db.get(op.event_id),
[&](event_object& eo) {
if( op.new_name )
eo.name = *op.new_name;
if( op.new_season )
eo.season = *op.new_season;
if( op.new_start_time )
eo.start_time = *op.new_start_time;
if( op.new_event_group_id )
eo.event_group_id = event_group_id;
if( op.new_status )
eo.dispatch_new_status(_db, *op.new_status);
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result event_update_status_evaluator::do_evaluate(const event_update_status_operation& op)
@ -138,14 +124,13 @@ void_result event_update_status_evaluator::do_evaluate(const event_update_status
void_result event_update_status_evaluator::do_apply(const event_update_status_operation& op)
{ try {
database& d = db();
//if event is canceled, first cancel all associated betting markets
if (op.status == event_status::canceled)
d.cancel_all_betting_markets_for_event(*_event_to_update);
//update event
d.modify( *_event_to_update, [&]( event_object& event_obj) {
d.modify( *_event_to_update, [&](event_object& event_obj) {
if (_event_to_update->get_status() != op.status)
event_obj.dispatch_new_status(d, op.status);
event_obj.scores = op.scores;
event_obj.status = op.status;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }

View file

@ -0,0 +1,547 @@
#define DEFAULT_LOGGER "betting"
#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS
#define BOOST_MPL_LIMIT_VECTOR_SIZE 30
#define BOOST_MPL_LIMIT_MAP_SIZE 30
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/betting_market_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 {
enum class event_state {
upcoming,
frozen_upcoming,
in_progress,
frozen_in_progress,
finished,
canceled,
settled
};
} }
FC_REFLECT_ENUM(graphene::chain::event_state,
(upcoming)
(frozen_upcoming)
(in_progress)
(frozen_in_progress)
(finished)
(canceled)
(settled))
namespace graphene { namespace chain {
namespace msm = boost::msm;
namespace mpl = boost::mpl;
namespace
{
// Events -- most events happen when the witnesses publish an event_update operation with a new
// status, so if they publish an event with the status set to `frozen`, we'll generate a `frozen_event`
struct upcoming_event
{
database& db;
upcoming_event(database& db) : db(db) {}
};
struct in_progress_event
{
database& db;
in_progress_event(database& db) : db(db) {}
};
struct frozen_event
{
database& db;
frozen_event(database& db) : db(db) {}
};
struct finished_event
{
database& db;
finished_event(database& db) : db(db) {}
};
struct canceled_event
{
database& db;
canceled_event(database& db) : db(db) {}
};
// event triggered when a betting market group in this event is resolved,
// when we get this, check and see if all betting market groups are now
// canceled/settled; if so, transition the event to canceled/settled,
// otherwise remain in the current state
struct betting_market_group_resolved_event
{
database& db;
betting_market_group_id_type resolved_group;
bool was_canceled;
betting_market_group_resolved_event(database& db, betting_market_group_id_type resolved_group, bool was_canceled) : db(db), resolved_group(resolved_group), was_canceled(was_canceled) {}
};
// event triggered when a betting market group is closed. When we get this,
// if all child betting market groups are closed, transition to finished
struct betting_market_group_closed_event
{
database& db;
betting_market_group_id_type closed_group;
betting_market_group_closed_event(database& db, betting_market_group_id_type closed_group) : db(db), closed_group(closed_group) {}
};
// Events
struct event_state_machine_ : public msm::front::state_machine_def<event_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 upcoming : public msm::front::state<> {
void on_entry(const betting_market_group_resolved_event& event, event_state_machine_& fsm) {} // transition to self
void on_entry(const upcoming_event& event, event_state_machine_& fsm) {
dlog("event ${id} -> upcoming", ("id", fsm.event_obj->id));
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(fsm.event_obj->id)))
try
{
event.db.modify(betting_market_group, [&event](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_upcoming_event(event.db);
});
}
catch (const graphene::chain::no_transition&)
{
// it's possible a betting market group has already been closed or canceled,
// in which case we can't freeze the group. just ignore the exception
}
}
};
struct in_progress : public msm::front::state<> {
void on_entry(const betting_market_group_resolved_event& event, event_state_machine_& fsm) {} // transition to self
void on_entry(const in_progress_event& event, event_state_machine_& fsm) {
dlog("event ${id} -> in_progress", ("id", fsm.event_obj->id));
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(fsm.event_obj->id)))
try
{
event.db.modify(betting_market_group, [&event](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_in_play_event(event.db);
});
}
catch (const graphene::chain::no_transition&)
{
// it's possible a betting market group has already been closed or canceled,
// in which case we can't freeze the group. just ignore the exception
}
}
};
struct frozen_upcoming : public msm::front::state<> {
void on_entry(const betting_market_group_resolved_event& event, event_state_machine_& fsm) {} // transition to self
template <class Event>
void on_entry(const Event& event, event_state_machine_& fsm) {
dlog("event ${id} -> frozen_upcoming", ("id", fsm.event_obj->id));
}
};
struct frozen_in_progress : public msm::front::state<> {
void on_entry(const betting_market_group_resolved_event& event, event_state_machine_& fsm) {} // transition to self
template <class Event>
void on_entry(const Event& event, event_state_machine_& fsm) {
dlog("event ${id} -> frozen_in_progress", ("id", fsm.event_obj->id));
}
};
struct finished : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, event_state_machine_& fsm) {
dlog("event ${id} -> finished", ("id", fsm.event_obj->id));
}
};
struct settled : public msm::front::state<>{
template <class Event>
void on_entry(const Event& event, event_state_machine_& fsm) {
dlog("event ${id} -> settled", ("id", fsm.event_obj->id));
}
};
struct canceled : public msm::front::state<> {
template <class Event>
void on_entry(const Event& event, event_state_machine_& fsm) {
dlog("event ${id} -> canceled", ("id", fsm.event_obj->id));
}
};
// actions
void record_whether_group_settled_or_canceled(const betting_market_group_resolved_event& event) {
if (!event.was_canceled)
event_obj->at_least_one_betting_market_group_settled = true;
}
void freeze_betting_market_groups(const frozen_event& event) {
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id)))
{
try
{
event.db.modify(betting_market_group, [&event](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_frozen_event(event.db);
});
}
catch (const graphene::chain::no_transition&)
{
// it's possible a betting market group has already been closed or canceled,
// in which case we can't freeze the group. just ignore the exception
}
}
}
void close_all_betting_market_groups(const finished_event& event) {
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id)))
{
try
{
event.db.modify(betting_market_group, [&event](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_closed_event(event.db, true);
});
}
catch (const graphene::chain::no_transition&)
{
// it's possible a betting market group has already been closed or canceled,
// in which case we can't close the group. just ignore the exception
}
}
}
void cancel_all_betting_market_groups(const canceled_event& event) {
auto& betting_market_group_index = event.db.template get_index_type<betting_market_group_object_index>().indices().template get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id)))
event.db.modify(betting_market_group, [&event](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.on_canceled_event(event.db, true);
});
}
// Guards
bool all_betting_market_groups_are_closed(const betting_market_group_closed_event& event)
{
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id)))
if (betting_market_group.id != event.closed_group)
{
betting_market_group_status status = betting_market_group.get_status();
if (status != betting_market_group_status::closed &&
status != betting_market_group_status::graded &&
status != betting_market_group_status::re_grading &&
status != betting_market_group_status::settled &&
status != betting_market_group_status::canceled)
return false;
}
return true;
}
bool all_betting_market_groups_are_canceled(const betting_market_group_resolved_event& event)
{
// if the bmg that just resolved was settled, obviously all didn't cancel
if (!event.was_canceled)
return false;
// if a previously-resolved group was settled, all didn't cancel
if (event_obj->at_least_one_betting_market_group_settled)
return false;
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id)))
if (betting_market_group.id != event.resolved_group)
if (betting_market_group.get_status() != betting_market_group_status::canceled)
return false;
return true;
}
bool all_betting_market_groups_are_resolved(const betting_market_group_resolved_event& event)
{
if (!event.was_canceled)
event_obj->at_least_one_betting_market_group_settled = true;
auto& betting_market_group_index = event.db.get_index_type<betting_market_group_object_index>().indices().get<by_event_id>();
for (const betting_market_group_object& betting_market_group :
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj->id))) {
if (betting_market_group.id != event.resolved_group) {
betting_market_group_status status = betting_market_group.get_status();
if (status != betting_market_group_status::canceled && status != betting_market_group_status::settled)
return false;
}
}
return true;
}
typedef upcoming initial_state;
typedef event_state_machine_ x; // makes transition table cleaner
// Transition table for tournament
struct transition_table : mpl::vector<
// Start Event Next Action Guard
// +---------------------+-----------------------------+--------------------+--------------------------------+----------------------+
_row < upcoming, in_progress_event, in_progress >,
a_row< upcoming, finished_event, finished, &x::close_all_betting_market_groups >,
a_row< upcoming, frozen_event, frozen_upcoming, &x::freeze_betting_market_groups >,
a_row< upcoming, canceled_event, canceled, &x::cancel_all_betting_market_groups >,
a_row< upcoming, betting_market_group_resolved_event,upcoming, &x::record_whether_group_settled_or_canceled >,
g_row< upcoming, betting_market_group_closed_event, finished, &x::all_betting_market_groups_are_closed >,
// +---------------------+-----------------------------+--------------------+--------------------------------+----------------------+
_row < frozen_upcoming, upcoming_event, upcoming >,
_row < frozen_upcoming, in_progress_event, in_progress >,
a_row< frozen_upcoming, finished_event, finished, &x::close_all_betting_market_groups >,
a_row< frozen_upcoming, canceled_event, canceled, &x::cancel_all_betting_market_groups >,
a_row< frozen_upcoming, betting_market_group_resolved_event,frozen_upcoming, &x::record_whether_group_settled_or_canceled >,
g_row< frozen_upcoming, betting_market_group_closed_event, finished, &x::all_betting_market_groups_are_closed >,
// +---------------------+-----------------------------+--------------------+--------------------------------+----------------------+
a_row< in_progress, frozen_event, frozen_in_progress, &x::freeze_betting_market_groups >,
a_row< in_progress, finished_event, finished, &x::close_all_betting_market_groups >,
a_row< in_progress, canceled_event, canceled, &x::cancel_all_betting_market_groups >,
a_row< in_progress, betting_market_group_resolved_event,in_progress, &x::record_whether_group_settled_or_canceled >,
g_row< in_progress, betting_market_group_closed_event, finished, &x::all_betting_market_groups_are_closed >,
// +---------------------+-----------------------------+--------------------+--------------------------------+----------------------+
_row < frozen_in_progress, in_progress_event, in_progress >,
a_row< frozen_in_progress, finished_event, finished, &x::close_all_betting_market_groups >,
a_row< frozen_in_progress, canceled_event, canceled, &x::cancel_all_betting_market_groups >,
a_row< frozen_in_progress, betting_market_group_resolved_event,frozen_in_progress, &x::record_whether_group_settled_or_canceled >,
g_row< frozen_in_progress, betting_market_group_closed_event, finished, &x::all_betting_market_groups_are_closed >,
// +---------------------+-----------------------------+--------------------+--------------------------------+----------------------+
a_row< finished, canceled_event, canceled, &x::cancel_all_betting_market_groups >,
g_row< finished, betting_market_group_resolved_event,settled, &x::all_betting_market_groups_are_resolved >,
g_row< finished, betting_market_group_resolved_event,canceled, &x::all_betting_market_groups_are_canceled >
> {};
template <class Fsm,class Event>
void no_transition(Event const& e, Fsm& ,int state)
{
FC_THROW_EXCEPTION(graphene::chain::no_transition, "No transition");
}
event_object* event_obj;
event_state_machine_(event_object* event_obj) : event_obj(event_obj) {}
};
typedef msm::back::state_machine<event_state_machine_> event_state_machine;
} // end anonymous namespace
class event_object::impl {
public:
event_state_machine state_machine;
impl(event_object* self) : state_machine(self) {}
};
event_object::event_object() :
at_least_one_betting_market_group_settled(false),
my(new impl(this))
{
}
event_object::event_object(const event_object& rhs) :
graphene::db::abstract_object<event_object>(rhs),
name(rhs.name),
season(rhs.season),
start_time(rhs.start_time),
event_group_id(rhs.event_group_id),
at_least_one_betting_market_group_settled(rhs.at_least_one_betting_market_group_settled),
scores(rhs.scores),
my(new impl(this))
{
my->state_machine = rhs.my->state_machine;
my->state_machine.event_obj = this;
}
event_object& event_object::operator=(const event_object& rhs)
{
//graphene::db::abstract_object<event_object>::operator=(rhs);
id = rhs.id;
name = rhs.name;
season = rhs.season;
start_time = rhs.start_time;
event_group_id = rhs.event_group_id;
at_least_one_betting_market_group_settled = rhs.at_least_one_betting_market_group_settled;
scores = rhs.scores;
my->state_machine = rhs.my->state_machine;
my->state_machine.event_obj = this;
return *this;
}
event_object::~event_object()
{
}
namespace {
bool verify_event_status_constants()
{
unsigned error_count = 0;
typedef msm::back::generate_state_set<event_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<event_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<event_state>::to_string((event_state)i);
if (!strcmp(fc_reflected_value_name, filled_state_names[i]))
fc_elog(fc::logger::get("default"),
"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("default"),
"Error, no reflection for value ${int_value} in enum event_status",
("int_value", i));
++error_count;
}
}
dlog("Done checking constants");
return error_count == 0;
}
} // end anonymous namespace
event_status event_object::get_status() const
{
static bool state_constants_are_correct = verify_event_status_constants();
(void)&state_constants_are_correct;
event_state state = (event_state)my->state_machine.current_state()[0];
ddump((state));
switch (state)
{
case event_state::upcoming:
return event_status::upcoming;
case event_state::frozen_upcoming:
case event_state::frozen_in_progress:
return event_status::frozen;
case event_state::in_progress:
return event_status::in_progress;
case event_state::finished:
return event_status::finished;
case event_state::canceled:
return event_status::canceled;
case event_state::settled:
return event_status::settled;
default:
FC_THROW("Unexpected event state");
};
}
void event_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 event_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 event_object::on_upcoming_event(database& db)
{
my->state_machine.process_event(upcoming_event(db));
}
void event_object::on_in_progress_event(database& db)
{
my->state_machine.process_event(in_progress_event(db));
}
void event_object::on_frozen_event(database& db)
{
my->state_machine.process_event(frozen_event(db));
}
void event_object::on_finished_event(database& db)
{
my->state_machine.process_event(finished_event(db));
}
void event_object::on_canceled_event(database& db)
{
my->state_machine.process_event(canceled_event(db));
}
void event_object::on_betting_market_group_resolved(database& db, betting_market_group_id_type resolved_group, bool was_canceled)
{
my->state_machine.process_event(betting_market_group_resolved_event(db, resolved_group, was_canceled));
}
void event_object::on_betting_market_group_closed(database& db, betting_market_group_id_type closed_group)
{
my->state_machine.process_event(betting_market_group_closed_event(db, closed_group));
}
// These are the only statuses that can be explicitly set by witness operations. The missing
// status, 'settled', is automatically set when all of the betting market groups have
// settled/canceled
void event_object::dispatch_new_status(database& db, event_status new_status)
{
switch (new_status) {
case event_status::upcoming: // by witnesses to unfreeze a frozen event
on_upcoming_event(db);
break;
case event_status::in_progress: // by witnesses when the event starts
on_in_progress_event(db);
break;
case event_status::frozen: // by witnesses when the event needs to be frozen
on_frozen_event(db);
break;
case event_status::finished: // by witnesses when the event is complete
on_finished_event(db);
break;
case event_status::canceled: // by witnesses to cancel the event
on_canceled_event(db);
break;
default:
FC_THROW("Status ${new_status} cannot be explicitly set", ("new_status", new_status));
}
}
} } // graphene::chain
namespace fc {
// Manually reflect event_object to variant to properly reflect "state"
void to_variant(const graphene::chain::event_object& event_obj, fc::variant& v)
{
fc::mutable_variant_object o;
o("id", event_obj.id)
("name", event_obj.name)
("season", event_obj.season)
("start_time", event_obj.start_time)
("event_group_id", event_obj.event_group_id)
("scores", event_obj.scores)
("status", event_obj.get_status());
v = o;
}
// Manually reflect event_object to variant to properly reflect "state"
void from_variant(const fc::variant& v, graphene::chain::event_object& event_obj)
{
event_obj.id = v["id"].as<graphene::chain::event_id_type>();
event_obj.name = v["name"].as<graphene::chain::internationalized_string_type>();
event_obj.season = v["season"].as<graphene::chain::internationalized_string_type>();
event_obj.start_time = v["start_time"].as<optional<time_point_sec> >();
event_obj.event_group_id = v["event_group_id"].as<graphene::chain::event_group_id_type>();
event_obj.scores = v["scores"].as<std::vector<std::string>>();
graphene::chain::event_status status = v["status"].as<graphene::chain::event_status>();
const_cast<int*>(event_obj.my->state_machine.current_state())[0] = (int)status;
}
} //end namespace fc

View file

@ -31,10 +31,24 @@
#include <boost/multi_index/composite_key.hpp>
namespace graphene { namespace chain {
class betting_market_object;
class betting_market_group_object;
} }
namespace fc {
void to_variant(const graphene::chain::betting_market_object& betting_market_obj, fc::variant& v);
void from_variant(const fc::variant& v, graphene::chain::betting_market_object& betting_market_obj);
void to_variant(const graphene::chain::betting_market_group_object& betting_market_group_obj, fc::variant& v);
void from_variant(const fc::variant& v, graphene::chain::betting_market_group_object& betting_market_group_obj);
} //end namespace fc
namespace graphene { namespace chain {
FC_DECLARE_EXCEPTION(no_transition, 100000, "Invalid state transition");
class database;
struct by_event_id;
struct by_settling_time;
struct by_betting_market_group_id;
class betting_market_rules_object : public graphene::db::abstract_object< betting_market_rules_object >
@ -54,6 +68,11 @@ class betting_market_group_object : public graphene::db::abstract_object< bettin
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = betting_market_group_object_type;
betting_market_group_object();
betting_market_group_object(const betting_market_group_object& rhs);
~betting_market_group_object();
betting_market_group_object& operator=(const betting_market_group_object& rhs);
internationalized_string_type description;
event_id_type event_id;
@ -64,9 +83,51 @@ class betting_market_group_object : public graphene::db::abstract_object< bettin
share_type total_matched_bets_amount;
bool frozen;
bool never_in_play;
bool delay_bets;
uint32_t delay_before_settling;
fc::optional<fc::time_point_sec> settling_time; // the time the payout will occur (set after grading)
bool bets_are_allowed() const {
return get_status() == betting_market_group_status::upcoming ||
get_status() == betting_market_group_status::in_play;
}
bool bets_are_delayed() const {
return get_status() == betting_market_group_status::in_play;
}
betting_market_group_status get_status() 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 betting_market_group_object& betting_market_group_obj );
template<typename Stream>
friend Stream& operator>>( Stream& s, betting_market_group_object& betting_market_group_obj );
friend void ::fc::to_variant(const graphene::chain::betting_market_group_object& betting_market_group_obj, fc::variant& v);
friend void ::fc::from_variant(const fc::variant& v, graphene::chain::betting_market_group_object& betting_market_group_obj);
void pack_impl(std::ostream& stream) const;
void unpack_impl(std::istream& stream);
void on_upcoming_event(database& db);
void on_in_play_event(database& db);
void on_frozen_event(database& db);
void on_closed_event(database& db, bool closed_by_event);
void on_graded_event(database& db);
void on_re_grading_event(database& db);
void on_settled_event(database& db);
void on_canceled_event(database& db, bool canceled_by_event);
void dispatch_new_status(database& db, betting_market_group_status new_status);
private:
class impl;
std::unique_ptr<impl> my;
};
class betting_market_object : public graphene::db::abstract_object< betting_market_object >
@ -75,11 +136,49 @@ class betting_market_object : public graphene::db::abstract_object< betting_mark
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = betting_market_object_type;
betting_market_object();
betting_market_object(const betting_market_object& rhs);
~betting_market_object();
betting_market_object& operator=(const betting_market_object& rhs);
betting_market_group_id_type group_id;
internationalized_string_type description;
internationalized_string_type payout_condition;
// once the market is graded, this holds the proposed grading
// after settling/canceling, this is the actual grading
fc::optional<betting_market_resolution_type> resolution;
betting_market_status get_status() const;
void cancel_all_unmatched_bets(database& db) 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 betting_market_object& betting_market_obj );
template<typename Stream>
friend Stream& operator>>( Stream& s, betting_market_object& betting_market_obj );
friend void ::fc::to_variant(const graphene::chain::betting_market_object& betting_market_obj, fc::variant& v);
friend void ::fc::from_variant(const fc::variant& v, graphene::chain::betting_market_object& betting_market_obj);
void pack_impl(std::ostream& stream) const;
void unpack_impl(std::istream& stream);
void on_unresolved_event(database& db);
void on_frozen_event(database& db);
void on_closed_event(database& db);
void on_graded_event(database& db, betting_market_resolution_type new_grading);
void on_settled_event(database& db);
void on_canceled_event(database& db);
private:
class impl;
std::unique_ptr<impl> my;
};
class bet_object : public graphene::db::abstract_object< bet_object >
@ -147,7 +246,8 @@ typedef multi_index_container<
betting_market_group_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_non_unique< tag<by_event_id>, member<betting_market_group_object, event_id_type, &betting_market_group_object::event_id> >
ordered_non_unique< tag<by_event_id>, member<betting_market_group_object, event_id_type, &betting_market_group_object::event_id> >,
ordered_non_unique< tag<by_settling_time>, member<betting_market_group_object, fc::optional<fc::time_point_sec>, &betting_market_group_object::settling_time> >
> > betting_market_group_object_multi_index_type;
typedef generic_index<betting_market_group_object, betting_market_group_object_multi_index_type> betting_market_group_object_index;
@ -513,11 +613,101 @@ typedef multi_index_container<
> > betting_market_position_multi_index_type;
typedef generic_index<betting_market_position_object, betting_market_position_multi_index_type> betting_market_position_index;
template<typename Stream>
inline Stream& operator<<( Stream& s, const betting_market_object& betting_market_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<betting_market_object> >(s, betting_market_obj);
fc::raw::pack(s, betting_market_obj.id);
fc::raw::pack(s, betting_market_obj.group_id);
fc::raw::pack(s, betting_market_obj.description);
fc::raw::pack(s, betting_market_obj.payout_condition);
fc::raw::pack(s, betting_market_obj.resolution);
// fc::raw::pack the contents hidden in the impl class
std::ostringstream stream;
betting_market_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, betting_market_object& betting_market_obj )
{
// unpack all fields exposed in the header in the usual way
//fc::raw::unpack<Stream, graphene::db::abstract_object<betting_market_object> >(s, betting_market_obj);
fc::raw::unpack(s, betting_market_obj.id);
fc::raw::unpack(s, betting_market_obj.group_id);
fc::raw::unpack(s, betting_market_obj.description);
fc::raw::unpack(s, betting_market_obj.payout_condition);
fc::raw::unpack(s, betting_market_obj.resolution);
// 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);
betting_market_obj.unpack_impl(stream);
return s;
}
template<typename Stream>
inline Stream& operator<<( Stream& s, const betting_market_group_object& betting_market_group_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<betting_market_group_object> >(s, betting_market_group_obj);
fc::raw::pack(s, betting_market_group_obj.id);
fc::raw::pack(s, betting_market_group_obj.description);
fc::raw::pack(s, betting_market_group_obj.event_id);
fc::raw::pack(s, betting_market_group_obj.rules_id);
fc::raw::pack(s, betting_market_group_obj.asset_id);
fc::raw::pack(s, betting_market_group_obj.total_matched_bets_amount);
fc::raw::pack(s, betting_market_group_obj.never_in_play);
fc::raw::pack(s, betting_market_group_obj.delay_before_settling);
fc::raw::pack(s, betting_market_group_obj.settling_time);
// fc::raw::pack the contents hidden in the impl class
std::ostringstream stream;
betting_market_group_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, betting_market_group_object& betting_market_group_obj )
{
// unpack all fields exposed in the header in the usual way
//fc::raw::unpack<Stream, graphene::db::abstract_object<betting_market_group_object> >(s, betting_market_group_obj);
fc::raw::unpack(s, betting_market_group_obj.id);
fc::raw::unpack(s, betting_market_group_obj.description);
fc::raw::unpack(s, betting_market_group_obj.event_id);
fc::raw::unpack(s, betting_market_group_obj.rules_id);
fc::raw::unpack(s, betting_market_group_obj.asset_id);
fc::raw::unpack(s, betting_market_group_obj.total_matched_bets_amount);
fc::raw::unpack(s, betting_market_group_obj.never_in_play);
fc::raw::unpack(s, betting_market_group_obj.delay_before_settling);
fc::raw::unpack(s, betting_market_group_obj.settling_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);
betting_market_group_obj.unpack_impl(stream);
return s;
}
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::betting_market_rules_object, (graphene::db::object), (name)(description) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_group_object, (graphene::db::object), (description)(event_id)(rules_id)(asset_id)(frozen)(delay_bets)(total_matched_bets_amount) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_object, (graphene::db::object), (group_id)(description)(payout_condition) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_group_object, (graphene::db::object), (description) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_object, (graphene::db::object), (group_id) )
FC_REFLECT_DERIVED( graphene::chain::bet_object, (graphene::db::object), (bettor_id)(betting_market_id)(amount_to_bet)(backer_multiplier)(back_or_lay)(end_of_delay) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_position_object, (graphene::db::object), (bettor_id)(betting_market_id)(pay_if_payout_condition)(pay_if_not_payout_condition)(pay_if_canceled)(pay_if_not_canceled)(fees_collected) )

View file

@ -391,12 +391,11 @@ namespace graphene { namespace chain {
void cancel_bet(const bet_object& bet, bool create_virtual_op = true);
void cancel_all_unmatched_bets_on_betting_market(const betting_market_object& betting_market);
void cancel_all_unmatched_bets_on_betting_market_group(const betting_market_group_object& betting_market_group);
void cancel_all_betting_markets_for_event(const event_object&);
void validate_betting_market_group_resolutions(const betting_market_group_object& betting_market_group,
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions);
void resolve_betting_market_group(const betting_market_group_object& betting_market_group,
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions,
bool do_not_remove = false);
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions);
void settle_betting_market_group(const betting_market_group_object& betting_market_group);
/**
* @brief Process a new bet
* @param new_bet_object The new bet to process
@ -481,6 +480,7 @@ namespace graphene { namespace chain {
void update_maintenance_flag( bool new_maintenance_flag );
void update_withdraw_permissions();
void update_tournaments();
void update_betting_markets(fc::time_point_sec current_block_time);
bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true );
///Steps performed only at maintenance intervals

View file

@ -26,6 +26,16 @@
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
#include <graphene/chain/protocol/event.hpp>
namespace graphene { namespace chain {
class event_object;
} }
namespace fc {
void to_variant(const graphene::chain::event_object& event_obj, fc::variant& v);
void from_variant(const fc::variant& v, graphene::chain::event_object& event_obj);
} //end namespace fc
namespace graphene { namespace chain {
@ -37,6 +47,11 @@ class event_object : public graphene::db::abstract_object< event_object >
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = event_object_type;
event_object();
event_object(const event_object& rhs);
~event_object();
event_object& operator=(const event_object& rhs);
internationalized_string_type name;
internationalized_string_type season;
@ -45,10 +60,38 @@ class event_object : public graphene::db::abstract_object< event_object >
event_group_id_type event_group_id;
bool is_live_market;
bool at_least_one_betting_market_group_settled;
event_status status;
event_status get_status() const;
vector<string> scores;
// 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 event_object& event_obj );
template<typename Stream>
friend Stream& operator>>( Stream& s, event_object& event_obj );
friend void ::fc::to_variant(const graphene::chain::event_object& event_obj, fc::variant& v);
friend void ::fc::from_variant(const fc::variant& v, graphene::chain::event_object& event_obj);
void pack_impl(std::ostream& stream) const;
void unpack_impl(std::istream& stream);
void on_upcoming_event(database& db);
void on_in_progress_event(database& db);
void on_frozen_event(database& db);
void on_finished_event(database& db);
void on_canceled_event(database& db);
void on_settled_event(database& db);
void on_betting_market_group_resolved(database& db, betting_market_group_id_type resolved_group, bool was_canceled);
void on_betting_market_group_closed(database& db, betting_market_group_id_type closed_group);
void dispatch_new_status(database& db, event_status new_status);
private:
class impl;
std::unique_ptr<impl> my;
};
struct by_event_group_id;
@ -59,6 +102,52 @@ typedef multi_index_container<
ordered_non_unique< tag<by_event_group_id>, member< event_object, event_group_id_type, &event_object::event_group_id > > > > event_object_multi_index_type;
typedef generic_index<event_object, event_object_multi_index_type> event_object_index;
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::event_object, (graphene::db::object), (name)(season)(start_time)(event_group_id)(is_live_market)(status)(scores) )
template<typename Stream>
inline Stream& operator<<( Stream& s, const event_object& event_obj )
{
fc_elog(fc::logger::get("event"), "In event_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<event_object> >(s, event_obj);
fc::raw::pack(s, event_obj.id);
fc::raw::pack(s, event_obj.name);
fc::raw::pack(s, event_obj.season);
fc::raw::pack(s, event_obj.start_time);
fc::raw::pack(s, event_obj.event_group_id);
fc::raw::pack(s, event_obj.at_least_one_betting_market_group_settled);
fc::raw::pack(s, event_obj.scores);
// fc::raw::pack the contents hidden in the impl class
std::ostringstream stream;
event_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, event_object& event_obj )
{
fc_elog(fc::logger::get("event"), "In event_obj from_raw");
// unpack all fields exposed in the header in the usual way
//fc::raw::unpack<Stream, graphene::db::abstract_object<event_object> >(s, event_obj);
fc::raw::unpack(s, event_obj.id);
fc::raw::unpack(s, event_obj.name);
fc::raw::unpack(s, event_obj.season);
fc::raw::unpack(s, event_obj.start_time);
fc::raw::unpack(s, event_obj.event_group_id);
fc::raw::unpack(s, event_obj.at_least_one_betting_market_group_settled);
fc::raw::unpack(s, event_obj.scores);
// 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);
event_obj.unpack_impl(stream);
return s;
}
} } // graphene::chain
FC_REFLECT(graphene::chain::event_object, (name))

View file

@ -69,6 +69,35 @@ struct betting_market_rules_update_operation : public base_operation
void validate()const;
};
enum class betting_market_status
{
unresolved, /// no grading has been published for this betting market
frozen, /// bets are suspended, no bets allowed
graded, /// grading of win or not_win has been published
canceled, /// the betting market is canceled, no further bets are allowed
settled, /// the betting market has been paid out
BETTING_MARKET_STATUS_COUNT
};
/**
* The status of a betting market group. This controls the behavior of the betting
* markets in the group.
*/
enum class betting_market_group_status
{
upcoming, /// betting markets are accepting bets, will never go "in_play"
in_play, /// betting markets are delaying bets
closed, /// betting markets are no longer accepting bets
graded, /// witnesses have published win/not win for the betting markets
re_grading, /// initial win/not win grading has been challenged
settled, /// paid out
frozen, /// betting markets are not accepting bets
canceled, /// canceled
BETTING_MARKET_GROUP_STATUS_COUNT
};
struct betting_market_group_create_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
@ -97,6 +126,20 @@ struct betting_market_group_create_operation : public base_operation
*/
asset_id_type asset_id;
/**
* If true, this market will never go "in-play"
*/
bool never_in_play;
/**
* After a grading has been published, the blockchain will wait this many
* seconds before settling (paying the winners).
* If the published grading is flagged (challenged) during this period,
* settling will be delayed indefinitely until the betting market
* group is re-graded (not implemented)
*/
uint32_t delay_before_settling;
extensions_type extensions;
account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; }
@ -114,9 +157,7 @@ struct betting_market_group_update_operation : public base_operation
optional<object_id_type> new_rules_id;
optional<bool> freeze;
optional<bool> delay_bets;
optional<betting_market_group_status> status;
extensions_type extensions;
@ -384,13 +425,33 @@ FC_REFLECT( graphene::chain::betting_market_rules_update_operation::fee_paramete
FC_REFLECT( graphene::chain::betting_market_rules_update_operation,
(fee)(new_name)(new_description)(extensions)(betting_market_rules_id) )
FC_REFLECT_ENUM( graphene::chain::betting_market_status,
(unresolved)
(frozen)
(graded)
(canceled)
(settled)
(BETTING_MARKET_STATUS_COUNT) )
FC_REFLECT_ENUM( graphene::chain::betting_market_group_status,
(upcoming)
(in_play)
(closed)
(graded)
(re_grading)
(settled)
(frozen)
(canceled)
(BETTING_MARKET_GROUP_STATUS_COUNT) )
FC_REFLECT( graphene::chain::betting_market_group_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_group_create_operation,
(fee)(description)(event_id)(rules_id)(asset_id)(extensions) )
(fee)(description)(event_id)(rules_id)(asset_id)
(never_in_play)(delay_before_settling)
(extensions) )
FC_REFLECT( graphene::chain::betting_market_group_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_group_update_operation,
(fee)(betting_market_group_id)(new_description)(new_rules_id)(freeze)(delay_bets)(extensions) )
(fee)(betting_market_group_id)(new_description)(new_rules_id)(status)(extensions) )
FC_REFLECT( graphene::chain::betting_market_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_create_operation,

View file

@ -54,6 +54,26 @@ struct event_create_operation : public base_operation
void validate()const;
};
/**
* The status of an event. This is used to display in the UI, and setting
* the event's status to certain values will propagate down to the
* betting market groups in the event:
* - when set to `in_progress`, all betting market groups are set to `in_play`
* - when set to `completed`, all betting market groups are set to `closed`
* - when set to `frozen`, all betting market groups are set to `frozen`
* - when set to `canceled`, all betting market groups are set to `canceled`
*/
enum class event_status
{
upcoming, /// Event has not started yet, betting is allowed
in_progress, /// Event is in progress, if "in-play" betting is enabled, bets will be delayed
frozen, /// Betting is temporarily disabled
finished, /// Event has finished, no more betting allowed
canceled, /// Event has been canceled, all betting markets have been canceled
settled, /// All betting markets have been paid out
STATUS_COUNT
};
struct event_update_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
@ -69,7 +89,7 @@ struct event_update_operation : public base_operation
optional<time_point_sec> new_start_time;
optional<bool> is_live_market;
optional<event_status> new_status;
extensions_type extensions;
@ -77,20 +97,6 @@ struct event_update_operation : public base_operation
void validate()const;
};
/**
* The status of an event; this is only used for display, the blockchain does
* not care about the event's status
*/
enum class event_status
{
upcoming,
in_progress,
frozen,
completed,
canceled,
STATUS_COUNT
};
/**
* The current (or final) score of an event.
* This is only used for display to the user, witnesses must resolve each
@ -132,9 +138,9 @@ FC_REFLECT( graphene::chain::event_create_operation,
FC_REFLECT( graphene::chain::event_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::event_update_operation,
(fee)(event_id)(new_event_group_id)(new_name)(new_season)(new_start_time)(is_live_market)(extensions) )
(fee)(event_id)(new_event_group_id)(new_name)(new_season)(new_start_time)(new_status)(extensions) )
FC_REFLECT_ENUM( graphene::chain::event_status, (upcoming)(in_progress)(frozen)(completed)(canceled)(STATUS_COUNT) )
FC_REFLECT_ENUM( graphene::chain::event_status, (upcoming)(in_progress)(frozen)(finished)(canceled)(settled)(STATUS_COUNT) )
FC_REFLECT( graphene::chain::event_update_status_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::event_update_status_operation,
(fee)(event_id)(status)(scores)(extensions) )

View file

@ -96,6 +96,111 @@ void persistent_bet_object_helper::object_modified(const object& after)
}
//////////// end bet_object ///////////////////
class persistent_betting_market_object_helper : public secondary_index
{
public:
virtual ~persistent_betting_market_object_helper() {}
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;
void set_plugin_instance(bookie_plugin* instance) { _bookie_plugin = instance; }
private:
bookie_plugin* _bookie_plugin;
};
void persistent_betting_market_object_helper::object_inserted(const object& obj)
{
const betting_market_object& betting_market_obj = *boost::polymorphic_downcast<const betting_market_object*>(&obj);
_bookie_plugin->database().create<persistent_betting_market_object>([&](persistent_betting_market_object& saved_betting_market_obj) {
saved_betting_market_obj.ephemeral_betting_market_object = betting_market_obj;
});
}
void persistent_betting_market_object_helper::object_modified(const object& after)
{
database& db = _bookie_plugin->database();
auto& persistent_betting_markets_by_betting_market_id = db.get_index_type<persistent_betting_market_index>().indices().get<by_betting_market_id>();
const betting_market_object& betting_market_obj = *boost::polymorphic_downcast<const betting_market_object*>(&after);
auto iter = persistent_betting_markets_by_betting_market_id.find(betting_market_obj.id);
assert (iter != persistent_betting_markets_by_betting_market_id.end());
if (iter != persistent_betting_markets_by_betting_market_id.end())
db.modify(*iter, [&](persistent_betting_market_object& saved_betting_market_obj) {
saved_betting_market_obj.ephemeral_betting_market_object = betting_market_obj;
});
}
//////////// end betting_market_object ///////////////////
class persistent_betting_market_group_object_helper : public secondary_index
{
public:
virtual ~persistent_betting_market_group_object_helper() {}
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;
void set_plugin_instance(bookie_plugin* instance) { _bookie_plugin = instance; }
private:
bookie_plugin* _bookie_plugin;
};
void persistent_betting_market_group_object_helper::object_inserted(const object& obj)
{
const betting_market_group_object& betting_market_group_obj = *boost::polymorphic_downcast<const betting_market_group_object*>(&obj);
_bookie_plugin->database().create<persistent_betting_market_group_object>([&](persistent_betting_market_group_object& saved_betting_market_group_obj) {
saved_betting_market_group_obj.ephemeral_betting_market_group_object = betting_market_group_obj;
});
}
void persistent_betting_market_group_object_helper::object_modified(const object& after)
{
database& db = _bookie_plugin->database();
auto& persistent_betting_market_groups_by_betting_market_group_id = db.get_index_type<persistent_betting_market_group_index>().indices().get<by_betting_market_group_id>();
const betting_market_group_object& betting_market_group_obj = *boost::polymorphic_downcast<const betting_market_group_object*>(&after);
auto iter = persistent_betting_market_groups_by_betting_market_group_id.find(betting_market_group_obj.id);
assert (iter != persistent_betting_market_groups_by_betting_market_group_id.end());
if (iter != persistent_betting_market_groups_by_betting_market_group_id.end())
db.modify(*iter, [&](persistent_betting_market_group_object& saved_betting_market_group_obj) {
saved_betting_market_group_obj.ephemeral_betting_market_group_object = betting_market_group_obj;
});
}
//////////// end betting_market_group_object ///////////////////
class persistent_event_object_helper : public secondary_index
{
public:
virtual ~persistent_event_object_helper() {}
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;
void set_plugin_instance(bookie_plugin* instance) { _bookie_plugin = instance; }
private:
bookie_plugin* _bookie_plugin;
};
void persistent_event_object_helper::object_inserted(const object& obj)
{
const event_object& event_obj = *boost::polymorphic_downcast<const event_object*>(&obj);
_bookie_plugin->database().create<persistent_event_object>([&](persistent_event_object& saved_event_obj) {
saved_event_obj.ephemeral_event_object = event_obj;
});
}
void persistent_event_object_helper::object_modified(const object& after)
{
database& db = _bookie_plugin->database();
auto& persistent_events_by_event_id = db.get_index_type<persistent_event_index>().indices().get<by_event_id>();
const event_object& event_obj = *boost::polymorphic_downcast<const event_object*>(&after);
auto iter = persistent_events_by_event_id.find(event_obj.id);
assert (iter != persistent_events_by_event_id.end());
if (iter != persistent_events_by_event_id.end())
db.modify(*iter, [&](persistent_event_object& saved_event_obj) {
saved_event_obj.ephemeral_event_object = event_obj;
});
}
//////////// end event_object ///////////////////
class bookie_plugin_impl
{
public:
@ -154,122 +259,14 @@ bookie_plugin_impl::~bookie_plugin_impl()
void bookie_plugin_impl::on_objects_new(const vector<object_id_type>& new_object_ids)
{
//idump((new_object_ids));
graphene::chain::database& db = database();
auto& event_id_index = db.get_index_type<persistent_event_index>().indices().get<by_event_id>();
auto& betting_market_group_id_index = db.get_index_type<persistent_betting_market_group_index>().indices().get<by_betting_market_group_id>();
auto& betting_market_id_index = db.get_index_type<persistent_betting_market_index>().indices().get<by_betting_market_id>();
for (const object_id_type& new_object_id : new_object_ids)
{
if (new_object_id.space() == event_id_type::space_id &&
new_object_id.type() == event_id_type::type_id)
{
event_id_type new_event_id = new_object_id;
ilog("Creating new persistent event object ${id}", ("id", new_event_id));
db.create<persistent_event_object>([&](persistent_event_object& saved_event_obj) {
saved_event_obj.ephemeral_event_object = new_event_id(db);
});
}
else if (new_object_id.space() == betting_market_group_object::space_id &&
new_object_id.type() == betting_market_group_object::type_id)
{
betting_market_group_id_type new_betting_market_group_id = new_object_id;
ilog("Creating new persistent betting_market_group object ${id}", ("id", new_betting_market_group_id));
db.create<persistent_betting_market_group_object>([&](persistent_betting_market_group_object& saved_betting_market_group_obj) {
saved_betting_market_group_obj.ephemeral_betting_market_group_object = new_betting_market_group_id(db);
});
}
else if (new_object_id.space() == betting_market_object::space_id &&
new_object_id.type() == betting_market_object::type_id)
{
betting_market_id_type new_betting_market_id = new_object_id;
ilog("Creating new persistent betting_market object ${id}", ("id", new_betting_market_id));
db.create<persistent_betting_market_object>([&](persistent_betting_market_object& saved_betting_market_obj) {
saved_betting_market_obj.ephemeral_betting_market_object = new_betting_market_id(db);
});
}
}
}
void bookie_plugin_impl::on_objects_removed(const vector<object_id_type>& removed_object_ids)
{
//idump((removed_object_ids));
}
void bookie_plugin_impl::on_objects_changed(const vector<object_id_type>& changed_object_ids)
{
//idump((changed_object_ids));
graphene::chain::database& db = database();
auto& event_id_index = db.get_index_type<persistent_event_index>().indices().get<by_event_id>();
auto& betting_market_group_id_index = db.get_index_type<persistent_betting_market_group_index>().indices().get<by_betting_market_group_id>();
auto& betting_market_id_index = db.get_index_type<persistent_betting_market_index>().indices().get<by_betting_market_id>();
for (const object_id_type& changed_object_id : changed_object_ids)
{
if (changed_object_id.space() == event_id_type::space_id &&
changed_object_id.type() == event_id_type::type_id)
{
event_id_type changed_event_id = changed_object_id;
const persistent_event_object* old_event_obj = nullptr;
auto persistent_event_iter = event_id_index.find(changed_event_id);
if (persistent_event_iter != event_id_index.end())
old_event_obj = &*persistent_event_iter;
if (old_event_obj)
{
ilog("Modifying persistent event object ${id}", ("id", changed_event_id));
db.modify(*old_event_obj, [&](persistent_event_object& saved_event_obj) {
saved_event_obj.ephemeral_event_object = changed_event_id(db);
});
}
else
elog("Received change notification on event ${event_id} that we didn't know about", ("event_id", changed_event_id));
}
else if (changed_object_id.space() == betting_market_group_object::space_id &&
changed_object_id.type() == betting_market_group_object::type_id)
{
betting_market_group_id_type changed_betting_market_group_id = changed_object_id;
const persistent_betting_market_group_object* old_betting_market_group_obj = nullptr;
auto persistent_betting_market_group_iter = betting_market_group_id_index.find(changed_betting_market_group_id);
if (persistent_betting_market_group_iter != betting_market_group_id_index.end())
old_betting_market_group_obj = &*persistent_betting_market_group_iter;
if (old_betting_market_group_obj)
{
ilog("Modifying persistent betting_market_group object ${id}", ("id", changed_betting_market_group_id));
db.modify(*old_betting_market_group_obj, [&](persistent_betting_market_group_object& saved_betting_market_group_obj) {
saved_betting_market_group_obj.ephemeral_betting_market_group_object = changed_betting_market_group_id(db);
});
}
else
elog("Received change notification on betting market group ${betting_market_group_id} that we didn't know about",
("betting_market_group_id", changed_betting_market_group_id));
}
else if (changed_object_id.space() == betting_market_object::space_id &&
changed_object_id.type() == betting_market_object::type_id)
{
betting_market_id_type changed_betting_market_id = changed_object_id;
const persistent_betting_market_object* old_betting_market_obj = nullptr;
auto persistent_betting_market_iter = betting_market_id_index.find(changed_betting_market_id);
if (persistent_betting_market_iter != betting_market_id_index.end())
old_betting_market_obj = &*persistent_betting_market_iter;
if (old_betting_market_obj)
{
ilog("Modifying persistent betting_market object ${id}", ("id", changed_betting_market_id));
db.modify(*old_betting_market_obj, [&](persistent_betting_market_object& saved_betting_market_obj) {
saved_betting_market_obj.ephemeral_betting_market_object = changed_betting_market_id(db);
});
}
else
elog("Received change notification on betting market ${betting_market_id} that we didn't know about",
("betting_market_id", changed_betting_market_id));
}
}
}
bool is_operation_history_object_stored(operation_history_id_type id)
@ -287,7 +284,8 @@ bool is_operation_history_object_stored(operation_history_id_type id)
}
void bookie_plugin_impl::on_block_applied( const signed_block& )
{
{ try {
graphene::chain::database& db = database();
const vector<optional<operation_history_object> >& hist = db.get_applied_operations();
for( const optional<operation_history_object>& o_op : hist )
@ -315,11 +313,28 @@ void bookie_plugin_impl::on_block_applied( const signed_block& )
});
const bet_object& bet_obj = bet_iter->ephemeral_bet_object;
const betting_market_object& betting_market = bet_obj.betting_market_id(db); // TODO: this needs to look at the persistent version
const betting_market_group_object& betting_market_group = betting_market.group_id(db); // TODO: as does this
db.modify( betting_market_group, [&]( betting_market_group_object& obj ){
obj.total_matched_bets_amount += amount_bet.amount;
});
auto& persistent_betting_market_idx = db.get_index_type<persistent_betting_market_index>().indices().get<by_betting_market_id>();
auto persistent_betting_market_object_iter = persistent_betting_market_idx.find(bet_obj.betting_market_id);
FC_ASSERT(persistent_betting_market_object_iter != persistent_betting_market_idx.end());
const betting_market_object& betting_market = persistent_betting_market_object_iter->ephemeral_betting_market_object;
auto& persistent_betting_market_group_idx = db.get_index_type<persistent_betting_market_group_index>().indices().get<by_betting_market_group_id>();
auto persistent_betting_market_group_object_iter = persistent_betting_market_group_idx.find(betting_market.group_id);
FC_ASSERT(persistent_betting_market_group_object_iter != persistent_betting_market_group_idx.end());
const betting_market_group_object& betting_market_group = persistent_betting_market_group_object_iter->ephemeral_betting_market_group_object;
// if the object is still in the main database, keep the running total there
// otherwise, add it directly to the persistent version
auto& betting_market_group_idx = db.get_index_type<betting_market_group_object_index>().indices().get<by_id>();
auto betting_market_group_iter = betting_market_group_idx.find(betting_market_group.id);
if (betting_market_group_iter != betting_market_group_idx.end())
db.modify( *betting_market_group_iter, [&]( betting_market_group_object& obj ){
obj.total_matched_bets_amount += amount_bet.amount;
});
else
db.modify( *persistent_betting_market_group_object_iter, [&]( persistent_betting_market_group_object& obj ){
obj.ephemeral_betting_market_group_object.total_matched_bets_amount += amount_bet.amount;
});
}
}
else if( op.op.which() == operation::tag<event_create_operation>::value )
@ -382,7 +397,7 @@ void bookie_plugin_impl::on_block_applied( const signed_block& )
}
}
}
} FC_RETHROW_EXCEPTIONS( warn, "" ) }
void bookie_plugin_impl::fill_localized_event_strings()
{
@ -470,6 +485,21 @@ void bookie_plugin::plugin_initialize(const boost::program_options::variables_ma
detail::persistent_bet_object_helper* persistent_bet_object_helper_index = nonconst_bet_object_idx.add_secondary_index<detail::persistent_bet_object_helper>();
persistent_bet_object_helper_index->set_plugin_instance(this);
const primary_index<betting_market_object_index>& betting_market_object_idx = database().get_index_type<primary_index<betting_market_object_index> >();
primary_index<betting_market_object_index>& nonconst_betting_market_object_idx = const_cast<primary_index<betting_market_object_index>&>(betting_market_object_idx);
detail::persistent_betting_market_object_helper* persistent_betting_market_object_helper_index = nonconst_betting_market_object_idx.add_secondary_index<detail::persistent_betting_market_object_helper>();
persistent_betting_market_object_helper_index->set_plugin_instance(this);
const primary_index<betting_market_group_object_index>& betting_market_group_object_idx = database().get_index_type<primary_index<betting_market_group_object_index> >();
primary_index<betting_market_group_object_index>& nonconst_betting_market_group_object_idx = const_cast<primary_index<betting_market_group_object_index>&>(betting_market_group_object_idx);
detail::persistent_betting_market_group_object_helper* persistent_betting_market_group_object_helper_index = nonconst_betting_market_group_object_idx.add_secondary_index<detail::persistent_betting_market_group_object_helper>();
persistent_betting_market_group_object_helper_index->set_plugin_instance(this);
const primary_index<event_object_index>& event_object_idx = database().get_index_type<primary_index<event_object_index> >();
primary_index<event_object_index>& nonconst_event_object_idx = const_cast<primary_index<event_object_index>&>(event_object_idx);
detail::persistent_event_object_helper* persistent_event_object_helper_index = nonconst_event_object_idx.add_secondary_index<detail::persistent_event_object_helper>();
persistent_event_object_helper_index->set_plugin_instance(this);
ilog("bookie plugin: plugin_startup() end");
}

View file

@ -1637,7 +1637,7 @@ class wallet_api
fc::optional<object_id_type> event_group_id,
fc::optional<internationalized_string_type> name,
fc::optional<internationalized_string_type> season,
fc::optional<bool> is_live_market,
fc::optional<event_status> status,
fc::optional<time_point_sec> start_time,
bool broadcast = false);
@ -1671,7 +1671,7 @@ class wallet_api
betting_market_group_id_type betting_market_group_id,
fc::optional<internationalized_string_type> description,
fc::optional<object_id_type> rules_id,
fc::optional<bool> freeze,
fc::optional<betting_market_group_status> status,
bool broadcast = false);
signed_transaction propose_create_betting_market(

View file

@ -5238,7 +5238,7 @@ signed_transaction wallet_api::propose_update_event(
fc::optional<object_id_type> event_group_id,
fc::optional<internationalized_string_type> name,
fc::optional<internationalized_string_type> season,
fc::optional<bool> is_live_market,
fc::optional<event_status> status,
fc::optional<time_point_sec> start_time,
bool broadcast /*= false*/)
{
@ -5251,7 +5251,7 @@ signed_transaction wallet_api::propose_update_event(
event_update_op.new_start_time = start_time;
event_update_op.new_name = name;
event_update_op.new_season = season;
event_update_op.is_live_market = is_live_market;
event_update_op.new_status = status;
proposal_create_operation prop_op;
prop_op.expiration_time = expiration_time;
@ -5367,7 +5367,7 @@ signed_transaction wallet_api::propose_update_betting_market_group(
betting_market_group_id_type betting_market_group_id,
fc::optional<internationalized_string_type> description,
fc::optional<object_id_type> rules_id,
fc::optional<bool> freeze,
fc::optional<betting_market_group_status> status,
bool broadcast /*= false*/)
{
FC_ASSERT( !is_locked() );
@ -5377,7 +5377,7 @@ signed_transaction wallet_api::propose_update_betting_market_group(
betting_market_group_update_op.betting_market_group_id = betting_market_group_id;
betting_market_group_update_op.new_description = description;
betting_market_group_update_op.new_rules_id = rules_id;
betting_market_group_update_op.freeze = freeze;
betting_market_group_update_op.status = status;
proposal_create_operation prop_op;
prop_op.expiration_time = expiration_time;

File diff suppressed because it is too large Load diff

View file

@ -1150,6 +1150,19 @@ void database_fixture::process_operation_by_witnesses(operation op)
}
}
void database_fixture::force_operation_by_witnesses(operation op)
{
const chain_parameters& params = db.get_global_properties().parameters;
signed_transaction trx;
trx.operations = {op};
for( auto& op : trx.operations )
db.current_fee_schedule().set_fee(op);
trx.validate();
trx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3));
sign(trx, init_account_priv_key);
PUSH_TX(db, trx);
}
void database_fixture::set_is_proposed_trx(operation op)
{
const flat_set<witness_id_type>& active_witnesses = db.get_global_properties().active_witnesses;
@ -1306,20 +1319,25 @@ const event_object& database_fixture::create_event(internationalized_string_type
return *event_index.rbegin();
} FC_CAPTURE_AND_RETHROW( (event_group_id) ) }
void database_fixture::update_event(event_id_type event_id,
fc::optional<object_id_type> event_group_id,
fc::optional<internationalized_string_type> name,
fc::optional<internationalized_string_type> season,
fc::optional<bool> is_live_market)
void database_fixture::update_event_impl(event_id_type event_id,
fc::optional<object_id_type> event_group_id,
fc::optional<internationalized_string_type> name,
fc::optional<internationalized_string_type> season,
fc::optional<event_status> status,
bool force)
{ try {
event_update_operation event_update_op;
event_update_op.event_id = event_id;
event_update_op.new_event_group_id = event_group_id;
event_update_op.new_name = name;
event_update_op.new_season = season;
event_update_op.is_live_market = is_live_market;
process_operation_by_witnesses(event_update_op);
} FC_CAPTURE_AND_RETHROW( (name)(season) ) }
event_update_op.new_status = status;
if (force)
force_operation_by_witnesses(event_update_op);
else
process_operation_by_witnesses(event_update_op);
} FC_CAPTURE_AND_RETHROW( (event_id) ) }
const betting_market_rules_object& database_fixture::create_betting_market_rules(internationalized_string_type name, internationalized_string_type description)
{ try {
@ -1342,33 +1360,44 @@ void database_fixture::update_betting_market_rules(betting_market_rules_id_type
process_operation_by_witnesses(betting_market_rules_update_op);
} FC_CAPTURE_AND_RETHROW( (name)(description) ) }
const betting_market_group_object& database_fixture::create_betting_market_group(internationalized_string_type description, event_id_type event_id, betting_market_rules_id_type rules_id, asset_id_type asset_id)
const betting_market_group_object& database_fixture::create_betting_market_group(internationalized_string_type description,
event_id_type event_id,
betting_market_rules_id_type rules_id,
asset_id_type asset_id,
bool never_in_play,
uint32_t delay_before_settling)
{ try {
betting_market_group_create_operation betting_market_group_create_op;
betting_market_group_create_op.description = description;
betting_market_group_create_op.event_id = event_id;
betting_market_group_create_op.rules_id = rules_id;
betting_market_group_create_op.asset_id = asset_id;
betting_market_group_create_op.never_in_play = never_in_play;
betting_market_group_create_op.delay_before_settling = delay_before_settling;
process_operation_by_witnesses(betting_market_group_create_op);
const auto& betting_market_group_index = db.get_index_type<betting_market_group_object_index>().indices().get<by_id>();
return *betting_market_group_index.rbegin();
} FC_CAPTURE_AND_RETHROW( (event_id) ) }
void database_fixture::update_betting_market_group(betting_market_group_id_type betting_market_group_id,
fc::optional<internationalized_string_type> description,
fc::optional<object_id_type> rules_id,
fc::optional<bool> freeze,
fc::optional<bool> delay_bets)
void database_fixture::update_betting_market_group_impl(betting_market_group_id_type betting_market_group_id,
fc::optional<internationalized_string_type> description,
fc::optional<object_id_type> rules_id,
fc::optional<betting_market_group_status> status,
bool force)
{ try {
betting_market_group_update_operation betting_market_group_update_op;
betting_market_group_update_op.betting_market_group_id = betting_market_group_id;
betting_market_group_update_op.new_description = description;
betting_market_group_update_op.new_rules_id = rules_id;
betting_market_group_update_op.freeze = freeze;
betting_market_group_update_op.delay_bets = delay_bets;
process_operation_by_witnesses(betting_market_group_update_op);
} FC_CAPTURE_AND_RETHROW( (betting_market_group_id)(description)(rules_id)(freeze)(delay_bets)) }
betting_market_group_update_op.status = status;
if (force)
force_operation_by_witnesses(betting_market_group_update_op);
else
process_operation_by_witnesses(betting_market_group_update_op);
} FC_CAPTURE_AND_RETHROW( (betting_market_group_id)(description)(rules_id)(status)) }
const betting_market_object& database_fixture::create_betting_market(betting_market_group_id_type group_id, internationalized_string_type payout_condition)
@ -1412,6 +1441,7 @@ void database_fixture::update_betting_market(betting_market_id_type betting_mark
return ptx.operation_results.front().get<object_id_type>().as<bet_id_type>();
} FC_CAPTURE_AND_RETHROW( (bettor_id)(back_or_lay)(amount_to_bet) ) }
void database_fixture::resolve_betting_market_group(betting_market_group_id_type betting_market_group_id,
std::map<betting_market_id_type, betting_market_resolution_type> resolutions)
{ try {

View file

@ -30,6 +30,8 @@
#include <graphene/chain/operation_history_object.hpp>
#include <boost/parameter.hpp>
#include <iostream>
using namespace graphene::db;
@ -144,6 +146,18 @@ extern uint32_t GRAPHENE_TESTING_GENESIS_TIMESTAMP;
namespace graphene { namespace chain {
namespace keywords {
BOOST_PARAMETER_NAME(event_id)
BOOST_PARAMETER_NAME(event_group_id)
BOOST_PARAMETER_NAME(name)
BOOST_PARAMETER_NAME(season)
BOOST_PARAMETER_NAME(status)
BOOST_PARAMETER_NAME(force)
BOOST_PARAMETER_NAME(betting_market_group_id)
BOOST_PARAMETER_NAME(description)
BOOST_PARAMETER_NAME(rules_id)
}
struct database_fixture {
// the reason we use an app is to exercise the indexes of built-in
// plugins
@ -285,6 +299,7 @@ struct database_fixture {
asset_id_type dividend_payout_asset_type) const;
vector< operation_history_object > get_operation_history( account_id_type account_id )const;
void process_operation_by_witnesses(operation op);
void force_operation_by_witnesses(operation op);
void set_is_proposed_trx(operation op);
const sport_object& create_sport(internationalized_string_type name);
void update_sport(sport_id_type sport_id, internationalized_string_type name);
@ -297,21 +312,47 @@ struct database_fixture {
fc::optional<internationalized_string_type> name,
bool dont_set_is_proposed_trx = false);
const event_object& create_event(internationalized_string_type name, internationalized_string_type season, event_group_id_type event_group_id);
void update_event(event_id_type event_id,
fc::optional<object_id_type> event_group_id,
fc::optional<internationalized_string_type> name,
fc::optional<internationalized_string_type> season,
fc::optional<bool> is_live_market);
void update_event_impl(event_id_type event_id,
fc::optional<object_id_type> event_group_id,
fc::optional<internationalized_string_type> name,
fc::optional<internationalized_string_type> season,
fc::optional<event_status> status,
bool force);
BOOST_PARAMETER_MEMBER_FUNCTION((void), update_event, keywords::tag,
(required (event_id, (event_id_type)))
(optional (event_group_id, (fc::optional<object_id_type>), fc::optional<object_id_type>())
(name, (fc::optional<internationalized_string_type>), fc::optional<internationalized_string_type>())
(season, (fc::optional<internationalized_string_type>), fc::optional<internationalized_string_type>())
(status, (fc::optional<event_status>), fc::optional<event_status>())
(force, (bool), false)))
{
update_event_impl(event_id, event_group_id, name, season, status, force);
}
const betting_market_rules_object& create_betting_market_rules(internationalized_string_type name, internationalized_string_type description);
void update_betting_market_rules(betting_market_rules_id_type rules_id,
fc::optional<internationalized_string_type> name,
fc::optional<internationalized_string_type> description);
const betting_market_group_object& create_betting_market_group(internationalized_string_type description, event_id_type event_id, betting_market_rules_id_type rules_id, asset_id_type asset_id);
void update_betting_market_group(betting_market_group_id_type betting_market_group_id,
fc::optional<internationalized_string_type> description,
fc::optional<object_id_type> rules_id,
fc::optional<bool> freeze,
fc::optional<bool> delay_bets);
const betting_market_group_object& create_betting_market_group(internationalized_string_type description,
event_id_type event_id,
betting_market_rules_id_type rules_id,
asset_id_type asset_id,
bool never_in_play,
uint32_t delay_before_settling);
void update_betting_market_group_impl(betting_market_group_id_type betting_market_group_id,
fc::optional<internationalized_string_type> description,
fc::optional<object_id_type> rules_id,
fc::optional<betting_market_group_status> status,
bool force);
BOOST_PARAMETER_MEMBER_FUNCTION((void), update_betting_market_group, keywords::tag,
(required (betting_market_group_id, (betting_market_group_id_type)))
(optional (description, (fc::optional<internationalized_string_type>), fc::optional<internationalized_string_type>())
(rules_id, (fc::optional<object_id_type>), fc::optional<object_id_type>())
(status, (fc::optional<betting_market_group_status>), fc::optional<betting_market_group_status>())
(force, (bool), false)))
{
update_betting_market_group_impl(betting_market_group_id, description, rules_id, status, force);
}
const betting_market_object& create_betting_market(betting_market_group_id_type group_id, internationalized_string_type payout_condition);
void update_betting_market(betting_market_id_type betting_market_id,