Fix update_call_order
- redefine price feeds - clean up unit test spam
This commit is contained in:
commit
ef4ed937cb
54 changed files with 1238 additions and 1601 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -5,7 +5,3 @@
|
|||
path = libraries/fc
|
||||
url = https://github.com/cryptonomex/fc.git
|
||||
ignore = dirty
|
||||
[submodule "libraries/leveldb"]
|
||||
path = libraries/leveldb
|
||||
url = https://github.com/bitcoin/leveldb.git
|
||||
ignore = dirty
|
||||
|
|
|
|||
|
|
@ -65,21 +65,6 @@ IF(NOT "${Boost_VERSION}" MATCHES "1.53(.*)")
|
|||
SET(Boost_LIBRARIES ${BOOST_LIBRARIES_TEMP} ${Boost_LIBRARIES})
|
||||
ENDIF()
|
||||
|
||||
set( LEVEL_DB_DIR "${CMAKE_SOURCE_DIR}/libraries/leveldb" )
|
||||
|
||||
file( GLOB LEVEL_DB_SOURCES "${LEVEL_DB_DIR}/db/*.cc"
|
||||
"${LEVEL_DB_DIR}/helpers/memenv/memenv.cc"
|
||||
"${LEVEL_DB_DIR}/table/*.cc"
|
||||
"${LEVEL_DB_DIR}/util/*.cc" )
|
||||
foreach( filename ${LEVEL_DB_SOURCES} )
|
||||
if( ${filename} MATCHES ".*_test.cc" OR ${filename} MATCHES ".*_bench.cc" OR ${filename} MATCHES ".*_main.cc" )
|
||||
list( REMOVE_ITEM LEVEL_DB_SOURCES ${filename} )
|
||||
endif()
|
||||
endforeach()
|
||||
set(LEVELDB_BUILD_DEFINES)
|
||||
set(LEVELDB_BUILD_LIBRARIES)
|
||||
set(LEVELDB_BUILD_PRIVATE_INCLUDES "${LEVEL_DB_DIR}")
|
||||
|
||||
if( WIN32 )
|
||||
message( STATUS "Configuring Graphene on WIN32")
|
||||
set( DB_VERSION 60 )
|
||||
|
|
@ -112,13 +97,7 @@ if( WIN32 )
|
|||
SET(TCL_LIBS "${TCL_LIBS}${TCL_LIB_PATH}/${TCL_LIB_NAME}g${TCL_LIB_EXT}")
|
||||
SET(TCL_LIBRARY ${TCL_LIBS})
|
||||
|
||||
SET(LEVELDB_PORT_FILE "${LEVEL_DB_DIR}/port/port_win.cc" )
|
||||
list(APPEND LEVELDB_BUILD_DEFINES OS_WINDOWS LEVELDB_PLATFORM_WINDOWS )
|
||||
list(APPEND LEVELDB_BUILD_LIBRARIES shlwapi.lib)
|
||||
list(INSERT LEVELDB_BUILD_PRIVATE_INCLUDES 0 "${CMAKE_SOURCE_DIR}/libraries/leveldb-msvc/include")
|
||||
else( WIN32 ) # Apple AND Linux
|
||||
SET(LEVELDB_PORT_FILE "${LEVEL_DB_DIR}/port/port_posix.cc" )
|
||||
|
||||
list(APPEND LEVELDB_BUILD_DEFINES LEVELDB_PLATFORM_POSIX LEVELDB_ATOMIC_PRESENT)
|
||||
if( APPLE )
|
||||
list(APPEND LEVELDB_BUILD_DEFINES OS_MACOSX)
|
||||
|
|
@ -169,13 +148,6 @@ else( WIN32 ) # Apple AND Linux
|
|||
|
||||
endif( WIN32 )
|
||||
|
||||
list(APPEND LEVEL_DB_SOURCES "${LEVELDB_PORT_FILE}")
|
||||
add_library( leveldb ${LEVEL_DB_SOURCES} )
|
||||
target_link_libraries( leveldb ${LEVELDB_BUILD_LIBRARIES} )
|
||||
target_include_directories( leveldb PRIVATE ${LEVELDB_BUILD_PRIVATE_INCLUDES}
|
||||
PUBLIC "${LEVEL_DB_DIR}/include" )
|
||||
set_target_properties(leveldb PROPERTIES COMPILE_DEFINITIONS "${LEVELDB_BUILD_DEFINES}")
|
||||
|
||||
find_package( BerkeleyDB )
|
||||
|
||||
set(ENABLE_COVERAGE_TESTING FALSE CACHE BOOL "Build Graphene for code coverage analysis")
|
||||
|
|
|
|||
|
|
@ -399,13 +399,13 @@ application::~application()
|
|||
{
|
||||
if( my->_p2p_network )
|
||||
{
|
||||
ilog("Closing p2p node");
|
||||
//ilog("Closing p2p node");
|
||||
my->_p2p_network->close();
|
||||
my->_p2p_network.reset();
|
||||
}
|
||||
if( my->_chain_db )
|
||||
{
|
||||
ilog("Closing chain database");
|
||||
//ilog("Closing chain database");
|
||||
my->_chain_db->close();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <graphene/app/application.hpp>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
#include <fc/io/json.hpp>
|
||||
|
||||
namespace graphene { namespace app {
|
||||
namespace bpo = boost::program_options;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ add_library( graphene_chain
|
|||
|
||||
transaction_evaluation_state.cpp
|
||||
fork_database.cpp
|
||||
block_database.cpp
|
||||
|
||||
db_balance.cpp
|
||||
db_block.cpp
|
||||
|
|
@ -49,7 +50,7 @@ add_library( graphene_chain
|
|||
${HEADERS}
|
||||
)
|
||||
|
||||
target_link_libraries( graphene_chain fc graphene_db leveldb )
|
||||
target_link_libraries( graphene_chain fc graphene_db )
|
||||
target_include_directories( graphene_chain
|
||||
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
|
||||
|
||||
|
|
|
|||
|
|
@ -25,18 +25,19 @@ namespace graphene { namespace chain {
|
|||
void_result account_create_evaluator::do_evaluate( const account_create_operation& op )
|
||||
{ try {
|
||||
database& d = db();
|
||||
FC_ASSERT( d.find_object(op.voting_account) );
|
||||
FC_ASSERT( is_relative(op.memo_key) || d.find_object(op.memo_key) );
|
||||
FC_ASSERT( d.find_object(op.options.voting_account) );
|
||||
FC_ASSERT( is_relative(op.options.memo_key) || d.find_object(op.options.memo_key) );
|
||||
FC_ASSERT( fee_paying_account->is_lifetime_member() );
|
||||
FC_ASSERT( op.referrer(d).is_member(d.head_block_time()) );
|
||||
|
||||
const auto& global_props = d.get_global_properties();
|
||||
uint32_t max_vote_id = global_props.next_available_vote_id;
|
||||
const auto& chain_params = global_props.parameters;
|
||||
FC_ASSERT( op.num_witness <= chain_params.maximum_witness_count );
|
||||
FC_ASSERT( op.num_committee <= chain_params.maximum_committee_count );
|
||||
|
||||
FC_ASSERT( op.owner.auths.size() <= chain_params.maximum_authority_membership );
|
||||
FC_ASSERT( op.active.auths.size() <= chain_params.maximum_authority_membership );
|
||||
check_relative_ids(op.owner);
|
||||
check_relative_ids(op.active);
|
||||
FC_ASSERT( d.find(key_id_type(get_relative_id(op.options.memo_key))) );
|
||||
for( auto id : op.owner.auths )
|
||||
{
|
||||
FC_ASSERT( is_relative(id.first) || d.find_object(id.first) );
|
||||
|
|
@ -45,18 +46,22 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio
|
|||
{
|
||||
FC_ASSERT( is_relative(id.first) || d.find_object(id.first) );
|
||||
}
|
||||
|
||||
uint32_t max_vote_id = global_props.next_available_vote_id;
|
||||
FC_ASSERT( op.options.num_witness <= chain_params.maximum_witness_count );
|
||||
FC_ASSERT( op.options.num_committee <= chain_params.maximum_committee_count );
|
||||
safe<uint32_t> counts[vote_id_type::VOTE_TYPE_COUNT];
|
||||
for( auto id : op.vote )
|
||||
for( auto id : op.options.votes )
|
||||
{
|
||||
FC_ASSERT( id < max_vote_id );
|
||||
counts[id.type()]++;
|
||||
}
|
||||
FC_ASSERT(counts[vote_id_type::witness] <= op.num_witness,
|
||||
FC_ASSERT(counts[vote_id_type::witness] <= op.options.num_witness,
|
||||
"",
|
||||
("count", counts[vote_id_type::witness])("num", op.num_witness));
|
||||
FC_ASSERT(counts[vote_id_type::committee] <= op.num_committee,
|
||||
("count", counts[vote_id_type::witness])("num", op.options.num_witness));
|
||||
FC_ASSERT(counts[vote_id_type::committee] <= op.options.num_committee,
|
||||
"",
|
||||
("count", counts[vote_id_type::committee])("num", op.num_committee));
|
||||
("count", counts[vote_id_type::committee])("num", op.options.num_committee));
|
||||
|
||||
auto& acnt_indx = d.get_index_type<account_index>();
|
||||
if( op.name.size() )
|
||||
|
|
@ -65,17 +70,6 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio
|
|||
FC_ASSERT( current_account_itr == acnt_indx.indices().get<by_name>().end() );
|
||||
}
|
||||
|
||||
// TODO: this check can be removed after GRAPHENE_LEGACY_NAME_IMPORT_PERIOD
|
||||
// legacy account check
|
||||
if( d.get_dynamic_global_properties().head_block_number < GRAPHENE_LEGACY_NAME_IMPORT_PERIOD )
|
||||
{
|
||||
auto legacy_account_itr = acnt_indx.indices().get<by_name>().find( "bts-"+op.name );
|
||||
if( legacy_account_itr != acnt_indx.indices().get<by_name>().end() )
|
||||
{
|
||||
FC_ASSERT( fee_paying_account->id == legacy_account_itr->id );
|
||||
}
|
||||
}
|
||||
|
||||
// verify child account authority
|
||||
auto pos = op.name.find( '/' );
|
||||
if( pos != string::npos )
|
||||
|
|
@ -93,9 +87,6 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio
|
|||
|
||||
object_id_type account_create_evaluator::do_apply( const account_create_operation& o )
|
||||
{ try {
|
||||
auto owner = resolve_relative_ids( o.owner );
|
||||
auto active = resolve_relative_ids( o.active );
|
||||
|
||||
const auto& stats_obj = db().create<account_statistics_object>( [&]( account_statistics_object& ){
|
||||
});
|
||||
|
||||
|
|
@ -110,14 +101,11 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio
|
|||
obj.referrer_rewards_percentage = o.referrer_percent;
|
||||
|
||||
obj.name = o.name;
|
||||
obj.owner = owner;
|
||||
obj.active = active;
|
||||
obj.owner = resolve_relative_ids(o.owner);
|
||||
obj.active = resolve_relative_ids(o.active);
|
||||
obj.statistics = stats_obj.id;
|
||||
obj.memo_key = get_relative_id(o.memo_key);
|
||||
obj.voting_account = o.voting_account;
|
||||
obj.votes = o.vote;
|
||||
obj.num_witness = o.num_witness;
|
||||
obj.num_committee = o.num_committee;
|
||||
obj.options = o.options;
|
||||
obj.options.memo_key = get_relative_id(obj.options.memo_key);
|
||||
});
|
||||
|
||||
return new_acnt_object.id;
|
||||
|
|
@ -128,14 +116,12 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio
|
|||
{
|
||||
database& d = db();
|
||||
|
||||
FC_ASSERT( !o.memo_key || is_relative(*o.memo_key) || db().find_object(*o.memo_key) );
|
||||
|
||||
const auto& chain_params = db().get_global_properties().parameters;
|
||||
FC_ASSERT( o.num_witness <= chain_params.maximum_witness_count );
|
||||
FC_ASSERT( o.num_committee <= chain_params.maximum_committee_count );
|
||||
|
||||
if( o.owner )
|
||||
{
|
||||
FC_ASSERT( o.owner->auths.size() <= chain_params.maximum_authority_membership );
|
||||
check_relative_ids(*o.owner);
|
||||
for( auto id : o.owner->auths )
|
||||
{
|
||||
FC_ASSERT( is_relative(id.first) || db().find<object>(id.first) );
|
||||
|
|
@ -144,6 +130,7 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio
|
|||
if( o.active )
|
||||
{
|
||||
FC_ASSERT( o.active->auths.size() <= chain_params.maximum_authority_membership );
|
||||
check_relative_ids(*o.active);
|
||||
for( auto id : o.active->auths )
|
||||
{
|
||||
FC_ASSERT( is_relative(id.first) || db().find<object>(id.first) );
|
||||
|
|
@ -152,10 +139,13 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio
|
|||
|
||||
acnt = &o.account(d);
|
||||
|
||||
if( o.vote )
|
||||
if( o.new_options )
|
||||
{
|
||||
FC_ASSERT( d.find(key_id_type(get_relative_id(o.new_options->memo_key))) );
|
||||
FC_ASSERT( o.new_options->num_witness <= chain_params.maximum_witness_count );
|
||||
FC_ASSERT( o.new_options->num_committee <= chain_params.maximum_committee_count );
|
||||
uint32_t max_vote_id = d.get_global_properties().next_available_vote_id;
|
||||
for( auto id : *o.vote )
|
||||
for( auto id : o.new_options->votes )
|
||||
{
|
||||
FC_ASSERT( id < max_vote_id );
|
||||
}
|
||||
|
|
@ -165,15 +155,15 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio
|
|||
}
|
||||
void_result account_update_evaluator::do_apply( const account_update_operation& o )
|
||||
{
|
||||
db().modify( *acnt, [&]( account_object& a ){
|
||||
if( o.owner ) a.owner = *o.owner;
|
||||
if( o.active ) a.active = *o.active;
|
||||
if( o.voting_account ) a.voting_account = *o.voting_account;
|
||||
if( o.memo_key ) a.memo_key = *o.memo_key;
|
||||
if( o.vote ) a.votes = *o.vote;
|
||||
a.num_witness = o.num_witness;
|
||||
a.num_committee = o.num_committee;
|
||||
});
|
||||
db().modify( *acnt, [&](account_object& a){
|
||||
if( o.owner ) a.owner = resolve_relative_ids(*o.owner);
|
||||
if( o.active ) a.active = resolve_relative_ids(*o.active);
|
||||
if( o.new_options )
|
||||
{
|
||||
a.options = *o.new_options;
|
||||
a.options.memo_key = get_relative_id(a.options.memo_key);
|
||||
}
|
||||
});
|
||||
return void_result();
|
||||
}
|
||||
|
||||
|
|
@ -224,6 +214,7 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator:
|
|||
if( o.upgrade_to_lifetime_member )
|
||||
{
|
||||
// Upgrade to lifetime member. I don't care what the account was before.
|
||||
a.statistics(d).process_fees(a, d);
|
||||
a.membership_expiration_date = time_point_sec::maximum();
|
||||
a.referrer = a.registrar = a.lifetime_referrer = a.get_id();
|
||||
a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - a.network_fee_percentage;
|
||||
|
|
@ -234,6 +225,7 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator:
|
|||
a.membership_expiration_date += fc::days(365);
|
||||
} else {
|
||||
// Upgrade from basic account.
|
||||
a.statistics(d).process_fees(a, d);
|
||||
assert(a.is_basic_account(d.head_block_time()));
|
||||
a.membership_expiration_date = d.head_block_time() + fc::days(365);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,24 @@
|
|||
*/
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <fc/uint128.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
share_type cut_fee(share_type a, uint16_t p)
|
||||
{
|
||||
if( a == 0 || p == 0 )
|
||||
return 0;
|
||||
if( p == GRAPHENE_100_PERCENT )
|
||||
return a;
|
||||
|
||||
fc::uint128 r(a.value);
|
||||
r *= p;
|
||||
r /= GRAPHENE_100_PERCENT;
|
||||
return r.to_uint64();
|
||||
}
|
||||
|
||||
bool account_object::is_authorized_asset(const asset_object& asset_obj) const {
|
||||
for( const auto id : blacklisting_accounts )
|
||||
if( asset_obj.options.blacklist_authorities.find(id) != asset_obj.options.blacklist_authorities.end() ) return false;
|
||||
|
|
@ -56,4 +70,80 @@ uint16_t account_statistics_object::calculate_bulk_discount_percent(const chain_
|
|||
return bulk_discount_percent;
|
||||
}
|
||||
|
||||
void account_statistics_object::process_fees(const account_object& a, database& d) const
|
||||
{
|
||||
if( pending_fees > 0 || pending_vested_fees > 0 )
|
||||
{
|
||||
const auto& props = d.get_global_properties();
|
||||
|
||||
auto pay_out_fees = [&](const account_object& account, share_type core_fee_total, bool require_vesting)
|
||||
{
|
||||
share_type network_cut = cut_fee(core_fee_total, account.network_fee_percentage);
|
||||
assert( network_cut <= core_fee_total );
|
||||
share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee);
|
||||
share_type accumulated = network_cut - burned;
|
||||
assert( accumulated + burned == network_cut );
|
||||
share_type lifetime_cut = cut_fee(core_fee_total, account.lifetime_referrer_fee_percentage);
|
||||
share_type referral = core_fee_total - network_cut - lifetime_cut;
|
||||
|
||||
d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) {
|
||||
d.accumulated_fees += network_cut;
|
||||
});
|
||||
|
||||
// Potential optimization: Skip some of this math and object lookups by special casing on the account type.
|
||||
// For example, if the account is a lifetime member, we can skip all this and just deposit the referral to
|
||||
// it directly.
|
||||
share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage);
|
||||
share_type registrar_cut = referral - referrer_cut;
|
||||
|
||||
d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting);
|
||||
d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting);
|
||||
d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting);
|
||||
|
||||
assert( referrer_cut + registrar_cut + accumulated + burned + lifetime_cut == core_fee_total );
|
||||
};
|
||||
|
||||
share_type vesting_fee_subtotal(pending_fees);
|
||||
share_type vested_fee_subtotal(pending_vested_fees);
|
||||
share_type vesting_cashback, vested_cashback;
|
||||
|
||||
if( lifetime_fees_paid > props.parameters.bulk_discount_threshold_min &&
|
||||
a.is_member(d.head_block_time()) )
|
||||
{
|
||||
auto bulk_discount_rate = calculate_bulk_discount_percent(props.parameters);
|
||||
vesting_cashback = cut_fee(vesting_fee_subtotal, bulk_discount_rate);
|
||||
vesting_fee_subtotal -= vesting_cashback;
|
||||
|
||||
vested_cashback = cut_fee(vested_fee_subtotal, bulk_discount_rate);
|
||||
vested_fee_subtotal -= vested_cashback;
|
||||
}
|
||||
|
||||
pay_out_fees(a, vesting_fee_subtotal, true);
|
||||
d.deposit_cashback(a, vesting_cashback, true);
|
||||
pay_out_fees(a, vested_fee_subtotal, false);
|
||||
d.deposit_cashback(a, vested_cashback, false);
|
||||
|
||||
d.modify(*this, [vested_fee_subtotal, vesting_fee_subtotal](account_statistics_object& s) {
|
||||
s.lifetime_fees_paid += vested_fee_subtotal + vesting_fee_subtotal;
|
||||
s.pending_fees = 0;
|
||||
s.pending_vested_fees = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void account_object::options_type::validate() const
|
||||
{
|
||||
auto needed_witnesses = num_witness;
|
||||
auto needed_committee = num_committee;
|
||||
|
||||
for( vote_id_type id : votes )
|
||||
if( id.type() == vote_id_type::witness && needed_witnesses )
|
||||
--needed_witnesses;
|
||||
else if ( id.type() == vote_id_type::committee && needed_committee )
|
||||
--needed_committee;
|
||||
|
||||
FC_ASSERT( needed_witnesses == 0 && needed_committee == 0,
|
||||
"May not specify fewer witnesses or committee members than the number voted for.");
|
||||
}
|
||||
|
||||
} } // graphene::chain
|
||||
|
|
|
|||
|
|
@ -101,13 +101,13 @@ namespace graphene { namespace chain {
|
|||
price price::min( asset_id_type base, asset_id_type quote ) { return asset( 1, base ) / asset( GRAPHENE_MAX_SHARE_SUPPLY, quote); }
|
||||
|
||||
price price::call_price(const asset& debt, const asset& collateral, uint16_t collateral_ratio)
|
||||
{
|
||||
{ try {
|
||||
fc::uint128 tmp( collateral.amount.value );
|
||||
tmp *= collateral_ratio - 1000;
|
||||
tmp /= 1000;
|
||||
FC_ASSERT( tmp <= GRAPHENE_MAX_SHARE_SUPPLY );
|
||||
return asset( tmp.to_uint64(), collateral.asset_id) / debt;
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW( (debt)(collateral)(collateral_ratio) ) }
|
||||
|
||||
bool price::is_null() const { return *this == price(); }
|
||||
|
||||
|
|
@ -120,13 +120,10 @@ namespace graphene { namespace chain {
|
|||
|
||||
void price_feed::validate() const
|
||||
{ try {
|
||||
if( !call_limit.is_null() )
|
||||
call_limit.validate();
|
||||
if( !settlement_price.is_null() )
|
||||
settlement_price.validate();
|
||||
FC_ASSERT( max_margin_period_sec > 0 );
|
||||
FC_ASSERT( required_maintenance_collateral >= 1000 );
|
||||
} FC_CAPTURE_AND_RETHROW( (call_limit.is_null())(call_limit)
|
||||
(max_margin_period_sec)(required_maintenance_collateral) ) }
|
||||
FC_ASSERT( maximum_short_squeeze_ratio >= 1000 );
|
||||
FC_ASSERT( maintenance_collateral_ratio >= maximum_short_squeeze_ratio );
|
||||
} FC_CAPTURE_AND_RETHROW( (*this) ) }
|
||||
|
||||
} } // graphene::chain
|
||||
|
|
|
|||
|
|
@ -365,16 +365,17 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_
|
|||
{ try {
|
||||
database& d = db();
|
||||
|
||||
const asset_object& quote = o.asset_id(d);
|
||||
const asset_object& base = o.asset_id(d);
|
||||
//Verify that this feed is for a market-issued asset and that asset is backed by the base
|
||||
FC_ASSERT(quote.is_market_issued());
|
||||
FC_ASSERT(base.is_market_issued());
|
||||
|
||||
const asset_bitasset_data_object& bitasset = quote.bitasset_data(d);
|
||||
FC_ASSERT(bitasset.options.short_backing_asset == o.feed.call_limit.base.asset_id);
|
||||
const asset_bitasset_data_object& bitasset = base.bitasset_data(d);
|
||||
FC_ASSERT(bitasset.options.short_backing_asset == o.feed.settlement_price.quote.asset_id);
|
||||
//Verify that the publisher is authoritative to publish a feed
|
||||
if( quote.issuer == account_id_type() )
|
||||
if( base.issuer == account_id_type() )
|
||||
{
|
||||
//It's a delegate-fed asset. Verify that publisher is an active delegate or witness.
|
||||
// TODO: replace account_id_type with global variable for delegates account id
|
||||
FC_ASSERT(d.get(account_id_type()).active.auths.count(o.publisher) ||
|
||||
d.get_global_properties().witness_accounts.count(o.publisher));
|
||||
} else {
|
||||
|
|
@ -388,9 +389,9 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope
|
|||
{ try {
|
||||
database& d = db();
|
||||
|
||||
const asset_object& quote = o.asset_id(d);
|
||||
const asset_object& base = o.asset_id(d);
|
||||
// Store medians for this asset
|
||||
d.modify(quote.bitasset_data(d), [&o,&d](asset_bitasset_data_object& a) {
|
||||
d.modify(base.bitasset_data(d), [&o,&d](asset_bitasset_data_object& a) {
|
||||
a.feeds[o.publisher] = make_pair(d.head_block_time(), o.feed);
|
||||
a.update_median_feeds(d.head_block_time());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ namespace graphene { namespace chain {
|
|||
checksum_type signed_block::calculate_merkle_root()const
|
||||
{
|
||||
if( transactions.size() == 0 ) return checksum_type();
|
||||
|
||||
vector<digest_type> ids;
|
||||
ids.resize( ((transactions.size() + 1)/2)*2 );
|
||||
for( uint32_t i = 0; i < transactions.size(); ++i )
|
||||
|
|
|
|||
189
libraries/chain/block_database.cpp
Normal file
189
libraries/chain/block_database.cpp
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
#include <graphene/chain/block_database.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
struct index_entry
|
||||
{
|
||||
uint64_t block_pos = 0;
|
||||
uint32_t block_size = 0;
|
||||
block_id_type block_id;
|
||||
};
|
||||
}}
|
||||
FC_REFLECT( graphene::chain::index_entry, (block_pos)(block_size)(block_id) );
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
void block_database::open( const fc::path& dbdir )
|
||||
{ try {
|
||||
fc::create_directories(dbdir);
|
||||
_block_num_to_pos.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
_blocks.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
|
||||
if( !fc::exists( dbdir/"index" ) )
|
||||
{
|
||||
_block_num_to_pos.open( (dbdir/"index").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc);
|
||||
_blocks.open( (dbdir/"blocks").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc);
|
||||
}
|
||||
else
|
||||
{
|
||||
_block_num_to_pos.open( (dbdir/"index").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out );
|
||||
_blocks.open( (dbdir/"blocks").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out );
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW( (dbdir) ) }
|
||||
|
||||
|
||||
bool block_database::is_open()const
|
||||
{
|
||||
return _blocks.is_open();
|
||||
}
|
||||
|
||||
|
||||
void block_database::close()
|
||||
{
|
||||
_blocks.close();
|
||||
_block_num_to_pos.close();
|
||||
}
|
||||
|
||||
void block_database::flush()
|
||||
{
|
||||
_blocks.flush();
|
||||
_block_num_to_pos.flush();
|
||||
}
|
||||
|
||||
|
||||
void block_database::store( const block_id_type& id, const signed_block& b )
|
||||
{
|
||||
auto num = block_header::num_from_id(id);
|
||||
_block_num_to_pos.seekp( sizeof( index_entry ) * num );
|
||||
index_entry e;
|
||||
_blocks.seekp( 0, _blocks.end );
|
||||
auto vec = fc::raw::pack( b );
|
||||
e.block_pos = _blocks.tellp();
|
||||
e.block_size = vec.size();
|
||||
e.block_id = id;
|
||||
_blocks.write( vec.data(), vec.size() );
|
||||
_block_num_to_pos.write( (char*)&e, sizeof(e) );
|
||||
}
|
||||
|
||||
|
||||
void block_database::remove( const block_id_type& id )
|
||||
{ try {
|
||||
index_entry e;
|
||||
auto index_pos = sizeof(e)*block_header::num_from_id(id);
|
||||
_block_num_to_pos.seekg( 0, _block_num_to_pos.end );
|
||||
FC_ASSERT( _block_num_to_pos.tellg() > index_pos );
|
||||
|
||||
_block_num_to_pos.seekg( index_pos );
|
||||
_block_num_to_pos.read( (char*)&e, sizeof(e) );
|
||||
|
||||
if( e.block_id == id )
|
||||
{
|
||||
e.block_size = 0;
|
||||
_block_num_to_pos.seekp( sizeof(e)*block_header::num_from_id(id) );
|
||||
_block_num_to_pos.write( (char*)&e, sizeof(e) );
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW( (id) ) }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool block_database::contains( const block_id_type& id )const
|
||||
{
|
||||
index_entry e;
|
||||
auto index_pos = sizeof(e)*block_header::num_from_id(id);
|
||||
_block_num_to_pos.seekg( 0, _block_num_to_pos.end );
|
||||
FC_ASSERT( _block_num_to_pos.tellg() > index_pos );
|
||||
|
||||
_block_num_to_pos.seekg( index_pos );
|
||||
_block_num_to_pos.read( (char*)&e, sizeof(e) );
|
||||
|
||||
return e.block_id == id;
|
||||
}
|
||||
|
||||
|
||||
block_id_type block_database::fetch_block_id( uint32_t block_num )const
|
||||
{
|
||||
index_entry e;
|
||||
auto index_pos = sizeof(e)*block_num;
|
||||
_block_num_to_pos.seekg( 0, _block_num_to_pos.end );
|
||||
FC_ASSERT( _block_num_to_pos.tellg() > index_pos );
|
||||
|
||||
_block_num_to_pos.seekg( index_pos );
|
||||
_block_num_to_pos.read( (char*)&e, sizeof(e) );
|
||||
|
||||
return e.block_id;
|
||||
}
|
||||
|
||||
|
||||
optional<signed_block> block_database::fetch_optional( const block_id_type& id )const
|
||||
{ try {
|
||||
index_entry e;
|
||||
auto index_pos = sizeof(e)*block_header::num_from_id(id);
|
||||
_block_num_to_pos.seekg( 0, _block_num_to_pos.end );
|
||||
FC_ASSERT( _block_num_to_pos.tellg() > index_pos );
|
||||
|
||||
_block_num_to_pos.seekg( index_pos );
|
||||
_block_num_to_pos.read( (char*)&e, sizeof(e) );
|
||||
|
||||
if( e.block_id != id ) return optional<signed_block>();
|
||||
|
||||
vector<char> data( e.block_size );
|
||||
_blocks.seekg( e.block_pos );
|
||||
_blocks.read( data.data(), e.block_size );
|
||||
auto result = fc::raw::unpack<signed_block>(data);
|
||||
FC_ASSERT( result.id() == e.block_id );
|
||||
return result;
|
||||
} FC_CAPTURE_AND_RETHROW( (id) ) }
|
||||
|
||||
|
||||
optional<signed_block> block_database::fetch_by_number( uint32_t block_num )const
|
||||
{ try {
|
||||
index_entry e;
|
||||
auto index_pos = sizeof(e)*block_num;
|
||||
_block_num_to_pos.seekg( 0, _block_num_to_pos.end );
|
||||
FC_ASSERT( _block_num_to_pos.tellg() > index_pos );
|
||||
|
||||
_block_num_to_pos.seekg( index_pos, _block_num_to_pos.beg );
|
||||
// wdump((int64_t(_block_num_to_pos.tellg())) );
|
||||
_block_num_to_pos.read( (char*)&e, sizeof(e) );
|
||||
// wdump((block_num)(e));
|
||||
|
||||
vector<char> data( e.block_size );
|
||||
_blocks.seekg( e.block_pos );
|
||||
_blocks.read( data.data(), e.block_size );
|
||||
auto result = fc::raw::unpack<signed_block>(data);
|
||||
FC_ASSERT( result.id() == e.block_id );
|
||||
return result;
|
||||
} FC_CAPTURE_AND_RETHROW( (block_num) ) }
|
||||
|
||||
|
||||
optional<signed_block> block_database::last()const
|
||||
{
|
||||
index_entry e;
|
||||
_block_num_to_pos.seekg( 0, _block_num_to_pos.end );
|
||||
|
||||
if( _block_num_to_pos.tellp() < sizeof(index_entry) )
|
||||
return optional<signed_block>();
|
||||
|
||||
_block_num_to_pos.seekg( -sizeof(index_entry), _block_num_to_pos.end );
|
||||
_block_num_to_pos.read( (char*)&e, sizeof(e) );
|
||||
while( e.block_size == 0 && _blocks.tellg() > 0 )
|
||||
{
|
||||
_block_num_to_pos.seekg( -sizeof(index_entry), _block_num_to_pos.cur );
|
||||
_block_num_to_pos.read( (char*)&e, sizeof(e) );
|
||||
}
|
||||
|
||||
if( e.block_size == 0 )
|
||||
return optional<signed_block>();
|
||||
|
||||
vector<char> data( e.block_size );
|
||||
_blocks.seekg( e.block_pos );
|
||||
_blocks.read( data.data(), e.block_size );
|
||||
auto result = fc::raw::unpack<signed_block>(data);
|
||||
//wdump((result));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
} }
|
||||
|
|
@ -24,112 +24,133 @@
|
|||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
asset call_order_update_evaluator::do_evaluate(const call_order_update_operation& o)
|
||||
void_result call_order_update_evaluator::do_evaluate(const call_order_update_operation& o)
|
||||
{ try {
|
||||
database& d = db();
|
||||
|
||||
_paying_account = &o.funding_account(d);
|
||||
|
||||
_debt_asset = &o.amount_to_cover.asset_id(d);
|
||||
_bitasset_data = &_debt_asset->bitasset_data(d);
|
||||
_debt_asset = &o.delta_debt.asset_id(d);
|
||||
FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.",
|
||||
("sym", _debt_asset->symbol) );
|
||||
FC_ASSERT( o.collateral_to_add.asset_id == _bitasset_data->options.short_backing_asset );
|
||||
|
||||
_bitasset_data = &_debt_asset->bitasset_data(d);
|
||||
|
||||
FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset );
|
||||
|
||||
if( _bitasset_data->is_prediction_market )
|
||||
{
|
||||
FC_ASSERT( o.collateral_to_add.amount == -o.amount_to_cover.amount );
|
||||
FC_ASSERT( o.maintenance_collateral_ratio == 0 );
|
||||
}
|
||||
FC_ASSERT( o.delta_collateral.amount == o.delta_debt.amount );
|
||||
else
|
||||
{
|
||||
FC_ASSERT( o.maintenance_collateral_ratio == 0 ||
|
||||
o.maintenance_collateral_ratio > _bitasset_data->current_feed.required_maintenance_collateral );
|
||||
}
|
||||
FC_ASSERT( !_bitasset_data->current_feed.settlement_price.is_null() );
|
||||
|
||||
if( o.amount_to_cover.amount > 0 )
|
||||
if( o.delta_debt.amount < 0 )
|
||||
{
|
||||
FC_ASSERT( d.get_balance(*_paying_account, *_debt_asset) >= o.amount_to_cover,
|
||||
FC_ASSERT( d.get_balance(*_paying_account, *_debt_asset) >= o.delta_debt,
|
||||
"Cannot cover by ${c} when payer only has ${b}",
|
||||
("c", o.amount_to_cover.amount)("b", d.get_balance(*_paying_account, *_debt_asset).amount) );
|
||||
}
|
||||
if( o.collateral_to_add.amount > 0 )
|
||||
{
|
||||
FC_ASSERT( d.get_balance(*_paying_account, _bitasset_data->options.short_backing_asset(d)) >= o.collateral_to_add,
|
||||
"Cannot increase collateral by ${c} when payer only has ${b}", ("c", o.amount_to_cover.amount)
|
||||
("b", d.get_balance(*_paying_account, _bitasset_data->options.short_backing_asset(d)).amount) );
|
||||
("c", o.delta_debt.amount)("b", d.get_balance(*_paying_account, *_debt_asset).amount) );
|
||||
}
|
||||
|
||||
return asset();
|
||||
if( o.delta_collateral.amount > 0 )
|
||||
{
|
||||
FC_ASSERT( d.get_balance(*_paying_account, _bitasset_data->options.short_backing_asset(d)) >= o.delta_collateral,
|
||||
"Cannot increase collateral by ${c} when payer only has ${b}", ("c", o.delta_collateral.amount)
|
||||
("b", d.get_balance(*_paying_account, o.delta_collateral.asset_id(d)).amount) );
|
||||
}
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||
|
||||
|
||||
asset call_order_update_evaluator::do_apply(const call_order_update_operation& o)
|
||||
void_result call_order_update_evaluator::do_apply(const call_order_update_operation& o)
|
||||
{
|
||||
database& d = db();
|
||||
asset collateral_returned = -o.collateral_to_add;
|
||||
//wdump( (_bitasset_data->current_feed) );
|
||||
|
||||
d.adjust_balance( o.funding_account, -o.amount_to_cover);
|
||||
d.adjust_balance( o.funding_account, -o.collateral_to_add);
|
||||
|
||||
// Deduct the debt paid from the total supply of the debt asset.
|
||||
if( o.amount_to_cover.amount != 0 )
|
||||
if( o.delta_debt.amount != 0 )
|
||||
{
|
||||
d.adjust_balance( o.funding_account, o.delta_debt );
|
||||
|
||||
// Deduct the debt paid from the total supply of the debt asset.
|
||||
d.modify(_debt_asset->dynamic_asset_data_id(d), [&](asset_dynamic_data_object& dynamic_asset) {
|
||||
dynamic_asset.current_supply -= o.amount_to_cover.amount;
|
||||
dynamic_asset.current_supply += o.delta_debt.amount;
|
||||
assert(dynamic_asset.current_supply >= 0);
|
||||
});
|
||||
}
|
||||
|
||||
if( o.delta_collateral.amount != 0 )
|
||||
{
|
||||
d.adjust_balance( o.funding_account, -o.delta_collateral );
|
||||
|
||||
// Adjust the total core in orders accodingly
|
||||
if( o.delta_collateral.asset_id == asset_id_type() )
|
||||
{
|
||||
d.modify(_paying_account->statistics(d), [&](account_statistics_object& stats) {
|
||||
stats.total_core_in_orders += o.delta_collateral.amount;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto& call_idx = d.get_index_type<call_order_index>().indices().get<by_account>();
|
||||
auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.amount_to_cover.asset_id) );
|
||||
auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) );
|
||||
const call_order_object* call_obj = nullptr;
|
||||
if( itr == call_idx.end() )
|
||||
{
|
||||
FC_ASSERT( o.collateral_to_add.amount > 0 );
|
||||
FC_ASSERT( o.amount_to_cover.amount < 0 );
|
||||
d.create<call_order_object>( [&](call_order_object& call ){
|
||||
FC_ASSERT( o.delta_collateral.amount > 0 );
|
||||
FC_ASSERT( o.delta_debt.amount > 0 );
|
||||
|
||||
call_obj = &d.create<call_order_object>( [&](call_order_object& call ){
|
||||
call.borrower = o.funding_account;
|
||||
call.collateral = o.collateral_to_add.amount;
|
||||
call.debt = -o.amount_to_cover.amount;
|
||||
call.maintenance_collateral_ratio = o.maintenance_collateral_ratio;
|
||||
// TODO: this is only necessary for non-prediction markets
|
||||
call.update_call_price();
|
||||
FC_ASSERT( call.call_price < _bitasset_data->current_feed.settlement_price );
|
||||
call.collateral = o.delta_collateral.amount;
|
||||
call.debt = o.delta_debt.amount;
|
||||
call.call_price = ~o.call_price;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if( itr->debt - o.amount_to_cover.amount == 0 )
|
||||
{
|
||||
FC_ASSERT( o.collateral_to_add.amount == 0 );
|
||||
collateral_returned = itr->get_collateral();
|
||||
d.adjust_balance( o.funding_account, collateral_returned );
|
||||
d.remove( *itr );
|
||||
}
|
||||
else
|
||||
{
|
||||
FC_ASSERT( (itr->debt - o.amount_to_cover.amount) >= 0 );
|
||||
FC_ASSERT( (itr->collateral + o.collateral_to_add.amount) >= 0 );
|
||||
call_obj = &*itr;
|
||||
|
||||
d.modify( *itr, [&]( call_order_object& call ){
|
||||
call.collateral += o.collateral_to_add.amount;
|
||||
call.debt -= o.amount_to_cover.amount;
|
||||
if( o.maintenance_collateral_ratio )
|
||||
call.maintenance_collateral_ratio = o.maintenance_collateral_ratio;
|
||||
|
||||
// TODO: this is only necessary for non-prediction markets
|
||||
call.update_call_price();
|
||||
FC_ASSERT( call.call_price < _bitasset_data->current_feed.settlement_price );
|
||||
});
|
||||
}
|
||||
}
|
||||
if( collateral_returned.asset_id == asset_id_type() && collateral_returned.amount != 0 )
|
||||
{
|
||||
d.modify(_paying_account->statistics(d), [&](account_statistics_object& stats) {
|
||||
stats.total_core_in_orders -= collateral_returned.amount;
|
||||
d.modify( *call_obj, [&]( call_order_object& call ){
|
||||
call.collateral += o.delta_collateral.amount;
|
||||
call.debt += o.delta_debt.amount;
|
||||
call.call_price = ~o.call_price;
|
||||
});
|
||||
}
|
||||
return collateral_returned;
|
||||
|
||||
auto debt = call_obj->get_debt();
|
||||
if( debt.amount == 0 )
|
||||
{
|
||||
FC_ASSERT( call_obj->collateral == 0 );
|
||||
d.remove( *call_obj );
|
||||
return void_result();
|
||||
}
|
||||
auto collateral = call_obj->get_collateral();
|
||||
|
||||
auto mp = _bitasset_data->current_feed.maintenance_price();
|
||||
|
||||
// edump((debt)(collateral)((debt/collateral).to_real())(mp.to_real()) );
|
||||
// edump((debt*mp));
|
||||
/// paying off the debt at the user specified call price should require
|
||||
/// less collateral than paying off the debt at the maitenance price
|
||||
auto col_at_call_price = debt * o.call_price;
|
||||
auto col_at_min_callprice = debt * mp;
|
||||
FC_ASSERT( col_at_call_price <= col_at_min_callprice, "", ("debt*o.callprice",debt*o.call_price)("debt*mp",debt*mp) );
|
||||
FC_ASSERT( col_at_call_price <= collateral );
|
||||
|
||||
//wdump( (o.call_price)(mp)(call_obj->call_price.to_real())(mp.to_real()) );
|
||||
//FC_ASSERT( call_obj->call_price <= mp );
|
||||
|
||||
auto call_order_id = call_obj->id;
|
||||
|
||||
//ilog( "checking call orders" );
|
||||
|
||||
// check to see if the order needs to be margin called now, but don't allow black swans and require there to be
|
||||
// limit orders available that could be used to fill the order.
|
||||
if( d.check_call_orders( *_debt_asset, false ) )
|
||||
{
|
||||
FC_ASSERT( !d.find_object( call_order_id ), "If updating the call order triggers a margin call, then it must completely cover the order" );
|
||||
}
|
||||
|
||||
return void_result();
|
||||
}
|
||||
|
||||
} } // graphene::chain
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace graphene { namespace chain {
|
|||
|
||||
bool database::is_known_block( const block_id_type& id )const
|
||||
{
|
||||
return _fork_db.is_known_block(id) || _block_id_to_block.find(id).valid();
|
||||
return _fork_db.is_known_block(id) || _block_id_to_block.contains(id);
|
||||
}
|
||||
/**
|
||||
* Only return true *if* the transaction has not expired or been invalidated. If this
|
||||
|
|
@ -45,10 +45,7 @@ bool database::is_known_transaction( const transaction_id_type& id )const
|
|||
|
||||
block_id_type database::get_block_id_for_num( uint32_t block_num )const
|
||||
{ try {
|
||||
block_id_type lb; lb._hash[0] = htonl(block_num);
|
||||
auto itr = _block_id_to_block.lower_bound( lb );
|
||||
FC_ASSERT( itr.valid() && itr.key()._hash[0] == lb._hash[0] );
|
||||
return itr.key();
|
||||
return _block_id_to_block.fetch_block_id( block_num );
|
||||
} FC_CAPTURE_AND_RETHROW( (block_num) ) }
|
||||
|
||||
optional<signed_block> database::fetch_block_by_id( const block_id_type& id )const
|
||||
|
|
@ -65,12 +62,7 @@ optional<signed_block> database::fetch_block_by_number( uint32_t num )const
|
|||
if( results.size() == 1 )
|
||||
return results[0]->data;
|
||||
else
|
||||
{
|
||||
block_id_type lb; lb._hash[0] = htonl(num);
|
||||
auto itr = _block_id_to_block.lower_bound( lb );
|
||||
if( itr.valid() && itr.key()._hash[0] == lb._hash[0] )
|
||||
return itr.value();
|
||||
}
|
||||
return _block_id_to_block.fetch_by_number(num);
|
||||
return optional<signed_block>();
|
||||
}
|
||||
|
||||
|
|
@ -92,12 +84,11 @@ bool database::push_block( const signed_block& new_block, uint32_t skip )
|
|||
{ try {
|
||||
if( !(skip&skip_fork_db) )
|
||||
{
|
||||
wdump((new_block.id())(new_block.previous));
|
||||
auto new_head = _fork_db.push_block( new_block );
|
||||
//If the head block from the longest chain does not build off of the current head, we need to switch forks.
|
||||
if( new_head->data.previous != head_block_id() )
|
||||
{
|
||||
edump((new_head->data.previous));
|
||||
//edump((new_head->data.previous));
|
||||
//If the newly pushed block is the same height as head, we get head back in new_head
|
||||
//Only switch forks if new_head is actually higher than head
|
||||
if( new_head->data.block_num() > head_block_num() )
|
||||
|
|
@ -105,11 +96,11 @@ bool database::push_block( const signed_block& new_block, uint32_t skip )
|
|||
auto branches = _fork_db.fetch_branch_from( new_head->data.id(), _pending_block.previous );
|
||||
for( auto item : branches.first )
|
||||
{
|
||||
wdump( ("new")(item->id)(item->data.previous) );
|
||||
// wdump( ("new")(item->id)(item->data.previous) );
|
||||
}
|
||||
for( auto item : branches.second )
|
||||
{
|
||||
wdump( ("old")(item->id)(item->data.previous) );
|
||||
// wdump( ("old")(item->id)(item->data.previous) );
|
||||
}
|
||||
|
||||
// pop blocks until we hit the forked block
|
||||
|
|
@ -123,14 +114,15 @@ bool database::push_block( const signed_block& new_block, uint32_t skip )
|
|||
try {
|
||||
auto session = _undo_db.start_undo_session();
|
||||
apply_block( (*ritr)->data, skip );
|
||||
_block_id_to_block.store( new_block.id(), (*ritr)->data );
|
||||
_block_id_to_block.store( (*ritr)->id, (*ritr)->data );
|
||||
session.commit();
|
||||
}
|
||||
catch ( const fc::exception& e ) { except = e; }
|
||||
if( except )
|
||||
{
|
||||
elog( "Encountered error when switching to a longer fork at id ${id}. Going back.",
|
||||
("id", (*ritr)->id) );
|
||||
//wdump((except->to_detail_string()));
|
||||
// elog( "Encountered error when switching to a longer fork at id ${id}. Going back.",
|
||||
// ("id", (*ritr)->id) );
|
||||
// remove the rest of branches.first from the fork_db, those blocks are invalid
|
||||
while( ritr != branches.first.rend() )
|
||||
{
|
||||
|
|
@ -225,8 +217,8 @@ processed_transaction database::push_proposal(const proposal_object& proposal)
|
|||
return std::make_pair(id, authority::owner);
|
||||
});
|
||||
|
||||
ilog("Attempting to push proposal ${prop}", ("prop", proposal));
|
||||
idump((eval_state.approved_by));
|
||||
//ilog("Attempting to push proposal ${prop}", ("prop", proposal));
|
||||
//idump((eval_state.approved_by));
|
||||
|
||||
eval_state.operation_results.reserve(proposal.proposed_transaction.operations.size());
|
||||
processed_transaction ptrx(proposal.proposed_transaction);
|
||||
|
|
@ -333,7 +325,7 @@ void database::apply_block( const signed_block& next_block, uint32_t skip )
|
|||
{ try {
|
||||
_applied_ops.clear();
|
||||
|
||||
FC_ASSERT( (skip & skip_merkle_check) || next_block.transaction_merkle_root == next_block.calculate_merkle_root() );
|
||||
FC_ASSERT( (skip & skip_merkle_check) || next_block.transaction_merkle_root == next_block.calculate_merkle_root(), "", ("next_block.transaction_merkle_root",next_block.transaction_merkle_root)("calc",next_block.calculate_merkle_root())("next_block",next_block)("id",next_block.id()) );
|
||||
|
||||
const witness_object& signing_witness = validate_block_header(skip, next_block);
|
||||
const auto& global_props = get_global_properties();
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ void database::init_genesis(const genesis_allocation& initial_allocation)
|
|||
n.owner.add_authority(genesis_key.get_id(), 1);
|
||||
n.owner.weight_threshold = 1;
|
||||
n.active = n.owner;
|
||||
n.memo_key = genesis_key.id;
|
||||
n.options.memo_key = genesis_key.id;
|
||||
n.statistics = genesis_statistics.id;
|
||||
});
|
||||
|
||||
|
|
@ -259,7 +259,7 @@ void database::init_genesis(const genesis_allocation& initial_allocation)
|
|||
auto mangle_to_name = [](const fc::static_variant<public_key_type, address>& key) {
|
||||
string addr = string(key.which() == std::decay<decltype(key)>::type::tag<address>::value? key.get<address>()
|
||||
: key.get<public_key_type>());
|
||||
string result = "bts";
|
||||
string result = "import";
|
||||
string key_string = string(addr).substr(sizeof(GRAPHENE_ADDRESS_PREFIX)-1);
|
||||
for( char c : key_string )
|
||||
{
|
||||
|
|
@ -292,7 +292,7 @@ void database::init_genesis(const genesis_allocation& initial_allocation)
|
|||
cop.registrar = account_id_type(1);
|
||||
cop.active = account_authority;
|
||||
cop.owner = account_authority;
|
||||
cop.memo_key = key_id;
|
||||
cop.options.memo_key = key_id;
|
||||
trx.operations.push_back(cop);
|
||||
trx.validate();
|
||||
auto ptrx = apply_transaction(trx, ~0);
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ vector<std::reference_wrapper<const ObjectType>> database::sort_votable_objects(
|
|||
|
||||
void database::pay_workers( share_type& budget )
|
||||
{
|
||||
ilog("Processing payroll! Available budget is ${b}", ("b", budget));
|
||||
// ilog("Processing payroll! Available budget is ${b}", ("b", budget));
|
||||
vector<std::reference_wrapper<const worker_object>> active_workers;
|
||||
get_index_type<worker_index>().inspect_all_objects([this, &active_workers](const object& o) {
|
||||
const worker_object& w = static_cast<const worker_object&>(o);
|
||||
|
|
@ -78,7 +78,7 @@ void database::pay_workers( share_type& budget )
|
|||
}
|
||||
|
||||
share_type actual_pay = std::min(budget, requested_pay);
|
||||
ilog(" ==> Paying ${a} to worker ${w}", ("w", active_worker.id)("a", actual_pay));
|
||||
//ilog(" ==> Paying ${a} to worker ${w}", ("w", active_worker.id)("a", actual_pay));
|
||||
modify(active_worker, [&](worker_object& w) {
|
||||
w.worker.visit(worker_pay_visitor(actual_pay, *this));
|
||||
});
|
||||
|
|
@ -307,15 +307,16 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
|||
// Usually they're the same, but if the stake account has specified a voting_account, that account is the one
|
||||
// specifying the opinions.
|
||||
const account_object& opinion_account =
|
||||
(stake_account.voting_account == account_id_type())? stake_account
|
||||
: d.get(stake_account.voting_account);
|
||||
(stake_account.options.voting_account ==
|
||||
account_id_type())? stake_account
|
||||
: d.get(stake_account.options.voting_account);
|
||||
|
||||
const auto& stats = stake_account.statistics(d);
|
||||
uint64_t voting_stake = stats.total_core_in_orders.value
|
||||
+ (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value: 0)
|
||||
+ d.get_balance(stake_account.get_id(), asset_id_type()).amount.value;
|
||||
|
||||
for( vote_id_type id : opinion_account.votes )
|
||||
for( vote_id_type id : opinion_account.options.votes )
|
||||
{
|
||||
uint32_t offset = id.instance();
|
||||
// if they somehow managed to specify an illegal offset, ignore it.
|
||||
|
|
@ -323,9 +324,9 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
|||
d._vote_tally_buffer[ offset ] += voting_stake;
|
||||
}
|
||||
|
||||
if( opinion_account.num_witness <= props.parameters.maximum_witness_count )
|
||||
if( opinion_account.options.num_witness <= props.parameters.maximum_witness_count )
|
||||
{
|
||||
uint16_t offset = std::min(size_t(opinion_account.num_witness/2),
|
||||
uint16_t offset = std::min(size_t(opinion_account.options.num_witness/2),
|
||||
d._witness_count_histogram_buffer.size() - 1);
|
||||
// votes for a number greater than maximum_witness_count
|
||||
// are turned into votes for maximum_witness_count.
|
||||
|
|
@ -335,9 +336,9 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
|||
// parameter was lowered.
|
||||
d._witness_count_histogram_buffer[ offset ] += voting_stake;
|
||||
}
|
||||
if( opinion_account.num_committee <= props.parameters.maximum_committee_count )
|
||||
if( opinion_account.options.num_committee <= props.parameters.maximum_committee_count )
|
||||
{
|
||||
uint16_t offset = std::min(size_t(opinion_account.num_committee/2),
|
||||
uint16_t offset = std::min(size_t(opinion_account.options.num_committee/2),
|
||||
d._committee_count_histogram_buffer.size() - 1);
|
||||
// votes for a number greater than maximum_committee_count
|
||||
// are turned into votes for maximum_committee_count.
|
||||
|
|
@ -357,77 +358,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
|||
process_fees_helper(database& d, const global_property_object& gpo)
|
||||
: d(d), props(gpo) {}
|
||||
|
||||
share_type cut_fee(share_type a, uint16_t p)const
|
||||
{
|
||||
if( a == 0 || p == 0 )
|
||||
return 0;
|
||||
if( p == GRAPHENE_100_PERCENT )
|
||||
return a;
|
||||
|
||||
fc::uint128 r(a.value);
|
||||
r *= p;
|
||||
r /= GRAPHENE_100_PERCENT;
|
||||
return r.to_uint64();
|
||||
}
|
||||
|
||||
void pay_out_fees(const account_object& account, share_type core_fee_total, bool require_vesting)
|
||||
{
|
||||
share_type network_cut = cut_fee(core_fee_total, account.network_fee_percentage);
|
||||
assert( network_cut <= core_fee_total );
|
||||
share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee);
|
||||
share_type accumulated = network_cut - burned;
|
||||
assert( accumulated + burned == network_cut );
|
||||
share_type lifetime_cut = cut_fee(core_fee_total, account.lifetime_referrer_fee_percentage);
|
||||
share_type referral = core_fee_total - network_cut - lifetime_cut;
|
||||
|
||||
d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) {
|
||||
d.accumulated_fees += network_cut;
|
||||
});
|
||||
|
||||
// Potential optimization: Skip some of this math and object lookups by special casing on the account type.
|
||||
// For example, if the account is a lifetime member, we can skip all this and just deposit the referral to
|
||||
// it directly.
|
||||
share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage);
|
||||
share_type registrar_cut = referral - referrer_cut;
|
||||
|
||||
d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting);
|
||||
d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting);
|
||||
d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting);
|
||||
|
||||
assert( referrer_cut + registrar_cut + accumulated + burned + lifetime_cut == core_fee_total );
|
||||
}
|
||||
|
||||
void operator()(const account_object& a) {
|
||||
const account_statistics_object& stats = a.statistics(d);
|
||||
|
||||
if( stats.pending_fees > 0 )
|
||||
{
|
||||
share_type vesting_fee_subtotal(stats.pending_fees);
|
||||
share_type vested_fee_subtotal(stats.pending_vested_fees);
|
||||
share_type vesting_cashback, vested_cashback;
|
||||
|
||||
if( stats.lifetime_fees_paid > props.parameters.bulk_discount_threshold_min &&
|
||||
a.is_member(d.head_block_time()) )
|
||||
{
|
||||
auto bulk_discount_rate = stats.calculate_bulk_discount_percent(props.parameters);
|
||||
vesting_cashback = cut_fee(vesting_fee_subtotal, bulk_discount_rate);
|
||||
vesting_fee_subtotal -= vesting_cashback;
|
||||
|
||||
vested_cashback = cut_fee(vested_fee_subtotal, bulk_discount_rate);
|
||||
vested_fee_subtotal -= vested_cashback;
|
||||
}
|
||||
|
||||
pay_out_fees(a, vesting_fee_subtotal, true);
|
||||
d.deposit_cashback(a, vesting_cashback, true);
|
||||
pay_out_fees(a, vested_fee_subtotal, false);
|
||||
d.deposit_cashback(a, vested_cashback, false);
|
||||
|
||||
d.modify(stats, [vested_fee_subtotal, vesting_fee_subtotal](account_statistics_object& s) {
|
||||
s.lifetime_fees_paid += vested_fee_subtotal + vesting_fee_subtotal;
|
||||
s.pending_fees = 0;
|
||||
s.pending_vested_fees = 0;
|
||||
});
|
||||
}
|
||||
a.statistics(d).process_fees(a, d);
|
||||
}
|
||||
} fee_helper(*this, gpo);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,20 +35,23 @@ database::~database(){
|
|||
|
||||
void database::open( const fc::path& data_dir, const genesis_allocation& initial_allocation )
|
||||
{ try {
|
||||
ilog("Open database in ${d}", ("d", data_dir));
|
||||
// ilog("Open database in ${d}", ("d", data_dir));
|
||||
object_database::open( data_dir );
|
||||
|
||||
_block_id_to_block.open( data_dir / "database" / "block_num_to_block" );
|
||||
|
||||
if( !find(global_property_id_type()) )
|
||||
{
|
||||
// ilog( "Init Genesis State" );
|
||||
init_genesis(initial_allocation);
|
||||
}
|
||||
|
||||
_pending_block.previous = head_block_id();
|
||||
_pending_block.timestamp = head_block_time();
|
||||
|
||||
auto last_block_itr = _block_id_to_block.last();
|
||||
if( last_block_itr.valid() )
|
||||
_fork_db.start_block( last_block_itr.value() );
|
||||
auto last_block= _block_id_to_block.last();
|
||||
if( last_block.valid() )
|
||||
_fork_db.start_block( *last_block );
|
||||
|
||||
} FC_CAPTURE_AND_RETHROW( (data_dir) ) }
|
||||
|
||||
|
|
@ -58,19 +61,22 @@ void database::reindex(fc::path data_dir, const genesis_allocation& initial_allo
|
|||
open(data_dir, initial_allocation);
|
||||
|
||||
auto start = fc::time_point::now();
|
||||
auto itr = _block_id_to_block.begin();
|
||||
auto last_block = _block_id_to_block.last();
|
||||
if( !last_block ) return;
|
||||
|
||||
const auto last_block_num = last_block->block_num();
|
||||
|
||||
// TODO: disable undo tracking durring reindex, this currently causes crashes in the benchmark test
|
||||
//_undo_db.disable();
|
||||
while( itr.valid() )
|
||||
for( uint32_t i = 1; i <= last_block_num; ++i )
|
||||
{
|
||||
apply_block( itr.value(), skip_delegate_signature |
|
||||
apply_block( *_block_id_to_block.fetch_by_number(i), skip_delegate_signature |
|
||||
skip_transaction_signatures |
|
||||
skip_undo_block |
|
||||
skip_undo_transaction |
|
||||
skip_transaction_dupe_check |
|
||||
skip_tapos_check |
|
||||
skip_authority_check );
|
||||
++itr;
|
||||
}
|
||||
//_undo_db.enable();
|
||||
auto end = fc::time_point::now();
|
||||
|
|
@ -93,6 +99,7 @@ void database::close(uint32_t blocks_to_rewind)
|
|||
for(uint32_t i = 0; i < blocks_to_rewind && head_block_num() > 0; ++i)
|
||||
pop_block();
|
||||
|
||||
object_database::flush();
|
||||
object_database::close();
|
||||
|
||||
if( _block_id_to_block.is_open() )
|
||||
|
|
|
|||
|
|
@ -69,9 +69,10 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett
|
|||
const limit_order_index& limit_index = get_index_type<limit_order_index>();
|
||||
const auto& limit_price_index = limit_index.indices().get<by_price>();
|
||||
|
||||
auto max_short_squeeze = bitasset.current_feed.max_short_squeeze_price();
|
||||
// cancel all orders selling the market issued asset
|
||||
auto limit_itr = limit_price_index.lower_bound(price::max(mia.id, bitasset.options.short_backing_asset));
|
||||
auto limit_end = limit_price_index.upper_bound(~bitasset.current_feed.call_limit);
|
||||
auto limit_end = limit_price_index.upper_bound(~max_short_squeeze);
|
||||
while( limit_itr != limit_end )
|
||||
{
|
||||
const auto& order = *limit_itr;
|
||||
|
|
@ -363,13 +364,22 @@ bool database::fill_order(const force_settlement_object& settle, const asset& pa
|
|||
} FC_CAPTURE_AND_RETHROW( (settle)(pays)(receives) ) }
|
||||
|
||||
/**
|
||||
* Starting with the least collateralized orders, fill them if their
|
||||
* call price is above the max(lowest bid,call_limit).
|
||||
*
|
||||
* This method will return true if it filled a short or limit
|
||||
*
|
||||
* @param mia - the market issued asset that should be called.
|
||||
* @param enable_black_swan - when adjusting collateral, triggering a black swan is invalid and will throw
|
||||
* if enable_black_swan is not set to true.
|
||||
*
|
||||
* @return true if a margin call was executed.
|
||||
*/
|
||||
bool database::check_call_orders( const asset_object& mia )
|
||||
bool database::check_call_orders( const asset_object& mia, bool enable_black_swan )
|
||||
{ try {
|
||||
if( !mia.is_market_issued() ) return false;
|
||||
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);
|
||||
if( bitasset.current_feed.call_limit.is_null() ) return false;
|
||||
if( bitasset.current_feed.settlement_price.is_null() ) return false;
|
||||
if( bitasset.is_prediction_market ) return false;
|
||||
|
||||
const call_order_index& call_index = get_index_type<call_order_index>();
|
||||
|
|
@ -378,9 +388,42 @@ bool database::check_call_orders( const asset_object& mia )
|
|||
const limit_order_index& limit_index = get_index_type<limit_order_index>();
|
||||
const auto& limit_price_index = limit_index.indices().get<by_price>();
|
||||
|
||||
auto max_price = price::max( mia.id, bitasset.options.short_backing_asset );
|
||||
auto min_price = bitasset.current_feed.max_short_squeeze_price();
|
||||
/*
|
||||
if( require_orders )
|
||||
{
|
||||
for( const auto& order : limit_price_index )
|
||||
wdump((order)(order.sell_price.to_real()));
|
||||
|
||||
auto limit_itr = limit_price_index.lower_bound( price::max( mia.id, bitasset.options.short_backing_asset ) );
|
||||
auto limit_end = limit_price_index.upper_bound( ~bitasset.current_feed.call_limit );
|
||||
for( const auto& call : call_price_index )
|
||||
idump((call)(call.call_price.to_real()));
|
||||
|
||||
// limit pirce index is sorted from highest price to lowest price.
|
||||
//auto limit_itr = limit_price_index.lower_bound( price::max( mia.id, bitasset.options.short_backing_asset ) );
|
||||
wdump((max_price)(max_price.to_real()));
|
||||
wdump((min_price)(min_price.to_real()));
|
||||
}
|
||||
*/
|
||||
|
||||
FC_ASSERT( max_price.base.asset_id == min_price.base.asset_id );
|
||||
// wlog( "from ${a} Debt/Col to ${b} Debt/Col ", ("a", max_price.to_real())("b",min_price.to_real()) );
|
||||
// NOTE limit_price_index is sorted from greatest to least
|
||||
auto limit_itr = limit_price_index.lower_bound( max_price );
|
||||
auto limit_end = limit_price_index.upper_bound( min_price );
|
||||
|
||||
/*
|
||||
if( limit_itr != limit_price_index.end() )
|
||||
wdump((*limit_itr)(limit_itr->sell_price.to_real()));
|
||||
if( limit_end != limit_price_index.end() )
|
||||
wdump((*limit_end)(limit_end->sell_price.to_real()));
|
||||
*/
|
||||
|
||||
if( limit_itr == limit_end )
|
||||
{
|
||||
//wlog( "no orders available to fill margin calls" );
|
||||
return false;
|
||||
}
|
||||
|
||||
auto call_itr = call_price_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) );
|
||||
auto call_end = call_price_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) );
|
||||
|
|
@ -411,6 +454,7 @@ bool database::check_call_orders( const asset_object& mia )
|
|||
|
||||
if( usd_to_buy * match_price > call_itr->get_collateral() )
|
||||
{
|
||||
FC_ASSERT( enable_black_swan );
|
||||
elog( "black swan, we do not have enough collateral to cover at this price" );
|
||||
globally_settle_asset( mia, call_itr->get_debt() / call_itr->get_collateral() );
|
||||
return true;
|
||||
|
|
@ -500,7 +544,7 @@ asset database::pay_market_fees( const asset_object& recv_asset, const asset& re
|
|||
{
|
||||
const auto& recv_dyn_data = recv_asset.dynamic_asset_data_id(*this);
|
||||
modify( recv_dyn_data, [&]( asset_dynamic_data_object& obj ){
|
||||
idump((issuer_fees));
|
||||
//idump((issuer_fees));
|
||||
obj.accumulated_fees += issuer_fees.amount;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,9 +177,11 @@ void database::clear_expired_orders()
|
|||
max_settlement_volume = mia_object.amount(mia.max_force_settlement_volume(mia_object.dynamic_data(*this).current_supply));
|
||||
if( mia.force_settled_volume >= max_settlement_volume.amount )
|
||||
{
|
||||
/*
|
||||
ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}",
|
||||
("asset", mia_object.symbol)("settlement_price_null",mia.current_feed.settlement_price.is_null())
|
||||
("settled_volume", mia.force_settled_volume)("max_volume", max_settlement_volume));
|
||||
*/
|
||||
if( next_asset() )
|
||||
continue;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -115,7 +115,15 @@ namespace graphene { namespace chain {
|
|||
return rel_id;
|
||||
}
|
||||
|
||||
authority generic_evaluator::resolve_relative_ids( const authority& a )const
|
||||
void generic_evaluator::check_relative_ids(const authority& a)const
|
||||
{
|
||||
for( const auto& item : a.auths )
|
||||
{
|
||||
auto id = get_relative_id( item.first );
|
||||
FC_ASSERT( id.type() == key_object_type || id.type() == account_object_type );
|
||||
}
|
||||
}
|
||||
authority generic_evaluator::resolve_relative_ids(const authority& a)const
|
||||
{
|
||||
authority result;
|
||||
result.auths.reserve( a.auths.size() );
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <boost/multi_index/composite_key.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
class database;
|
||||
|
||||
/**
|
||||
* @class account_statistics_object
|
||||
|
|
@ -64,8 +65,8 @@ namespace graphene { namespace chain {
|
|||
* them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid
|
||||
* doing massive amounts of uint128 arithmetic on each and every operation.
|
||||
*
|
||||
*These fees will be paid out as vesting cash-back, and this counter will reset during the maintenance
|
||||
*interval.
|
||||
* These fees will be paid out as vesting cash-back, and this counter will reset during the maintenance
|
||||
* interval.
|
||||
*/
|
||||
share_type pending_fees;
|
||||
/**
|
||||
|
|
@ -76,6 +77,8 @@ namespace graphene { namespace chain {
|
|||
|
||||
/// @brief Calculate the percentage discount this user receives on his fees
|
||||
uint16_t calculate_bulk_discount_percent(const chain_parameters& params)const;
|
||||
/// @brief Split up and pay out @ref pending_fees and @ref pending_vested_fees
|
||||
void process_fees(const account_object& a, database& d) const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -141,6 +144,7 @@ namespace graphene { namespace chain {
|
|||
|
||||
/// The account's name. This name must be unique among all account names on the graph. May not be empty.
|
||||
string name;
|
||||
|
||||
/**
|
||||
* The owner authority represents absolute control over the account. Usually the keys in this authority will
|
||||
* be kept in cold storage, as they should not be needed very often and compromise of these keys constitutes
|
||||
|
|
@ -151,19 +155,30 @@ namespace graphene { namespace chain {
|
|||
/// The owner authority contains the hot keys of the account. This authority has control over nearly all
|
||||
/// operations the account may perform.
|
||||
authority active;
|
||||
/// The memo key is the key this account will typically use to encrypt/sign transaction memos and other non-
|
||||
/// validated account activities. This field is here to prevent confusion if the active authority has zero or
|
||||
/// multiple keys in it.
|
||||
key_id_type memo_key;
|
||||
/// If this field is set to an account ID other than 0, this account's votes will be ignored and its stake
|
||||
/// will be counted as voting for the referenced account's selected votes instead.
|
||||
account_id_type voting_account;
|
||||
|
||||
uint16_t num_witness = 0;
|
||||
uint16_t num_committee = 0;
|
||||
/// This is the list of vote IDs this account votes for. The weight of these votes is determined by this
|
||||
/// account's balance of core asset.
|
||||
flat_set<vote_id_type> votes;
|
||||
/// These are the fields which can be updated by the active authority.
|
||||
struct options_type {
|
||||
/// The memo key is the key this account will typically use to encrypt/sign transaction memos and other non-
|
||||
/// validated account activities. This field is here to prevent confusion if the active authority has zero or
|
||||
/// multiple keys in it.
|
||||
object_id_type memo_key = key_id_type();
|
||||
key_id_type get_memo_key()const { return memo_key; }
|
||||
/// If this field is set to an account ID other than 0, this account's votes will be ignored and its stake
|
||||
/// will be counted as voting for the referenced account's selected votes instead.
|
||||
account_id_type voting_account;
|
||||
|
||||
/// The number of active witnesses this account votes the blockchain should appoint
|
||||
/// Must not exceed the actual number of witnesses voted for in @ref votes
|
||||
uint16_t num_witness = 0;
|
||||
/// The number of active committee members this account votes the blockchain should appoint
|
||||
/// Must not exceed the actual number of committee members voted for in @ref votes
|
||||
uint16_t num_committee = 0;
|
||||
/// This is the list of vote IDs this account votes for. The weight of these votes is determined by this
|
||||
/// account's balance of core asset.
|
||||
flat_set<vote_id_type> votes;
|
||||
|
||||
void validate()const;
|
||||
} options;
|
||||
|
||||
/// The reference implementation records the account's statistics in a separate object. This field contains the
|
||||
/// ID of that object.
|
||||
|
|
@ -189,6 +204,12 @@ namespace graphene { namespace chain {
|
|||
* Vesting balance which receives cashback_reward deposits.
|
||||
*/
|
||||
optional<vesting_balance_id_type> cashback_vb;
|
||||
template<typename DB>
|
||||
const vesting_balance_object& cashback_balance(const DB& db)const
|
||||
{
|
||||
FC_ASSERT(cashback_vb);
|
||||
return db.get(*cashback_vb);
|
||||
}
|
||||
|
||||
/// @return true if this is a lifetime member account; false otherwise.
|
||||
bool is_lifetime_member()const
|
||||
|
|
@ -284,8 +305,10 @@ FC_REFLECT_DERIVED( graphene::chain::account_object,
|
|||
(graphene::db::annotated_object<graphene::chain::account_object>),
|
||||
(membership_expiration_date)(registrar)(referrer)(lifetime_referrer)
|
||||
(network_fee_percentage)(lifetime_referrer_fee_percentage)(referrer_rewards_percentage)
|
||||
(name)(owner)(active)(memo_key)(voting_account)(num_witness)(num_committee)(votes)
|
||||
(statistics)(whitelisting_accounts)(blacklisting_accounts)(cashback_vb) )
|
||||
(name)(owner)(active)(options)(statistics)(whitelisting_accounts)(blacklisting_accounts)
|
||||
(cashback_vb) )
|
||||
|
||||
FC_REFLECT(graphene::chain::account_object::options_type, (memo_key)(voting_account)(num_witness)(num_committee)(votes))
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::account_balance_object,
|
||||
(graphene::db::object),
|
||||
|
|
|
|||
|
|
@ -125,21 +125,6 @@ namespace graphene { namespace chain {
|
|||
*/
|
||||
struct price_feed
|
||||
{
|
||||
/**
|
||||
* This is the lowest price at which margin positions will be forced to sell their collateral. This does not
|
||||
* directly affect the price at which margin positions will be called; it is only a safety to prevent calls at
|
||||
* unreasonable prices.
|
||||
*/
|
||||
price call_limit;
|
||||
/**
|
||||
* Forced settlements will evaluate using this price.
|
||||
*/
|
||||
price settlement_price;
|
||||
/**
|
||||
* Maximum number of seconds margin positions should be able to remain open.
|
||||
*/
|
||||
uint32_t max_margin_period_sec = GRAPHENE_DEFAULT_MARGIN_PERIOD_SEC;
|
||||
|
||||
/**
|
||||
* Required maintenance collateral is defined
|
||||
* as a fixed point number with a maximum value of 10.000
|
||||
|
|
@ -151,19 +136,49 @@ namespace graphene { namespace chain {
|
|||
* equals value_of_collateral using rate.
|
||||
*
|
||||
* Default requirement is $1.75 of collateral per $1 of debt
|
||||
*
|
||||
* BlackSwan ---> SQR ---> MCR ----> SP
|
||||
*/
|
||||
uint16_t required_maintenance_collateral = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO;
|
||||
///@{
|
||||
/**
|
||||
* Forced settlements will evaluate using this price, defined as BITASSET / COLLATERAL
|
||||
*/
|
||||
price settlement_price;
|
||||
|
||||
friend bool operator < ( const price_feed& a, const price_feed& b )
|
||||
/** Fixed point between 1.000 and 10.000 */
|
||||
uint16_t maintenance_collateral_ratio = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO;
|
||||
|
||||
/** Fixed point between 1.000 and 10.000 */
|
||||
uint16_t maximum_short_squeeze_ratio = GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO;
|
||||
|
||||
/**
|
||||
* When updating a call order the following condition must be maintained:
|
||||
*
|
||||
* debt * maintenance_price() < collateral
|
||||
* debt * settlement_price < debt * maintenance
|
||||
* debt * maintenance_price() < debt * max_short_squeeze_price()
|
||||
*/
|
||||
price maintenance_price()const
|
||||
{
|
||||
return std::tie( a.call_limit.base.asset_id, a.call_limit.quote.asset_id ) <
|
||||
std::tie( b.call_limit.base.asset_id, b.call_limit.quote.asset_id );
|
||||
return ~price::call_price( settlement_price.base, settlement_price.quote, maintenance_collateral_ratio );
|
||||
}
|
||||
|
||||
/** When selling collateral to pay off debt, the least amount of debt to receive should be
|
||||
* min_usd = max_short_squeeze_price() * collateral
|
||||
*
|
||||
* This is provided to ensure that a black swan cannot be trigged due to poor liquidity alone, it
|
||||
* must be confirmed by having the max_short_squeeze_price() move below the black swan price.
|
||||
*/
|
||||
price max_short_squeeze_price()const
|
||||
{
|
||||
return ~price::call_price( settlement_price.base, settlement_price.quote, maximum_short_squeeze_ratio );
|
||||
}
|
||||
///@}
|
||||
|
||||
friend bool operator == ( const price_feed& a, const price_feed& b )
|
||||
{
|
||||
return std::tie( a.call_limit.base.asset_id, a.call_limit.quote.asset_id ) ==
|
||||
std::tie( b.call_limit.base.asset_id, b.call_limit.quote.asset_id );
|
||||
return std::tie( a.settlement_price, a.maintenance_collateral_ratio, a.maximum_short_squeeze_ratio ) ==
|
||||
std::tie( b.settlement_price, b.maintenance_collateral_ratio, b.maximum_short_squeeze_ratio );
|
||||
}
|
||||
|
||||
void validate() const;
|
||||
|
|
@ -174,6 +189,7 @@ namespace graphene { namespace chain {
|
|||
FC_REFLECT( graphene::chain::asset, (amount)(asset_id) )
|
||||
FC_REFLECT( graphene::chain::price, (base)(quote) )
|
||||
|
||||
#define GRAPHENE_PRICE_FEED_FIELDS (call_limit)(settlement_price)(max_margin_period_sec)(required_maintenance_collateral)
|
||||
#define GRAPHENE_PRICE_FEED_FIELDS (settlement_price)(maintenance_collateral_ratio)(maximum_short_squeeze_ratio)
|
||||
|
||||
FC_REFLECT( graphene::chain::price_feed, GRAPHENE_PRICE_FEED_FIELDS )
|
||||
|
||||
|
|
|
|||
26
libraries/chain/include/graphene/chain/block_database.hpp
Normal file
26
libraries/chain/include/graphene/chain/block_database.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
#include <fstream>
|
||||
#include <graphene/chain/block.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
class block_database
|
||||
{
|
||||
public:
|
||||
void open( const fc::path& dbdir );
|
||||
bool is_open()const;
|
||||
void flush();
|
||||
void close();
|
||||
|
||||
void store( const block_id_type& id, const signed_block& b );
|
||||
void remove( const block_id_type& id );
|
||||
|
||||
bool contains( const block_id_type& id )const;
|
||||
block_id_type fetch_block_id( uint32_t block_num )const;
|
||||
optional<signed_block> fetch_optional( const block_id_type& id )const;
|
||||
optional<signed_block> fetch_by_number( uint32_t block_num )const;
|
||||
optional<signed_block> last()const;
|
||||
private:
|
||||
mutable std::fstream _blocks;
|
||||
mutable std::fstream _block_num_to_pos;
|
||||
};
|
||||
} }
|
||||
|
|
@ -27,8 +27,8 @@ namespace graphene { namespace chain {
|
|||
public:
|
||||
typedef call_order_update_operation operation_type;
|
||||
|
||||
asset do_evaluate( const call_order_update_operation& o );
|
||||
asset do_apply( const call_order_update_operation& o );
|
||||
void_result do_evaluate( const call_order_update_operation& o );
|
||||
void_result do_apply( const call_order_update_operation& o );
|
||||
|
||||
bool _closing_order = false;
|
||||
const asset_object* _debt_asset = nullptr;
|
||||
|
|
|
|||
|
|
@ -45,13 +45,10 @@ namespace graphene { namespace chain {
|
|||
asset_id_type debt_type()const { return call_price.quote.asset_id; }
|
||||
price collateralization()const { return get_collateral() / get_debt(); }
|
||||
|
||||
void update_call_price() { call_price = price::call_price(get_debt(), get_collateral(), maintenance_collateral_ratio); }
|
||||
|
||||
account_id_type borrower;
|
||||
share_type collateral; ///< call_price.base.asset_id, access via get_collateral
|
||||
share_type debt; ///< call_price.quote.asset_id, access via get_collateral
|
||||
price call_price;
|
||||
uint16_t maintenance_collateral_ratio;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -128,6 +125,6 @@ namespace graphene { namespace chain {
|
|||
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::call_order_object, (graphene::db::object),
|
||||
(borrower)(collateral)(debt)(call_price)(maintenance_collateral_ratio) )
|
||||
(borrower)(collateral)(debt)(call_price) )
|
||||
|
||||
FC_REFLECT( graphene::chain::force_settlement_object, (owner)(balance)(settlement_date) )
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@
|
|||
#define GRAPHENE_MAX_COLLATERAL_RATIO 32000 // higher than this is unnecessary and may exceed int16 storage
|
||||
#define GRAPHENE_DEFAULT_INITIAL_COLLATERAL_RATIO 2000
|
||||
#define GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO 1750
|
||||
#define GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO 1500
|
||||
#define GRAPHENE_DEFAULT_MARGIN_PERIOD_SEC (30*60*60*24)
|
||||
|
||||
#define GRAPHENE_DEFAULT_NUM_WITNESSES (101)
|
||||
|
|
@ -112,7 +113,6 @@
|
|||
#define GRAPHENE_DEFAULT_WORKER_BUDGET_PER_DAY (GRAPHENE_BLOCKCHAIN_PRECISION * int64_t(500) * 1000 )
|
||||
|
||||
#define GRAPHENE_MAX_INTEREST_APR uint16_t( 10000 )
|
||||
#define GRAPHENE_LEGACY_NAME_IMPORT_PERIOD 3000000 /** 3 million blocks */
|
||||
|
||||
/**
|
||||
* Reserved Account IDs with special meaning
|
||||
|
|
|
|||
|
|
@ -23,11 +23,10 @@
|
|||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/fork_database.hpp>
|
||||
#include <graphene/chain/block_database.hpp>
|
||||
|
||||
#include <graphene/db/object_database.hpp>
|
||||
#include <graphene/db/object.hpp>
|
||||
#include <graphene/db/level_map.hpp>
|
||||
#include <graphene/db/level_pod_map.hpp>
|
||||
#include <graphene/db/simple_index.hpp>
|
||||
#include <fc/signals.hpp>
|
||||
|
||||
|
|
@ -295,7 +294,7 @@ namespace graphene { namespace chain {
|
|||
bool fill_order( const call_order_object& order, const asset& pays, const asset& receives );
|
||||
bool fill_order( const force_settlement_object& settle, const asset& pays, const asset& receives );
|
||||
|
||||
bool check_call_orders( const asset_object& mia );
|
||||
bool check_call_orders( const asset_object& mia, bool enable_black_swan = true );
|
||||
|
||||
// helpers to fill_order
|
||||
void pay_order( const account_object& receiver, const asset& receives, const asset& pays );
|
||||
|
|
@ -374,7 +373,7 @@ namespace graphene { namespace chain {
|
|||
* until the fork is resolved. This should make maintaining
|
||||
* the fork tree relatively simple.
|
||||
*/
|
||||
graphene::db::level_map<block_id_type, signed_block> _block_id_to_block;
|
||||
block_database _block_id_to_block;
|
||||
|
||||
/**
|
||||
* Contains the set of ops that are in the process of being applied from
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ namespace graphene { namespace chain {
|
|||
|
||||
object_id_type get_relative_id( object_id_type rel_id )const;
|
||||
|
||||
void check_relative_ids(const authority& a)const;
|
||||
authority resolve_relative_ids( const authority& a )const;
|
||||
|
||||
asset fee_from_account;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include <graphene/chain/authority.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/worker_object.hpp>
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
|
||||
#include <fc/static_variant.hpp>
|
||||
#include <fc/uint128.hpp>
|
||||
|
|
@ -132,17 +133,13 @@ namespace graphene { namespace chain {
|
|||
account_id_type referrer;
|
||||
/// Of the fee split between registrar and referrer, this percentage goes to the referrer. The rest goes to the
|
||||
/// registrar.
|
||||
uint8_t referrer_percent = 0;
|
||||
uint16_t referrer_percent = 0;
|
||||
|
||||
string name;
|
||||
authority owner;
|
||||
authority active;
|
||||
account_id_type voting_account;
|
||||
object_id_type memo_key = key_id_type();
|
||||
|
||||
uint16_t num_witness = 0;
|
||||
uint16_t num_committee = 0;
|
||||
flat_set<vote_id_type> vote;
|
||||
account_object::options_type options;
|
||||
|
||||
account_id_type fee_payer()const { return registrar; }
|
||||
void get_required_auth(flat_set<account_id_type>& active_auth_set , flat_set<account_id_type>&)const;
|
||||
|
|
@ -203,15 +200,17 @@ namespace graphene { namespace chain {
|
|||
*/
|
||||
struct account_update_operation
|
||||
{
|
||||
asset fee;
|
||||
account_id_type account;
|
||||
optional<authority> owner;
|
||||
optional<authority> active;
|
||||
optional<account_id_type> voting_account;
|
||||
optional<object_id_type> memo_key;
|
||||
optional<flat_set<vote_id_type>> vote;
|
||||
uint16_t num_witness = 0;
|
||||
uint16_t num_committee = 0;
|
||||
asset fee;
|
||||
/// The account to update
|
||||
account_id_type account;
|
||||
|
||||
/// New owner authority. If set, this operation requires owner authority to execute.
|
||||
optional<authority> owner;
|
||||
/// New active authority. If set, this operation requires owner authority to execute.
|
||||
optional<authority> active;
|
||||
|
||||
/// New account options
|
||||
optional<account_object::options_type> new_options;
|
||||
|
||||
account_id_type fee_payer()const { return account; }
|
||||
void get_required_auth(flat_set<account_id_type>& active_auth_set , flat_set<account_id_type>& owner_auth_set)const;
|
||||
|
|
@ -810,10 +809,10 @@ namespace graphene { namespace chain {
|
|||
*/
|
||||
struct limit_order_cancel_operation
|
||||
{
|
||||
asset fee;
|
||||
limit_order_id_type order;
|
||||
/** must be order->seller */
|
||||
account_id_type fee_paying_account;
|
||||
asset fee;
|
||||
|
||||
account_id_type fee_payer()const { return fee_paying_account; }
|
||||
void get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const;
|
||||
|
|
@ -831,26 +830,22 @@ namespace graphene { namespace chain {
|
|||
/**
|
||||
* @ingroup operations
|
||||
*
|
||||
* This operation can be used to add collateral, cover, and adjust the margin call price with a new maintenance
|
||||
* collateral ratio.
|
||||
* This operation can be used to add collateral, cover, and adjust the margin call price for a particular user.
|
||||
*
|
||||
* The only way to "cancel" a call order is to pay off the balance due. The order is invalid if the payoff amount
|
||||
* is greater than the amount due.
|
||||
* For prediction markets the collateral and debt must always be equal.
|
||||
*
|
||||
* @note the call_order_id is implied by the funding_account and assets involved. This implies that the assets must
|
||||
* have appropriate asset_ids, even if the amount is zero.
|
||||
* This operation will fail if it would trigger a margin call that couldn't be filled. If the margin call hits
|
||||
* the call price limit then it will fail if the call price is above the settlement price.
|
||||
*
|
||||
* @note this operation can be used to force a market order using the collateral without requiring outside funds.
|
||||
*
|
||||
* @note this operation can be used to issue new bitassets provided there is sufficient collateral
|
||||
*/
|
||||
struct call_order_update_operation
|
||||
{
|
||||
account_id_type funding_account; ///< pays fee, collateral, and cover
|
||||
asset fee; ///< paid by funding_account
|
||||
asset collateral_to_add; ///< the amount of collateral to add to the margin position
|
||||
asset amount_to_cover; ///< the amount of the debt to be paid off, may be negative to issue new debt
|
||||
uint16_t maintenance_collateral_ratio = 0; ///< 0 means don't change, 1000 means feed
|
||||
account_id_type funding_account; ///< pays fee, collateral, and cover
|
||||
asset delta_collateral; ///< the amount of collateral to add to the margin position
|
||||
asset delta_debt; ///< the amount of the debt to be paid off, may be negative to issue new debt
|
||||
price call_price; ///< the price at which the collateral will be sold to cover the debt
|
||||
|
||||
account_id_type fee_payer()const { return funding_account; }
|
||||
void get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const;
|
||||
|
|
@ -859,8 +854,8 @@ namespace graphene { namespace chain {
|
|||
void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const
|
||||
{
|
||||
acc.adjust( fee_payer(), -fee );
|
||||
acc.adjust( funding_account, -collateral_to_add );
|
||||
acc.adjust( funding_account, -amount_to_cover );
|
||||
acc.adjust( funding_account, -delta_collateral );
|
||||
acc.adjust( funding_account, delta_debt );
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1483,13 +1478,11 @@ FC_REFLECT( graphene::chain::key_create_operation,
|
|||
FC_REFLECT( graphene::chain::account_create_operation,
|
||||
(fee)(registrar)
|
||||
(referrer)(referrer_percent)
|
||||
(name)
|
||||
(owner)(active)(voting_account)(memo_key)
|
||||
(num_witness)(num_committee)(vote)
|
||||
(name)(owner)(active)(options)
|
||||
)
|
||||
|
||||
FC_REFLECT( graphene::chain::account_update_operation,
|
||||
(fee)(account)(owner)(active)(voting_account)(memo_key)(num_witness)(num_committee)(vote)
|
||||
(fee)(account)(owner)(active)(new_options)
|
||||
)
|
||||
FC_REFLECT( graphene::chain::account_upgrade_operation, (fee)(account_to_upgrade)(upgrade_to_lifetime_member) )
|
||||
|
||||
|
|
@ -1511,7 +1504,7 @@ FC_REFLECT( graphene::chain::limit_order_create_operation,
|
|||
)
|
||||
FC_REFLECT( graphene::chain::fill_order_operation, (fee)(order_id)(account_id)(pays)(receives) )
|
||||
FC_REFLECT( graphene::chain::limit_order_cancel_operation,(fee)(fee_paying_account)(order) )
|
||||
FC_REFLECT( graphene::chain::call_order_update_operation, (fee)(funding_account)(collateral_to_add)(amount_to_cover)(maintenance_collateral_ratio) )
|
||||
FC_REFLECT( graphene::chain::call_order_update_operation, (fee)(funding_account)(delta_collateral)(delta_debt)(call_price) )
|
||||
|
||||
FC_REFLECT( graphene::chain::transfer_operation,
|
||||
(fee)(from)(to)(amount)(memo) )
|
||||
|
|
|
|||
|
|
@ -98,8 +98,7 @@ namespace graphene { namespace chain {
|
|||
> vesting_policy;
|
||||
|
||||
/**
|
||||
* Timelocked balance object is a balance that is locked by the
|
||||
* blockchain for a period of time.
|
||||
* Vesting balance object is a balance that is locked by the blockchain for a period of time.
|
||||
*/
|
||||
class vesting_balance_object : public abstract_object<vesting_balance_object>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -149,9 +149,11 @@ void account_update_operation::validate()const
|
|||
{
|
||||
FC_ASSERT( fee.amount >= 0 );
|
||||
FC_ASSERT( account != account_id_type() );
|
||||
FC_ASSERT( owner || active || voting_account || memo_key || vote );
|
||||
}
|
||||
FC_ASSERT( owner || active || new_options );
|
||||
|
||||
if( new_options )
|
||||
new_options->validate();
|
||||
}
|
||||
|
||||
share_type asset_create_operation::calculate_fee( const fee_schedule_type& schedule )const
|
||||
{
|
||||
|
|
@ -201,8 +203,7 @@ void account_create_operation::validate()const
|
|||
{
|
||||
FC_ASSERT( fee.amount >= 0 );
|
||||
FC_ASSERT( is_valid_name( name ) );
|
||||
FC_ASSERT( referrer_percent >= 0 );
|
||||
FC_ASSERT( referrer_percent <= 100 );
|
||||
FC_ASSERT( referrer_percent <= GRAPHENE_100_PERCENT );
|
||||
FC_ASSERT( !owner.auths.empty() );
|
||||
auto pos = name.find( '/' );
|
||||
if( pos != string::npos )
|
||||
|
|
@ -210,10 +211,7 @@ void account_create_operation::validate()const
|
|||
FC_ASSERT( owner.weight_threshold == 1 );
|
||||
FC_ASSERT( owner.auths.size() == 1 );
|
||||
}
|
||||
FC_ASSERT( num_witness + num_committee >= num_witness ); // no overflow
|
||||
FC_ASSERT( num_witness + num_committee <= vote.size() );
|
||||
// FC_ASSERT( (num_witness == 0) || (num_witness&0x01) == 0, "must be odd number" );
|
||||
// FC_ASSERT( (num_committee == 0) || (num_committee&0x01) == 0, "must be odd number" );
|
||||
options.validate();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -400,11 +398,13 @@ void call_order_update_operation::get_required_auth(flat_set<account_id_type>& a
|
|||
}
|
||||
|
||||
void call_order_update_operation::validate()const
|
||||
{
|
||||
{ try {
|
||||
FC_ASSERT( fee.amount >= 0 );
|
||||
FC_ASSERT( amount_to_cover.asset_id != collateral_to_add.asset_id );
|
||||
FC_ASSERT( maintenance_collateral_ratio == 0 || maintenance_collateral_ratio >= 1000 );
|
||||
}
|
||||
FC_ASSERT( delta_collateral.asset_id != delta_debt.asset_id );
|
||||
FC_ASSERT( delta_debt.asset_id == call_price.base.asset_id );
|
||||
FC_ASSERT( delta_collateral.asset_id == call_price.quote.asset_id );
|
||||
call_price.validate();
|
||||
} FC_CAPTURE_AND_RETHROW((*this)) }
|
||||
|
||||
share_type call_order_update_operation::calculate_fee(const fee_schedule_type& k) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ namespace graphene { namespace chain {
|
|||
{
|
||||
if( depth == GRAPHENE_MAX_SIG_CHECK_DEPTH )
|
||||
{
|
||||
elog("Failing authority verification due to recursion depth.");
|
||||
//elog("Failing authority verification due to recursion depth.");
|
||||
return false;
|
||||
}
|
||||
if( check_authority( *dynamic_cast<const account_object*>( &auth_item ), auth_class, depth + 1 ) )
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
file(GLOB HEADERS "include/graphene/db/*.hpp")
|
||||
add_library( graphene_db undo_database.cpp index.cpp object_database.cpp upgrade_leveldb.cpp ${HEADERS} )
|
||||
target_link_libraries( graphene_db fc leveldb )
|
||||
add_library( graphene_db undo_database.cpp index.cpp object_database.cpp ${HEADERS} )
|
||||
target_link_libraries( graphene_db fc )
|
||||
target_include_directories( graphene_db PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
|
||||
|
|
|
|||
|
|
@ -17,10 +17,13 @@
|
|||
*/
|
||||
#pragma once
|
||||
#include <graphene/db/object.hpp>
|
||||
#include <graphene/db/level_map.hpp>
|
||||
#include <fc/interprocess/file_mapping.hpp>
|
||||
#include <fc/io/raw.hpp>
|
||||
#include <fstream>
|
||||
|
||||
namespace graphene { namespace db {
|
||||
class object_database;
|
||||
using fc::path;
|
||||
|
||||
/**
|
||||
* @class index_observer
|
||||
|
|
@ -80,9 +83,12 @@ namespace graphene { namespace db {
|
|||
virtual const object& create( const std::function<void(object&)>& constructor ) = 0;
|
||||
|
||||
/**
|
||||
* Opens the index loading objects from a level_db database
|
||||
* Opens the index loading objects from a file
|
||||
*/
|
||||
virtual void open( const shared_ptr<graphene::db::level_map<object_id_type, vector<char> >>& db ){}
|
||||
virtual void open( const fc::path& db ) = 0;
|
||||
virtual void save( const fc::path& db ) = 0;
|
||||
|
||||
|
||||
|
||||
/** @return the object with id or nullptr if not found */
|
||||
virtual const object* find( object_id_type id )const = 0;
|
||||
|
|
@ -170,22 +176,42 @@ namespace graphene { namespace db {
|
|||
virtual void use_next_id()override { ++_next_id.number; }
|
||||
virtual void set_next_id( object_id_type id )override { _next_id = id; }
|
||||
|
||||
virtual void open( const path& db )override
|
||||
{
|
||||
if( !fc::exists( db ) ) return;
|
||||
fc::file_mapping fm( db.generic_string().c_str(), fc::read_only );
|
||||
fc::mapped_region mr( fm, fc::read_only, 0, fc::file_size(db) );
|
||||
fc::datastream<const char*> ds( (const char*)mr.get_address(), mr.get_size() );
|
||||
fc::raw::unpack(ds, _next_id);
|
||||
try {
|
||||
vector<char> tmp;
|
||||
while( true )
|
||||
{
|
||||
fc::raw::unpack( ds, tmp );
|
||||
load( tmp );
|
||||
}
|
||||
} catch ( const fc::exception& ){}
|
||||
}
|
||||
|
||||
virtual void save( const path& db ) override
|
||||
{
|
||||
std::ofstream out( db.generic_string(),
|
||||
std::ofstream::binary | std::ofstream::out | std::ofstream::trunc );
|
||||
FC_ASSERT( out );
|
||||
out.write( (char*)&_next_id, sizeof(_next_id) );
|
||||
this->inspect_all_objects( [&]( const object& o ) {
|
||||
auto vec = fc::raw::pack( static_cast<const object_type&>(o) );
|
||||
auto packed_vec = fc::raw::pack( vec );
|
||||
out.write( packed_vec.data(), packed_vec.size() );
|
||||
});
|
||||
}
|
||||
|
||||
virtual const object& load( const std::vector<char>& data )override
|
||||
{
|
||||
return DerivedIndex::insert( fc::raw::unpack<object_type>( data ) );
|
||||
}
|
||||
|
||||
virtual void open( const shared_ptr<graphene::db::level_map<object_id_type, vector<char> >>& db )override
|
||||
{
|
||||
auto first = object_id_type( DerivedIndex::object_type::space_id, DerivedIndex::object_type::type_id, 0 );
|
||||
auto last = object_id_type( DerivedIndex::object_type::space_id, DerivedIndex::object_type::type_id+1, 0 );
|
||||
auto itr = db->lower_bound( first );
|
||||
while( itr.valid() && itr.key() < last )
|
||||
{
|
||||
load( itr.value() );
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
virtual const object& create(const std::function<void(object&)>& constructor )override
|
||||
{
|
||||
const auto& result = DerivedIndex::create( constructor );
|
||||
|
|
|
|||
|
|
@ -1,463 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Cryptonomex, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
|
||||
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted until September 8, 2015, provided that the following conditions are met:
|
||||
*
|
||||
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <leveldb/cache.h>
|
||||
#include <leveldb/comparator.h>
|
||||
#include <leveldb/db.h>
|
||||
#include <leveldb/write_batch.h>
|
||||
|
||||
#include <graphene/db/exception.hpp>
|
||||
#include <graphene/db/upgrade_leveldb.hpp>
|
||||
|
||||
#include <fc/filesystem.hpp>
|
||||
#include <fc/io/json.hpp>
|
||||
#include <fc/io/raw.hpp>
|
||||
#include <fc/reflect/reflect.hpp>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace graphene { namespace db {
|
||||
|
||||
namespace ldb = leveldb;
|
||||
|
||||
/**
|
||||
* @brief implements a high-level API on top of Level DB that stores items using fc::raw / reflection
|
||||
*/
|
||||
template<typename Key, typename Value>
|
||||
class level_map
|
||||
{
|
||||
public:
|
||||
void open( const fc::path& dir, bool create = true, size_t cache_size = 0 )
|
||||
{ try {
|
||||
idump( (dir)(create));
|
||||
FC_ASSERT( !is_open(), "Database is already open!" );
|
||||
|
||||
ldb::Options opts;
|
||||
opts.comparator = &_comparer;
|
||||
opts.create_if_missing = create;
|
||||
opts.max_open_files = 64;
|
||||
opts.compression = leveldb::kNoCompression;
|
||||
|
||||
if( cache_size > 0 )
|
||||
{
|
||||
opts.write_buffer_size = cache_size / 4; // up to two write buffers may be held in memory simultaneously
|
||||
_cache.reset( leveldb::NewLRUCache( cache_size / 2 ) );
|
||||
opts.block_cache = _cache.get();
|
||||
}
|
||||
|
||||
if( ldb::kMajorVersion > 1 || ( leveldb::kMajorVersion == 1 && leveldb::kMinorVersion >= 16 ) )
|
||||
{
|
||||
// LevelDB versions before 1.16 consider short writes to be corruption. Only trigger error
|
||||
// on corruption in later versions.
|
||||
opts.paranoid_checks = true;
|
||||
}
|
||||
|
||||
_read_options.verify_checksums = true;
|
||||
_iter_options.verify_checksums = true;
|
||||
_iter_options.fill_cache = false;
|
||||
_sync_options.sync = true;
|
||||
|
||||
// Given path must exist to succeed toNativeAnsiPath
|
||||
fc::create_directories( dir );
|
||||
std::string ldbPath = dir.to_native_ansi_path();
|
||||
|
||||
ldb::DB* ndb = nullptr;
|
||||
const auto ntrxstat = ldb::DB::Open( opts, ldbPath.c_str(), &ndb );
|
||||
if( !ntrxstat.ok() )
|
||||
{
|
||||
elog( "Failure opening database: ${db}\nStatus: ${msg}", ("db",dir)("msg",ntrxstat.ToString()) );
|
||||
FC_THROW_EXCEPTION( level_map_open_failure, "Failure opening database: ${db}\nStatus: ${msg}",
|
||||
("db",dir)("msg",ntrxstat.ToString()) );
|
||||
}
|
||||
_db.reset( ndb );
|
||||
|
||||
try_upgrade_db( dir, ndb, fc::get_typename<Value>::name(), sizeof( Value ) );
|
||||
} FC_CAPTURE_AND_RETHROW( (dir)(create)(cache_size) ) }
|
||||
|
||||
bool is_open()const
|
||||
{
|
||||
return !!_db;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
_db.reset();
|
||||
_cache.reset();
|
||||
}
|
||||
|
||||
fc::optional<Value> fetch_optional( const Key& k )const
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
auto itr = find( k );
|
||||
if( itr.valid() ) return itr.value();
|
||||
return fc::optional<Value>();
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "" ) }
|
||||
|
||||
Value fetch( const Key& k )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
std::vector<char> kslice = fc::raw::pack( k );
|
||||
ldb::Slice ks( kslice.data(), kslice.size() );
|
||||
std::string value;
|
||||
auto status = _db->Get( _read_options, ks, &value );
|
||||
if( status.IsNotFound() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( fc::key_not_found_exception, "unable to find key ${key}", ("key",k) );
|
||||
}
|
||||
if( !status.ok() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( level_map_failure, "database error: ${msg}", ("msg", status.ToString() ) );
|
||||
}
|
||||
fc::datastream<const char*> ds(value.c_str(), value.size());
|
||||
Value tmp;
|
||||
fc::raw::unpack(ds, tmp);
|
||||
return tmp;
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "failure fetching key ${key}", ("key",k) ); }
|
||||
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
iterator(){}
|
||||
bool valid()const
|
||||
{
|
||||
return _it && _it->Valid();
|
||||
}
|
||||
|
||||
Key key()const
|
||||
{
|
||||
Key tmp_key;
|
||||
fc::datastream<const char*> ds2( _it->key().data(), _it->key().size() );
|
||||
fc::raw::unpack( ds2, tmp_key );
|
||||
return tmp_key;
|
||||
}
|
||||
|
||||
Value value()const
|
||||
{
|
||||
Value tmp_val;
|
||||
fc::datastream<const char*> ds( _it->value().data(), _it->value().size() );
|
||||
fc::raw::unpack( ds, tmp_val );
|
||||
return tmp_val;
|
||||
}
|
||||
|
||||
iterator& operator++() { _it->Next(); return *this; }
|
||||
iterator& operator--() { _it->Prev(); return *this; }
|
||||
|
||||
protected:
|
||||
friend class level_map;
|
||||
iterator( ldb::Iterator* it )
|
||||
:_it(it){}
|
||||
|
||||
std::shared_ptr<ldb::Iterator> _it;
|
||||
};
|
||||
|
||||
iterator begin() const
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
iterator itr( _db->NewIterator( _iter_options ) );
|
||||
itr._it->SeekToFirst();
|
||||
|
||||
if( itr._it->status().IsNotFound() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( fc::key_not_found_exception, "" );
|
||||
}
|
||||
if( !itr._it->status().ok() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( level_map_failure, "database error: ${msg}", ("msg", itr._it->status().ToString() ) );
|
||||
}
|
||||
|
||||
if( itr.valid() )
|
||||
{
|
||||
return itr;
|
||||
}
|
||||
return iterator();
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error seeking to first" ) }
|
||||
|
||||
iterator find( const Key& key )const
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
ldb::Slice key_slice;
|
||||
|
||||
/** avoid dynamic memory allocation at this step if possible, most
|
||||
* keys should be relatively small in size and not require dynamic
|
||||
* memory allocation to seralize the key.
|
||||
*/
|
||||
fc::array<char,256+sizeof(Key)> stack_buffer;
|
||||
|
||||
size_t pack_size = fc::raw::pack_size(key);
|
||||
if( pack_size <= stack_buffer.size() )
|
||||
{
|
||||
fc::datastream<char*> ds( stack_buffer.data, stack_buffer.size() );
|
||||
fc::raw::pack( ds ,key );
|
||||
key_slice = ldb::Slice( stack_buffer.data, pack_size );
|
||||
}
|
||||
else
|
||||
{
|
||||
auto kslice = fc::raw::pack( key );
|
||||
key_slice = ldb::Slice( kslice.data(), kslice.size() );
|
||||
}
|
||||
|
||||
iterator itr( _db->NewIterator( _iter_options ) );
|
||||
itr._it->Seek( key_slice );
|
||||
if( itr.valid() && itr.key() == key )
|
||||
{
|
||||
return itr;
|
||||
}
|
||||
return iterator();
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error finding ${key}", ("key",key) ) }
|
||||
|
||||
iterator lower_bound( const Key& key )const
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
std::vector<char> kslice = fc::raw::pack( key );
|
||||
ldb::Slice key_slice( kslice.data(), kslice.size() );
|
||||
|
||||
iterator itr( _db->NewIterator( _iter_options ) );
|
||||
itr._it->Seek( key_slice );
|
||||
return itr;
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error finding ${key}", ("key",key) ) }
|
||||
|
||||
iterator last( )const
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
iterator itr( _db->NewIterator( _iter_options ) );
|
||||
itr._it->SeekToLast();
|
||||
return itr;
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error finding last" ) }
|
||||
|
||||
bool last( Key& k )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
std::unique_ptr<ldb::Iterator> it( _db->NewIterator( _iter_options ) );
|
||||
FC_ASSERT( it != nullptr );
|
||||
it->SeekToLast();
|
||||
if( !it->Valid() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
fc::datastream<const char*> ds2( it->key().data(), it->key().size() );
|
||||
fc::raw::unpack( ds2, k );
|
||||
return true;
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error reading last item from database" ); }
|
||||
|
||||
bool last( Key& k, Value& v )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
std::unique_ptr<ldb::Iterator> it( _db->NewIterator( _iter_options ) );
|
||||
FC_ASSERT( it != nullptr );
|
||||
it->SeekToLast();
|
||||
if( !it->Valid() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
fc::datastream<const char*> ds( it->value().data(), it->value().size() );
|
||||
fc::raw::unpack( ds, v );
|
||||
|
||||
fc::datastream<const char*> ds2( it->key().data(), it->key().size() );
|
||||
fc::raw::unpack( ds2, k );
|
||||
return true;
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error reading last item from database" ); }
|
||||
|
||||
/** this class allows batched, atomic database writes.
|
||||
* usage:
|
||||
* {
|
||||
* write_batch batch = _db.create_batch();
|
||||
* batch.store(key1, value1);
|
||||
* batch.store(key2, value2);
|
||||
* }
|
||||
* when the batch goes out of scope, the operations are commited to the database
|
||||
*/
|
||||
class write_batch
|
||||
{
|
||||
private:
|
||||
leveldb::WriteBatch _batch;
|
||||
level_map* _map = nullptr;
|
||||
leveldb::WriteOptions _write_options;
|
||||
|
||||
friend class level_map;
|
||||
write_batch( level_map* map, bool sync = false ) : _map(map)
|
||||
{
|
||||
_write_options.sync = sync;
|
||||
}
|
||||
public:
|
||||
~write_batch()
|
||||
{
|
||||
try
|
||||
{
|
||||
commit();
|
||||
}
|
||||
catch (const fc::canceled_exception&)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (const fc::exception&)
|
||||
{
|
||||
// we're in a destructor, nothing we can do...
|
||||
}
|
||||
}
|
||||
|
||||
void commit()
|
||||
{
|
||||
try
|
||||
{
|
||||
FC_ASSERT(_map->is_open(), "Database is not open!");
|
||||
|
||||
ldb::Status status = _map->_db->Write( _write_options, &_batch );
|
||||
if (!status.ok())
|
||||
FC_THROW_EXCEPTION(level_map_failure, "database error while applying batch: ${msg}", ("msg", status.ToString()));
|
||||
_batch.Clear();
|
||||
}
|
||||
FC_RETHROW_EXCEPTIONS(warn, "error applying batch");
|
||||
}
|
||||
|
||||
void abort()
|
||||
{
|
||||
_batch.Clear();
|
||||
}
|
||||
|
||||
void store( const Key& k, const Value& v )
|
||||
{
|
||||
std::vector<char> kslice = fc::raw::pack(k);
|
||||
ldb::Slice ks(kslice.data(), kslice.size());
|
||||
|
||||
auto vec = fc::raw::pack(v);
|
||||
ldb::Slice vs(vec.data(), vec.size());
|
||||
|
||||
_batch.Put(ks, vs);
|
||||
}
|
||||
|
||||
void remove( const Key& k )
|
||||
{
|
||||
std::vector<char> kslice = fc::raw::pack(k);
|
||||
ldb::Slice ks(kslice.data(), kslice.size());
|
||||
_batch.Delete(ks);
|
||||
}
|
||||
};
|
||||
|
||||
write_batch create_batch( bool sync = false )
|
||||
{
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
return write_batch( this, sync );
|
||||
}
|
||||
|
||||
void store(const Key& k, const Value& v, bool sync = false)
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
std::vector<char> kslice = fc::raw::pack( k );
|
||||
ldb::Slice ks( kslice.data(), kslice.size() );
|
||||
|
||||
auto vec = fc::raw::pack(v);
|
||||
ldb::Slice vs( vec.data(), vec.size() );
|
||||
|
||||
auto status = _db->Put( sync ? _sync_options : _write_options, ks, vs );
|
||||
if( !status.ok() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( level_map_failure, "database error: ${msg}", ("msg", status.ToString() ) );
|
||||
}
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error storing ${key} = ${value}", ("key",k)("value",v) ); }
|
||||
|
||||
void remove( const Key& k, bool sync = false )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
std::vector<char> kslice = fc::raw::pack( k );
|
||||
ldb::Slice ks( kslice.data(), kslice.size() );
|
||||
auto status = _db->Delete( sync ? _sync_options : _write_options, ks );
|
||||
if( !status.ok() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( level_map_failure, "database error: ${msg}", ("msg", status.ToString() ) );
|
||||
}
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error removing ${key}", ("key",k) ); }
|
||||
|
||||
void export_to_json( const fc::path& path )const
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
FC_ASSERT( !fc::exists( path ) );
|
||||
|
||||
std::ofstream fs( path.string() );
|
||||
fs.write( "[\n", 2 );
|
||||
|
||||
auto iter = begin();
|
||||
while( iter.valid() )
|
||||
{
|
||||
auto str = fc::json::to_pretty_string( std::make_pair( iter.key(), iter.value() ) );
|
||||
if( (++iter).valid() ) str += ",";
|
||||
str += "\n";
|
||||
fs.write( str.c_str(), str.size() );
|
||||
}
|
||||
|
||||
fs.write( "]", 1 );
|
||||
} FC_CAPTURE_AND_RETHROW( (path) ) }
|
||||
|
||||
// note: this loops through all the items in the database, so it's not exactly fast. it's intended for debugging, nothing else.
|
||||
size_t size() const
|
||||
{
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
iterator it = begin();
|
||||
size_t count = 0;
|
||||
while (it.valid())
|
||||
{
|
||||
++count;
|
||||
++it;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private:
|
||||
class key_compare : public leveldb::Comparator
|
||||
{
|
||||
public:
|
||||
int Compare( const leveldb::Slice& a, const leveldb::Slice& b )const
|
||||
{
|
||||
Key ak,bk;
|
||||
fc::datastream<const char*> dsa( a.data(), a.size() );
|
||||
fc::raw::unpack( dsa, ak );
|
||||
fc::datastream<const char*> dsb( b.data(), b.size() );
|
||||
fc::raw::unpack( dsb, bk );
|
||||
|
||||
if( ak < bk ) return -1;
|
||||
if( ak == bk ) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* Name()const { return "key_compare"; }
|
||||
void FindShortestSeparator( std::string*, const leveldb::Slice& )const{}
|
||||
void FindShortSuccessor( std::string* )const{};
|
||||
};
|
||||
|
||||
std::unique_ptr<leveldb::DB> _db;
|
||||
std::unique_ptr<leveldb::Cache> _cache;
|
||||
key_compare _comparer;
|
||||
|
||||
ldb::ReadOptions _read_options;
|
||||
ldb::ReadOptions _iter_options;
|
||||
ldb::WriteOptions _write_options;
|
||||
ldb::WriteOptions _sync_options;
|
||||
};
|
||||
|
||||
} } // graphene::db
|
||||
|
|
@ -1,309 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Cryptonomex, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
|
||||
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted until September 8, 2015, provided that the following conditions are met:
|
||||
*
|
||||
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <leveldb/cache.h>
|
||||
#include <leveldb/comparator.h>
|
||||
#include <leveldb/db.h>
|
||||
|
||||
#include <graphene/db/exception.hpp>
|
||||
#include <graphene/db/upgrade_leveldb.hpp>
|
||||
|
||||
#include <fc/filesystem.hpp>
|
||||
#include <fc/io/raw.hpp>
|
||||
#include <fc/reflect/reflect.hpp>
|
||||
|
||||
namespace graphene { namespace db {
|
||||
|
||||
namespace ldb = leveldb;
|
||||
|
||||
/**
|
||||
* @brief implements a high-level API on top of Level DB that stores items using fc::raw / reflection
|
||||
* @note Key must be a POD type
|
||||
*/
|
||||
template<typename Key, typename Value>
|
||||
class level_pod_map
|
||||
{
|
||||
public:
|
||||
void open( const fc::path& dir, bool create = true, size_t cache_size = 0 )
|
||||
{ try {
|
||||
FC_ASSERT( !is_open(), "Database is already open!" );
|
||||
|
||||
ldb::Options opts;
|
||||
opts.comparator = &_comparer;
|
||||
opts.create_if_missing = create;
|
||||
opts.max_open_files = 64;
|
||||
opts.compression = leveldb::kNoCompression;
|
||||
|
||||
if( cache_size > 0 )
|
||||
{
|
||||
opts.write_buffer_size = cache_size / 4; // up to two write buffers may be held in memory simultaneously
|
||||
_cache.reset( leveldb::NewLRUCache( cache_size / 2 ) );
|
||||
opts.block_cache = _cache.get();
|
||||
}
|
||||
|
||||
if( ldb::kMajorVersion > 1 || ( leveldb::kMajorVersion == 1 && leveldb::kMinorVersion >= 16 ) )
|
||||
{
|
||||
// LevelDB versions before 1.16 consider short writes to be corruption. Only trigger error
|
||||
// on corruption in later versions.
|
||||
opts.paranoid_checks = true;
|
||||
}
|
||||
|
||||
_read_options.verify_checksums = true;
|
||||
_iter_options.verify_checksums = true;
|
||||
_iter_options.fill_cache = false;
|
||||
_sync_options.sync = true;
|
||||
|
||||
// Given path must exist to succeed toNativeAnsiPath
|
||||
fc::create_directories( dir );
|
||||
std::string ldbPath = dir.to_native_ansi_path();
|
||||
|
||||
ldb::DB* ndb = nullptr;
|
||||
const auto ntrxstat = ldb::DB::Open( opts, ldbPath.c_str(), &ndb );
|
||||
if( !ntrxstat.ok() )
|
||||
{
|
||||
elog( "Failure opening database: ${db}\nStatus: ${msg}", ("db",dir)("msg",ntrxstat.ToString()) );
|
||||
FC_THROW_EXCEPTION( level_pod_map_open_failure, "Failure opening database: ${db}\nStatus: ${msg}",
|
||||
("db",dir)("msg",ntrxstat.ToString()) );
|
||||
}
|
||||
_db.reset( ndb );
|
||||
|
||||
try_upgrade_db( dir, ndb, fc::get_typename<Value>::name(), sizeof( Value ) );
|
||||
} FC_CAPTURE_AND_RETHROW( (dir)(create)(cache_size) ) }
|
||||
|
||||
bool is_open()const
|
||||
{
|
||||
return !!_db;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
_db.reset();
|
||||
_cache.reset();
|
||||
}
|
||||
|
||||
fc::optional<Value> fetch_optional( const Key& k )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
auto itr = find( k );
|
||||
if( itr.valid() ) return itr.value();
|
||||
return fc::optional<Value>();
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "" ) }
|
||||
|
||||
Value fetch( const Key& key )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
ldb::Slice key_slice( (char*)&key, sizeof(key) );
|
||||
std::string value;
|
||||
auto status = _db->Get( _read_options, key_slice, &value );
|
||||
if( status.IsNotFound() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( fc::key_not_found_exception, "unable to find key ${key}", ("key",key) );
|
||||
}
|
||||
if( !status.ok() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( level_pod_map_failure, "database error: ${msg}", ("msg", status.ToString() ) );
|
||||
}
|
||||
fc::datastream<const char*> datastream(value.c_str(), value.size());
|
||||
Value tmp;
|
||||
fc::raw::unpack(datastream, tmp);
|
||||
return tmp;
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error fetching key ${key}", ("key",key) ); }
|
||||
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
iterator(){}
|
||||
bool valid()const
|
||||
{
|
||||
return _it && _it->Valid();
|
||||
}
|
||||
|
||||
Key key()const
|
||||
{
|
||||
FC_ASSERT( sizeof(Key) == _it->key().size() );
|
||||
return *((Key*)_it->key().data());
|
||||
}
|
||||
|
||||
Value value()const
|
||||
{
|
||||
Value tmp_val;
|
||||
fc::datastream<const char*> ds( _it->value().data(), _it->value().size() );
|
||||
fc::raw::unpack( ds, tmp_val );
|
||||
return tmp_val;
|
||||
}
|
||||
|
||||
iterator& operator++() { _it->Next(); return *this; }
|
||||
iterator& operator--() { _it->Prev(); return *this; }
|
||||
|
||||
protected:
|
||||
friend class level_pod_map;
|
||||
iterator( ldb::Iterator* it )
|
||||
:_it(it){}
|
||||
|
||||
std::shared_ptr<ldb::Iterator> _it;
|
||||
};
|
||||
iterator begin()
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
iterator itr( _db->NewIterator( _iter_options ) );
|
||||
itr._it->SeekToFirst();
|
||||
|
||||
if( itr._it->status().IsNotFound() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( fc::key_not_found_exception, "" );
|
||||
}
|
||||
if( !itr._it->status().ok() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( level_pod_map_failure, "database error: ${msg}", ("msg", itr._it->status().ToString() ) );
|
||||
}
|
||||
|
||||
if( itr.valid() )
|
||||
{
|
||||
return itr;
|
||||
}
|
||||
return iterator();
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error seeking to first" ) }
|
||||
|
||||
iterator find( const Key& key )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
ldb::Slice key_slice( (char*)&key, sizeof(key) );
|
||||
iterator itr( _db->NewIterator( _iter_options ) );
|
||||
itr._it->Seek( key_slice );
|
||||
if( itr.valid() && itr.key() == key )
|
||||
{
|
||||
return itr;
|
||||
}
|
||||
return iterator();
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error finding ${key}", ("key",key) ) }
|
||||
|
||||
iterator lower_bound( const Key& key )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
ldb::Slice key_slice( (char*)&key, sizeof(key) );
|
||||
iterator itr( _db->NewIterator( _iter_options ) );
|
||||
itr._it->Seek( key_slice );
|
||||
if( itr.valid() )
|
||||
{
|
||||
return itr;
|
||||
}
|
||||
return iterator();
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error finding ${key}", ("key",key) ) }
|
||||
|
||||
bool last( Key& k )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
std::unique_ptr<ldb::Iterator> it( _db->NewIterator( _iter_options ) );
|
||||
FC_ASSERT( it != nullptr );
|
||||
it->SeekToLast();
|
||||
if( !it->Valid() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
FC_ASSERT( sizeof( Key) == it->key().size() );
|
||||
k = *((Key*)it->key().data());
|
||||
return true;
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error reading last item from database" ); }
|
||||
|
||||
bool last( Key& k, Value& v )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
std::unique_ptr<ldb::Iterator> it( _db->NewIterator( _iter_options ) );
|
||||
FC_ASSERT( it != nullptr );
|
||||
it->SeekToLast();
|
||||
if( !it->Valid() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
fc::datastream<const char*> ds( it->value().data(), it->value().size() );
|
||||
fc::raw::unpack( ds, v );
|
||||
|
||||
FC_ASSERT( sizeof( Key) == it->key().size() );
|
||||
k = *((Key*)it->key().data());
|
||||
return true;
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error reading last item from database" ); }
|
||||
|
||||
void store( const Key& k, const Value& v, bool sync = false )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
ldb::Slice ks( (char*)&k, sizeof(k) );
|
||||
auto vec = fc::raw::pack(v);
|
||||
ldb::Slice vs( vec.data(), vec.size() );
|
||||
|
||||
auto status = _db->Put( sync ? _sync_options : _write_options, ks, vs );
|
||||
if( !status.ok() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( level_pod_map_failure, "database error: ${msg}", ("msg", status.ToString() ) );
|
||||
}
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error storing ${key} = ${value}", ("key",k)("value",v) ); }
|
||||
|
||||
void remove( const Key& k, bool sync = false )
|
||||
{ try {
|
||||
FC_ASSERT( is_open(), "Database is not open!" );
|
||||
|
||||
ldb::Slice ks( (char*)&k, sizeof(k) );
|
||||
auto status = _db->Delete( sync ? _sync_options : _write_options, ks );
|
||||
if( status.IsNotFound() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( fc::key_not_found_exception, "unable to find key ${key}", ("key",k) );
|
||||
}
|
||||
if( !status.ok() )
|
||||
{
|
||||
FC_THROW_EXCEPTION( level_pod_map_failure, "database error: ${msg}", ("msg", status.ToString() ) );
|
||||
}
|
||||
} FC_RETHROW_EXCEPTIONS( warn, "error removing ${key}", ("key",k) ); }
|
||||
|
||||
private:
|
||||
class key_compare : public leveldb::Comparator
|
||||
{
|
||||
public:
|
||||
int Compare( const leveldb::Slice& a, const leveldb::Slice& b )const
|
||||
{
|
||||
FC_ASSERT( (a.size() == sizeof(Key)) && (b.size() == sizeof( Key )) );
|
||||
Key* ak = (Key*)a.data();
|
||||
Key* bk = (Key*)b.data();
|
||||
if( *ak < *bk ) return -1;
|
||||
if( *ak == *bk ) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* Name()const { return "key_compare"; }
|
||||
void FindShortestSeparator( std::string*, const leveldb::Slice& )const{}
|
||||
void FindShortSuccessor( std::string* )const{};
|
||||
};
|
||||
|
||||
std::unique_ptr<leveldb::DB> _db;
|
||||
std::unique_ptr<leveldb::Cache> _cache;
|
||||
key_compare _comparer;
|
||||
|
||||
ldb::ReadOptions _read_options;
|
||||
ldb::ReadOptions _iter_options;
|
||||
ldb::WriteOptions _write_options;
|
||||
ldb::WriteOptions _sync_options;
|
||||
};
|
||||
|
||||
} } // graphene::db
|
||||
|
|
@ -20,9 +20,6 @@
|
|||
#include <graphene/db/index.hpp>
|
||||
#include <graphene/db/undo_database.hpp>
|
||||
|
||||
#include <graphene/db/level_map.hpp>
|
||||
#include <graphene/db/level_pod_map.hpp>
|
||||
|
||||
#include <fc/log/logger.hpp>
|
||||
|
||||
#include <map>
|
||||
|
|
@ -150,7 +147,6 @@ namespace graphene { namespace db {
|
|||
|
||||
fc::path _data_dir;
|
||||
vector< vector< unique_ptr<index> > > _index;
|
||||
shared_ptr<db::level_map<object_id_type, vector<char> >> _object_id_to_object;
|
||||
};
|
||||
|
||||
} } // graphene::db
|
||||
|
|
|
|||
|
|
@ -28,19 +28,12 @@ object_database::object_database()
|
|||
{
|
||||
_index.resize(255);
|
||||
_undo_db.enable();
|
||||
|
||||
_object_id_to_object = std::make_shared<db::level_map<object_id_type,vector<char>>>();
|
||||
}
|
||||
|
||||
object_database::~object_database(){}
|
||||
|
||||
void object_database::close()
|
||||
{
|
||||
if( _object_id_to_object->is_open() )
|
||||
{
|
||||
flush();
|
||||
_object_id_to_object->close();
|
||||
}
|
||||
}
|
||||
|
||||
const object* object_database::find_object( object_id_type id )const
|
||||
|
|
@ -72,20 +65,15 @@ index& object_database::get_mutable_index(uint8_t space_id, uint8_t type_id)
|
|||
|
||||
void object_database::flush()
|
||||
{
|
||||
if( !_object_id_to_object->is_open() )
|
||||
return;
|
||||
|
||||
vector<object_id_type> next_ids;
|
||||
for( auto& space : _index )
|
||||
for( const unique_ptr<index>& type_index : space )
|
||||
if( type_index )
|
||||
{
|
||||
type_index->inspect_all_objects([&] (const object& obj) {
|
||||
_object_id_to_object->store(obj.id, obj.pack());
|
||||
});
|
||||
next_ids.push_back( type_index->get_next_id() );
|
||||
}
|
||||
_object_id_to_object->store( object_id_type(), fc::raw::pack(next_ids) );
|
||||
// ilog("Save object_database in ${d}", ("d", _data_dir));
|
||||
for( uint32_t space = 0; space < _index.size(); ++space )
|
||||
{
|
||||
fc::create_directories( _data_dir / "object_database" / fc::to_string(space) );
|
||||
const auto types = _index[space].size();
|
||||
for( uint32_t type = 0; type < types; ++type )
|
||||
if( _index[space][type] )
|
||||
_index[space][type]->save( _data_dir / "object_database" / fc::to_string(space)/fc::to_string(type) );
|
||||
}
|
||||
}
|
||||
|
||||
void object_database::wipe(const fc::path& data_dir)
|
||||
|
|
@ -98,36 +86,13 @@ void object_database::wipe(const fc::path& data_dir)
|
|||
|
||||
void object_database::open( const fc::path& data_dir )
|
||||
{ try {
|
||||
ilog("Open object_database in ${d}", ("d", data_dir));
|
||||
|
||||
_object_id_to_object->open( data_dir / "object_database" / "objects" );
|
||||
|
||||
for( auto& space : _index )
|
||||
{
|
||||
for( auto& type_index : space )
|
||||
{
|
||||
if( type_index )
|
||||
{
|
||||
type_index->open( _object_id_to_object );
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
auto next_ids = fc::raw::unpack<vector<object_id_type>>( _object_id_to_object->fetch( object_id_type() ) );
|
||||
wdump((next_ids));
|
||||
for( auto id : next_ids )
|
||||
{
|
||||
try {
|
||||
get_mutable_index( id ).set_next_id( id );
|
||||
} FC_CAPTURE_AND_RETHROW( (id) );
|
||||
}
|
||||
}
|
||||
catch ( const fc::exception& e )
|
||||
{
|
||||
// dlog( "unable to fetch next ids, must be new object_database\n ${e}", ("e",e.to_detail_string()) );
|
||||
}
|
||||
|
||||
// ilog("Open object_database in ${d}", ("d", data_dir));
|
||||
_data_dir = data_dir;
|
||||
for( uint32_t space = 0; space < _index.size(); ++space )
|
||||
for( uint32_t type = 0; type < _index[space].size(); ++type )
|
||||
if( _index[space][type] )
|
||||
_index[space][type]->open( _data_dir / "object_database" / fc::to_string(space)/fc::to_string(type) );
|
||||
|
||||
} FC_CAPTURE_AND_RETHROW( (data_dir) ) }
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Cryptonomex, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
|
||||
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted until September 8, 2015, provided that the following conditions are met:
|
||||
*
|
||||
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include <graphene/db/exception.hpp>
|
||||
#include <graphene/db/upgrade_leveldb.hpp>
|
||||
#include <fc/log/logger.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <fstream>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
|
||||
namespace graphene { namespace db {
|
||||
|
||||
upgrade_db_mapper& upgrade_db_mapper::instance()
|
||||
{
|
||||
static upgrade_db_mapper mapper;
|
||||
return mapper;
|
||||
}
|
||||
|
||||
int32_t upgrade_db_mapper::add_type( const std::string& type_name, const upgrade_db_function& function)
|
||||
{
|
||||
_upgrade_db_function_registry[type_name] = function;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// this code has no graphene dependencies, and it
|
||||
// could be moved to fc, if fc ever adds a leveldb dependency
|
||||
void try_upgrade_db( const fc::path& dir, leveldb::DB* dbase, const char* record_type, size_t record_type_size )
|
||||
{
|
||||
size_t old_record_type_size = 0;
|
||||
std::string old_record_type;
|
||||
fc::path record_type_filename = dir / "RECORD_TYPE";
|
||||
//if no RECORD_TYPE file exists
|
||||
if ( !boost::filesystem::exists( record_type_filename ) )
|
||||
{
|
||||
//must be original type for the database
|
||||
old_record_type = record_type;
|
||||
int last_char = old_record_type.length() - 1;
|
||||
//strip version number from current_record_name and append 0 to set old_record_type (e.g. mytype0)
|
||||
while (last_char >= 0 && isdigit(old_record_type[last_char]))
|
||||
{
|
||||
--last_char;
|
||||
}
|
||||
|
||||
//upgradeable record types should always end with version number
|
||||
if( 'v' != old_record_type[last_char] )
|
||||
{
|
||||
//ilog("Database ${db} is not upgradeable",("db",dir.to_native_ansi_path()));
|
||||
return;
|
||||
}
|
||||
|
||||
++last_char;
|
||||
old_record_type[last_char] = '0';
|
||||
old_record_type.resize(last_char+1);
|
||||
}
|
||||
else //read record type from file
|
||||
{
|
||||
boost::filesystem::ifstream is(record_type_filename);
|
||||
char buffer[120];
|
||||
is.getline(buffer,120);
|
||||
old_record_type = buffer;
|
||||
is >> old_record_type_size;
|
||||
}
|
||||
if (old_record_type != record_type)
|
||||
{
|
||||
//check if upgrade function in registry
|
||||
auto upgrade_function_itr = upgrade_db_mapper::instance()._upgrade_db_function_registry.find( old_record_type );
|
||||
if (upgrade_function_itr != upgrade_db_mapper::instance()._upgrade_db_function_registry.end())
|
||||
{
|
||||
ilog("Upgrading database ${db} from ${old} to ${new}",("db",dir.preferred_string())
|
||||
("old",old_record_type)
|
||||
("new",record_type));
|
||||
//update database's RECORD_TYPE to new record type name
|
||||
boost::filesystem::ofstream os(record_type_filename);
|
||||
os << record_type << std::endl;
|
||||
os << record_type_size;
|
||||
//upgrade the database using upgrade function
|
||||
upgrade_function_itr->second(dbase);
|
||||
}
|
||||
else
|
||||
{
|
||||
elog("In ${db}, record types ${old} and ${new} do not match, but no upgrade function found!",
|
||||
("db",dir.preferred_string())("old",old_record_type)("new",record_type));
|
||||
}
|
||||
}
|
||||
else if (old_record_type_size == 0) //if record type file never created, create it now
|
||||
{
|
||||
boost::filesystem::ofstream os(record_type_filename);
|
||||
os << record_type << std::endl;
|
||||
os << record_type_size;
|
||||
}
|
||||
else if (old_record_type_size != record_type_size)
|
||||
{
|
||||
elog("In ${db}, record type matches ${new}, but record sizes do not match!",
|
||||
("db",dir.preferred_string())("new",record_type));
|
||||
|
||||
}
|
||||
}
|
||||
} } // namespace graphene::db
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit c09035dba0cdab7fcb2c11bf81aaeaffaa981f66
|
||||
Subproject commit dde8ed9d7ab49807f2556488c0815f3741b11e00
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 7d41e6f89ff04ce9e6a742932924796f69c6e23d
|
||||
|
|
@ -10,7 +10,7 @@ set(SOURCES node.cpp
|
|||
add_library( graphene_net ${SOURCES} ${HEADERS} )
|
||||
|
||||
target_link_libraries( graphene_net
|
||||
PUBLIC fc graphene_db leveldb )
|
||||
PUBLIC fc graphene_db )
|
||||
target_include_directories( graphene_net
|
||||
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
PRIVATE "${CMAKE_SOURCE_DIR}/libraries/chain/include"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
#include <fc/io/json.hpp>
|
||||
|
||||
#include <graphene/net/peer_database.hpp>
|
||||
#include <graphene/db/level_pod_map.hpp>
|
||||
//#include <graphene/db/level_pod_map.hpp>
|
||||
|
||||
|
||||
|
||||
|
|
@ -76,8 +76,8 @@ namespace graphene { namespace net {
|
|||
>
|
||||
> potential_peer_set;
|
||||
//private:
|
||||
typedef graphene::db::level_pod_map<uint32_t, potential_peer_record> potential_peer_leveldb;
|
||||
potential_peer_leveldb _leveldb;
|
||||
//typedef graphene::db::level_pod_map<uint32_t, potential_peer_record> potential_peer_leveldb;
|
||||
//potential_peer_leveldb _leveldb;
|
||||
|
||||
potential_peer_set _potential_peer_set;
|
||||
|
||||
|
|
@ -109,6 +109,7 @@ namespace graphene { namespace net {
|
|||
|
||||
void peer_database_impl::open(const fc::path& databaseFilename)
|
||||
{
|
||||
/*
|
||||
try
|
||||
{
|
||||
_leveldb.open(databaseFilename);
|
||||
|
|
@ -135,16 +136,18 @@ namespace graphene { namespace net {
|
|||
iter = _potential_peer_set.erase(iter);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void peer_database_impl::close()
|
||||
{
|
||||
_leveldb.close();
|
||||
//_leveldb.close();
|
||||
_potential_peer_set.clear();
|
||||
}
|
||||
|
||||
void peer_database_impl::clear()
|
||||
{
|
||||
/*
|
||||
auto iter = _leveldb.begin();
|
||||
while (iter.valid())
|
||||
{
|
||||
|
|
@ -159,6 +162,7 @@ namespace graphene { namespace net {
|
|||
// shouldn't happen, and if it does there's not much we can do
|
||||
}
|
||||
}
|
||||
*/
|
||||
_potential_peer_set.clear();
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +171,7 @@ namespace graphene { namespace net {
|
|||
auto iter = _potential_peer_set.get<endpoint_index>().find(endpointToErase);
|
||||
if (iter != _potential_peer_set.get<endpoint_index>().end())
|
||||
{
|
||||
_leveldb.remove(iter->database_key);
|
||||
//_leveldb.remove(iter->database_key);
|
||||
_potential_peer_set.get<endpoint_index>().erase(iter);
|
||||
}
|
||||
}
|
||||
|
|
@ -178,16 +182,16 @@ namespace graphene { namespace net {
|
|||
if (iter != _potential_peer_set.get<endpoint_index>().end())
|
||||
{
|
||||
_potential_peer_set.get<endpoint_index>().modify(iter, [&updatedRecord](potential_peer_database_entry& entry) { entry.peer_record = updatedRecord; });
|
||||
_leveldb.store(iter->database_key, updatedRecord);
|
||||
//_leveldb.store(iter->database_key, updatedRecord);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t last_database_key;
|
||||
_leveldb.last(last_database_key);
|
||||
//_leveldb.last(last_database_key);
|
||||
uint32_t new_database_key = last_database_key + 1;
|
||||
potential_peer_database_entry new_database_entry(new_database_key, updatedRecord);
|
||||
_potential_peer_set.get<endpoint_index>().insert(new_database_entry);
|
||||
_leveldb.store(new_database_key, updatedRecord);
|
||||
//_leveldb.store(new_database_key, updatedRecord);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -314,12 +318,14 @@ namespace graphene { namespace net {
|
|||
std::vector<potential_peer_record> peer_database::get_all()const
|
||||
{
|
||||
std::vector<potential_peer_record> results;
|
||||
/*
|
||||
auto itr = my->_leveldb.begin();
|
||||
while( itr.valid() )
|
||||
{
|
||||
results.push_back( itr.value() );
|
||||
++itr;
|
||||
}
|
||||
*/
|
||||
return results;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@ flat_set<key_id_type> account_history_plugin_impl::get_keys_for_account( const a
|
|||
flat_set<key_id_type> key_id_set;
|
||||
key_id_set.reserve(owner_auths.size() + active_auths.size() + 2);
|
||||
|
||||
key_id_set.insert(acct.memo_key);
|
||||
key_id_set.insert(acct.options.memo_key);
|
||||
|
||||
// we don't use get_keys() here to avoid an intermediate copy operation
|
||||
for( const pair<object_id_type, weight_type>& item : active_auths )
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
#define GRAPHENE_GIT_REVISION_UNIX_TIMESTAMP @GRAPHENE_GIT_REVISION_UNIX_TIMESTAMP@
|
||||
#define GRAPHENE_GIT_REVISION_DESCRIPTION "@GRAPHENE_GIT_REVISION_DESCRIPTION@"
|
||||
|
||||
namespace bts { namespace utilities {
|
||||
namespace graphene { namespace utilities {
|
||||
|
||||
const char* const git_revision_sha = GRAPHENE_GIT_REVISION_SHA;
|
||||
const uint32_t git_revision_unix_timestamp = GRAPHENE_GIT_REVISION_UNIX_TIMESTAMP;
|
||||
const char* const git_revision_description = GRAPHENE_GIT_REVISION_DESCRIPTION;
|
||||
|
||||
} } // end namespace bts::utilities
|
||||
} } // end namespace graphene::utilities
|
||||
|
|
|
|||
|
|
@ -724,7 +724,7 @@ public:
|
|||
account_create_op.name = name;
|
||||
account_create_op.owner = authority(1, owner_rkid, 1);
|
||||
account_create_op.active = authority(1, active_rkid, 1);
|
||||
account_create_op.memo_key = active_rkid;
|
||||
account_create_op.options.memo_key = active_rkid;
|
||||
|
||||
signed_transaction tx;
|
||||
|
||||
|
|
@ -820,7 +820,7 @@ public:
|
|||
account_create_op.name = account_name;
|
||||
account_create_op.owner = authority(1, owner_rkid, 1);
|
||||
account_create_op.active = authority(1, active_rkid, 1);
|
||||
account_create_op.memo_key = active_rkid;
|
||||
account_create_op.options.memo_key = active_rkid;
|
||||
|
||||
// current_fee_schedule()
|
||||
// find_account(pay_from_account)
|
||||
|
|
@ -1021,8 +1021,8 @@ public:
|
|||
|
||||
call_order_update_operation op;
|
||||
op.funding_account = seller.id;
|
||||
op.amount_to_cover = -mia.amount_from_string(amount_to_sell);
|
||||
op.collateral_to_add = collateral.amount_from_string(amount_of_collateral);
|
||||
op.delta_debt = mia.amount_from_string(amount_to_sell);
|
||||
op.delta_collateral = collateral.amount_from_string(amount_of_collateral);
|
||||
|
||||
signed_transaction trx;
|
||||
trx.operations = {op};
|
||||
|
|
@ -1070,10 +1070,10 @@ public:
|
|||
if( memo.size() )
|
||||
{
|
||||
xfer_op.memo = memo_data();
|
||||
xfer_op.memo->from = from_account.memo_key;
|
||||
xfer_op.memo->to = to_account.memo_key;
|
||||
xfer_op.memo->set_message(get_private_key(from_account.memo_key),
|
||||
get_public_key(to_account.memo_key), memo);
|
||||
xfer_op.memo->from = from_account.options.memo_key;
|
||||
xfer_op.memo->to = to_account.options.memo_key;
|
||||
xfer_op.memo->set_message(get_private_key(from_account.options.memo_key),
|
||||
get_public_key(to_account.options.memo_key), memo);
|
||||
}
|
||||
|
||||
signed_transaction tx;
|
||||
|
|
@ -1100,10 +1100,10 @@ public:
|
|||
if( memo.size() )
|
||||
{
|
||||
issue_op.memo = memo_data();
|
||||
issue_op.memo->from = issuer.memo_key;
|
||||
issue_op.memo->to = to.memo_key;
|
||||
issue_op.memo->set_message(get_private_key(issuer.memo_key),
|
||||
get_public_key(to.memo_key), memo);
|
||||
issue_op.memo->from = issuer.options.memo_key;
|
||||
issue_op.memo->to = to.options.memo_key;
|
||||
issue_op.memo->set_message(get_private_key(issuer.options.memo_key),
|
||||
get_public_key(to.options.memo_key), memo);
|
||||
}
|
||||
|
||||
signed_transaction tx;
|
||||
|
|
@ -1183,7 +1183,7 @@ void dbg_make_mia(string creator, string symbol)
|
|||
|
||||
void flood_network(string prefix, uint32_t number_of_transactions)
|
||||
{
|
||||
const account_object& master = *_wallet.my_accounts.get<by_name>().lower_bound("bts");
|
||||
const account_object& master = *_wallet.my_accounts.get<by_name>().lower_bound("import");
|
||||
int number_of_accounts = number_of_transactions / 3;
|
||||
number_of_transactions -= number_of_accounts;
|
||||
auto key = derive_private_key("floodshill", 0);
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ void database_fixture::_push_transaction( const signed_transaction& tx, uint32_t
|
|||
|
||||
void database_fixture::verify_asset_supplies( )const
|
||||
{
|
||||
wlog("*** Begin asset supply verification ***");
|
||||
//wlog("*** Begin asset supply verification ***");
|
||||
const asset_dynamic_data_object& core_asset_data = db.get_core_asset().dynamic_asset_data_id(db);
|
||||
BOOST_CHECK(core_asset_data.fee_pool == 0);
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ void database_fixture::verify_asset_supplies( )const
|
|||
|
||||
BOOST_CHECK_EQUAL( core_in_orders.value , reported_core_in_orders.value );
|
||||
BOOST_CHECK_EQUAL( total_balances[asset_id_type()].value , core_asset_data.current_supply.value );
|
||||
wlog("*** End asset supply verification ***");
|
||||
// wlog("*** End asset supply verification ***");
|
||||
}
|
||||
|
||||
void database_fixture::verify_account_history_plugin_index( )const
|
||||
|
|
@ -187,7 +187,7 @@ void database_fixture::verify_account_history_plugin_index( )const
|
|||
if( auth.first.type() == key_object_type )
|
||||
acct_addresses.insert( key_id_type( auth.first )(db).key_address() );
|
||||
}
|
||||
acct_addresses.insert( acct.memo_key(db).key_address() );
|
||||
acct_addresses.insert( acct.options.get_memo_key()(db).key_address() );
|
||||
for( const address& addr : acct_addresses )
|
||||
tuples_from_db.emplace_back( account_id, addr );
|
||||
}
|
||||
|
|
@ -264,11 +264,17 @@ void database_fixture::generate_blocks( uint32_t block_count )
|
|||
generate_block();
|
||||
}
|
||||
|
||||
void database_fixture::generate_blocks( fc::time_point_sec timestamp )
|
||||
void database_fixture::generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks)
|
||||
{
|
||||
if( miss_intermediate_blocks )
|
||||
{
|
||||
auto slots_to_miss = db.get_slot_at_time(timestamp) - 1;
|
||||
assert(slots_to_miss > 0);
|
||||
generate_block(~0, generate_private_key("genesis"), slots_to_miss);
|
||||
return;
|
||||
}
|
||||
while( db.head_block_time() < timestamp )
|
||||
generate_block();
|
||||
return;
|
||||
}
|
||||
|
||||
account_create_operation database_fixture::make_account(
|
||||
|
|
@ -282,7 +288,7 @@ account_create_operation database_fixture::make_account(
|
|||
create_account.name = name;
|
||||
create_account.owner = authority(123, key, 123);
|
||||
create_account.active = authority(321, key, 321);
|
||||
create_account.memo_key = key;
|
||||
create_account.options.memo_key = key;
|
||||
|
||||
auto& active_delegates = db.get_global_properties().active_delegates;
|
||||
if( active_delegates.size() > 0 )
|
||||
|
|
@ -293,9 +299,9 @@ account_create_operation database_fixture::make_account(
|
|||
votes.insert(active_delegates[rand() % active_delegates.size()](db).vote_id);
|
||||
votes.insert(active_delegates[rand() % active_delegates.size()](db).vote_id);
|
||||
votes.insert(active_delegates[rand() % active_delegates.size()](db).vote_id);
|
||||
create_account.vote = flat_set<vote_id_type>(votes.begin(), votes.end());
|
||||
create_account.options.votes = flat_set<vote_id_type>(votes.begin(), votes.end());
|
||||
}
|
||||
create_account.num_committee = create_account.vote.size();
|
||||
create_account.options.num_committee = create_account.options.votes.size();
|
||||
|
||||
create_account.fee = create_account.calculate_fee(db.current_fee_schedule());
|
||||
return create_account;
|
||||
|
|
@ -320,7 +326,7 @@ account_create_operation database_fixture::make_account(
|
|||
create_account.name = name;
|
||||
create_account.owner = authority(123, key, 123);
|
||||
create_account.active = authority(321, key, 321);
|
||||
create_account.memo_key = key;
|
||||
create_account.options.memo_key = key;
|
||||
|
||||
const vector<delegate_id_type>& active_delegates = db.get_global_properties().active_delegates;
|
||||
if( active_delegates.size() > 0 )
|
||||
|
|
@ -331,9 +337,9 @@ account_create_operation database_fixture::make_account(
|
|||
votes.insert(active_delegates[rand() % active_delegates.size()](db).vote_id);
|
||||
votes.insert(active_delegates[rand() % active_delegates.size()](db).vote_id);
|
||||
votes.insert(active_delegates[rand() % active_delegates.size()](db).vote_id);
|
||||
create_account.vote = flat_set<vote_id_type>(votes.begin(), votes.end());
|
||||
create_account.options.votes = flat_set<vote_id_type>(votes.begin(), votes.end());
|
||||
}
|
||||
create_account.num_committee = create_account.vote.size();
|
||||
create_account.options.num_committee = create_account.options.votes.size();
|
||||
|
||||
create_account.fee = create_account.calculate_fee(db.current_fee_schedule());
|
||||
return create_account;
|
||||
|
|
@ -461,13 +467,13 @@ const account_object& database_fixture::create_account(
|
|||
account_create_op.name = name;
|
||||
account_create_op.owner = authority(1234, key_rkid, 1234);
|
||||
account_create_op.active = authority(5678, key_rkid, 5678);
|
||||
account_create_op.memo_key = key_rkid;
|
||||
account_create_op.options.memo_key = key_rkid;
|
||||
trx.operations.push_back( account_create_op );
|
||||
|
||||
trx.validate();
|
||||
|
||||
processed_transaction ptx = db.push_transaction(trx, ~0);
|
||||
wdump( (ptx) );
|
||||
//wdump( (ptx) );
|
||||
const account_object& result = db.get<account_object>(ptx.operation_results[1].get<object_id_type>());
|
||||
trx.operations.clear();
|
||||
return result;
|
||||
|
|
@ -546,6 +552,7 @@ const limit_order_object*database_fixture::create_sell_order(account_id_type use
|
|||
|
||||
const limit_order_object* database_fixture::create_sell_order( const account_object& user, const asset& amount, const asset& recv )
|
||||
{
|
||||
//wdump((amount)(recv));
|
||||
limit_order_create_operation buy_order;
|
||||
buy_order.seller = user.id;
|
||||
buy_order.amount_to_sell = amount;
|
||||
|
|
@ -555,6 +562,7 @@ const limit_order_object* database_fixture::create_sell_order( const account_obj
|
|||
trx.validate();
|
||||
auto processed = db.push_transaction(trx, ~0);
|
||||
trx.operations.clear();
|
||||
//wdump((processed));
|
||||
return db.find<limit_order_object>( processed.operation_results[0].get<object_id_type>() );
|
||||
}
|
||||
|
||||
|
|
@ -603,6 +611,63 @@ void database_fixture::transfer(
|
|||
} FC_CAPTURE_AND_RETHROW( (from.id)(to.id)(amount)(fee) )
|
||||
}
|
||||
|
||||
void database_fixture::update_feed_producers( const asset_object& mia, flat_set<account_id_type> producers )
|
||||
{ try {
|
||||
trx.set_expiration(db.head_block_time() + fc::minutes(1));
|
||||
trx.operations.clear();
|
||||
asset_update_feed_producers_operation op;
|
||||
op.asset_to_update = mia.id;
|
||||
op.issuer = mia.issuer;
|
||||
op.new_feed_producers = std::move(producers);
|
||||
trx.operations.emplace_back( std::move(op) );
|
||||
|
||||
for( auto& op : trx.operations ) op.visit( operation_set_fee( db.current_fee_schedule() ) );
|
||||
trx.validate();
|
||||
db.push_transaction(trx, ~0);
|
||||
trx.operations.clear();
|
||||
} FC_CAPTURE_AND_RETHROW( (mia)(producers) ) }
|
||||
|
||||
|
||||
void database_fixture::publish_feed( const asset_object& mia, const account_object& by, const price_feed& f )
|
||||
{
|
||||
trx.set_expiration(db.head_block_time() + fc::minutes(1));
|
||||
trx.operations.clear();
|
||||
|
||||
asset_publish_feed_operation op;
|
||||
op.publisher = by.id;
|
||||
op.asset_id = mia.id;
|
||||
op.feed = f;
|
||||
trx.operations.emplace_back( std::move(op) );
|
||||
|
||||
for( auto& op : trx.operations ) op.visit( operation_set_fee( db.current_fee_schedule() ) );
|
||||
trx.validate();
|
||||
db.push_transaction(trx, ~0);
|
||||
trx.operations.clear();
|
||||
}
|
||||
|
||||
void database_fixture::borrow( const account_object& who, asset what, asset collateral, price call_price )
|
||||
{ try {
|
||||
asset call_price_collateral((collateral.amount.value * 3)/4, collateral.asset_id );
|
||||
trx.set_expiration(db.head_block_time() + fc::minutes(1));
|
||||
trx.operations.clear();
|
||||
trx.operations.push_back( call_order_update_operation({ asset(), who.id, collateral, what, call_price }));;
|
||||
for( auto& op : trx.operations ) op.visit( operation_set_fee( db.current_fee_schedule() ) );
|
||||
trx.validate();
|
||||
db.push_transaction(trx, ~0);
|
||||
trx.operations.clear();
|
||||
} FC_CAPTURE_AND_RETHROW( (who.name)(what)(collateral) ) }
|
||||
|
||||
void database_fixture::cover( const account_object& who, asset what, asset collateral, price call_price )
|
||||
{ try {
|
||||
trx.set_expiration(db.head_block_time() + fc::minutes(1));
|
||||
trx.operations.clear();
|
||||
trx.operations.push_back( call_order_update_operation({ asset(), who.id, -collateral, -what, call_price }));
|
||||
for( auto& op : trx.operations ) op.visit( operation_set_fee( db.current_fee_schedule() ) );
|
||||
trx.validate();
|
||||
db.push_transaction(trx, ~0);
|
||||
trx.operations.clear();
|
||||
} FC_CAPTURE_AND_RETHROW( (who.name)(what)(collateral) ) }
|
||||
|
||||
void database_fixture::fund_fee_pool( const account_object& from, const asset_object& asset_to_fund, const share_type amount )
|
||||
{
|
||||
trx.operations.push_back( asset_fund_fee_pool_operation({asset(), from.id, asset_to_fund.id, amount}) );
|
||||
|
|
@ -639,14 +704,33 @@ void database_fixture::upgrade_to_lifetime_member( const account_object& account
|
|||
account_upgrade_operation op;
|
||||
op.account_to_upgrade = account.get_id();
|
||||
op.upgrade_to_lifetime_member = true;
|
||||
op.fee = op.calculate_fee(db.get_global_properties().parameters.current_fees);
|
||||
trx.operations = {op};
|
||||
db.push_transaction( trx, ~0 );
|
||||
db.push_transaction(trx, ~0);
|
||||
FC_ASSERT( op.account_to_upgrade(db).is_lifetime_member() );
|
||||
trx.clear();
|
||||
}
|
||||
FC_CAPTURE_AND_RETHROW((account))
|
||||
}
|
||||
|
||||
void database_fixture::upgrade_to_annual_member(account_id_type account)
|
||||
{
|
||||
upgrade_to_annual_member(account(db));
|
||||
}
|
||||
|
||||
void database_fixture::upgrade_to_annual_member(const account_object& account)
|
||||
{
|
||||
try {
|
||||
account_upgrade_operation op;
|
||||
op.account_to_upgrade = account.get_id();
|
||||
op.fee = op.calculate_fee(db.get_global_properties().parameters.current_fees);
|
||||
trx.operations = {op};
|
||||
db.push_transaction(trx, ~0);
|
||||
FC_ASSERT( op.account_to_upgrade(db).is_member(db.head_block_time()) );
|
||||
trx.clear();
|
||||
} FC_CAPTURE_AND_RETHROW((account))
|
||||
}
|
||||
|
||||
void database_fixture::print_market( const string& syma, const string& symb )const
|
||||
{
|
||||
const auto& limit_idx = db.get_index_type<limit_order_index>();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <graphene/app/application.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/key_object.hpp>
|
||||
#include <fc/io/json.hpp>
|
||||
|
||||
using namespace graphene::db;
|
||||
|
||||
|
|
@ -67,13 +68,18 @@ using namespace graphene::db;
|
|||
#define PUSH_TX( tx, skip_flags ) \
|
||||
_push_transaction( tx, skip_flags, __FILE__, __LINE__ )
|
||||
|
||||
#define ACTOR(name) \
|
||||
#define PREP_ACTOR(name) \
|
||||
fc::ecc::private_key name ## _private_key = generate_private_key(BOOST_PP_STRINGIZE(name)); \
|
||||
key_id_type name ## _key_id = register_key(name ## _private_key.get_public_key()).get_id(); \
|
||||
account_id_type name ## _id = create_account(BOOST_PP_STRINGIZE(name), name ## _key_id).id;
|
||||
key_id_type name ## _key_id = register_key(name ## _private_key.get_public_key()).get_id();
|
||||
#define ACTOR(name) \
|
||||
PREP_ACTOR(name) \
|
||||
const auto& name = create_account(BOOST_PP_STRINGIZE(name), name ## _key_id); \
|
||||
account_id_type name ## _id = name.id; (void)name ## _id;
|
||||
|
||||
#define GET_ACTOR(name) \
|
||||
fc::ecc::private_key name ## _private_key = generate_private_key(BOOST_PP_STRINGIZE(name)); \
|
||||
account_id_type name ## _id = get_account(BOOST_PP_STRINGIZE(name)).id; \
|
||||
const account_object& name = get_account(BOOST_PP_STRINGIZE(name)); \
|
||||
account_id_type name ## _id = name.id; \
|
||||
key_id_type name ## _key_id = name ## _id(db).active.auths.begin()->first;
|
||||
|
||||
#define ACTORS_IMPL(r, data, elem) ACTOR(elem)
|
||||
|
|
@ -117,13 +123,13 @@ struct database_fixture {
|
|||
* @brief Generates block_count blocks
|
||||
* @param block_count number of blocks to generate
|
||||
*/
|
||||
void generate_blocks( uint32_t block_count );
|
||||
void generate_blocks(uint32_t block_count);
|
||||
|
||||
/**
|
||||
* @brief Generates blocks until the head block time matches or exceeds timestamp
|
||||
* @param timestamp target time to generate blocks until
|
||||
*/
|
||||
void generate_blocks( fc::time_point_sec timestamp );
|
||||
void generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks = false);
|
||||
|
||||
account_create_operation make_account(
|
||||
const std::string& name = "nathan",
|
||||
|
|
@ -138,6 +144,11 @@ struct database_fixture {
|
|||
key_id_type key = key_id_type()
|
||||
);
|
||||
|
||||
void update_feed_producers( const asset_object& mia, flat_set<account_id_type> producers );
|
||||
void publish_feed( const asset_object& mia, const account_object& by, const price_feed& f );
|
||||
void borrow( const account_object& who, asset what, asset collateral, price call_price = price());
|
||||
void cover( const account_object& who, asset what, asset collateral_freed, price call_price = price());
|
||||
|
||||
const asset_object& get_asset( const string& symbol )const;
|
||||
const account_object& get_account( const string& name )const;
|
||||
const asset_object& create_bitasset(const string& name,
|
||||
|
|
@ -189,6 +200,8 @@ struct database_fixture {
|
|||
void enable_fees( share_type fee = GRAPHENE_BLOCKCHAIN_PRECISION );
|
||||
void upgrade_to_lifetime_member( account_id_type account );
|
||||
void upgrade_to_lifetime_member( const account_object& account );
|
||||
void upgrade_to_annual_member( account_id_type account );
|
||||
void upgrade_to_annual_member( const account_object& account );
|
||||
void print_market( const string& syma, const string& symb )const;
|
||||
string pretty( const asset& a )const;
|
||||
void print_limit_order( const limit_order_object& cur )const;
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture )
|
|||
// size() < num_active_keys is possible when some keys are duplicates
|
||||
create_op.active.weight_threshold = create_op.active.auths.size();
|
||||
|
||||
create_op.memo_key = key_ids[ *(it++) ] ;
|
||||
create_op.options.memo_key = key_ids[ *(it++) ] ;
|
||||
create_op.registrar = sam_account_object.id;
|
||||
trx.operations.push_back( create_op );
|
||||
// trx.sign( sam_key );
|
||||
|
|
@ -191,6 +191,7 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture )
|
|||
update_op.account = alice_account_id;
|
||||
update_op.owner = authority();
|
||||
update_op.active = authority();
|
||||
update_op.new_options = create_op.options;
|
||||
|
||||
for( int owner_index=0; owner_index<num_owner_keys; owner_index++ )
|
||||
update_op.owner->auths[ key_ids[ *(it++) ] ] = 1;
|
||||
|
|
@ -200,7 +201,8 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture )
|
|||
update_op.active->auths[ key_ids[ *(it++) ] ] = 1;
|
||||
// size() < num_active_keys is possible when some keys are duplicates
|
||||
update_op.active->weight_threshold = update_op.active->auths.size();
|
||||
update_op.memo_key = key_ids[ *(it++) ] ;
|
||||
FC_ASSERT( update_op.new_options.valid() );
|
||||
update_op.new_options->memo_key = key_ids[ *(it++) ] ;
|
||||
|
||||
trx.operations.push_back( update_op );
|
||||
for( int i=0; i<int(create_op.owner.weight_threshold); i++)
|
||||
|
|
|
|||
|
|
@ -474,7 +474,8 @@ BOOST_FIXTURE_TEST_CASE( fired_delegates, database_fixture )
|
|||
//Oh noes! Nathan votes for a whole new slate of delegates!
|
||||
account_update_operation op;
|
||||
op.account = nathan->id;
|
||||
op.vote = delegates;
|
||||
op.new_options = nathan->options;
|
||||
op.new_options->votes = delegates;
|
||||
trx.operations.push_back(op);
|
||||
trx.set_expiration(db.head_block_time() + GRAPHENE_DEFAULT_MAX_TIME_UNTIL_EXPIRATION);
|
||||
db.push_transaction(trx, ~0);
|
||||
|
|
@ -874,7 +875,7 @@ BOOST_FIXTURE_TEST_CASE( max_authority_membership, database_fixture )
|
|||
anon_create_op.owner = owner_auth;
|
||||
anon_create_op.active = active_auth;
|
||||
anon_create_op.registrar = sam_account_object.id;
|
||||
anon_create_op.memo_key = sam_account_object.memo_key;
|
||||
anon_create_op.options.memo_key = sam_account_object.options.memo_key;
|
||||
anon_create_op.name = generate_anon_acct_name();
|
||||
|
||||
tx.operations.push_back( anon_create_op );
|
||||
|
|
@ -913,10 +914,10 @@ BOOST_FIXTURE_TEST_CASE( bogus_signature, database_fixture )
|
|||
account_object bob_account_object = create_account( "bob", bob_key );
|
||||
account_object charlie_account_object = create_account( "charlie", charlie_key );
|
||||
|
||||
key_id_type alice_key_id = alice_account_object.memo_key;
|
||||
key_id_type alice_key_id = alice_account_object.options.memo_key;
|
||||
// unneeded, comment it out to silence compiler warning
|
||||
//key_id_type bob_key_id = bob_account_object.memo_key;
|
||||
key_id_type charlie_key_id = charlie_account_object.memo_key;
|
||||
key_id_type charlie_key_id = charlie_account_object.options.memo_key;
|
||||
|
||||
uint32_t skip = database::skip_transaction_dupe_check;
|
||||
|
||||
|
|
@ -939,14 +940,13 @@ BOOST_FIXTURE_TEST_CASE( bogus_signature, database_fixture )
|
|||
|
||||
flat_set<account_id_type> active_set, owner_set;
|
||||
xfer_op.get<transfer_operation>().get_required_auth(active_set, owner_set);
|
||||
wdump( (active_set)(owner_set)(alice_key_id)
|
||||
(alice_account_object) );
|
||||
// wdump( (active_set)(owner_set)(alice_key_id) (alice_account_object) );
|
||||
|
||||
PUSH_TX( trx, skip );
|
||||
|
||||
trx.operations.push_back( xfer_op );
|
||||
// Alice's signature is now invalid
|
||||
edump((trx));
|
||||
//edump((trx));
|
||||
BOOST_REQUIRE_THROW( PUSH_TX( trx, skip ), fc::exception );
|
||||
// Re-sign, now OK (sig is replaced)
|
||||
trx.sign( alice_key_id, alice_key );
|
||||
|
|
@ -994,9 +994,10 @@ BOOST_FIXTURE_TEST_CASE( voting_account, database_fixture )
|
|||
{
|
||||
account_update_operation op;
|
||||
op.account = nathan_id;
|
||||
op.voting_account = vikram_id;
|
||||
op.vote = flat_set<vote_id_type>{nathan_delegate(db).vote_id};
|
||||
op.num_committee = 1;
|
||||
op.new_options = nathan_id(db).options;
|
||||
op.new_options->voting_account = vikram_id;
|
||||
op.new_options->votes = flat_set<vote_id_type>{nathan_delegate(db).vote_id};
|
||||
op.new_options->num_committee = 1;
|
||||
trx.operations.push_back(op);
|
||||
trx.sign(nathan_key_id, nathan_private_key);
|
||||
db.push_transaction(trx);
|
||||
|
|
@ -1005,11 +1006,16 @@ BOOST_FIXTURE_TEST_CASE( voting_account, database_fixture )
|
|||
{
|
||||
account_update_operation op;
|
||||
op.account = vikram_id;
|
||||
op.vote = vikram_id(db).votes;
|
||||
op.vote->insert(vikram_delegate(db).vote_id);
|
||||
op.num_committee = 11;
|
||||
op.new_options = vikram_id(db).options;
|
||||
op.new_options->votes.insert(vikram_delegate(db).vote_id);
|
||||
op.new_options->num_committee = 11;
|
||||
trx.operations.push_back(op);
|
||||
trx.sign(vikram_key_id, vikram_private_key);
|
||||
// Fails because num_committee is larger than the cardinality of committee members being voted for
|
||||
BOOST_CHECK_THROW(db.push_transaction(trx), fc::exception);
|
||||
op.new_options->num_committee = 3;
|
||||
trx.operations = {op};
|
||||
trx.sign(vikram_key_id, vikram_private_key);
|
||||
db.push_transaction(trx);
|
||||
trx.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,73 @@ using namespace graphene::chain;
|
|||
|
||||
BOOST_AUTO_TEST_SUITE(block_tests)
|
||||
|
||||
BOOST_AUTO_TEST_CASE( block_database_test )
|
||||
{
|
||||
try {
|
||||
fc::temp_directory data_dir;
|
||||
|
||||
block_database bdb;
|
||||
bdb.open( data_dir.path() );
|
||||
FC_ASSERT( bdb.is_open() );
|
||||
bdb.close();
|
||||
FC_ASSERT( !bdb.is_open() );
|
||||
bdb.open( data_dir.path() );
|
||||
|
||||
signed_block b;
|
||||
for( uint32_t i = 0; i < 5; ++i )
|
||||
{
|
||||
if( i > 0 ) b.previous = b.id();
|
||||
b.witness = witness_id_type(i+1);
|
||||
//edump((b));
|
||||
bdb.store( b.id(), b );
|
||||
|
||||
auto fetch = bdb.fetch_by_number( b.block_num() );
|
||||
//idump((fetch));
|
||||
FC_ASSERT( fetch.valid() );
|
||||
FC_ASSERT( fetch->witness == b.witness );
|
||||
fetch = bdb.fetch_by_number( i+1 );
|
||||
//idump((fetch));
|
||||
FC_ASSERT( fetch.valid() );
|
||||
FC_ASSERT( fetch->witness == b.witness );
|
||||
fetch = bdb.fetch_optional( b.id() );
|
||||
//idump((fetch));
|
||||
FC_ASSERT( fetch.valid() );
|
||||
FC_ASSERT( fetch->witness == b.witness );
|
||||
}
|
||||
//ilog("-----------" );
|
||||
|
||||
for( uint32_t i = 1; i < 5; ++i )
|
||||
{
|
||||
auto blk = bdb.fetch_by_number( i );
|
||||
FC_ASSERT( blk.valid() );
|
||||
//idump((blk)(i));
|
||||
FC_ASSERT( blk->witness == witness_id_type(blk->block_num()) );
|
||||
}
|
||||
|
||||
auto last = bdb.last();
|
||||
FC_ASSERT( last );
|
||||
FC_ASSERT( last->id() == b.id() );
|
||||
|
||||
bdb.close();
|
||||
bdb.open( data_dir.path() );
|
||||
last = bdb.last();
|
||||
FC_ASSERT( last );
|
||||
FC_ASSERT( last->id() == b.id() );
|
||||
|
||||
for( uint32_t i = 0; i < 5; ++i )
|
||||
{
|
||||
auto blk = bdb.fetch_by_number( i+1 );
|
||||
FC_ASSERT( blk.valid() );
|
||||
//idump((blk)(i));
|
||||
FC_ASSERT( blk->witness == witness_id_type(blk->block_num()) );
|
||||
}
|
||||
|
||||
} catch (fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( generate_empty_blocks )
|
||||
{
|
||||
try {
|
||||
|
|
@ -66,7 +133,7 @@ BOOST_AUTO_TEST_CASE( generate_empty_blocks )
|
|||
db.close();
|
||||
}
|
||||
{
|
||||
wlog( "------------------------------------------------" );
|
||||
//wlog( "------------------------------------------------" );
|
||||
database db;
|
||||
db.open(data_dir.path() );
|
||||
BOOST_CHECK_EQUAL( db.head_block_num(), 200 );
|
||||
|
|
@ -106,15 +173,15 @@ BOOST_AUTO_TEST_CASE( undo_block )
|
|||
BOOST_CHECK( db.head_block_num() == 5 );
|
||||
db.pop_block();
|
||||
now -= db.block_interval();
|
||||
wdump( (witness_schedule_id_type()(db)) );
|
||||
//wdump( (witness_schedule_id_type()(db)) );
|
||||
BOOST_CHECK( db.head_block_num() == 4 );
|
||||
db.pop_block();
|
||||
now -= db.block_interval();
|
||||
wdump( (witness_schedule_id_type()(db)) );
|
||||
//wdump( (witness_schedule_id_type()(db)) );
|
||||
BOOST_CHECK( db.head_block_num() == 3 );
|
||||
db.pop_block();
|
||||
now -= db.block_interval();
|
||||
wdump( (witness_schedule_id_type()(db)) );
|
||||
//wdump( (witness_schedule_id_type()(db)) );
|
||||
BOOST_CHECK( db.head_block_num() == 2 );
|
||||
for( uint32_t i = 0; i < 5; ++i )
|
||||
{
|
||||
|
|
@ -142,7 +209,7 @@ BOOST_AUTO_TEST_CASE( fork_blocks )
|
|||
db2.open( data_dir2.path(), genesis_allocation() );
|
||||
|
||||
auto delegate_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("genesis")) );
|
||||
for( uint32_t i = 0; i < 20; ++i )
|
||||
for( uint32_t i = 0; i < 10; ++i )
|
||||
{
|
||||
now += db1.block_interval();
|
||||
auto b = db1.generate_block( now, db1.get_scheduled_witness( 1 ).first, delegate_priv_key );
|
||||
|
|
@ -150,20 +217,19 @@ BOOST_AUTO_TEST_CASE( fork_blocks )
|
|||
db2.push_block(b);
|
||||
} FC_CAPTURE_AND_RETHROW( ("db2") );
|
||||
}
|
||||
for( uint32_t i = 20; i < 23; ++i )
|
||||
for( uint32_t i = 10; i < 13; ++i )
|
||||
{
|
||||
now += db1.block_interval();
|
||||
auto b = db1.generate_block( now, db1.get_scheduled_witness( 1 ).first, delegate_priv_key );
|
||||
}
|
||||
string db1_tip = db1.head_block_id().str();
|
||||
for( uint32_t i = 23; i < 26; ++i )
|
||||
for( uint32_t i = 13; i < 16; ++i )
|
||||
{
|
||||
now += db2.block_interval();
|
||||
auto b = db2.generate_block( now, db2.get_scheduled_witness( db2.get_slot_at_time( now ) ).first, delegate_priv_key );
|
||||
// notify both databases of the new block.
|
||||
// only db2 should switch to the new fork, db1 should not
|
||||
db1.push_block(b);
|
||||
db2.push_block(b);
|
||||
BOOST_CHECK_EQUAL(db1.head_block_id().str(), db1_tip);
|
||||
BOOST_CHECK_EQUAL(db2.head_block_id().str(), b.id().str());
|
||||
}
|
||||
|
|
@ -171,8 +237,8 @@ BOOST_AUTO_TEST_CASE( fork_blocks )
|
|||
//The two databases are on distinct forks now, but at the same height. Make a block on db2, make it invalid, then
|
||||
//pass it to db1 and assert that db1 doesn't switch to the new fork.
|
||||
signed_block good_block;
|
||||
BOOST_CHECK_EQUAL(db1.head_block_num(), 23);
|
||||
BOOST_CHECK_EQUAL(db2.head_block_num(), 23);
|
||||
BOOST_CHECK_EQUAL(db1.head_block_num(), 13);
|
||||
BOOST_CHECK_EQUAL(db2.head_block_num(), 13);
|
||||
{
|
||||
now += db2.block_interval();
|
||||
auto b = db2.generate_block( now, db2.get_scheduled_witness( 1 ).first, delegate_priv_key );
|
||||
|
|
@ -180,14 +246,14 @@ BOOST_AUTO_TEST_CASE( fork_blocks )
|
|||
b.transactions.emplace_back(signed_transaction());
|
||||
b.transactions.back().operations.emplace_back(transfer_operation());
|
||||
b.sign(delegate_priv_key);
|
||||
BOOST_CHECK_EQUAL(b.block_num(), 24);
|
||||
BOOST_CHECK_EQUAL(b.block_num(), 14);
|
||||
BOOST_CHECK_THROW(db1.push_block(b), fc::exception);
|
||||
}
|
||||
BOOST_CHECK_EQUAL(db1.head_block_num(), 23);
|
||||
BOOST_CHECK_EQUAL(db1.head_block_num(), 13);
|
||||
BOOST_CHECK_EQUAL(db1.head_block_id().str(), db1_tip);
|
||||
|
||||
// assert that db1 switches to new fork with good block
|
||||
BOOST_CHECK_EQUAL(db2.head_block_num(), 24);
|
||||
BOOST_CHECK_EQUAL(db2.head_block_num(), 14);
|
||||
db1.push_block(good_block);
|
||||
BOOST_CHECK_EQUAL(db1.head_block_id().str(), db2.head_block_id().str());
|
||||
} catch (fc::exception& e) {
|
||||
|
|
@ -429,8 +495,8 @@ BOOST_FIXTURE_TEST_CASE( maintenance_interval, database_fixture )
|
|||
{
|
||||
account_update_operation op;
|
||||
op.account = nathan.id;
|
||||
op.vote = nathan.votes;
|
||||
op.vote->insert(nathans_delegate.vote_id);
|
||||
op.new_options = nathan.options;
|
||||
op.new_options->votes.insert(nathans_delegate.vote_id);
|
||||
trx.operations.push_back(op);
|
||||
db.push_transaction(trx, ~0);
|
||||
trx.operations.clear();
|
||||
|
|
@ -755,9 +821,9 @@ BOOST_FIXTURE_TEST_CASE( witness_scheduler_missed_blocks, database_fixture )
|
|||
});
|
||||
|
||||
near_schedule = db.get_near_witness_schedule();
|
||||
idump((db.head_block_time()));
|
||||
//idump((db.head_block_time()));
|
||||
generate_block(0, generate_private_key("genesis"), 2);
|
||||
idump((db.head_block_time()));
|
||||
//idump((db.head_block_time()));
|
||||
BOOST_CHECK(db.get_dynamic_global_properties().current_witness == near_schedule[2]);
|
||||
|
||||
near_schedule.erase(near_schedule.begin(), near_schedule.begin() + 3);
|
||||
|
|
|
|||
107
tests/tests/fee_tests.cpp
Normal file
107
tests/tests/fee_tests.cpp
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Cryptonomex, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
|
||||
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted until September 8, 2015, provided that the following conditions are met:
|
||||
*
|
||||
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <graphene/chain/vesting_balance_object.hpp>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "../common/database_fixture.hpp"
|
||||
|
||||
using namespace graphene::chain;
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE( fee_tests, database_fixture )
|
||||
|
||||
BOOST_AUTO_TEST_CASE( cashback_test )
|
||||
{ try {
|
||||
/* Account Structure used in this test *
|
||||
* *
|
||||
* /-----------------\ /-------------------\ *
|
||||
* | life (Lifetime) | | reggie (Lifetime) | *
|
||||
* \-----------------/ \-------------------/ *
|
||||
* | Refers & | Refers | Registers | Registers *
|
||||
* v Registers v v | *
|
||||
* /----------------\ /----------------\ | *
|
||||
* | ann (Annual) | | dumy (basic) | | *
|
||||
* \----------------/ \----------------/ |-------------. *
|
||||
* | Refers L--------------------------------. | | *
|
||||
* v Refers v v | *
|
||||
* /----------------\ /----------------\ | *
|
||||
* | scud (basic) |<------------------------| stud (basic) | | *
|
||||
* \----------------/ Registers | (Upgrades to | | *
|
||||
* | Lifetime) | v *
|
||||
* \----------------/ /--------------\ *
|
||||
* L------->| pleb (Basic) | *
|
||||
* Refers \--------------/ *
|
||||
* *
|
||||
*/
|
||||
ACTOR(life);
|
||||
ACTOR(reggie);
|
||||
PREP_ACTOR(ann);
|
||||
PREP_ACTOR(scud);
|
||||
PREP_ACTOR(dumy);
|
||||
PREP_ACTOR(stud);
|
||||
PREP_ACTOR(pleb);
|
||||
transfer(account_id_type(), life_id, asset(100000000));
|
||||
transfer(account_id_type(), reggie_id, asset(100000000));
|
||||
upgrade_to_lifetime_member(life_id);
|
||||
upgrade_to_lifetime_member(reggie_id);
|
||||
enable_fees(10000);
|
||||
const auto& fees = db.get_global_properties().parameters.current_fees;
|
||||
|
||||
#define CustomRegisterActor(actor_name, registrar_name, referrer_name, referrer_rate) \
|
||||
account_id_type actor_name ## _id; \
|
||||
{ \
|
||||
account_create_operation op; \
|
||||
op.registrar = registrar_name ## _id; \
|
||||
op.referrer = referrer_name ## _id; \
|
||||
op.referrer_percent = referrer_rate*GRAPHENE_1_PERCENT; \
|
||||
op.name = BOOST_PP_STRINGIZE(actor_name); \
|
||||
op.options.memo_key = actor_name ## _key_id; \
|
||||
op.active = authority(1, actor_name ## _key_id, 1); \
|
||||
op.owner = op.active; \
|
||||
op.fee = op.calculate_fee(fees); \
|
||||
trx.operations = {op}; \
|
||||
trx.sign(registrar_name ## _key_id, registrar_name ## _private_key); \
|
||||
actor_name ## _id = db.push_transaction(trx).operation_results.front().get<object_id_type>(); \
|
||||
trx.clear(); \
|
||||
}
|
||||
|
||||
CustomRegisterActor(ann, life, life, 75);
|
||||
|
||||
transfer(life_id, ann_id, asset(1000000));
|
||||
upgrade_to_annual_member(ann_id);
|
||||
|
||||
CustomRegisterActor(dumy, reggie, life, 75);
|
||||
CustomRegisterActor(stud, reggie, ann, 80);
|
||||
|
||||
transfer(life_id, stud_id, asset(1000000));
|
||||
upgrade_to_lifetime_member(stud_id);
|
||||
|
||||
CustomRegisterActor(pleb, reggie, stud, 95);
|
||||
CustomRegisterActor(scud, stud, ann, 80);
|
||||
|
||||
generate_block();
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true);
|
||||
|
||||
BOOST_CHECK_EQUAL(life_id(db).cashback_balance(db).balance.amount.value, 78000);
|
||||
BOOST_CHECK_EQUAL(reggie_id(db).cashback_balance(db).balance.amount.value, 34000);
|
||||
BOOST_CHECK_EQUAL(ann_id(db).cashback_balance(db).balance.amount.value, 40000);
|
||||
BOOST_CHECK_EQUAL(stud_id(db).cashback_balance(db).balance.amount.value, 8000);
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
@ -39,6 +39,88 @@ using namespace graphene::chain;
|
|||
|
||||
BOOST_FIXTURE_TEST_SUITE( operation_tests, database_fixture )
|
||||
|
||||
BOOST_AUTO_TEST_CASE( call_order_update_test )
|
||||
{
|
||||
try {
|
||||
BOOST_TEST_MESSAGE("creating actors dan and sam" );
|
||||
ACTORS((dan)(sam));
|
||||
const auto& bitusd = create_bitasset("BITUSD");
|
||||
const auto& core = asset_id_type()(db);
|
||||
|
||||
transfer(genesis_account, dan_id, asset(10000000));
|
||||
update_feed_producers( bitusd, {sam.id} );
|
||||
|
||||
price_feed current_feed; current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);
|
||||
publish_feed( bitusd, sam, current_feed );
|
||||
|
||||
FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );
|
||||
|
||||
auto default_call_price = ~price::call_price( bitusd.amount(5000), asset(5000), 1750);
|
||||
|
||||
BOOST_TEST_MESSAGE( "attempting to borrow using 2x collateral at 1:1 price now that there is a valid order" );
|
||||
borrow( dan, bitusd.amount(5000), asset(10000), default_call_price );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 );
|
||||
|
||||
BOOST_TEST_MESSAGE( "covering 2500 usd and freeing 5000 core..." );
|
||||
cover( dan, bitusd.amount(2500), asset(5000), default_call_price );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 5000 );
|
||||
|
||||
BOOST_TEST_MESSAGE( "verifying that attempting to cover the full amount without claiming the collateral fails" );
|
||||
BOOST_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(0), default_call_price ), fc::exception );
|
||||
|
||||
cover( dan, bitusd.amount(2500), core.amount(5000), default_call_price );
|
||||
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 0 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 );
|
||||
|
||||
borrow( dan, bitusd.amount(5000), asset(10000), default_call_price );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 );
|
||||
|
||||
|
||||
// test just increasing collateral
|
||||
BOOST_TEST_MESSAGE( "increasing collateral" );
|
||||
borrow( dan, bitusd.amount(0), asset(10000), default_call_price );
|
||||
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 20000 );
|
||||
|
||||
// test just decreasing debt
|
||||
BOOST_TEST_MESSAGE( "decreasing debt" );
|
||||
cover( dan, bitusd.amount(1000), asset(0), default_call_price );
|
||||
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 4000 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 20000 );
|
||||
|
||||
BOOST_TEST_MESSAGE( "increasing debt without increasing collateral" );
|
||||
borrow( dan, bitusd.amount(1000), asset(0), default_call_price );
|
||||
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 20000 );
|
||||
|
||||
BOOST_TEST_MESSAGE( "increasing debt without increasing collateral again" );
|
||||
BOOST_REQUIRE_THROW( borrow( dan, bitusd.amount(80000), asset(0), default_call_price ), fc::exception );
|
||||
BOOST_TEST_MESSAGE( "attempting to claim all collateral without paying off debt" );
|
||||
BOOST_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(20000), default_call_price ), fc::exception );
|
||||
BOOST_TEST_MESSAGE( "attempting reduce collateral without paying off any debt" );
|
||||
cover( dan, bitusd.amount(0), asset(1000), default_call_price );
|
||||
|
||||
BOOST_TEST_MESSAGE( "attempting change call price without changing debt/collateral ratio" );
|
||||
default_call_price = ~price::call_price( bitusd.amount(100), asset(50), 1750);
|
||||
cover( dan, bitusd.amount(0), asset(0), default_call_price );
|
||||
|
||||
BOOST_TEST_MESSAGE( "attempting change call price to be below minimum for debt/collateral ratio" );
|
||||
default_call_price = ~price::call_price( bitusd.amount(100), asset(500), 1750);
|
||||
BOOST_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(0), default_call_price ), fc::exception );
|
||||
|
||||
} catch (fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( create_account_test )
|
||||
{
|
||||
try {
|
||||
|
|
@ -57,8 +139,8 @@ BOOST_AUTO_TEST_CASE( create_account_test )
|
|||
REQUIRE_THROW_WITH_VALUE(op, name, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
REQUIRE_THROW_WITH_VALUE(op, name, "aaaa.");
|
||||
REQUIRE_THROW_WITH_VALUE(op, name, ".aaaa");
|
||||
REQUIRE_THROW_WITH_VALUE(op, voting_account, account_id_type(999999999));
|
||||
REQUIRE_THROW_WITH_VALUE(op, memo_key, key_id_type(999999999));
|
||||
REQUIRE_THROW_WITH_VALUE(op, options.voting_account, account_id_type(999999999));
|
||||
REQUIRE_THROW_WITH_VALUE(op, options.memo_key, key_id_type(999999999));
|
||||
|
||||
auto auth_bak = op.owner;
|
||||
op.owner.add_authority(account_id_type(9999999999), 10);
|
||||
|
|
@ -85,8 +167,8 @@ BOOST_AUTO_TEST_CASE( create_account_test )
|
|||
BOOST_CHECK(nathan_account.owner.auths.at(genesis_key) == 123);
|
||||
BOOST_REQUIRE(nathan_account.active.auths.size() == 1);
|
||||
BOOST_CHECK(nathan_account.active.auths.at(genesis_key) == 321);
|
||||
BOOST_CHECK(nathan_account.voting_account == account_id_type());
|
||||
BOOST_CHECK(nathan_account.memo_key == genesis_key);
|
||||
BOOST_CHECK(nathan_account.options.voting_account == account_id_type());
|
||||
BOOST_CHECK(nathan_account.options.memo_key == genesis_key);
|
||||
|
||||
const account_statistics_object& statistics = nathan_account.statistics(db);
|
||||
BOOST_CHECK(statistics.id.space() == implementation_ids);
|
||||
|
|
@ -159,13 +241,13 @@ BOOST_AUTO_TEST_CASE( update_account )
|
|||
op.account = nathan.id;
|
||||
op.owner = authority(2, key_id, 1, key_id_type(), 1);
|
||||
op.active = authority(2, key_id, 1, key_id_type(), 1);
|
||||
//op.voting_account = key_id;
|
||||
op.vote = flat_set<vote_id_type>({active_delegates[0](db).vote_id, active_delegates[5](db).vote_id});
|
||||
op.new_options = nathan.options;
|
||||
op.new_options->votes = flat_set<vote_id_type>({active_delegates[0](db).vote_id, active_delegates[5](db).vote_id});
|
||||
op.new_options->num_committee = 2;
|
||||
trx.operations.back() = op;
|
||||
db.push_transaction(trx, ~0);
|
||||
|
||||
//BOOST_CHECK(nathan.voting_key == key_id);
|
||||
BOOST_CHECK(nathan.memo_key == key_id_type());
|
||||
BOOST_CHECK(nathan.options.memo_key == key_id_type());
|
||||
BOOST_CHECK(nathan.active.weight_threshold == 2);
|
||||
BOOST_CHECK(nathan.active.auths.size() == 2);
|
||||
BOOST_CHECK(nathan.active.auths.at(key_id) == 1);
|
||||
|
|
@ -174,7 +256,7 @@ BOOST_AUTO_TEST_CASE( update_account )
|
|||
BOOST_CHECK(nathan.owner.auths.size() == 2);
|
||||
BOOST_CHECK(nathan.owner.auths.at(key_id) == 1);
|
||||
BOOST_CHECK(nathan.owner.auths.at(key_id_type()) == 1);
|
||||
BOOST_CHECK(nathan.votes.size() == 2);
|
||||
BOOST_CHECK(nathan.options.votes.size() == 2);
|
||||
|
||||
/** these votes are no longer tallied in real time
|
||||
BOOST_CHECK(active_delegates[0](db).vote(db).total_votes == 30000);
|
||||
|
|
@ -313,13 +395,12 @@ BOOST_AUTO_TEST_CASE( update_mia )
|
|||
pop.asset_id = bit_usd.get_id();
|
||||
pop.publisher = account_id_type(1);
|
||||
price_feed feed;
|
||||
feed.call_limit = price(bit_usd.amount(5), bit_usd.amount(5));
|
||||
feed.settlement_price = price(bit_usd.amount(5), bit_usd.amount(5));
|
||||
REQUIRE_THROW_WITH_VALUE(pop, feed, feed);
|
||||
feed.call_limit = price(bit_usd.amount(5), asset(5));
|
||||
feed.settlement_price = price(bit_usd.amount(5), asset(5));
|
||||
REQUIRE_THROW_WITH_VALUE(pop, feed, feed);
|
||||
pop.feed = feed;
|
||||
REQUIRE_THROW_WITH_VALUE(pop, feed.max_margin_period_sec, 0);
|
||||
REQUIRE_THROW_WITH_VALUE(pop, feed.required_maintenance_collateral, 0);
|
||||
REQUIRE_THROW_WITH_VALUE(pop, feed.maintenance_collateral_ratio, 0);
|
||||
trx.operations.back() = pop;
|
||||
db.push_transaction(trx, ~0);
|
||||
}
|
||||
|
|
@ -533,9 +614,9 @@ BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new )
|
|||
|
||||
BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 9700 );
|
||||
|
||||
print_market( "", "" );
|
||||
//print_market( "", "" );
|
||||
auto unmatched = create_sell_order( seller_account, core_asset.amount(300), test_asset.amount(150) );
|
||||
print_market( "", "" );
|
||||
//print_market( "", "" );
|
||||
BOOST_CHECK( !db.find( first_id ) );
|
||||
BOOST_CHECK( !db.find( second_id ) );
|
||||
BOOST_CHECK( db.find( third_id ) );
|
||||
|
|
@ -573,9 +654,9 @@ BOOST_AUTO_TEST_CASE( create_buy_exact_match_uia )
|
|||
|
||||
BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 9700 );
|
||||
|
||||
print_market( "", "" );
|
||||
//print_market( "", "" );
|
||||
auto unmatched = create_sell_order( seller_account, core_asset.amount(100), test_asset.amount(100) );
|
||||
print_market( "", "" );
|
||||
//print_market( "", "" );
|
||||
BOOST_CHECK( !db.find( first_id ) );
|
||||
BOOST_CHECK( db.find( second_id ) );
|
||||
BOOST_CHECK( db.find( third_id ) );
|
||||
|
|
@ -614,9 +695,9 @@ BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse )
|
|||
|
||||
BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 9700 );
|
||||
|
||||
print_market( "", "" );
|
||||
//print_market( "", "" );
|
||||
auto unmatched = create_sell_order( seller_account, core_asset.amount(300), test_asset.amount(150) );
|
||||
print_market( "", "" );
|
||||
//print_market( "", "" );
|
||||
BOOST_CHECK( !db.find( first_id ) );
|
||||
BOOST_CHECK( !db.find( second_id ) );
|
||||
BOOST_CHECK( db.find( third_id ) );
|
||||
|
|
@ -656,9 +737,9 @@ BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse_fract )
|
|||
|
||||
BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 9700 );
|
||||
|
||||
print_market( "", "" );
|
||||
//print_market( "", "" );
|
||||
auto unmatched = create_sell_order( seller_account, core_asset.amount(30), test_asset.amount(150) );
|
||||
print_market( "", "" );
|
||||
//print_market( "", "" );
|
||||
BOOST_CHECK( !db.find( first_id ) );
|
||||
BOOST_CHECK( !db.find( second_id ) );
|
||||
BOOST_CHECK( db.find( third_id ) );
|
||||
|
|
@ -786,9 +867,7 @@ BOOST_AUTO_TEST_CASE( delegate_feeds )
|
|||
|
||||
asset_publish_feed_operation op({asset(), active_witnesses[0]});
|
||||
op.asset_id = bit_usd.get_id();
|
||||
op.feed.call_limit = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(30));
|
||||
// We'll expire margins after a month
|
||||
op.feed.max_margin_period_sec = fc::days(30).to_seconds();
|
||||
op.feed.settlement_price = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(30));
|
||||
// Accept defaults for required collateral
|
||||
trx.operations.emplace_back(op);
|
||||
db.push_transaction(trx, ~0);
|
||||
|
|
@ -807,31 +886,26 @@ BOOST_AUTO_TEST_CASE( delegate_feeds )
|
|||
}
|
||||
|
||||
const asset_bitasset_data_object& bitasset = bit_usd.bitasset_data(db);
|
||||
BOOST_CHECK(bitasset.current_feed.call_limit.to_real() == GRAPHENE_BLOCKCHAIN_PRECISION / 30.0);
|
||||
BOOST_CHECK(bitasset.current_feed.max_margin_period_sec == fc::days(30).to_seconds());
|
||||
BOOST_CHECK(bitasset.current_feed.required_maintenance_collateral == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
BOOST_CHECK(bitasset.current_feed.settlement_price.to_real() == GRAPHENE_BLOCKCHAIN_PRECISION / 30.0);
|
||||
BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
|
||||
op.publisher = active_witnesses[1];
|
||||
op.feed.call_limit = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(25));
|
||||
op.feed.max_margin_period_sec = fc::days(10).to_seconds();
|
||||
op.feed.settlement_price = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(25));
|
||||
trx.operations.back() = op;
|
||||
db.push_transaction(trx, ~0);
|
||||
|
||||
BOOST_CHECK_EQUAL(bitasset.current_feed.call_limit.to_real(), GRAPHENE_BLOCKCHAIN_PRECISION / 25.0);
|
||||
BOOST_CHECK(bitasset.current_feed.max_margin_period_sec == fc::days(30).to_seconds());
|
||||
BOOST_CHECK(bitasset.current_feed.required_maintenance_collateral == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), GRAPHENE_BLOCKCHAIN_PRECISION / 25.0);
|
||||
BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
|
||||
op.publisher = active_witnesses[2];
|
||||
op.feed.call_limit = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(40));
|
||||
op.feed.max_margin_period_sec = fc::days(100).to_seconds();
|
||||
op.feed.settlement_price = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(40));
|
||||
// But this delegate is an idiot.
|
||||
op.feed.required_maintenance_collateral = 1000;
|
||||
op.feed.maintenance_collateral_ratio = 1000;
|
||||
trx.operations.back() = op;
|
||||
db.push_transaction(trx, ~0);
|
||||
|
||||
BOOST_CHECK_EQUAL(bitasset.current_feed.call_limit.to_real(), GRAPHENE_BLOCKCHAIN_PRECISION / 30.0);
|
||||
BOOST_CHECK(bitasset.current_feed.max_margin_period_sec == fc::days(30).to_seconds());
|
||||
BOOST_CHECK(bitasset.current_feed.required_maintenance_collateral == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), GRAPHENE_BLOCKCHAIN_PRECISION / 30.0);
|
||||
BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
} catch (const fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
|
|
@ -839,119 +913,6 @@ BOOST_AUTO_TEST_CASE( delegate_feeds )
|
|||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE( full_cover_test )
|
||||
{
|
||||
try {
|
||||
FC_ASSERT( "Reimplement with new borrowing semantics" );
|
||||
/*
|
||||
const asset_object& bit_usd = get_asset("BITUSD");
|
||||
const asset_object& core = asset_id_type()(db);
|
||||
const account_object& debt_holder = get_account("shorter1");
|
||||
const account_object& usd_holder = get_account("buyer");
|
||||
auto& index = db.get_index_type<call_order_index>().indices().get<by_account>();
|
||||
|
||||
BOOST_CHECK(index.find(boost::make_tuple(debt_holder.id, bit_usd.id)) != index.end());
|
||||
|
||||
transfer(usd_holder, debt_holder, bit_usd.amount(100), bit_usd.amount(0));
|
||||
|
||||
call_order_update_operation op;
|
||||
op.funding_account = debt_holder.id;
|
||||
op.collateral_to_add = core.amount(-400);
|
||||
op.amount_to_cover = bit_usd.amount(100);
|
||||
|
||||
trx.operations.push_back(op);
|
||||
REQUIRE_THROW_WITH_VALUE(op, funding_account, usd_holder.id);
|
||||
REQUIRE_THROW_WITH_VALUE(op, amount_to_cover, bit_usd.amount(-20));
|
||||
REQUIRE_THROW_WITH_VALUE(op, amount_to_cover, bit_usd.amount(200));
|
||||
REQUIRE_THROW_WITH_VALUE(op, collateral_to_add, core.amount(GRAPHENE_INITIAL_SUPPLY));
|
||||
REQUIRE_THROW_WITH_VALUE(op, collateral_to_add, bit_usd.amount(20));
|
||||
REQUIRE_THROW_WITH_VALUE(op, maintenance_collateral_ratio, 2);
|
||||
trx.operations.back() = op;
|
||||
db.push_transaction(trx, ~0);
|
||||
|
||||
BOOST_CHECK_EQUAL(get_balance(debt_holder, bit_usd), 0);
|
||||
BOOST_CHECK(index.find(boost::make_tuple(debt_holder.id, bit_usd.id)) == index.end());
|
||||
*/
|
||||
} catch( fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( partial_cover_test )
|
||||
{
|
||||
try {
|
||||
FC_ASSERT( "Reimplement with new borrowing semantics" );
|
||||
const asset_object& bit_usd = get_asset("BITUSD");
|
||||
const asset_object& core = asset_id_type()(db);
|
||||
const account_object& debt_holder = get_account("shorter1");
|
||||
const account_object& usd_holder = get_account("buyer");
|
||||
auto& index = db.get_index_type<call_order_index>().indices().get<by_account>();
|
||||
const call_order_object& debt = *index.find(boost::make_tuple(debt_holder.id, bit_usd.id));
|
||||
|
||||
BOOST_CHECK(index.find(boost::make_tuple(debt_holder.id, bit_usd.id)) != index.end());
|
||||
|
||||
ilog("..." );
|
||||
transfer(usd_holder, debt_holder, bit_usd.amount(50), bit_usd.amount(0));
|
||||
ilog("..." );
|
||||
BOOST_CHECK_EQUAL(get_balance(debt_holder, bit_usd), 50);
|
||||
|
||||
trx.operations.clear();
|
||||
call_order_update_operation op;
|
||||
op.funding_account = debt_holder.id;
|
||||
op.collateral_to_add = core.amount(0);
|
||||
op.amount_to_cover = bit_usd.amount(50);
|
||||
trx.operations.push_back(op);
|
||||
db.push_transaction(trx, ~0);
|
||||
|
||||
BOOST_CHECK_EQUAL(get_balance(debt_holder, bit_usd), 0);
|
||||
BOOST_CHECK(index.find(boost::make_tuple(debt_holder.id, bit_usd.id)) != index.end());
|
||||
BOOST_CHECK_EQUAL(debt.debt.value, 50);
|
||||
BOOST_CHECK_EQUAL(debt.collateral.value, 400);
|
||||
BOOST_CHECK(debt.call_price == price(core.amount(300), bit_usd.amount(50)));
|
||||
|
||||
op.collateral_to_add = core.amount(52);
|
||||
op.amount_to_cover = bit_usd.amount(0);
|
||||
trx.operations.back() = op;
|
||||
db.push_transaction(trx, ~0);
|
||||
ilog("..." );
|
||||
|
||||
BOOST_CHECK(debt.call_price == price(core.amount(339), bit_usd.amount(50)));
|
||||
|
||||
op.collateral_to_add = core.amount(0);
|
||||
op.amount_to_cover = bit_usd.amount(0);
|
||||
op.maintenance_collateral_ratio = 1800;
|
||||
REQUIRE_THROW_WITH_VALUE(op, maintenance_collateral_ratio, 1300);
|
||||
REQUIRE_THROW_WITH_VALUE(op, maintenance_collateral_ratio, 2500);
|
||||
op.collateral_to_add = core.amount(8);
|
||||
trx.operations.back() = op;
|
||||
db.push_transaction(trx, ~0);
|
||||
|
||||
BOOST_CHECK(debt.call_price == price(core.amount(368), bit_usd.amount(50)));
|
||||
|
||||
op.amount_to_cover = bit_usd.amount(50);
|
||||
op.collateral_to_add.amount = 0;
|
||||
trx.operations.back() = op;
|
||||
BOOST_CHECK_EQUAL(get_balance(debt_holder, bit_usd), 0);
|
||||
BOOST_CHECK_THROW(db.push_transaction(trx, ~0), fc::exception);
|
||||
|
||||
trx.operations.clear();
|
||||
ilog("..." );
|
||||
transfer(usd_holder, debt_holder, bit_usd.amount(50), bit_usd.amount(0));
|
||||
trx.operations.clear();
|
||||
op.collateral_to_add.amount = -460;
|
||||
op.validate();
|
||||
ilog("..." );
|
||||
trx.operations.push_back(op);
|
||||
db.push_transaction(trx, ~0);
|
||||
|
||||
BOOST_CHECK(index.find(boost::make_tuple(debt_holder.id, bit_usd.id)) == index.end());
|
||||
} catch( fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an order such that when the trade executes at the
|
||||
|
|
@ -976,9 +937,9 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero )
|
|||
BOOST_CHECK_EQUAL(get_balance(core_seller, test), 0);
|
||||
BOOST_CHECK_EQUAL(get_balance(core_seller, core), 100000000);
|
||||
|
||||
ilog( "=================================== START===================================\n\n");
|
||||
//ilog( "=================================== START===================================\n\n");
|
||||
create_sell_order(core_seller, core.amount(1), test.amount(900000));
|
||||
ilog( "=================================== STEP===================================\n\n");
|
||||
//ilog( "=================================== STEP===================================\n\n");
|
||||
create_sell_order(core_buyer, test.amount(900001), core.amount(1));
|
||||
} catch( const fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
|
|
@ -1380,7 +1341,7 @@ BOOST_AUTO_TEST_CASE( witness_withdraw_pay_test )
|
|||
* 3) Ensure that margin calls do not occur even if the highest bid would indicate it
|
||||
* 4) Match some Orders
|
||||
* 5) Trigger Global Settle on the Asset
|
||||
* 6) The maitenance collateral must always be 1:1
|
||||
* 6) The maintenance collateral must always be 1:1
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES( unimp_prediction_market_test, 1 )
|
||||
BOOST_AUTO_TEST_CASE( unimp_prediction_market_test )
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE( withdraw_permission_nominal_case )
|
|||
while(true)
|
||||
{
|
||||
const withdraw_permission_object& permit_object = permit(db);
|
||||
wdump( (permit_object) );
|
||||
//wdump( (permit_object) );
|
||||
withdraw_permission_claim_operation op;
|
||||
op.withdraw_permission = permit;
|
||||
op.withdraw_from_account = nathan_id;
|
||||
|
|
@ -336,38 +336,32 @@ BOOST_AUTO_TEST_CASE( mia_feeds )
|
|||
const asset_object& bit_usd = bit_usd_id(db);
|
||||
asset_publish_feed_operation op({asset(), vikram_id});
|
||||
op.asset_id = bit_usd_id;
|
||||
op.feed.call_limit = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(30));
|
||||
op.feed.settlement_price = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(30));
|
||||
// We'll expire margins after a month
|
||||
op.feed.max_margin_period_sec = fc::days(30).to_seconds();
|
||||
// Accept defaults for required collateral
|
||||
trx.operations.emplace_back(op);
|
||||
db.push_transaction(trx, ~0);
|
||||
|
||||
const asset_bitasset_data_object& bitasset = bit_usd.bitasset_data(db);
|
||||
BOOST_CHECK(bitasset.current_feed.call_limit.to_real() == GRAPHENE_BLOCKCHAIN_PRECISION / 30.0);
|
||||
BOOST_CHECK(bitasset.current_feed.max_margin_period_sec == fc::days(30).to_seconds());
|
||||
BOOST_CHECK(bitasset.current_feed.required_maintenance_collateral == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
BOOST_CHECK(bitasset.current_feed.settlement_price.to_real() == GRAPHENE_BLOCKCHAIN_PRECISION / 30.0);
|
||||
BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
|
||||
op.publisher = ben_id;
|
||||
op.feed.call_limit = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(25));
|
||||
op.feed.max_margin_period_sec = fc::days(10).to_seconds();
|
||||
op.feed.settlement_price = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(25));
|
||||
trx.operations.back() = op;
|
||||
db.push_transaction(trx, ~0);
|
||||
|
||||
BOOST_CHECK_EQUAL(bitasset.current_feed.call_limit.to_real(), GRAPHENE_BLOCKCHAIN_PRECISION / 25.0);
|
||||
BOOST_CHECK(bitasset.current_feed.max_margin_period_sec == fc::days(30).to_seconds());
|
||||
BOOST_CHECK(bitasset.current_feed.required_maintenance_collateral == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), GRAPHENE_BLOCKCHAIN_PRECISION / 25.0);
|
||||
BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
|
||||
op.publisher = dan_id;
|
||||
op.feed.call_limit = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(40));
|
||||
op.feed.max_margin_period_sec = fc::days(100).to_seconds();
|
||||
op.feed.required_maintenance_collateral = 1000;
|
||||
op.feed.settlement_price = price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(40));
|
||||
op.feed.maintenance_collateral_ratio = 1000;
|
||||
trx.operations.back() = op;
|
||||
db.push_transaction(trx, ~0);
|
||||
|
||||
BOOST_CHECK_EQUAL(bitasset.current_feed.call_limit.to_real(), GRAPHENE_BLOCKCHAIN_PRECISION / 30.0);
|
||||
BOOST_CHECK(bitasset.current_feed.max_margin_period_sec == fc::days(30).to_seconds());
|
||||
BOOST_CHECK(bitasset.current_feed.required_maintenance_collateral == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), GRAPHENE_BLOCKCHAIN_PRECISION / 30.0);
|
||||
BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);
|
||||
|
||||
op.publisher = nathan_id;
|
||||
trx.operations.back() = op;
|
||||
|
|
@ -389,10 +383,12 @@ BOOST_AUTO_TEST_CASE( witness_create )
|
|||
{
|
||||
account_update_operation op;
|
||||
op.account = nathan_id;
|
||||
op.vote = nathan_id(db).votes;
|
||||
op.vote->insert(nathan_witness_id(db).vote_id);
|
||||
op.num_witness = std::count_if(op.vote->begin(), op.vote->end(), [](vote_id_type id) { return id.type() == vote_id_type::witness; });
|
||||
op.num_committee = std::count_if(op.vote->begin(), op.vote->end(), [](vote_id_type id) { return id.type() == vote_id_type::committee; });
|
||||
op.new_options = nathan_id(db).options;
|
||||
op.new_options->votes.insert(nathan_witness_id(db).vote_id);
|
||||
op.new_options->num_witness = std::count_if(op.new_options->votes.begin(), op.new_options->votes.end(),
|
||||
[](vote_id_type id) { return id.type() == vote_id_type::witness; });
|
||||
op.new_options->num_committee = std::count_if(op.new_options->votes.begin(), op.new_options->votes.end(),
|
||||
[](vote_id_type id) { return id.type() == vote_id_type::committee; });
|
||||
trx.operations.push_back(op);
|
||||
trx.sign(nathan_key_id, nathan_private_key);
|
||||
db.push_transaction(trx);
|
||||
|
|
@ -533,8 +529,8 @@ BOOST_AUTO_TEST_CASE( worker_pay_test )
|
|||
{
|
||||
account_update_operation op;
|
||||
op.account = nathan_id;
|
||||
op.vote = nathan_id(db).votes;
|
||||
op.vote->insert(worker_id_type()(db).vote_for);
|
||||
op.new_options = nathan_id(db).options;
|
||||
op.new_options->votes.insert(worker_id_type()(db).vote_for);
|
||||
trx.operations.push_back(op);
|
||||
db.push_transaction(trx, ~0);
|
||||
trx.clear();
|
||||
|
|
@ -573,8 +569,8 @@ BOOST_AUTO_TEST_CASE( worker_pay_test )
|
|||
{
|
||||
account_update_operation op;
|
||||
op.account = nathan_id;
|
||||
op.vote = nathan_id(db).votes;
|
||||
op.vote->erase(worker_id_type()(db).vote_for);
|
||||
op.new_options = nathan_id(db).options;
|
||||
op.new_options->votes.erase(worker_id_type()(db).vote_for);
|
||||
trx.operations.push_back(op);
|
||||
db.push_transaction(trx, ~0);
|
||||
trx.clear();
|
||||
|
|
@ -646,8 +642,8 @@ BOOST_AUTO_TEST_CASE( refund_worker_test )
|
|||
{
|
||||
account_update_operation op;
|
||||
op.account = nathan_id;
|
||||
op.vote = nathan_id(db).votes;
|
||||
op.vote->insert(worker_id_type()(db).vote_for);
|
||||
op.new_options = nathan_id(db).options;
|
||||
op.new_options->votes.insert(worker_id_type()(db).vote_for);
|
||||
trx.operations.push_back(op);
|
||||
db.push_transaction(trx, ~0);
|
||||
trx.clear();
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ BOOST_AUTO_TEST_CASE( json_tests )
|
|||
{
|
||||
try {
|
||||
auto var = fc::json::variants_from_string( "10.6 " );
|
||||
wdump((var));
|
||||
var = fc::json::variants_from_string( "10.5" );
|
||||
wdump((var));
|
||||
} catch ( const fc::exception& e )
|
||||
{
|
||||
edump((e.to_detail_string()));
|
||||
|
|
|
|||
Loading…
Reference in a new issue