Safety Check: Part 2 -- Implement and Integrate Checks

Implement a safety check mechanism on object_database, based on the
safety_check_policy abstract interface. Create two standard
implementations of the safety_check_policy interface, one
(null_safety_check) which allows all modifications unconditionally, and
the other (database_lock_safety_check) which allows modifications only
when unlocked.

Integrate these safety checks into chain::database and plugins, so that
the consensus databases are locked at all times except during core
consensus code pathways. Also ensures that databases are re-locked when
calling code outside of consensus pathways from consensus pathways.

To make this work, it was necessary to move two objects from the
consensus object spaces to a new API object space. The
operation_history_object and account_transaction_history_object were
moved to the API object space, as they are not actually used by
consensus and are maintained by a plugin (which can no longer modify the
consensus object spaces, due to the safety checks).

Finally, add a mechanism to application and chain::database, which
allows the chain to start in "unit testing mode" and allows unchecked
actions upon the database within delimited scopes. This was necessary
because many tests edit the database directly to set up the environment
for their respective tests. This mode is activated by database_fixture so
tests can utilize it conveniently, but it is architecturally difficult to
enable this mode in production, i.e. from a plugin.
This commit is contained in:
Nathaniel 2022-03-12 14:04:08 -06:00
parent e8b432c19f
commit f9fcffbb4d
No known key found for this signature in database
GPG key ID: B4344309A110851E
39 changed files with 772 additions and 300 deletions

View file

@ -291,6 +291,10 @@ public:
_self(self),
_chain_db(std::make_shared<chain::database>()) {
}
explicit application_impl(application *self, application::TESTING_MODE) :
_self(self),
_chain_db(std::make_shared<chain::database>(true)) {
}
~application_impl() {
}
@ -846,7 +850,11 @@ public:
} // namespace detail
application::application() :
my(new detail::application_impl(this)) {
my(new detail::application_impl(this)) {
}
application::application(TESTING_MODE) :
my(new detail::application_impl(this, TESTING_MODE())) {
}
application::~application() {

View file

@ -430,9 +430,9 @@ fc::variants database_api::get_objects(const vector<object_id_type> &ids) const
fc::variants database_api_impl::get_objects(const vector<object_id_type> &ids) const {
if (_subscribe_callback) {
for (auto id : ids) {
if (id.type() == operation_history_object_type && id.space() == protocol_ids)
if (id.type() == api_operation_history_object_type && id.space() == api_ids)
continue;
if (id.type() == impl_account_transaction_history_object_type && id.space() == implementation_ids)
if (id.type() == api_account_transaction_history_object_type && id.space() == api_ids)
continue;
this->subscribe_to_item(id);

View file

@ -39,7 +39,10 @@ class abstract_plugin;
class application {
public:
struct TESTING_MODE {};
application();
application(TESTING_MODE);
~application();
void set_program_options(boost::program_options::options_description &cli,

View file

@ -381,6 +381,7 @@ processed_transaction database::push_transaction( const signed_transaction& trx,
processed_transaction database::_push_transaction( const signed_transaction& trx )
{
scoped_database_unlocker unlocker(*_check_policy_1, *_check_policy_2);
// If this is the first transaction pushed after applying a block, start a new undo session.
// This allows us to quickly rewind to the clean state of the head block, in case a new block arrives.
if( !_pending_tx_session.valid() )
@ -412,6 +413,7 @@ processed_transaction database::validate_transaction( const signed_transaction&
processed_transaction database::push_proposal(const proposal_object& proposal)
{ try {
scoped_database_unlocker unlocker(*_check_policy_1, *_check_policy_2);
transaction_evaluation_state eval_state(this);
eval_state._is_proposed_trx = true;
@ -457,6 +459,7 @@ signed_block database::generate_block(
uint32_t skip /* = 0 */
)
{ try {
scoped_database_unlocker unlocker(*_check_policy_1, *_check_policy_2);
signed_block result;
detail::with_skip_flags( *this, skip, [&]()
{
@ -588,6 +591,7 @@ signed_block database::_generate_block(
*/
void database::pop_block()
{ try {
scoped_database_unlocker unlocker(*_check_policy_1, *_check_policy_2);
_pending_tx_session.reset();
auto head_id = head_block_id();
optional<signed_block> head_block = fetch_block_by_id( head_id );
@ -602,6 +606,7 @@ void database::pop_block()
void database::clear_pending()
{ try {
scoped_database_unlocker unlocker(*_check_policy_1, *_check_policy_2);
assert( (_pending_tx.size() == 0) || _pending_tx_session.valid() );
_pending_tx.clear();
_pending_tx_session.reset();
@ -661,6 +666,7 @@ void database::apply_block( const signed_block& next_block, uint32_t skip )
void database::_apply_block( const signed_block& next_block )
{ try {
scoped_database_unlocker unlocker(*_check_policy_1, *_check_policy_2);
uint32_t next_block_num = next_block.block_num();
uint32_t skip = get_node_properties().skip_flags;
_applied_ops.clear();
@ -780,6 +786,7 @@ class undo_size_restorer {
processed_transaction database::_apply_transaction(const signed_transaction& trx)
{ try {
scoped_database_unlocker unlocker(*_check_policy_1, *_check_policy_2);
uint32_t skip = get_node_properties().skip_flags;
if( true || !(skip&skip_validate) ) /* issue #505 explains why this skip_flag is disabled */
@ -864,6 +871,7 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx
operation_result database::apply_operation(transaction_evaluation_state& eval_state, const operation& op)
{ try {
scoped_database_unlocker unlocker(*_check_policy_1, *_check_policy_2);
int i_which = op.which();
uint64_t u_which = uint64_t( i_which );
FC_ASSERT( i_which >= 0, "Negative operation tag in operation ${op}", ("op",op) );
@ -874,10 +882,14 @@ operation_result database::apply_operation(transaction_evaluation_state& eval_st
auto result = eval->evaluate( eval_state, op, true );
set_applied_operation_result( op_id, result );
// Run third party evaluator chain
// Run third party evaluator chain. Check that they don't yield, and lock consensus databases while they run.
if (_third_party_operation_evaluators.size() > u_which && _third_party_operation_evaluators[u_which]) {
ASSERT_TASK_NOT_PREEMPTED();
_check_policy_1->lock();
_check_policy_2->lock();
_third_party_operation_evaluators[u_which]->evaluate( eval_state, op, true );
_check_policy_1->unlock();
_check_policy_2->unlock();
}
return result;
} FC_CAPTURE_AND_RETHROW( (op) ) }

View file

@ -335,6 +335,12 @@ void database::initialize_indexes()
reset_indexes();
_undo_db.set_max_size( GRAPHENE_MIN_UNDO_HISTORY );
_check_policy_1 = allocate_object_space<database_lock_safety_check>(protocol_ids);
_check_policy_2 = allocate_object_space<database_lock_safety_check>(implementation_ids);
// No checks on API objects
allocate_object_space<null_safety_check>(api_ids);
FC_ASSERT(_check_policy_1 != nullptr && _check_policy_2 != nullptr, "Failed to allocate object spaces");
//Protocol object indexes
add_index< primary_index<asset_index, 13> >(); // 8192 assets per chunk
add_index< primary_index<force_settlement_index> >();
@ -421,6 +427,8 @@ void database::initialize_indexes()
add_index< primary_index<son_stats_index > >();
add_index< primary_index<random_number_index > >();
_check_policy_1->lock();
_check_policy_2->lock();
}
void database::init_genesis(const genesis_state_type& genesis_state)
@ -434,6 +442,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
"initial_active_witnesses is larger than the number of candidate witnesses.");
_undo_db.disable();
scoped_database_unlocker unlocker(*_check_policy_1, *_check_policy_2);
struct auth_inhibitor {
auth_inhibitor(database& db) : db(db), old_flags(db.node_properties().skip_flags)
{ db.node_properties().skip_flags |= skip_authority_check; }

View file

@ -40,9 +40,12 @@
namespace graphene { namespace chain {
database::database() :
_random_number_generator(fc::ripemd160().data())
database::database(bool allow_testing_edits) :
_random_number_generator(fc::ripemd160().data()),
_allow_safety_check_bypass(allow_testing_edits)
{
if (allow_testing_edits)
elog("UNIT TESTING MODE ENABLED -- NOT FOR PRODUCTION USE");
initialize_indexes();
initialize_evaluators();
}

View file

@ -495,11 +495,7 @@ void get_relevant_accounts( const object* obj, flat_set<account_id_type>& accoun
transaction_get_impacted_accounts( aobj->proposed_transaction, accounts,
ignore_custom_operation_required_auths);
break;
} case operation_history_object_type:{
const auto& aobj = dynamic_cast<const operation_history_object*>(obj);
assert( aobj != nullptr );
operation_get_impacted_accounts( aobj->op, accounts,
ignore_custom_operation_required_auths);
} case reserved0_object_type:{
break;
} case withdraw_permission_object_type:{
const auto& aobj = dynamic_cast<const withdraw_permission_object*>(obj);
@ -668,7 +664,7 @@ void get_relevant_accounts( const object* obj, flat_set<account_id_type>& accoun
break;
} case impl_block_summary_object_type:
break;
case impl_account_transaction_history_object_type:
case impl_reserved1_object_type:
break;
case impl_chain_property_object_type:
break;
@ -725,6 +721,19 @@ void get_relevant_accounts( const object* obj, flat_set<account_id_type>& accoun
default:
break;
}
} else if( obj->id.space() == api_ids ) {
switch( (api_object_type)obj->id.type() )
{
case graphene::chain::api_operation_history_object_type: {
const auto& aobj = dynamic_cast<const operation_history_object*>(obj);
assert( aobj != nullptr );
operation_get_impacted_accounts( aobj->op, accounts,
ignore_custom_operation_required_auths);
break;
}
case api_account_transaction_history_object_type:
break;
}
}
} // end get_relevant_accounts( const object* obj, flat_set<account_id_type>& accounts )

View file

@ -75,7 +75,7 @@ namespace graphene { namespace chain {
public:
//////////////////// db_management.cpp ////////////////////
database();
database(bool allow_testing_edits = false);
~database();
enum validation_steps
@ -454,6 +454,12 @@ namespace graphene { namespace chain {
void debug_dump();
void apply_debug_updates();
void debug_update( const fc::variant_object& update );
template<typename Action>
auto bypass_safety_checks(Action&& action) {
FC_ASSERT(_allow_safety_check_bypass, "Safety check bypass disallowed.");
scoped_database_unlocker unlocker(*_check_policy_1, *_check_policy_2);
return action();
}
//////////////////// db_market.cpp ////////////////////
@ -735,6 +741,13 @@ namespace graphene { namespace chain {
const chain_property_object* _p_chain_property_obj = nullptr;
const witness_schedule_object* _p_witness_schedule_obj = nullptr;
///@}
/// Whether or not to allow safety check bypassing (for unit testing only)
bool _allow_safety_check_bypass;
/// Safety check policy for object space 1
database_lock_safety_check* _check_policy_1 = nullptr;
/// Safety check policy for object space 2
database_lock_safety_check* _check_policy_2 = nullptr;
};
namespace detail

View file

@ -51,8 +51,8 @@ namespace graphene { namespace chain {
class operation_history_object : public abstract_object<operation_history_object>
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = operation_history_object_type;
static const uint8_t space_id = api_ids;
static const uint8_t type_id = api_operation_history_object_type;
operation_history_object( const operation& o ):op(o){}
operation_history_object(){}
@ -93,8 +93,8 @@ class operation_history_object : public abstract_object<operation_history_object
class account_transaction_history_object : public abstract_object<account_transaction_history_object>
{
public:
static const uint8_t space_id = implementation_ids;
static const uint8_t type_id = impl_account_transaction_history_object_type;
static const uint8_t space_id = api_ids;
static const uint8_t type_id = api_account_transaction_history_object_type;
account_id_type account; /// the account this operation applies to
operation_history_id_type operation_id;
uint32_t sequence = 0; /// the operation position within the given account

View file

@ -37,7 +37,7 @@ GRAPHENE_DEFINE_IDS(chain, implementation_ids, impl_,
(account_statistics)
(transaction_history)
(block_summary)
(account_transaction_history)
(reserved1/*account_transaction_history, moved to api_ids*/)
(blinded_balance)
(chain_property)
(witness_schedule)
@ -56,3 +56,6 @@ GRAPHENE_DEFINE_IDS(chain, implementation_ids, impl_,
(son_statistics)
(son_schedule)
(nft_lottery_balance))
GRAPHENE_DEFINE_IDS(chain, api_ids, api_,
(operation_history)
(account_transaction_history))

View file

@ -4,6 +4,7 @@ set( GRAPHENE_DB_FILES
undo_database.cpp
index.cpp
object_database.cpp
safety_check_policy.cpp
${HEADERS}
)

View file

@ -23,6 +23,7 @@
*/
#pragma once
#include <graphene/db/object.hpp>
#include <graphene/db/safety_check_policy.hpp>
#include <fc/interprocess/file_mapping.hpp>
#include <fc/io/raw.hpp>
@ -161,7 +162,7 @@ namespace graphene { namespace db {
class base_primary_index
{
public:
base_primary_index( object_database& db ):_db(db){}
base_primary_index( object_database& db ) : _db(db){}
/** called just before obj is modified */
void save_undo( const object& obj );
@ -332,8 +333,8 @@ namespace graphene { namespace db {
public:
typedef typename DerivedIndex::object_type object_type;
primary_index( object_database& db )
:base_primary_index(db),_next_id(object_type::space_id,object_type::type_id,0)
primary_index( object_database& db, safety_check_policy& check_policy )
: base_primary_index(db),_next_id(object_type::space_id,object_type::type_id,0),_check(check_policy)
{
if( DirectBits > 0 )
_direct_by_id = add_secondary_index< direct_index< object_type, DirectBits > >();
@ -400,37 +401,78 @@ namespace graphene { namespace db {
virtual const object& load( const std::vector<char>& data )override
{
const auto& result = DerivedIndex::insert( fc::raw::unpack<object_type>( data ) );
for( const auto& item : _sindex )
uint8_t type_id = object_type::type_id;
for( const auto& item : _sindex ) {
_check.pre_secondary_index_notification(type_id, *item);
item->object_loaded( result );
_check.post_secondary_index_notification(type_id, *item);
}
return result;
}
virtual const object& create(const std::function<void(object&)>& constructor )override
{
uint8_t type_id = object_type::type_id;
try {
FC_ASSERT(_check.allow_object_creation(_next_id),
"Safety Check: Creation of object ${ID} is not allowed", ("ID", _next_id));
} catch(...) {
// When debugging a safety check failure, throw a breakpoint here to see where it's coming from
throw;
}
const auto& result = DerivedIndex::create( constructor );
for( const auto& item : _sindex )
for( const auto& item : _sindex ) {
_check.pre_secondary_index_notification(type_id, *item);
item->object_created( result );
_check.post_secondary_index_notification(type_id, *item);
}
on_add( result );
return result;
}
virtual void remove( const object& obj ) override
{
for( const auto& item : _sindex )
uint8_t type_id = object_type::type_id;
try {
FC_ASSERT(_check.allow_object_deletion(_next_id), "Safety Check: Deletion of object ${ID} is not allowed",
("ID", obj.id));
} catch(...) {
// When debugging a safety check failure, throw a breakpoint here to see where it's coming from
throw;
}
for( const auto& item : _sindex ) {
_check.pre_secondary_index_notification(type_id, *item);
item->object_removed( obj );
_check.post_secondary_index_notification(type_id, *item);
}
on_remove(obj);
DerivedIndex::remove(obj);
}
virtual void modify( const object& obj, const std::function<void(object&)>& m )override
{
uint8_t type_id = object_type::type_id;
try {
FC_ASSERT(_check.allow_object_modification(_next_id),
"Safety Check: Modification of object ${ID} is not allowed", ("ID", obj.id));
} catch(...) {
// When debugging a safety check failure, throw a breakpoint here to see where it's coming from
throw;
}
save_undo( obj );
for( const auto& item : _sindex )
for( const auto& item : _sindex ) {
_check.pre_secondary_index_notification(type_id, *item);
item->about_to_modify( obj );
_check.post_secondary_index_notification(type_id, *item);
}
DerivedIndex::modify( obj, m );
for( const auto& item : _sindex )
for( const auto& item : _sindex ) {
_check.pre_secondary_index_notification(type_id, *item);
item->object_modified( obj );
_check.post_secondary_index_notification(type_id, *item);
}
on_modify( obj );
}
@ -460,6 +502,7 @@ namespace graphene { namespace db {
private:
object_id_type _next_id;
const direct_index< object_type, DirectBits >* _direct_by_id = nullptr;
safety_check_policy& _check;
};
} } // graphene::db

View file

@ -25,6 +25,7 @@
#include <graphene/db/object.hpp>
#include <graphene/db/index.hpp>
#include <graphene/db/undo_database.hpp>
#include <graphene/db/safety_check_policy.hpp>
#include <fc/log/logger.hpp>
@ -42,7 +43,7 @@ namespace graphene { namespace db {
object_database();
~object_database();
void reset_indexes() { _index.clear(); _index.resize(255); }
void reset_indexes();
void open(const fc::path& data_dir );
@ -53,15 +54,24 @@ namespace graphene { namespace db {
void wipe(const fc::path& data_dir); // remove from disk
void close();
template<typename T, typename F>
const T& create( F&& constructor )
{
auto& idx = get_mutable_index<T>();
return static_cast<const T&>( idx.create( [&](object& o)
{
assert( dynamic_cast<T*>(&o) );
constructor( static_cast<T&>(o) );
} ));
/**
* @brief Allocate an object space, setting a safety check policy for that object space
* @param space_id The ID of the object space to allocate
* @tparam SafetyCheckPolicy Specific type of the safety check policy for the object space
* @tparam ConstructorArgs Variadic of arguments for the constructor of SafetyCheckPolicy
* @return A non-owning pointer to the safety_check_policy object for the newly allocated object space
*
* Allocate a new object space in the database, setting the safety check policy for that object space. The
* policy object's lifetime is managed by the database, and a non-owning pointer to the newly created policy
* object is returned to the caller. The pointer will remain valid until the database is closed, or the
* indexes are reset.
*/
template<typename SafetyCheckPolicy, typename... ConstructorArgs>
SafetyCheckPolicy* allocate_object_space(uint8_t space_id, ConstructorArgs&&... args) {
FC_ASSERT(_safety_checks[space_id] == nullptr,
"Cannot allocate object space ${ID}: object space already allocated.", ("ID", space_id));
_safety_checks[space_id] = std::make_unique<SafetyCheckPolicy>(std::forward<ConstructorArgs>(args)...);
return static_cast<SafetyCheckPolicy*>(_safety_checks[space_id].get());
}
///These methods are used to retrieve indexes on the object_database. All public index accessors are const-access only.
@ -88,6 +98,17 @@ namespace graphene { namespace db {
/// in order to maintain proper undo history.
///@{
template<typename T, typename F>
const T& create( F&& constructor )
{
auto& idx = get_mutable_index<T>();
return static_cast<const T&>( idx.create( [&](object& o)
{
assert( dynamic_cast<T*>(&o) );
constructor( static_cast<T&>(o) );
} ));
}
const object& insert( object&& obj ) { return get_mutable_index(obj.id).insert( std::move(obj) ); }
void remove( const object& obj ) { get_mutable_index(obj.id).remove( obj ); }
template<typename T, typename Lambda>
@ -139,37 +160,70 @@ namespace graphene { namespace db {
IndexType* add_index()
{
typedef typename IndexType::object_type ObjectType;
if( _index[ObjectType::space_id].size() <= ObjectType::type_id )
_index[ObjectType::space_id].resize( 255 );
assert(!_index[ObjectType::space_id][ObjectType::type_id]);
unique_ptr<index> indexptr( new IndexType(*this) );
_index[ObjectType::space_id][ObjectType::type_id] = std::move(indexptr);
return static_cast<IndexType*>(_index[ObjectType::space_id][ObjectType::type_id].get());
uint8_t space_id = ObjectType::space_id;
uint8_t type_id = ObjectType::type_id;
if( _index[space_id].size() <= type_id )
_index[space_id].resize( 255 );
assert(!_index[space_id][type_id]);
safety_check_policy* check = _safety_checks[space_id].get();
FC_ASSERT(check != nullptr && check->allow_new_index(type_id),
"Safety Check: Addition of new index ${S}.${T} not allowed!",
("S", space_id)("T", type_id));
unique_ptr<index> indexptr( new IndexType(*this, *check) );
_index[space_id][type_id] = std::move(indexptr);
return static_cast<IndexType*>(_index[space_id][type_id].get());
}
template<typename SecondaryIndexType, typename PrimaryIndexType = typename SecondaryIndexType::watched_index>
SecondaryIndexType* add_secondary_index()
{
return get_mutable_index_type<PrimaryIndexType>().template add_secondary_index<SecondaryIndexType>();
uint8_t space_id = PrimaryIndexType::object_type::space_id;
uint8_t type_id = PrimaryIndexType::object_type::type_id;
auto new_index =
get_mutable_index_type<PrimaryIndexType>().template add_secondary_index<SecondaryIndexType>();
safety_check_policy* check = _safety_checks[space_id].get();
FC_ASSERT(check != nullptr &&
check->allow_new_secondary_index(type_id, *new_index),
"Safety Check: Addition of new secondary index on ${S}.${T} not allowed!",
("S", space_id)("T", type_id));
return new_index;
}
template<typename SecondaryIndexType>
SecondaryIndexType* add_secondary_index(const uint8_t space_id, const uint8_t type_id)
{
auto* base_primary = dynamic_cast<base_primary_index*>(&get_mutable_index(space_id, type_id));
FC_ASSERT(base_primary != nullptr,
"Cannot add secondary index: index for space ID ${S} and type ID ${T} does not support secondary indexes.",
FC_ASSERT(base_primary != nullptr, "Cannot add secondary index: index for space ID ${S} and type ID ${T} "
"does not support secondary indexes.",
("S", space_id)("T", type_id));
return base_primary->template add_secondary_index<SecondaryIndexType>();
auto new_index = base_primary->template add_secondary_index<SecondaryIndexType>();
safety_check_policy* check = _safety_checks[space_id].get();
FC_ASSERT(check != nullptr && check->allow_new_secondary_index(type_id, *new_index),
"Safety Check: Addition of new secondary index on ${S}.${T} not allowed!",
("S", space_id)("T", type_id));
return new_index;
}
template<typename PrimaryIndexType>
void delete_secondary_index(const secondary_index& secondary) {
uint8_t space_id = PrimaryIndexType::object_type::space_id;
uint8_t type_id = PrimaryIndexType::object_type::type_id;
safety_check_policy* check = _safety_checks[space_id].get();
FC_ASSERT(check != nullptr &&
check->allow_delete_secondary_index(type_id, secondary),
"Safety Check: Deletion of secondary index on ${S}.${T} not allowed!",
("S", space_id)("T", type_id));
get_mutable_index_type<PrimaryIndexType>().delete_secondary_index(secondary);
}
void delete_secondary_index(const uint8_t space_id, const uint8_t type_id, const secondary_index& secondary) {
safety_check_policy* check = _safety_checks[space_id].get();
FC_ASSERT(check != nullptr && check->allow_delete_secondary_index(type_id, secondary),
"Safety Check: Deletion of secondary index on ${S}.${T} not allowed!",
("S", space_id)("T", type_id));
auto* base_primary = dynamic_cast<base_primary_index*>(&get_mutable_index(space_id, type_id));
FC_ASSERT(base_primary != nullptr,
"Cannot add secondary index: index for space ID ${S} and type ID ${T} does not support secondary indexes.",
FC_ASSERT(base_primary != nullptr, "Cannot delete secondary index: index for space ID ${S} and type ID "
"${T} does not support secondary indexes.",
("S", space_id)("T", type_id));
base_primary->delete_secondary_index(secondary);
}
@ -210,6 +264,7 @@ namespace graphene { namespace db {
void save_undo_remove( const object& obj );
fc::path _data_dir;
vector< std::unique_ptr<safety_check_policy> > _safety_checks;
vector< vector< unique_ptr<index> > > _index;
};

View file

@ -0,0 +1,143 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <graphene/protocol/object_id.hpp>
namespace graphene {
namespace db {
class secondary_index;
/**
* @brief A polymorphic safety check policy for validating modifications to the database
*
* The safety check policy is used to catch bugs surrounding modifications to databases early in development. In
* particular, it can be used to validate that modifications to a given database object space are made by specific
* pathways of the code, and modifications made outside of these pathways can be blocked to preserve architectural
* invariants of the system. A safety check policy can be installed on an object space when that space is allocated
* to check modifications made to that space.
*
* Pursuant to this goal, the policy is used to validate modifications to the database, including new indexes (both
* primary and secondary), new objects, modifications to objects, and deletion of objects. Additionally, as secondary
* indexes represent transfer of execution to a pathway distinct from the pathway that led to the change being
* notified about, the policy will also be notified before and after executing a secondary index.
*
* @note Safety checks are not queried when loading objects from persistence when the database is opened, but are
* invoked when secondary indexes are notified of such.
*/
class safety_check_policy {
public:
virtual ~safety_check_policy();
virtual bool allow_new_index(uint8_t type_id) = 0;
virtual bool allow_new_secondary_index(uint8_t type_id, const secondary_index& new_secondary_index) = 0;
virtual bool allow_delete_secondary_index(uint8_t type_id, const secondary_index& secondary_index) = 0;
virtual bool allow_object_creation(object_id_type new_object_id) = 0;
virtual bool allow_object_modification(object_id_type modified_object_id) = 0;
virtual bool allow_object_deletion(object_id_type deleted_object_id) = 0;
virtual void pre_secondary_index_notification(uint8_t type_id, const secondary_index& notified_index) = 0;
virtual void post_secondary_index_notification(uint8_t type_id, const secondary_index& notified_index) = 0;
};
/** @brief A safety check that allows all modifications and does nothing when secondary indexes are invoked */
class null_safety_check : public safety_check_policy {
public:
null_safety_check() = default;
null_safety_check(null_safety_check&&) = default;
null_safety_check(const null_safety_check&) = delete;
virtual ~null_safety_check();
null_safety_check& operator=(const null_safety_check&) = delete;
null_safety_check& operator=(null_safety_check&&) = default;
bool allow_new_index(uint8_t) override { return true; }
bool allow_new_secondary_index(uint8_t, const secondary_index&) override { return true; }
bool allow_delete_secondary_index(uint8_t, const secondary_index&) override { return true; }
bool allow_object_creation(object_id_type) override { return true; }
bool allow_object_modification(object_id_type) override { return true; }
bool allow_object_deletion(object_id_type) override { return true; }
void pre_secondary_index_notification(uint8_t, const secondary_index&) override {}
void post_secondary_index_notification(uint8_t, const secondary_index&) override {}
};
/**
* @brief A safety check that allows the database object space to be locked and unlocked for changes
*
* This class implements a time-based database safety check using a lock, such that modifications made at times that
* the database is unlocked are always allowed, but modifications made at times the database is locked are always
* rejected. The database is initially unlocked, and is automatically locked while secondary indexes are being
* notified of changes to the database, and unlocked after.
*
* Secondary indexes are always allowed to be added or deleted, regardless of lock state.
*/
class database_lock_safety_check : public safety_check_policy {
bool locked;
public:
database_lock_safety_check() : locked(false) {}
database_lock_safety_check(database_lock_safety_check&&) = default;
database_lock_safety_check(const database_lock_safety_check&) = delete;
virtual ~database_lock_safety_check();
database_lock_safety_check& operator=(const database_lock_safety_check&) = delete;
database_lock_safety_check& operator=(database_lock_safety_check&&) = default;
bool is_locked() const { return locked; }
void lock() { locked = true; }
void unlock() { locked = false; }
bool allow_new_index(uint8_t) override { return !locked; }
bool allow_new_secondary_index(uint8_t, const secondary_index&) override { return true; }
bool allow_delete_secondary_index(uint8_t, const secondary_index&) override { return true; }
bool allow_object_creation(object_id_type) override { return !locked; }
bool allow_object_modification(object_id_type) override { return !locked; }
bool allow_object_deletion(object_id_type) override { return !locked; }
void pre_secondary_index_notification(uint8_t, const secondary_index&) override { locked = true; }
void post_secondary_index_notification(uint8_t, const secondary_index&) override { locked = false; }
};
class scoped_database_unlocker {
std::vector<database_lock_safety_check*> locks;
std::vector<bool> original_states;
public:
template<typename... Locks>
scoped_database_unlocker(Locks&... locks) : locks{&locks...}, original_states{locks.is_locked()...} {
for (const auto& lock : this->locks)
lock->unlock();
}
scoped_database_unlocker(scoped_database_unlocker&&) = default;
scoped_database_unlocker(const scoped_database_unlocker&) = delete;
scoped_database_unlocker& operator=(const scoped_database_unlocker&) = delete;
scoped_database_unlocker& operator=(scoped_database_unlocker&&) = default;
~scoped_database_unlocker() {
for (unsigned int i = 0; i < locks.size(); ++i)
if (original_states[i] == true) locks[i]->lock();
else locks[i]->unlock();
}
};
} } // namespace graphene::db

View file

@ -22,6 +22,7 @@
* THE SOFTWARE.
*/
#include <graphene/db/object_database.hpp>
#include <graphene/db/safety_check_policy.hpp>
#include <fc/io/raw.hpp>
#include <fc/container/flat.hpp>
@ -32,11 +33,19 @@ object_database::object_database()
:_undo_db(*this)
{
_index.resize(255);
_safety_checks.resize(255);
_undo_db.enable();
}
object_database::~object_database(){}
void object_database::reset_indexes() {
_index.clear();
_index.resize(255);
_safety_checks.clear();
_safety_checks.resize(255);
}
void object_database::close()
{
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/db/safety_check_policy.hpp>
namespace graphene { namespace db {
// Implement virtual destructors in .cpp file to give vtables a place to live
safety_check_policy::~safety_check_policy() = default;
null_safety_check::~null_safety_check() = default;
database_lock_safety_check::~database_lock_safety_check() = default;
} } // namespace graphene::db

View file

@ -53,7 +53,8 @@ namespace graphene { namespace account_history {
enum account_history_object_type
{
key_account_object_type = 0,
bucket_object_type = 1 ///< used in market_history_plugin
bucket_object_type = 1, ///< used in market_history_plugin
order_history_object_type = 2 ///< used in market_history_plugin
};

View file

@ -197,6 +197,7 @@ void affiliate_stats_plugin::plugin_set_program_options(
void affiliate_stats_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{
database().applied_block.connect( [this]( const signed_block& b){ my->update_affiliate_stats(b); } );
database().allocate_object_space<null_safety_check>(AFFILIATE_STATS_SPACE_ID);
my->_ar_index = database().add_index< primary_index< app_reward_index > >();
my->_rr_index = database().add_index< primary_index< referral_reward_index > >();

View file

@ -516,6 +516,8 @@ void bookie_plugin::plugin_initialize(const boost::program_options::variables_ma
database().new_objects.connect([this](const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts) { my->on_objects_new(ids); });
database().removed_objects.connect([this](const vector<object_id_type>& ids, const vector<const object*>& objs, const flat_set<account_id_type>& impacted_accounts) { my->on_objects_removed(ids); });
database().allocate_object_space<null_safety_check>(bookie_objects);
//auto event_index =
database().add_index<primary_index<detail::persistent_event_index> >();
database().add_index<primary_index<detail::persistent_betting_market_group_index> >();

View file

@ -101,6 +101,9 @@ struct history_key {
};
struct order_history_object : public abstract_object<order_history_object>
{
static const uint8_t space_id = ACCOUNT_HISTORY_SPACE_ID;
static const uint8_t type_id = 2; // market_history_plugin type, referenced from account_history_plugin.hpp
history_key key;
fc::time_point_sec time;
fill_order_operation op;
@ -133,9 +136,9 @@ namespace detail
}
/**
* The market history plugin can be configured to track any number of intervals via its configuration. Once per block it
* will scan the virtual operations and look for fill_order_operations and then adjust the appropriate bucket objects for
* each fill order.
* The market history plugin can be configured to track any number of intervals via its configuration. Once per
* block it will scan the virtual operations and look for fill_order_operations and then adjust the appropriate
* bucket objects for each fill order.
*/
class market_history_plugin : public graphene::app::plugin
{

View file

@ -265,6 +265,7 @@ void market_history_plugin::plugin_set_program_options(
void market_history_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{ try {
database().applied_block.connect( [this]( const signed_block& b){ my->update_market_histories(b); } );
database().allocate_object_space<null_safety_check>(ACCOUNT_HISTORY_SPACE_ID);
database().add_index< primary_index< bucket_index > >();
database().add_index< primary_index< history_index > >();

View file

@ -78,7 +78,7 @@ namespace raw { \
#define GRAPHENE_NAME_TO_OBJECT_TYPE(x, prefix, name) BOOST_PP_CAT(prefix, BOOST_PP_CAT(name, _object_type))
#define GRAPHENE_NAME_TO_ID_TYPE(x, y, name) BOOST_PP_CAT(name, _id_type)
#define GRAPHENE_DECLARE_ID(x, space_prefix_seq, name) \
using BOOST_PP_CAT(name, _id_type) = object_id<BOOST_PP_TUPLE_ELEM(2, 0, space_prefix_seq), \
using BOOST_PP_CAT(name, _id_type) = graphene::protocol::object_id<BOOST_PP_TUPLE_ELEM(2, 0, space_prefix_seq), \
GRAPHENE_NAME_TO_OBJECT_TYPE(x, BOOST_PP_TUPLE_ELEM(2, 1, space_prefix_seq), name)>;
#define GRAPHENE_REFLECT_ID(x, id_namespace, name) FC_REFLECT_TYPENAME(graphene::id_namespace::name)
@ -89,7 +89,7 @@ namespace raw { \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(GRAPHENE_NAME_TO_OBJECT_TYPE, object_type_prefix, names_seq)) \
}; \
\
BOOST_PP_SEQ_FOR_EACH(GRAPHENE_DECLARE_ID, (object_space, object_type_prefix), names_seq) \
BOOST_PP_SEQ_FOR_EACH(GRAPHENE_DECLARE_ID, (graphene::protocol::object_space, object_type_prefix), names_seq) \
\
} } \
\
@ -165,7 +165,8 @@ const static uint32_t UIA_ASSET_ISSUER_PERMISSION_MASK =
enum reserved_spaces {
relative_protocol_ids = 0,
protocol_ids = 1,
implementation_ids = 2
implementation_ids = 2,
api_ids = 3 ///< Non-consensus objects used for API. Tracked by respective plugins.
};
inline bool is_relative(object_id_type o) { return o.space() == 0; }
@ -243,7 +244,7 @@ GRAPHENE_DEFINE_IDS(protocol, protocol_ids, /*protocol objects are not prefixed*
(call_order)
(custom)
(proposal)
(operation_history)
(reserved0/*operation_history, moved to api_ids*/)
(withdraw_permission)
(vesting_balance)
(worker)

View file

@ -1634,76 +1634,78 @@ BOOST_AUTO_TEST_CASE(event_group_delete_test)
{
try
{
ACTORS( (alice)(bob) )
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
db.bypass_safety_checks([this] {
ACTORS( (alice)(bob) )
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
const int initialAccountAsset = 10000000;
const int betAsset = 1000000;
const int initialAccountAsset = 10000000;
const int betAsset = 1000000;
transfer(account_id_type(), alice_id, asset(initialAccountAsset));
transfer(account_id_type(), bob_id, asset(initialAccountAsset));
transfer(account_id_type(), alice_id, asset(initialAccountAsset));
transfer(account_id_type(), bob_id, asset(initialAccountAsset));
const auto& event = create_event({{"en", "event"}}, {{"en", "2016-17"}}, nhl_id);
const auto& event = create_event({{"en", "event"}}, {{"en", "2016-17"}}, nhl_id);
const auto& market_group = create_betting_market_group({{"en", "market group"}}, event.id, betting_market_rules_id, asset_id_type(), false, 0);
//to make bets be not removed immediately
update_betting_market_group_impl(market_group.id,
fc::optional<internationalized_string_type>(),
fc::optional<object_id_type>(),
betting_market_group_status::in_play,
false);
const auto& market_group = create_betting_market_group({{"en", "market group"}}, event.id, betting_market_rules_id, asset_id_type(), false, 0);
//to make bets be not removed immediately
update_betting_market_group_impl(market_group.id,
fc::optional<internationalized_string_type>(),
fc::optional<object_id_type>(),
betting_market_group_status::in_play,
false);
const auto& market = create_betting_market(market_group.id, {{"en", "market"}});
const auto& market = create_betting_market(market_group.id, {{"en", "market"}});
test_events events(*this, nhl_id);
test_markets_groups markets_groups(*this, event.id, betting_market_rules_id);
test_markets markets(*this, market_group.id);
test_events events(*this, nhl_id);
test_markets_groups markets_groups(*this, event.id, betting_market_rules_id);
test_markets markets(*this, market_group.id);
const auto& bet_1_id = place_bet(alice_id, market.id, bet_type::back, asset(betAsset, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
const auto& bet_2_id = place_bet(bob_id, market.id, bet_type::lay, asset(betAsset, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
const auto& bet_1_id = place_bet(alice_id, market.id, bet_type::back, asset(betAsset, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
const auto& bet_2_id = place_bet(bob_id, market.id, bet_type::lay, asset(betAsset, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
delete_event_group(nhl_id);
delete_event_group(nhl_id);
const auto& event_group_by_id = db.get_index_type<event_group_object_index>().indices().get<by_id>();
BOOST_CHECK(event_group_by_id.end() == event_group_by_id.find(nhl_id));
const auto& event_group_by_id = db.get_index_type<event_group_object_index>().indices().get<by_id>();
BOOST_CHECK(event_group_by_id.end() == event_group_by_id.find(nhl_id));
BOOST_CHECK(event_status::canceled == event.get_status());
BOOST_CHECK(event_status::canceled == event.get_status());
BOOST_CHECK(event_status::canceled == events.event_upcoming->get_status());
BOOST_CHECK(event_status::canceled == events.event_in_progress->get_status());
BOOST_CHECK(event_status::canceled == events.event_frozen_in_progress->get_status());
BOOST_CHECK(event_status::canceled == events.event_frozen_upcoming->get_status());
BOOST_CHECK(event_status::canceled == events.event_finished->get_status());
BOOST_CHECK(event_status::canceled == events.event_canceled->get_status());
BOOST_CHECK(event_status::settled == events.event_settled->get_status());
BOOST_CHECK(event_status::canceled == events.event_upcoming->get_status());
BOOST_CHECK(event_status::canceled == events.event_in_progress->get_status());
BOOST_CHECK(event_status::canceled == events.event_frozen_in_progress->get_status());
BOOST_CHECK(event_status::canceled == events.event_frozen_upcoming->get_status());
BOOST_CHECK(event_status::canceled == events.event_finished->get_status());
BOOST_CHECK(event_status::canceled == events.event_canceled->get_status());
BOOST_CHECK(event_status::settled == events.event_settled->get_status());
BOOST_CHECK(betting_market_group_status::canceled == market_group.get_status());
BOOST_CHECK(betting_market_group_status::canceled == market_group.get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_upcoming->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_frozen_upcoming->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_in_play->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_frozen_in_play->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_closed->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_graded->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_canceled->get_status());
BOOST_CHECK(betting_market_group_status::settled == markets_groups.market_group_settled->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_upcoming->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_frozen_upcoming->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_in_play->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_frozen_in_play->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_closed->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_graded->get_status());
BOOST_CHECK(betting_market_group_status::canceled == markets_groups.market_group_canceled->get_status());
BOOST_CHECK(betting_market_group_status::settled == markets_groups.market_group_settled->get_status());
BOOST_CHECK(betting_market_status::canceled == market.get_status());
BOOST_CHECK(betting_market_status::canceled == market.get_status());
BOOST_CHECK(betting_market_status::canceled == markets.market_unresolved->get_status());
BOOST_CHECK(betting_market_status::canceled == markets.market_frozen->get_status());
BOOST_CHECK(betting_market_status::canceled == markets.market_closed->get_status());
BOOST_CHECK(betting_market_status::canceled == markets.market_graded->get_status());
BOOST_CHECK(betting_market_status::canceled == markets.market_canceled->get_status());
BOOST_CHECK(betting_market_status::settled == markets.market_settled->get_status()); //settled market should not be canceled
BOOST_CHECK(betting_market_status::canceled == markets.market_unresolved->get_status());
BOOST_CHECK(betting_market_status::canceled == markets.market_frozen->get_status());
BOOST_CHECK(betting_market_status::canceled == markets.market_closed->get_status());
BOOST_CHECK(betting_market_status::canceled == markets.market_graded->get_status());
BOOST_CHECK(betting_market_status::canceled == markets.market_canceled->get_status());
BOOST_CHECK(betting_market_status::settled == markets.market_settled->get_status()); //settled market should not be canceled
//check canceled bets and reverted balance changes
const auto& bet_by_id = db.get_index_type<bet_object_index>().indices().get<by_id>();
BOOST_CHECK(bet_by_id.end() == bet_by_id.find(bet_1_id));
BOOST_CHECK(bet_by_id.end() == bet_by_id.find(bet_2_id));
//check canceled bets and reverted balance changes
const auto& bet_by_id = db.get_index_type<bet_object_index>().indices().get<by_id>();
BOOST_CHECK(bet_by_id.end() == bet_by_id.find(bet_1_id));
BOOST_CHECK(bet_by_id.end() == bet_by_id.find(bet_2_id));
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), initialAccountAsset);
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), initialAccountAsset);
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), initialAccountAsset);
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), initialAccountAsset);
});
} FC_LOG_AND_RETHROW()
}

View file

@ -68,7 +68,7 @@ using std::cout;
using std::cerr;
database_fixture::database_fixture()
: app(), db( *app.chain_database() )
: app(app::application::TESTING_MODE()), db( *app.chain_database() )
{
try {
int argc = boost::unit_test::framework::master_test_suite().argc;
@ -1056,9 +1056,11 @@ void database_fixture::fund_fee_pool( const account_object& from, const asset_ob
void database_fixture::enable_fees()
{
db.modify(global_property_id_type()(db), [](global_property_object& gpo)
{
gpo.parameters.current_fees = std::make_shared<fee_schedule>(fee_schedule::get_default());
db.bypass_safety_checks([this] {
db.modify(global_property_id_type()(db), [](global_property_object& gpo)
{
gpo.parameters.current_fees = std::make_shared<fee_schedule>(fee_schedule::get_default());
});
});
}

View file

@ -78,8 +78,10 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture )
//Get a sane head block time
generate_block( skip_flags );
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();
db.bypass_safety_checks([this] {
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();
});
});
transaction tx;

View file

@ -299,9 +299,11 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test )
int64_t audrey_btc = 0;
{
const tournament_object& game = db.create<tournament_object>( []( tournament_object& t ) {
t.options.game_options = rock_paper_scissors_game_options();
t.options.buy_in = asset( 10 );
const tournament_object& game = db.bypass_safety_checks([this] {
return db.create<tournament_object>( []( tournament_object& t ) {
t.options.game_options = rock_paper_scissors_game_options();
t.options.buy_in = asset( 10 );
});
});
affiliate_payout_helper helper = affiliate_payout_helper( db, game );
// Alice has no distribution set
@ -326,7 +328,9 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test )
ath.audrey_ppy += 10;
ath.ann_ppy += 1;
helper.commit();
db.bypass_safety_checks([&helper] {
helper.commit();
});
BOOST_CHECK_EQUAL( ath.alice_ppy, get_balance( ath.alice_id, asset_id_type() ) );
BOOST_CHECK_EQUAL( ath.ann_ppy, get_balance( ath.ann_id, asset_id_type() ) );
@ -334,9 +338,11 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test )
}
{
const tournament_object& game = db.create<tournament_object>( [btc_id]( tournament_object& t ) {
t.options.game_options = rock_paper_scissors_game_options();
t.options.buy_in = asset( 10, btc_id );
const tournament_object& game = db.bypass_safety_checks([this, &btc_id] {
return db.create<tournament_object>( [btc_id]( tournament_object& t ) {
t.options.game_options = rock_paper_scissors_game_options();
t.options.buy_in = asset( 10, btc_id );
});
});
affiliate_payout_helper helper = affiliate_payout_helper( db, game );
// 20% of 60 = 12: 2 to Alice, 3 to Ann, 7 to Audrey
@ -344,16 +350,20 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test )
alice_btc += 2;
ann_btc += 3;
audrey_btc += 7;
helper.commit();
db.bypass_safety_checks([&helper] {
helper.commit();
});
BOOST_CHECK_EQUAL( alice_btc, get_balance( ath.alice_id, btc_id ) );
BOOST_CHECK_EQUAL( ann_btc, get_balance( ath.ann_id, btc_id ) );
BOOST_CHECK_EQUAL( audrey_btc, get_balance( ath.audrey_id, btc_id ) );
}
{
const betting_market_group_object& game = db.create<betting_market_group_object>( []( betting_market_group_object& b ) {
b.asset_id = asset_id_type();
} );
const betting_market_group_object& game = db.bypass_safety_checks([this] {
return db.create<betting_market_group_object>( []( betting_market_group_object& b ) {
b.asset_id = asset_id_type();
});
});
affiliate_payout_helper helper = affiliate_payout_helper( db, game );
// Alice has no distribution set
BOOST_CHECK_EQUAL( 0, helper.payout( ath.alice_id, 1000 ).value );
@ -371,14 +381,18 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test )
ath.alice_ppy += 8;
// intermediate commit should clear internal accumulator
helper.commit();
db.bypass_safety_checks([&helper] {
helper.commit();
});
// 20% of 59 = 11: 6 to Alice, 5 to Ann
BOOST_CHECK_EQUAL( 11, helper.payout( ath.penny_id, 59 ).value );
ath.alice_ppy += 6;
ath.ann_ppy += 5;
helper.commit();
db.bypass_safety_checks([&helper] {
helper.commit();
});
BOOST_CHECK_EQUAL( ath.alice_ppy, get_balance( ath.alice_id, asset_id_type() ) );
BOOST_CHECK_EQUAL( ath.ann_ppy, get_balance( ath.ann_id, asset_id_type() ) );
@ -386,15 +400,19 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test )
}
{
const betting_market_group_object& game = db.create<betting_market_group_object>( [btc_id]( betting_market_group_object& b ) {
b.asset_id = btc_id;
} );
const betting_market_group_object& game = db.bypass_safety_checks([this, &btc_id] {
return db.create<betting_market_group_object>( [btc_id]( betting_market_group_object& b ) {
b.asset_id = btc_id;
});
});
affiliate_payout_helper helper = affiliate_payout_helper( db, game );
// 20% of 60 = 12: 7 to Alice, 5 to Ann
BOOST_CHECK_EQUAL( 12, helper.payout( ath.penny_id, 60 ).value );
alice_btc += 7;
ann_btc += 5;
helper.commit();
db.bypass_safety_checks([&helper] {
helper.commit();
});
BOOST_CHECK_EQUAL( alice_btc, get_balance( ath.alice_id, btc_id ) );
BOOST_CHECK_EQUAL( ann_btc, get_balance( ath.ann_id, btc_id ) );
BOOST_CHECK_EQUAL( audrey_btc, get_balance( ath.audrey_id, btc_id ) );
@ -405,14 +423,18 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test )
auto& index = db.get_index_type< primary_index< account_balance_index > >().get_secondary_index<balances_by_account_index>();
auto abo = index.get_account_balance( account_id_type(), asset_id_type() );
BOOST_CHECK( abo != nullptr );
db.modify( *abo, [&ath]( account_balance_object& bal ) {
bal.balance -= ath.alice_ppy + ath.ann_ppy + ath.audrey_ppy;
db.bypass_safety_checks([this, &abo, &ath] {
db.modify( *abo, [&ath]( account_balance_object& bal ) {
bal.balance -= ath.alice_ppy + ath.ann_ppy + ath.audrey_ppy;
});
});
abo = index.get_account_balance( irene_id, btc_id );
BOOST_CHECK( abo != nullptr );
db.modify( *abo, [alice_btc,ann_btc,audrey_btc]( account_balance_object& bal ) {
bal.balance -= alice_btc + ann_btc + audrey_btc;
db.bypass_safety_checks([this, &abo, &alice_btc, &ann_btc, &audrey_btc] {
db.modify( *abo, [alice_btc,ann_btc,audrey_btc]( account_balance_object& bal ) {
bal.balance -= alice_btc + ann_btc + audrey_btc;
});
});
}
}

View file

@ -435,9 +435,11 @@ BOOST_AUTO_TEST_CASE( committee_authority )
generate_block();
// Signatures are for suckers.
db.modify(db.get_global_properties(), [](global_property_object& p) {
// Turn the review period WAY down, so it doesn't take long to produce blocks to that point in simulated time.
p.parameters.committee_proposal_review_period = fc::days(1).to_seconds();
db.bypass_safety_checks([this] {
db.modify(db.get_global_properties(), [](global_property_object& p) {
// Turn the review period WAY down, so it doesn't take long to produce blocks to that point in simulated time.
p.parameters.committee_proposal_review_period = fc::days(1).to_seconds();
});
});
BOOST_TEST_MESSAGE( "transfering 100000 CORE to nathan, signing with committee key should fail because this requires it to be part of a proposal" );
@ -927,8 +929,10 @@ BOOST_FIXTURE_TEST_CASE( max_authority_membership, database_fixture )
//Get a sane head block time
generate_block();
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();
db.bypass_safety_checks([this] {
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();
});
});
transaction tx;

View file

@ -870,8 +870,10 @@ BOOST_FIXTURE_TEST_CASE( change_block_interval, database_fixture )
{ try {
generate_block();
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();
db.bypass_safety_checks([this] {
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();
});
});
BOOST_TEST_MESSAGE( "Creating a proposal to change the block_interval to 1 second" );
@ -954,8 +956,10 @@ BOOST_FIXTURE_TEST_CASE( pop_block_twice, database_fixture )
//Get a sane head block time
generate_block( skip_flags );
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();
db.bypass_safety_checks([this] {
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();
});
});
transaction tx;
@ -990,8 +994,10 @@ BOOST_FIXTURE_TEST_CASE( witness_scheduler_missed_blocks, database_fixture )
uint8_t witness_schedule_algorithm = db.get_global_properties().parameters.witness_schedule_algorithm;
if (witness_schedule_algorithm != GRAPHENE_WITNESS_SCHEDULED_ALGORITHM)
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.witness_schedule_algorithm = GRAPHENE_WITNESS_SCHEDULED_ALGORITHM;
db.bypass_safety_checks([this] {
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.witness_schedule_algorithm = GRAPHENE_WITNESS_SCHEDULED_ALGORITHM;
});
});
db.get_near_witness_schedule();
@ -1019,8 +1025,10 @@ BOOST_FIXTURE_TEST_CASE( witness_scheduler_missed_blocks, database_fixture )
});
if (db.get_global_properties().parameters.witness_schedule_algorithm != witness_schedule_algorithm)
db.modify(db.get_global_properties(), [&witness_schedule_algorithm](global_property_object& p) {
p.parameters.witness_schedule_algorithm = witness_schedule_algorithm;
db.bypass_safety_checks([this, &witness_schedule_algorithm] {
db.modify(db.get_global_properties(), [&witness_schedule_algorithm](global_property_object& p) {
p.parameters.witness_schedule_algorithm = witness_schedule_algorithm;
});
});
} FC_LOG_AND_RETHROW() }

View file

@ -39,22 +39,24 @@ BOOST_FIXTURE_TEST_SUITE( database_tests, database_fixture )
BOOST_AUTO_TEST_CASE( undo_test )
{
try {
database db;
auto ses = db._undo_db.start_undo_session();
const auto& bal_obj1 = db.create<account_balance_object>( [&]( account_balance_object& obj ){
/* no balances right now */
});
auto id1 = bal_obj1.id;
// abandon changes
ses.undo();
// start a new session
ses = db._undo_db.start_undo_session();
database db(true);
db.bypass_safety_checks([&db] {
auto ses = db._undo_db.start_undo_session();
const auto& bal_obj1 = db.create<account_balance_object>( [&]( account_balance_object& ){
/* no balances right now */
});
auto id1 = bal_obj1.id;
// abandon changes
ses.undo();
// start a new session
ses = db._undo_db.start_undo_session();
const auto& bal_obj2 = db.create<account_balance_object>( [&]( account_balance_object& obj ){
/* no balances right now */
const auto& bal_obj2 = db.create<account_balance_object>( [&]( account_balance_object& obj ){
/* no balances right now */
});
auto id2 = bal_obj2.id;
BOOST_CHECK( id1 == id2 );
});
auto id2 = bal_obj2.id;
BOOST_CHECK( id1 == id2 );
} catch ( const fc::exception& e )
{
edump( (e.to_detail_string()) );
@ -65,10 +67,12 @@ BOOST_AUTO_TEST_CASE( undo_test )
BOOST_AUTO_TEST_CASE( merge_test )
{
try {
database db;
database db(true);
auto ses = db._undo_db.start_undo_session();
db.create<account_balance_object>( [&]( account_balance_object& obj ){
obj.balance = 42;
db.bypass_safety_checks([&db] {
db.create<account_balance_object>( [&]( account_balance_object& obj ){
obj.balance = 42;
});
});
ses.merge();
@ -92,13 +96,16 @@ BOOST_AUTO_TEST_CASE( flat_index_test )
FC_ASSERT( bitusd.bitasset_data_id->instance.value == 0 );
FC_ASSERT( !(*bitusd.bitasset_data_id)(db).current_feed.settlement_price.is_null() );
try {
auto ses = db._undo_db.start_undo_session();
const auto& obj1 = db.create<asset_bitasset_data_object>( [&]( asset_bitasset_data_object& obj ){
obj.settlement_fund = 17;
// Bypass safety checks for whole block, because ses will trigger database modifications when destroyed
db.bypass_safety_checks([this] {
auto ses = db._undo_db.start_undo_session();
const auto& obj1 = db.create<asset_bitasset_data_object>( [&]( asset_bitasset_data_object& obj ){
obj.settlement_fund = 17;
});
FC_ASSERT( obj1.settlement_fund == 17 );
throw std::string("Expected");
// With flat_index, obj1 will not really be removed from the index
});
FC_ASSERT( obj1.settlement_fund == 17 );
throw std::string("Expected");
// With flat_index, obj1 will not really be removed from the index
} catch ( const std::string& e )
{ // ignore
}
@ -112,12 +119,13 @@ BOOST_AUTO_TEST_CASE( flat_index_test )
BOOST_AUTO_TEST_CASE( direct_index_test )
{ try {
null_safety_check check;
try {
const graphene::db::primary_index< account_index, 6 > small_chunkbits( db );
const graphene::db::primary_index< account_index, 6 > small_chunkbits( db, check );
BOOST_FAIL( "Expected assertion failure!" );
} catch( const fc::assert_exception& expected ) {}
graphene::db::primary_index< account_index, 8 > my_accounts( db );
graphene::db::primary_index< account_index, 8 > my_accounts( db, check );
const auto& direct = my_accounts.get_secondary_index<graphene::db::direct_index< account_object, 8 >>();
BOOST_CHECK_EQUAL( 0, my_accounts.indices().size() );
BOOST_CHECK( nullptr == direct.find( account_id_type( 1 ) ) );

View file

@ -581,10 +581,12 @@ REG : net' ltm' ref'
BOOST_AUTO_TEST_CASE( account_create_fee_scaling )
{ try {
auto accounts_per_scale = db.get_global_properties().parameters.accounts_per_fee_scale;
db.modify(global_property_id_type()(db), [](global_property_object& gpo)
{
gpo.parameters.current_fees = std::make_shared<fee_schedule>(fee_schedule::get_default());
gpo.parameters.current_fees->get<account_create_operation>().basic_fee = 1;
db.bypass_safety_checks([this] {
db.modify(global_property_id_type()(db), [](global_property_object& gpo)
{
gpo.parameters.current_fees = std::make_shared<fee_schedule>(fee_schedule::get_default());
gpo.parameters.current_fees->get<account_create_operation>().basic_fee = 1;
});
});
for( int i = db.get_dynamic_global_properties().accounts_registered_this_interval; i < accounts_per_scale; ++i )

View file

@ -104,11 +104,13 @@ struct gpos_fixture: database_fixture
void update_gpos_global(uint32_t vesting_period, uint32_t vesting_subperiod, fc::time_point_sec period_start)
{
db.modify(db.get_global_properties(), [vesting_period, vesting_subperiod, period_start](global_property_object& p) {
p.parameters.extensions.value.gpos_period = vesting_period;
p.parameters.extensions.value.gpos_subperiod = vesting_subperiod;
p.parameters.extensions.value.gpos_period_start = period_start.sec_since_epoch();
p.parameters.extensions.value.gpos_vesting_lockin_period = vesting_subperiod;
db.bypass_safety_checks([this, vesting_period, vesting_subperiod, &period_start] {
db.modify(db.get_global_properties(), [vesting_period, vesting_subperiod, period_start](global_property_object& p) {
p.parameters.extensions.value.gpos_period = vesting_period;
p.parameters.extensions.value.gpos_subperiod = vesting_subperiod;
p.parameters.extensions.value.gpos_period_start = period_start.sec_since_epoch();
p.parameters.extensions.value.gpos_vesting_lockin_period = vesting_subperiod;
});
});
BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period(), vesting_period);
BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_subperiod(), vesting_subperiod);
@ -118,8 +120,10 @@ struct gpos_fixture: database_fixture
void update_maintenance_interval(uint32_t new_interval)
{
db.modify(db.get_global_properties(), [new_interval](global_property_object& p) {
p.parameters.maintenance_interval = new_interval;
db.bypass_safety_checks([this, new_interval] {
db.modify(db.get_global_properties(), [new_interval](global_property_object& p) {
p.parameters.maintenance_interval = new_interval;
});
});
BOOST_CHECK_EQUAL(db.get_global_properties().parameters.maintenance_interval, new_interval);
}

View file

@ -204,9 +204,13 @@ BOOST_AUTO_TEST_CASE( lottery_end_by_stage_test )
BOOST_CHECK( participants_percents_sum == winners_part );
BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value == (jackpot * (GRAPHENE_100_PERCENT - winners_part) / (double)GRAPHENE_100_PERCENT) + jackpot * winners_part * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT / (double)GRAPHENE_100_PERCENT );
test_asset.distribute_benefactors_part( db );
db.bypass_safety_checks([this, &test_asset] {
test_asset.distribute_benefactors_part( db );
});
BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value == jackpot * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT * winners_part / (double)GRAPHENE_100_PERCENT );
test_asset.distribute_sweeps_holders_part( db );
db.bypass_safety_checks([this, &test_asset] {
test_asset.distribute_sweeps_holders_part( db );
});
BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value == 0 );
uint64_t benefactor_recieved = db.get_balance( account_id_type(), asset_id_type() ).amount.value - benefactor_balance_before_end;
@ -225,8 +229,10 @@ BOOST_AUTO_TEST_CASE( lottery_end_by_stage_with_fractional_test )
try {
asset_id_type test_asset_id = db.get_index<asset_object>().get_next_id();
INVOKE( create_lottery_asset_test );
db.modify(test_asset_id(db), [&](asset_object& ao) {
ao.lottery_options->is_active = true;
db.bypass_safety_checks([this, &test_asset_id] {
db.modify(test_asset_id(db), [&](asset_object& ao) {
ao.lottery_options->is_active = true;
});
});
auto test_asset = test_asset_id(db);
for( int i = 1; i < 17; ++i ) {
@ -261,9 +267,13 @@ BOOST_AUTO_TEST_CASE( lottery_end_by_stage_with_fractional_test )
BOOST_CHECK( participants_percents_sum == winners_part );
// balance should be bigger than expected because of rouning during distribution
BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value > (jackpot * (GRAPHENE_100_PERCENT - winners_part) / (double)GRAPHENE_100_PERCENT) + jackpot * winners_part * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT / (double)GRAPHENE_100_PERCENT );
test_asset.distribute_benefactors_part( db );
db.bypass_safety_checks([this, &test_asset] {
test_asset.distribute_benefactors_part( db );
});
BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value > jackpot * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT * winners_part / (double)GRAPHENE_100_PERCENT );
test_asset.distribute_sweeps_holders_part( db );
db.bypass_safety_checks([this, &test_asset] {
test_asset.distribute_sweeps_holders_part( db );
});
// but at the end is always equals 0
BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value == 0 );
@ -373,9 +383,13 @@ BOOST_AUTO_TEST_CASE( more_winners_then_participants_test )
generate_block();
test_asset = test_asset_id(db);
auto holders = test_asset.get_holders(db);
auto participants = test_asset.distribute_winners_part( db );
test_asset.distribute_benefactors_part( db );
test_asset.distribute_sweeps_holders_part( db );
auto participants = db.bypass_safety_checks([this, &test_asset] {
auto ret = test_asset.distribute_winners_part( db );
test_asset.distribute_benefactors_part( db );
test_asset.distribute_sweeps_holders_part( db );
return ret;
});
generate_block();
for( auto p: participants ) {
idump(( get_operation_history(p.first) ));

View file

@ -47,9 +47,11 @@ namespace
transaction.operations = operations;
fixture.db.create<proposal_object>([&](proposal_object& proposal)
{
proposal.proposed_transaction = transaction;
fixture.db.bypass_safety_checks([&fixture, &transaction] {
fixture.db.create<proposal_object>([&](proposal_object& proposal)
{
proposal.proposed_transaction = transaction;
});
});
}

View file

@ -136,9 +136,11 @@ BOOST_AUTO_TEST_CASE(list_pending_proposals_postponed) {
try {
graphene::app::network_node_api network_node_api(app);
db.modify(db.get_global_properties(), [](global_property_object& properties) {
//Size in bytes. Empiricaly found to limit block size for two test transactions
properties.parameters.maximum_block_size = 650;
db.bypass_safety_checks([this] {
db.modify(db.get_global_properties(), [](global_property_object& properties) {
//Size in bytes. Empiricaly found to limit block size for two test transactions
properties.parameters.maximum_block_size = 650;
});
});
auto sam_transaction = push_transaction_for_account_creation("sam");

View file

@ -239,10 +239,14 @@ BOOST_AUTO_TEST_CASE(lottery_end_by_stage_test)
test_nft_md_obj = test_nft_md_id(db);
BOOST_CHECK(participants_percents_sum == winners_part);
BOOST_CHECK(test_nft_md_obj.get_lottery_jackpot(db).amount.value == (jackpot * (GRAPHENE_100_PERCENT - winners_part) / (double)GRAPHENE_100_PERCENT) + jackpot * winners_part * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT / (double)GRAPHENE_100_PERCENT);
test_nft_md_obj.distribute_benefactors_part(db);
db.bypass_safety_checks([this, &test_nft_md_obj] {
test_nft_md_obj.distribute_benefactors_part(db);
});
test_nft_md_obj = test_nft_md_id(db);
BOOST_CHECK(test_nft_md_obj.get_lottery_jackpot(db).amount.value == jackpot * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT * winners_part / (double)GRAPHENE_100_PERCENT);
test_nft_md_obj.distribute_sweeps_holders_part(db);
db.bypass_safety_checks([this, &test_nft_md_obj] {
test_nft_md_obj.distribute_sweeps_holders_part(db);
});
test_nft_md_obj = test_nft_md_id(db);
BOOST_CHECK(test_nft_md_obj.get_lottery_jackpot(db).amount.value == 0);
@ -263,8 +267,10 @@ BOOST_AUTO_TEST_CASE(lottery_end_by_stage_with_fractional_test)
{
nft_metadata_id_type test_nft_md_id = db.get_index<nft_metadata_object>().get_next_id();
INVOKE(create_lottery_nft_md_test);
db.modify(test_nft_md_id(db), [&](nft_metadata_object &obj) {
obj.lottery_data->lottery_options.is_active = true;
db.bypass_safety_checks([this, &test_nft_md_id] {
db.modify(test_nft_md_id(db), [&](nft_metadata_object &obj) {
obj.lottery_data->lottery_options.is_active = true;
});
});
auto test_nft_md_obj = test_nft_md_id(db);
@ -294,7 +300,9 @@ BOOST_AUTO_TEST_CASE(lottery_end_by_stage_with_fractional_test)
winners_part += win;
uint16_t participants_percents_sum = 0;
auto participants = test_nft_md_obj.distribute_winners_part(db);
auto participants = db.bypass_safety_checks([this, &test_nft_md_obj] {
return test_nft_md_obj.distribute_winners_part(db);
});
for (auto p : participants)
for (auto e : p.second)
participants_percents_sum += e;
@ -303,10 +311,14 @@ BOOST_AUTO_TEST_CASE(lottery_end_by_stage_with_fractional_test)
// balance should be bigger than expected because of rouning during distribution
test_nft_md_obj = test_nft_md_id(db);
BOOST_CHECK(test_nft_md_obj.get_lottery_jackpot(db).amount.value > (jackpot * (GRAPHENE_100_PERCENT - winners_part) / (double)GRAPHENE_100_PERCENT) + jackpot * winners_part * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT / (double)GRAPHENE_100_PERCENT);
test_nft_md_obj.distribute_benefactors_part(db);
db.bypass_safety_checks([this, &test_nft_md_obj] {
test_nft_md_obj.distribute_benefactors_part(db);
});
test_nft_md_obj = test_nft_md_id(db);
BOOST_CHECK(test_nft_md_obj.get_lottery_jackpot(db).amount.value > jackpot * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT * winners_part / (double)GRAPHENE_100_PERCENT);
test_nft_md_obj.distribute_sweeps_holders_part(db);
db.bypass_safety_checks([this, &test_nft_md_obj] {
test_nft_md_obj.distribute_sweeps_holders_part(db);
});
test_nft_md_obj = test_nft_md_id(db);
// but at the end is always equals 0
BOOST_CHECK(test_nft_md_obj.get_lottery_jackpot(db).amount.value == 0);
@ -429,12 +441,15 @@ BOOST_AUTO_TEST_CASE(more_winners_then_participants_test)
generate_block();
test_nft_md_obj = test_nft_md_id(db);
auto holders = test_nft_md_obj.get_holders(db);
auto participants = test_nft_md_obj.distribute_winners_part(db);
test_nft_md_obj = test_nft_md_id(db);
test_nft_md_obj.distribute_benefactors_part(db);
test_nft_md_obj = test_nft_md_id(db);
test_nft_md_obj.distribute_sweeps_holders_part(db);
test_nft_md_obj = test_nft_md_id(db);
auto participants = db.bypass_safety_checks([this, &test_nft_md_obj, &test_nft_md_id] {
auto ret = test_nft_md_obj.distribute_winners_part(db);
test_nft_md_obj = test_nft_md_id(db);
test_nft_md_obj.distribute_benefactors_part(db);
test_nft_md_obj = test_nft_md_id(db);
test_nft_md_obj.distribute_sweeps_holders_part(db);
test_nft_md_obj = test_nft_md_id(db);
return ret;
});
generate_block();
for (auto p : participants)
{

View file

@ -1307,10 +1307,12 @@ BOOST_AUTO_TEST_CASE( witness_pay_test )
BOOST_CHECK_LT( witness_ppb * 2, ref_budget );
BOOST_CHECK_GT( witness_ppb * 3, ref_budget );
db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
{
_gpo.parameters.witness_pay_per_block = witness_ppb;
} );
db.bypass_safety_checks([this, &witness_ppb] {
db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
{
_gpo.parameters.witness_pay_per_block = witness_ppb;
});
});
BOOST_CHECK_EQUAL(core->dynamic_asset_data_id(db).accumulated_fees.value, 0);
BOOST_TEST_MESSAGE( "Upgrading account" );
@ -1335,10 +1337,12 @@ BOOST_AUTO_TEST_CASE( witness_pay_test )
auto schedule_maint = [&]()
{
// now we do maintenance
db.modify( db.get_dynamic_global_properties(), [&]( dynamic_global_property_object& _dpo )
{
_dpo.next_maintenance_time = db.head_block_time() + 1;
} );
db.bypass_safety_checks([this] {
db.modify( db.get_dynamic_global_properties(), [&]( dynamic_global_property_object& _dpo )
{
_dpo.next_maintenance_time = db.head_block_time() + 1;
});
});
};
BOOST_TEST_MESSAGE( "Generating some blocks" );
@ -1632,10 +1636,12 @@ BOOST_AUTO_TEST_CASE( vesting_balance_withdraw_test )
{
// HACK: This just modifies the DB creation record to be further
// in the past
db.modify( vbo, [&]( vesting_balance_object& _vbo )
{
_vbo.policy.get<cdd_vesting_policy>().coin_seconds_earned_last_update -= dt_secs;
} );
db.bypass_safety_checks([this, &vbo, dt_secs] {
db.modify( vbo, [&]( vesting_balance_object& _vbo )
{
_vbo.policy.get<cdd_vesting_policy>().coin_seconds_earned_last_update -= dt_secs;
});
});
};
auto create_vbo = [&](

View file

@ -78,17 +78,21 @@ BOOST_AUTO_TEST_CASE( sidechain_address_update_test ) {
std::string new_withdraw_address = "withdraw_address";
generate_block();
auto& son = db.create<son_object>( [&]( son_object& sobj )
{
sobj.son_account = bob_id;
sobj.statistics = db.create<son_statistics_object>([&](son_statistics_object& s){s.owner = sobj.id;}).id;
const auto& son = db.bypass_safety_checks([this, &bob_id] {
return db.create<son_object>( [&]( son_object& sobj )
{
sobj.son_account = bob_id;
sobj.statistics = db.create<son_statistics_object>([&](son_statistics_object& s){s.owner = sobj.id;}).id;
});
});
generate_block();
db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
{
son_info sinfo;
sinfo.son_id = son.id;
_gpo.active_sons.push_back(sinfo);
db.bypass_safety_checks([this, &son] {
db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
{
son_info sinfo;
sinfo.son_id = son.id;
_gpo.active_sons.push_back(sinfo);
});
});
generate_block();
{

View file

@ -191,14 +191,16 @@ try {
BOOST_REQUIRE( son_stats_obj != sidx.end() );
// Modify SON's status to active
db.modify( *obj, [&]( son_object& _s)
{
_s.status = son_status::in_maintenance;
});
db.bypass_safety_checks([this, &obj, &son_stats_obj] {
db.modify( *obj, [&]( son_object& _s)
{
_s.status = son_status::in_maintenance;
});
db.modify( *son_stats_obj, [&]( son_statistics_object& _s)
{
_s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - db.get_global_properties().parameters.son_deregister_time());
db.modify( *son_stats_obj, [&]( son_statistics_object& _s)
{
_s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - db.get_global_properties().parameters.son_deregister_time());
});
});
auto deposit_vesting = db.get<vesting_balance_object>(vesting_balance_id_type(0));
@ -319,11 +321,13 @@ BOOST_AUTO_TEST_CASE( son_pay_test )
enable_fees();
// Make SON Budget small for testing purposes
// Make witness budget zero so that amount can be allocated to SON
db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
{
_gpo.parameters.extensions.value.son_pay_max = 200;
_gpo.parameters.witness_pay_per_block = 0;
} );
db.bypass_safety_checks([this] {
db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
{
_gpo.parameters.extensions.value.son_pay_max = 200;
_gpo.parameters.witness_pay_per_block = 0;
});
});
GET_ACTOR(alice);
GET_ACTOR(bob);
@ -336,10 +340,12 @@ BOOST_AUTO_TEST_CASE( son_pay_test )
// Do maintenance from the upcoming block
auto schedule_maint = [&]()
{
db.modify( db.get_dynamic_global_properties(), [&]( dynamic_global_property_object& _dpo )
{
_dpo.next_maintenance_time = db.head_block_time() + 1;
} );
db.bypass_safety_checks([this] {
db.modify( db.get_dynamic_global_properties(), [&]( dynamic_global_property_object& _dpo )
{
_dpo.next_maintenance_time = db.head_block_time() + 1;
});
});
};
// Generate enough blocks to make budget
@ -489,34 +495,36 @@ BOOST_AUTO_TEST_CASE( son_pay_test )
BOOST_REQUIRE( son_stats_obj1 != sidx.end() );
BOOST_REQUIRE( son_stats_obj2 != sidx.end() );
// Modify the transaction signed statistics of Alice's SON
db.modify( *son_stats_obj1, [&]( son_statistics_object& _s)
{
_s.txs_signed[sidechain_type::bitcoin] = 2;
_s.txs_signed[sidechain_type::hive] = 4;
db.bypass_safety_checks([this, &son_stats_obj1, &son_stats_obj2] {
db.modify( *son_stats_obj1, [&]( son_statistics_object& _s)
{
_s.txs_signed[sidechain_type::bitcoin] = 2;
_s.txs_signed[sidechain_type::hive] = 4;
_s.total_txs_signed[sidechain_type::bitcoin] = 2;
_s.total_txs_signed[sidechain_type::hive] = 4;
_s.total_txs_signed[sidechain_type::bitcoin] = 2;
_s.total_txs_signed[sidechain_type::hive] = 4;
_s.sidechain_txs_reported[sidechain_type::bitcoin] = 4;
_s.sidechain_txs_reported[sidechain_type::hive] = 8;
_s.sidechain_txs_reported[sidechain_type::bitcoin] = 4;
_s.sidechain_txs_reported[sidechain_type::hive] = 8;
_s.total_sidechain_txs_reported[sidechain_type::bitcoin] = 4;
_s.total_sidechain_txs_reported[sidechain_type::hive] = 8;
});
// Modify the transaction signed statistics of Bob's SON
db.modify( *son_stats_obj2, [&]( son_statistics_object& _s)
{
_s.txs_signed[sidechain_type::bitcoin] = 3;
_s.txs_signed[sidechain_type::hive] = 6;
_s.total_sidechain_txs_reported[sidechain_type::bitcoin] = 4;
_s.total_sidechain_txs_reported[sidechain_type::hive] = 8;
});
// Modify the transaction signed statistics of Bob's SON
db.modify( *son_stats_obj2, [&]( son_statistics_object& _s)
{
_s.txs_signed[sidechain_type::bitcoin] = 3;
_s.txs_signed[sidechain_type::hive] = 6;
_s.total_txs_signed[sidechain_type::bitcoin] = 3;
_s.total_txs_signed[sidechain_type::hive] = 6;
_s.total_txs_signed[sidechain_type::bitcoin] = 3;
_s.total_txs_signed[sidechain_type::hive] = 6;
_s.sidechain_txs_reported[sidechain_type::bitcoin] = 6;
_s.sidechain_txs_reported[sidechain_type::hive] = 12;
_s.sidechain_txs_reported[sidechain_type::bitcoin] = 6;
_s.sidechain_txs_reported[sidechain_type::hive] = 12;
_s.total_sidechain_txs_reported[sidechain_type::bitcoin] = 6;
_s.total_sidechain_txs_reported[sidechain_type::hive] = 12;
_s.total_sidechain_txs_reported[sidechain_type::bitcoin] = 6;
_s.total_sidechain_txs_reported[sidechain_type::hive] = 12;
});
});
// Note the balances before the maintenance
@ -602,14 +610,16 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) {
BOOST_REQUIRE( son_stats_obj != sidx.end() );
// Modify SON's status to active
db.modify( *obj, [&]( son_object& _s)
{
_s.status = son_status::active;
});
db.bypass_safety_checks([this, &obj, &son_stats_obj] {
db.modify( *obj, [&]( son_object& _s)
{
_s.status = son_status::active;
});
db.modify( *son_stats_obj, [&]( son_statistics_object& _s)
{
_s.last_down_timestamp = fc::time_point_sec(db.head_block_time());
db.modify( *son_stats_obj, [&]( son_statistics_object& _s)
{
_s.last_down_timestamp = fc::time_point_sec(db.head_block_time());
});
});
{
@ -647,9 +657,11 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) {
}
// Modify SON's status to in_maintenance
db.modify( *obj, [&]( son_object& _s)
{
_s.status = son_status::in_maintenance;
db.bypass_safety_checks([this, &obj] {
db.modify( *obj, [&]( son_object& _s)
{
_s.status = son_status::in_maintenance;
});
});
uint64_t downtime = 0;
@ -674,18 +686,20 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) {
BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts);
}
// Modify SON's status to in_maintenance
db.modify( *obj, [&]( son_object& _s)
{
_s.status = son_status::in_maintenance;
});
db.bypass_safety_checks([this, &obj] {
// Modify SON's status to in_maintenance
db.modify( *obj, [&]( son_object& _s)
{
_s.status = son_status::in_maintenance;
});
// SON is selected as one of the active SONs
db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
{
son_info son_inf;
son_inf.son_id = son_id_type(0);
_gpo.active_sons.push_back(son_inf);
// SON is selected as one of the active SONs
db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
{
son_info son_inf;
son_inf.son_id = son_id_type(0);
_gpo.active_sons.push_back(son_inf);
});
});
{

View file

@ -530,8 +530,10 @@ BOOST_FIXTURE_TEST_CASE( delay_between_games_must_not_be_less, database_fixture
{ std::string reason("Delay between games must not be less");
BOOST_TEST_MESSAGE("Starting test '" + reason + "'");
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.min_round_delay = 1;
db.bypass_safety_checks([this] {
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.min_round_delay = 1;
});
});
ACTORS((nathan));
@ -604,8 +606,10 @@ BOOST_FIXTURE_TEST_CASE( time_to_commit_the_move_must_not_be_less, database_fixt
{ std::string reason("Time to commit the next move must not be less");
BOOST_TEST_MESSAGE("Starting test '" + reason + "'");
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.min_time_per_commit_move = 1;
db.bypass_safety_checks([this] {
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.min_time_per_commit_move = 1;
});
});
ACTORS((nathan));
@ -678,8 +682,10 @@ BOOST_FIXTURE_TEST_CASE( time_to_reveal_the_move_must_not_be_less, database_fixt
{ std::string reason("Time to reveal the move must not be less");
BOOST_TEST_MESSAGE("Starting test '" + reason + "'");
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.min_time_per_reveal_move = 1;
db.bypass_safety_checks([this] {
db.modify(db.get_global_properties(), [](global_property_object& p) {
p.parameters.min_time_per_reveal_move = 1;
});
});
ACTORS((nathan));