Merge branch 'github_master'

This commit is contained in:
Eric Frias 2015-10-06 17:15:34 -04:00
commit 9261314997
52 changed files with 1153 additions and 253 deletions

View file

@ -260,7 +260,7 @@ namespace graphene { namespace app {
{
case impl_global_property_object_type:{
} case impl_dynamic_global_property_object_type:{
} case impl_index_meta_object_type:{
} case impl_reserved0_object_type:{
} case impl_asset_dynamic_data_type:{
} case impl_asset_bitasset_data_type:{
break;
@ -293,6 +293,7 @@ namespace graphene { namespace app {
} case impl_account_transaction_history_object_type:{
} case impl_chain_property_object_type: {
} case impl_witness_schedule_object_type: {
} case impl_budget_record_object_type: {
}
}
}

View file

@ -334,7 +334,7 @@ namespace detail {
reset_p2p_node(_data_dir);
reset_websocket_server();
reset_websocket_tls_server();
} FC_CAPTURE_AND_RETHROW() }
} FC_LOG_AND_RETHROW() }
optional< api_access_info > get_api_access_info(const string& username)const
{
@ -859,7 +859,15 @@ void application::initialize(const fc::path& data_dir, const boost::program_opti
void application::startup()
{
try {
my->startup();
} catch ( const fc::exception& e ) {
elog( "${e}", ("e",e.to_detail_string()) );
throw;
} catch ( ... ) {
elog( "unexpected exception" );
throw;
}
}
std::shared_ptr<abstract_plugin> application::get_plugin(const string& name) const

View file

@ -92,6 +92,7 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
std::string get_transaction_hex(const signed_transaction& trx)const;
set<public_key_type> get_required_signatures( const signed_transaction& trx, const flat_set<public_key_type>& available_keys )const;
set<public_key_type> get_potential_signatures( const signed_transaction& trx )const;
set<address> get_potential_address_signatures( const signed_transaction& trx )const;
bool verify_authority( const signed_transaction& trx )const;
bool verify_account_authority( const string& name_or_id, const flat_set<public_key_type>& signers )const;
processed_transaction validate_transaction( const signed_transaction& trx )const;
@ -1213,6 +1214,10 @@ set<public_key_type> database_api::get_potential_signatures( const signed_transa
{
return my->get_potential_signatures( trx );
}
set<address> database_api::get_potential_address_signatures( const signed_transaction& trx )const
{
return my->get_potential_address_signatures( trx );
}
set<public_key_type> database_api_impl::get_potential_signatures( const signed_transaction& trx )const
{
@ -1242,6 +1247,31 @@ set<public_key_type> database_api_impl::get_potential_signatures( const signed_t
return result;
}
set<address> database_api_impl::get_potential_address_signatures( const signed_transaction& trx )const
{
set<address> result;
trx.get_required_signatures(
_db.get_chain_id(),
flat_set<public_key_type>(),
[&]( account_id_type id )
{
const auto& auth = id(_db).active;
for( const auto& k : auth.get_addresses() )
result.insert(k);
return &auth;
},
[&]( account_id_type id )
{
const auto& auth = id(_db).owner;
for( const auto& k : auth.get_addresses() )
result.insert(k);
return &auth;
},
_db.get_global_properties().parameters.max_authority_depth
);
return result;
}
bool database_api::verify_authority( const signed_transaction& trx )const
{
return my->verify_authority( trx );

View file

@ -416,6 +416,7 @@ class database_api
* to get the minimum subset.
*/
set<public_key_type> get_potential_signatures( const signed_transaction& trx )const;
set<address> get_potential_address_signatures( const signed_transaction& trx )const;
/**
* @return true of the @ref trx has all of the required signatures, otherwise throws an exception
@ -536,6 +537,7 @@ FC_API(graphene::app::database_api,
(get_transaction_hex)
(get_required_signatures)
(get_potential_signatures)
(get_potential_address_signatures)
(verify_authority)
(verify_account_authority)
(validate_transaction)

View file

@ -180,12 +180,26 @@ void_result account_whitelist_evaluator::do_apply(const account_whitelist_operat
a.whitelisting_accounts.insert(o.authorizing_account);
else
a.whitelisting_accounts.erase(o.authorizing_account);
if( o.new_listing & o.black_listed )
a.blacklisting_accounts.insert(o.authorizing_account);
else
a.blacklisting_accounts.erase(o.authorizing_account);
});
/** for tracking purposes only, this state is not needed to evaluate */
d.modify( o.authorizing_account(d), [&]( account_object& a ) {
if( o.new_listing & o.white_listed )
a.whitelisted_accounts.insert( o.account_to_list );
else
a.whitelisted_accounts.erase( o.account_to_list );
if( o.new_listing & o.black_listed )
a.blacklisted_accounts.insert( o.account_to_list );
else
a.blacklisted_accounts.erase( o.account_to_list );
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (o) ) }

View file

@ -245,7 +245,7 @@ processed_transaction database::validate_transaction( const signed_transaction&
}
processed_transaction database::push_proposal(const proposal_object& proposal)
{
{ try {
transaction_evaluation_state eval_state(this);
eval_state._is_proposed_trx = true;
@ -253,15 +253,20 @@ processed_transaction database::push_proposal(const proposal_object& proposal)
processed_transaction ptrx(proposal.proposed_transaction);
eval_state._trx = &ptrx;
auto session = _undo_db.start_undo_session();
for( auto& op : proposal.proposed_transaction.operations )
eval_state.operation_results.emplace_back(apply_operation(eval_state, op));
remove(proposal);
session.merge();
try {
auto session = _undo_db.start_undo_session(true);
for( auto& op : proposal.proposed_transaction.operations )
eval_state.operation_results.emplace_back(apply_operation(eval_state, op));
remove(proposal);
session.merge();
} catch ( const fc::exception& e ) {
elog( "e", ("e",e.to_detail_string() ) );
throw;
}
ptrx.operation_results = std::move(eval_state.operation_results);
return ptrx;
}
} FC_CAPTURE_AND_RETHROW( (proposal) ) }
signed_block database::generate_block(
fc::time_point_sec when,
@ -273,7 +278,7 @@ signed_block database::generate_block(
signed_block result;
detail::with_skip_flags( *this, skip, [&]()
{
result = _generate_block( when, witness_id, block_signing_private_key, true );
result = _generate_block( when, witness_id, block_signing_private_key );
} );
return result;
}
@ -281,8 +286,7 @@ signed_block database::generate_block(
signed_block database::_generate_block(
fc::time_point_sec when,
witness_id_type witness_id,
const fc::ecc::private_key& block_signing_private_key,
bool retry_on_failure
const fc::ecc::private_key& block_signing_private_key
)
{
try {
@ -353,8 +357,15 @@ signed_block database::_generate_block(
{
wlog( "Postponed ${n} transactions due to block size limit", ("n", postponed_tx_count) );
}
_pending_tx_session.reset();
// We have temporarily broken the invariant that
// _pending_tx_session is the result of applying _pending_tx, as
// _pending_tx now consists of the set of postponed transactions.
// However, the push_block() call below will re-create the
// _pending_tx_session.
pending_block.previous = head_block_id();
pending_block.timestamp = when;
pending_block.transaction_merkle_root = pending_block.calculate_merkle_root();
@ -363,6 +374,7 @@ signed_block database::_generate_block(
if( !(skip & skip_witness_signature) )
pending_block.sign( block_signing_private_key );
// TODO: Move this to _push_block() so session is restored.
FC_ASSERT( fc::raw::pack_size(pending_block) <= get_global_properties().parameters.maximum_block_size );
push_block( pending_block, skip );
@ -467,6 +479,7 @@ void database::_apply_block( const signed_block& next_block )
update_global_dynamic_data(next_block);
update_signing_witness(signing_witness, next_block);
update_last_irreversible_block();
// Are we at the maintenance interval?
if( maint_needed )
@ -643,4 +656,9 @@ void database::add_checkpoints( const flat_map<uint32_t,block_id_type>& checkpts
_checkpoints[i.first] = i.second;
}
bool database::before_last_checkpoint()const
{
return (_checkpoints.size() > 0) && (_checkpoints.rbegin()->first >= head_block_num());
}
} }

View file

@ -44,24 +44,24 @@ void database::debug_dump()
for( const account_balance_object& a : balance_index )
{
idump(("balance")(a));
// idump(("balance")(a));
total_balances[a.asset_type] += a.balance;
}
for( const account_statistics_object& s : statistics_index )
{
idump(("statistics")(s));
// idump(("statistics")(s));
reported_core_in_orders += s.total_core_in_orders;
}
for( const limit_order_object& o : db.get_index_type<limit_order_index>().indices() )
{
idump(("limit_order")(o));
// idump(("limit_order")(o));
auto for_sale = o.amount_for_sale();
if( for_sale.asset_id == asset_id_type() ) core_in_orders += for_sale.amount;
total_balances[for_sale.asset_id] += for_sale.amount;
}
for( const call_order_object& o : db.get_index_type<call_order_index>().indices() )
{
idump(("call_order")(o));
// idump(("call_order")(o));
auto col = o.get_collateral();
if( col.asset_id == asset_id_type() ) core_in_orders += col.amount;
total_balances[col.asset_id] += col.amount;
@ -71,17 +71,23 @@ void database::debug_dump()
{
total_balances[asset_obj.id] += asset_obj.dynamic_asset_data_id(db).accumulated_fees;
total_balances[asset_id_type()] += asset_obj.dynamic_asset_data_id(db).fee_pool;
// edump((total_balances[asset_obj.id])(asset_obj.dynamic_asset_data_id(db).current_supply ) );
}
if( total_balances[asset_id_type()].value != core_asset_data.current_supply.value )
{
edump( (total_balances[asset_id_type()].value)(core_asset_data.current_supply.value ));
}
edump((core_in_orders)(reported_core_in_orders));
/*
const auto& vbidx = db.get_index_type<simple_index<vesting_balance_object>>();
for( const auto& s : vbidx )
{
idump(("vesting_balance")(s));
// idump(("vesting_balance")(s));
}
*/
}
} }

View file

@ -22,6 +22,7 @@
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/balance_object.hpp>
#include <graphene/chain/block_summary_object.hpp>
#include <graphene/chain/budget_record_object.hpp>
#include <graphene/chain/chain_property_object.hpp>
#include <graphene/chain/committee_member_object.hpp>
#include <graphene/chain/global_property_object.hpp>
@ -194,6 +195,7 @@ void database::initialize_indexes()
add_index< primary_index<flat_index< block_summary_object >> >();
add_index< primary_index<simple_index<chain_property_object > > >();
add_index< primary_index<simple_index<witness_schedule_object > > >();
add_index< primary_index<simple_index<budget_record_object > > >();
}
void database::init_genesis(const genesis_state_type& genesis_state)
@ -458,6 +460,10 @@ void database::init_genesis(const genesis_state_type& genesis_state)
cop.active = cop.owner;
account_id_type owner_account_id = apply_operation(genesis_eval_state, cop).get<object_id_type>();
modify( owner_account_id(*this).statistics(*this), [&]( account_statistics_object& o ) {
o.total_core_in_orders = collateral_rec.collateral;
});
create<call_order_object>([&](call_order_object& c) {
c.borrower = owner_account_id;
c.collateral = collateral_rec.collateral;
@ -609,6 +615,8 @@ void database::init_genesis(const genesis_state_type& genesis_state)
wso.current_shuffled_witnesses.push_back( wid );
});
debug_dump();
_undo_db.enable();
} FC_CAPTURE_AND_RETHROW() }

View file

@ -1,19 +1,6 @@
/*
* 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 <boost/multiprecision/integer.hpp>
@ -143,19 +130,32 @@ void database::pay_workers( share_type& budget )
void database::update_active_witnesses()
{ try {
assert( _witness_count_histogram_buffer.size() > 0 );
share_type stake_target = _total_voting_stake / 2;
share_type stake_tally = _witness_count_histogram_buffer[0];
share_type stake_target = (_total_voting_stake-_witness_count_histogram_buffer[0]) / 2;
/// accounts that vote for 0 or 1 witness do not get to express an opinion on
/// the number of witnesses to have (they abstain and are non-voting accounts)
share_type stake_tally = 0;
size_t witness_count = 0;
if( stake_target > 0 )
{
while( (witness_count < _witness_count_histogram_buffer.size() - 1)
&& (stake_tally <= stake_target) )
{
stake_tally += _witness_count_histogram_buffer[++witness_count];
}
}
const chain_property_object& cpo = get_chain_properties();
auto wits = sort_votable_objects<witness_index>(std::max(witness_count*2+1, (size_t)cpo.immutable_parameters.min_witness_count));
edump((wits.size())(witness_count*2+1));
const global_property_object& gpo = get_global_properties();
for( const witness_object& wit : wits )
const auto& all_witnesses = get_index_type<witness_index>().indices();
for( const witness_object& wit : all_witnesses )
{
modify( wit, [&]( witness_object& obj ){
obj.total_votes = _vote_tally_buffer[wit.vote_id];
@ -212,8 +212,11 @@ void database::update_active_witnesses()
void database::update_active_committee_members()
{ try {
assert( _committee_count_histogram_buffer.size() > 0 );
uint64_t stake_target = _total_voting_stake / 2;
uint64_t stake_tally = _committee_count_histogram_buffer[0];
share_type stake_target = (_total_voting_stake-_witness_count_histogram_buffer[0]) / 2;
/// accounts that vote for 0 or 1 witness do not get to express an opinion on
/// the number of witnesses to have (they abstain and are non-voting accounts)
uint64_t stake_tally = 0; // _committee_count_histogram_buffer[0];
size_t committee_member_count = 0;
if( stake_target > 0 )
while( (committee_member_count < _committee_count_histogram_buffer.size() - 1)
@ -271,17 +274,25 @@ void database::update_active_committee_members()
});
} FC_CAPTURE_AND_RETHROW() }
share_type database::get_max_budget( fc::time_point_sec now )const
void database::initialize_budget_record( fc::time_point_sec now, budget_record& rec )const
{
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
const asset_object& core = asset_id_type(0)(*this);
const asset_dynamic_data_object& core_dd = core.dynamic_asset_data_id(*this);
rec.from_initial_reserve = core.reserved(*this);
rec.from_accumulated_fees = core_dd.accumulated_fees;
rec.from_unused_witness_budget = dpo.witness_budget;
if( (dpo.last_budget_time == fc::time_point_sec())
|| (now <= dpo.last_budget_time) )
return share_type(0);
{
rec.time_since_last_budget = 0;
return;
}
int64_t dt = (now - dpo.last_budget_time).to_seconds();
rec.time_since_last_budget = uint64_t( dt );
// We'll consider accumulated_fees to be reserved at the BEGINNING
// of the maintenance interval. However, for speed we only
@ -289,7 +300,7 @@ share_type database::get_max_budget( fc::time_point_sec now )const
// end of the maintenance interval. Thus the accumulated_fees
// are available for the budget at this point, but not included
// in core.reserved().
share_type reserve = core.reserved(*this) + core_dd.accumulated_fees;
share_type reserve = rec.from_initial_reserve + core_dd.accumulated_fees;
// Similarly, we consider leftover witness_budget to be burned
// at the BEGINNING of the maintenance interval.
reserve += dpo.witness_budget;
@ -304,11 +315,11 @@ share_type database::get_max_budget( fc::time_point_sec now )const
budget_u128 >>= GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS;
share_type budget;
if( budget_u128 < reserve.value )
budget = share_type(budget_u128.to_uint64());
rec.total_budget = share_type(budget_u128.to_uint64());
else
budget = reserve;
rec.total_budget = reserve;
return budget;
return;
}
/**
@ -342,10 +353,14 @@ void database::process_budget()
// blocks_to_maint > 0 because time_to_maint > 0,
// which means numerator is at least equal to block_interval
share_type available_funds = get_max_budget(now);
budget_record rec;
initialize_budget_record( now, rec );
share_type available_funds = rec.total_budget;
share_type witness_budget = gpo.parameters.witness_pay_per_block.value * blocks_to_maint;
rec.requested_witness_budget = witness_budget;
witness_budget = std::min(witness_budget, available_funds);
rec.witness_budget = witness_budget;
available_funds -= witness_budget;
fc::uint128_t worker_budget_u128 = gpo.parameters.worker_budget_per_day.value;
@ -357,24 +372,34 @@ void database::process_budget()
worker_budget = available_funds;
else
worker_budget = worker_budget_u128.to_uint64();
rec.worker_budget = worker_budget;
available_funds -= worker_budget;
share_type leftover_worker_funds = worker_budget;
pay_workers(leftover_worker_funds);
rec.leftover_worker_funds = leftover_worker_funds;
available_funds += leftover_worker_funds;
share_type unused_prev_witness_budget = dpo.witness_budget;
rec.supply_delta = rec.witness_budget
+ rec.worker_budget
- rec.leftover_worker_funds
- rec.from_accumulated_fees
- rec.from_unused_witness_budget;
modify(core, [&]( asset_dynamic_data_object& _core )
{
_core.current_supply = (_core.current_supply
+ witness_budget
_core.current_supply = (_core.current_supply + rec.supply_delta );
assert( rec.supply_delta ==
witness_budget
+ worker_budget
- leftover_worker_funds
- _core.accumulated_fees
- unused_prev_witness_budget
- dpo.witness_budget
);
_core.accumulated_fees = 0;
});
modify(dpo, [&]( dynamic_global_property_object& _dpo )
{
// Since initial witness_budget was rolled into
@ -384,6 +409,12 @@ void database::process_budget()
_dpo.last_budget_time = now;
});
create< budget_record_object >( [&]( budget_record_object& _rec )
{
_rec.time = head_block_time();
_rec.record = rec;
});
// available_funds is money we could spend, but don't want to.
// we simply let it evaporate back into the reserve.
}

View file

@ -21,6 +21,9 @@
#include <graphene/chain/operation_history_object.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
#include <fc/io/fstream.hpp>
#include <fstream>
#include <functional>
#include <iostream>
@ -105,6 +108,31 @@ void database::open(
{
try
{
auto is_new = [&]() -> bool
{
// directory doesn't exist
if( !fc::exists( data_dir ) )
return true;
// if directory exists but is empty, return true; else false.
return ( fc::directory_iterator( data_dir ) == fc::directory_iterator() );
};
auto is_outdated = [&]() -> bool
{
if( !fc::exists( data_dir / "db_version" ) )
return true;
std::string version_str;
fc::read_file_contents( data_dir / "db_version", version_str );
return (version_str != GRAPHENE_CURRENT_DB_VERSION);
};
if( (!is_new()) && is_outdated() )
{
ilog( "Old database version detected, reindex is required" );
wipe( data_dir, false );
fc::remove_all( data_dir / "db_version" );
}
object_database::open(data_dir);
_block_id_to_block.open(data_dir / "database" / "block_num_to_block");
@ -122,9 +150,22 @@ void database::open(
FC_ASSERT( head_block_num() == 0, "last block ID does not match current chain state" );
}
}
// doing this down here helps ensure that DB will be wiped
// if any of the above steps were interrupted on a previous run
if( !fc::exists( data_dir / "db_version" ) )
{
std::ofstream db_version(
(data_dir / "db_version").generic_string().c_str(),
std::ios::out | std::ios::binary | std::ios::trunc );
std::string version_string = GRAPHENE_CURRENT_DB_VERSION;
db_version.write( version_string.c_str(), version_string.size() );
db_version.close();
}
//idump((head_block_id())(head_block_num()));
}
FC_CAPTURE_AND_RETHROW( (data_dir) )
FC_CAPTURE_LOG_AND_RETHROW( (data_dir) )
}
void database::close(uint32_t blocks_to_rewind)
@ -155,6 +196,11 @@ void database::close(uint32_t blocks_to_rewind)
{
}
// Since pop_block() will move tx's in the popped blocks into pending,
// we have to clear_pending() after we're done popping to get a clean
// DB state (issue #336).
clear_pending();
object_database::flush();
object_database::close();

View file

@ -107,7 +107,9 @@ void database::cancel_order( const limit_order_object& order, bool create_virtua
modify( order.seller(*this).statistics(*this),[&]( account_statistics_object& obj ){
if( refunded.asset_id == asset_id_type() )
{
obj.total_core_in_orders -= refunded.amount;
}
});
adjust_balance(order.seller, refunded);
@ -220,19 +222,30 @@ int database::match( const limit_order_object& bid, const limit_order_object& as
}
asset database::match( const call_order_object& call, const force_settlement_object& settle, const price& match_price,
asset max_settlement )
{
assert(call.get_debt().asset_id == settle.balance.asset_id );
assert(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0);
asset database::match( const call_order_object& call,
const force_settlement_object& settle,
const price& match_price,
asset max_settlement )
{ try {
FC_ASSERT(call.get_debt().asset_id == settle.balance.asset_id );
FC_ASSERT(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0);
auto settle_for_sale = std::min(settle.balance, max_settlement);
auto call_debt = call.get_debt();
asset call_receives = std::min(settle_for_sale, call_debt),
call_pays = call_receives * match_price,
settle_pays = call_receives,
settle_receives = call_pays;
asset call_receives = std::min(settle_for_sale, call_debt);
asset call_pays = call_receives * match_price;
asset settle_pays = call_receives;
asset settle_receives = call_pays;
/**
* If the least collateralized call position lacks sufficient
* collateral to cover at the match price then this indicates a black
* swan event according to the price feed, but only the market
* can trigger a black swan. So now we must cancel the forced settlement
* object.
*/
GRAPHENE_ASSERT( call_pays < call.get_collateral(), black_swan_exception, "" );
assert( settle_pays == settle_for_sale || call_receives == call.get_debt() );
@ -240,12 +253,12 @@ asset database::match( const call_order_object& call, const force_settlement_obj
fill_order(settle, settle_pays, settle_receives);
return call_receives;
}
} FC_CAPTURE_AND_RETHROW( (call)(settle)(match_price)(max_settlement) ) }
bool database::fill_order( const limit_order_object& order, const asset& pays, const asset& receives )
{
assert( order.amount_for_sale().asset_id == pays.asset_id );
assert( pays.asset_id != receives.asset_id );
{ try {
FC_ASSERT( order.amount_for_sale().asset_id == pays.asset_id );
FC_ASSERT( pays.asset_id != receives.asset_id );
const account_object& seller = order.seller(*this);
const asset_object& recv_asset = receives.asset_id(*this);
@ -279,15 +292,15 @@ bool database::fill_order( const limit_order_object& order, const asset& pays, c
}
return false;
}
}
} FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) }
bool database::fill_order( const call_order_object& order, const asset& pays, const asset& receives )
{ try {
//idump((pays)(receives)(order));
assert( order.get_debt().asset_id == receives.asset_id );
assert( order.get_collateral().asset_id == pays.asset_id );
assert( order.get_collateral() >= pays );
FC_ASSERT( order.get_debt().asset_id == receives.asset_id );
FC_ASSERT( order.get_collateral().asset_id == pays.asset_id );
FC_ASSERT( order.get_collateral() >= pays );
optional<asset> collateral_freed;
modify( order, [&]( call_order_object& o ){
@ -315,11 +328,13 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co
const account_statistics_object& borrower_statistics = borrower.statistics(*this);
if( collateral_freed )
adjust_balance(borrower.get_id(), *collateral_freed);
modify( borrower_statistics, [&]( account_statistics_object& b ){
if( collateral_freed && collateral_freed->amount > 0 )
b.total_core_in_orders -= collateral_freed->amount;
if( pays.asset_id == asset_id_type() )
b.total_core_in_orders -= pays.amount;
assert( b.total_core_in_orders >= 0 );
});
}
@ -374,6 +389,10 @@ bool database::fill_order(const force_settlement_object& settle, const asset& pa
bool database::check_call_orders(const asset_object& mia, bool enable_black_swan)
{ try {
if( !mia.is_market_issued() ) return false;
if( check_for_blackswan( mia, enable_black_swan ) )
return false;
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);
if( bitasset.is_prediction_market ) return false;
if( bitasset.current_feed.settlement_price.is_null() ) return false;
@ -395,15 +414,23 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan
auto limit_end = limit_price_index.upper_bound( min_price );
if( limit_itr == limit_end ) {
/*
if( head_block_num() > 300000 )
ilog( "no orders below between: ${p} and: ${m}",
("p", bitasset.current_feed.max_short_squeeze_price())
("m", max_price) );
*/
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 ) );
auto call_min = price::min( bitasset.options.short_backing_asset, mia.id );
auto call_max = price::max( bitasset.options.short_backing_asset, mia.id );
auto call_itr = call_price_index.lower_bound( call_min );
auto call_end = call_price_index.upper_bound( call_max );
bool filled_limit = false;
while( call_itr != call_end )
while( !check_for_blackswan( mia, enable_black_swan ) && call_itr != call_end )
{
bool filled_call = false;
price match_price;
@ -419,17 +446,16 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan
match_price.validate();
if( match_price > ~call_itr->call_price )
{
return filled_limit;
}
auto usd_to_buy = call_itr->get_debt();
if( usd_to_buy * match_price > call_itr->get_collateral() )
{
elog( "black swan detected" );
edump((enable_black_swan));
FC_ASSERT( enable_black_swan );
//globally_settle_asset(mia, call_itr->get_debt() / call_itr->get_collateral());
globally_settle_asset(mia, bitasset.current_feed.settlement_price );// call_itr->get_debt() / call_itr->get_collateral());
globally_settle_asset(mia, bitasset.current_feed.settlement_price );
return true;
}
@ -458,6 +484,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan
auto old_limit_itr = filled_limit ? limit_itr++ : limit_itr;
fill_order(*old_limit_itr, order_pays, order_receives);
} // whlie call_itr != call_end
return filled_limit;
@ -468,7 +495,9 @@ void database::pay_order( const account_object& receiver, const asset& receives,
const auto& balances = receiver.statistics(*this);
modify( balances, [&]( account_statistics_object& b ){
if( pays.asset_id == asset_id_type() )
{
b.total_core_in_orders -= pays.amount;
}
});
adjust_balance(receiver.get_id(), receives);
}

View file

@ -43,8 +43,8 @@ void database::update_global_dynamic_data( const signed_block& b )
for( uint32_t i = 0; i < missed_blocks; ++i ) {
const auto& witness_missed = get_scheduled_witness( i+1 )(*this);
if( witness_missed.id != b.witness ) {
const auto& witness_account = witness_missed.witness_account(*this);
/*
const auto& witness_account = witness_missed.witness_account(*this);
if( (fc::time_point::now() - b.timestamp) < fc::seconds(30) )
wlog( "Witness ${name} missed block ${n} around ${t}", ("name",witness_account.name)("n",b.block_num())("t",b.timestamp) );
*/
@ -80,14 +80,15 @@ void database::update_global_dynamic_data( const signed_block& b )
if( !(get_node_properties().skip_flags & skip_undo_history_check) )
{
GRAPHENE_ASSERT( _dgp.recently_missed_count < GRAPHENE_MAX_UNDO_HISTORY, undo_database_exception,
GRAPHENE_ASSERT( _dgp.head_block_number - _dgp.last_irreversible_block_num < GRAPHENE_MAX_UNDO_HISTORY, undo_database_exception,
"The database does not have enough undo history to support a blockchain with so many missed blocks. "
"Please add a checkpoint if you would like to continue applying blocks beyond this point.",
("last_irreversible_block_num",_dgp.last_irreversible_block_num)("head", _dgp.head_block_number)
("recently_missed",_dgp.recently_missed_count)("max_undo",GRAPHENE_MAX_UNDO_HISTORY) );
}
_undo_db.set_max_size( _dgp.recently_missed_count + GRAPHENE_MIN_UNDO_HISTORY );
_fork_db.set_max_size( _dgp.recently_missed_count + GRAPHENE_MIN_UNDO_HISTORY );
_undo_db.set_max_size( _dgp.head_block_number - _dgp.last_irreversible_block_num + GRAPHENE_MIN_UNDO_HISTORY );
_fork_db.set_max_size( _dgp.head_block_number - _dgp.last_irreversible_block_num + GRAPHENE_MIN_UNDO_HISTORY );
}
void database::update_signing_witness(const witness_object& signing_witness, const signed_block& new_block)
@ -108,9 +109,45 @@ void database::update_signing_witness(const witness_object& signing_witness, con
modify( signing_witness, [&]( witness_object& _wit )
{
_wit.last_aslot = new_block_aslot;
_wit.last_confirmed_block_num = new_block.block_num();
} );
}
void database::update_last_irreversible_block()
{
const global_property_object& gpo = get_global_properties();
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
vector< const witness_object* > wit_objs;
wit_objs.reserve( gpo.active_witnesses.size() );
for( const witness_id_type& wid : gpo.active_witnesses )
wit_objs.push_back( &(wid(*this)) );
static_assert( GRAPHENE_IRREVERSIBLE_THRESHOLD > 0, "irreversible threshold must be nonzero" );
// 1 1 1 2 2 2 2 2 2 2 -> 2 .7*10 = 7
// 1 1 1 1 1 1 1 2 2 2 -> 1
// 3 3 3 3 3 3 3 3 3 3 -> 3
size_t offset = ((GRAPHENE_100_PERCENT - GRAPHENE_IRREVERSIBLE_THRESHOLD) * wit_objs.size() / GRAPHENE_100_PERCENT);
std::nth_element( wit_objs.begin(), wit_objs.begin() + offset, wit_objs.end(),
[]( const witness_object* a, const witness_object* b )
{
return a->last_confirmed_block_num < b->last_confirmed_block_num;
} );
uint32_t new_last_irreversible_block_num = wit_objs[offset]->last_confirmed_block_num;
if( new_last_irreversible_block_num > dpo.last_irreversible_block_num )
{
modify( dpo, [&]( dynamic_global_property_object& _dpo )
{
_dpo.last_irreversible_block_num = new_last_irreversible_block_num;
} );
}
}
void database::clear_expired_transactions()
{
//Look for expired transactions in the deduplication list, and remove them.
@ -143,6 +180,72 @@ void database::clear_expired_proposals()
}
}
/**
* let HB = the highest bid for the collateral (aka who will pay the most DEBT for the least collateral)
* let SP = current median feed's Settlement Price
* let LC = the least collateralized call order's swan price (debt/collateral)
*
* If there is no valid price feed or no bids then there is no black swan.
*
* A black swan occurs if MAX(HB,SP) <= LC
*/
bool database::check_for_blackswan( const asset_object& mia, bool enable_black_swan )
{
if( !mia.is_market_issued() ) return false;
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);
if( bitasset.has_settlement() ) return true; // already force settled
auto settle_price = bitasset.current_feed.settlement_price;
if( settle_price.is_null() ) return false; // no feed
const call_order_index& call_index = get_index_type<call_order_index>();
const auto& call_price_index = call_index.indices().get<by_price>();
const limit_order_index& limit_index = get_index_type<limit_order_index>();
const auto& limit_price_index = limit_index.indices().get<by_price>();
// looking for limit orders selling the most USD for the least CORE
auto highest_possible_bid = price::max( mia.id, bitasset.options.short_backing_asset );
// stop when limit orders are selling too little USD for too much CORE
auto lowest_possible_bid = price::min( mia.id, bitasset.options.short_backing_asset );
assert( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id );
// NOTE limit_price_index is sorted from greatest to least
auto limit_itr = limit_price_index.lower_bound( highest_possible_bid );
auto limit_end = limit_price_index.upper_bound( lowest_possible_bid );
auto call_min = price::min( bitasset.options.short_backing_asset, mia.id );
auto call_max = price::max( bitasset.options.short_backing_asset, mia.id );
auto call_itr = call_price_index.lower_bound( call_min );
auto call_end = call_price_index.upper_bound( call_max );
if( call_itr == call_end ) return false; // no call orders
price highest = settle_price;
if( limit_itr != limit_end ) {
assert( settle_price.base.asset_id == limit_itr->sell_price.base.asset_id );
highest = std::max( limit_itr->sell_price, settle_price );
}
auto least_collateral = call_itr->collateralization();
if( ~least_collateral >= highest )
{
elog( "Black Swan detected: \n"
" Least collateralized call: ${lc} ${~lc}\n"
// " Highest Bid: ${hb} ${~hb}\n"
" Settle Price: ${sp} ${~sp}\n"
" Max: ${h} ${~h}\n",
("lc",least_collateral.to_real())("~lc",(~least_collateral).to_real())
// ("hb",limit_itr->sell_price.to_real())("~hb",(~limit_itr->sell_price).to_real())
("sp",settle_price.to_real())("~sp",(~settle_price).to_real())
("h",highest.to_real())("~h",(~highest).to_real()) );
FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" );
globally_settle_asset(mia, ~least_collateral );
return true;
}
return false;
}
void database::clear_expired_orders()
{
detail::with_skip_flags( *this,
@ -187,6 +290,13 @@ void database::clear_expired_orders()
const asset_object& mia_object = get(current_asset);
const asset_bitasset_data_object mia = mia_object.bitasset_data(*this);
if( mia.has_settlement() )
{
ilog( "Canceling a force settlement because of black swan" );
cancel_order( order );
continue;
}
// Has this order not reached its settlement date?
if( order.settlement_date > head_block_time() )
{
@ -234,7 +344,15 @@ void database::clear_expired_orders()
// There should always be a call order, since asset exists!
assert(itr != call_index.end() && itr->debt_type() == mia_object.get_id());
asset max_settlement = max_settlement_volume - settled;
settled += match(*itr, order, settlement_price, max_settlement);
try {
settled += match(*itr, order, settlement_price, max_settlement);
}
catch ( const black_swan_exception& e ) {
wlog( "black swan detected: ${e}", ("e", e.to_detail_string() ) );
cancel_order( order );
break;
}
}
modify(mia, [settled](asset_bitasset_data_object& b) {
b.force_settled_volume = settled.amount;

View file

@ -32,7 +32,6 @@ fc::variant_object get_config()
result[ "GRAPHENE_MAX_ACCOUNT_NAME_LENGTH" ] = GRAPHENE_MAX_ACCOUNT_NAME_LENGTH;
result[ "GRAPHENE_MIN_ASSET_SYMBOL_LENGTH" ] = GRAPHENE_MIN_ASSET_SYMBOL_LENGTH;
result[ "GRAPHENE_MAX_ASSET_SYMBOL_LENGTH" ] = GRAPHENE_MAX_ASSET_SYMBOL_LENGTH;
result[ "GRAPHENE_MAX_ASSET_NAME_LENGTH" ] = GRAPHENE_MAX_ASSET_NAME_LENGTH;
result[ "GRAPHENE_MAX_SHARE_SUPPLY" ] = GRAPHENE_MAX_SHARE_SUPPLY;
result[ "GRAPHENE_MAX_PAY_RATE" ] = GRAPHENE_MAX_PAY_RATE;
result[ "GRAPHENE_MAX_SIG_CHECK_DEPTH" ] = GRAPHENE_MAX_SIG_CHECK_DEPTH;

View file

@ -41,11 +41,7 @@ namespace graphene { namespace chain {
account_id_type owner;
/**
* Keep the most recent operation as a root pointer to a linked list of the transaction history. This field is
* not required by core validation and could in theory be made an annotation on the account object, but
* because transaction history is so common and this object is already cached in the undo buffer (because it
* likely affected the balances of this account) it is convienent to simply track this data here. Account
* balance objects don't currenty inherit from annotated object.
* Keep the most recent operation as a root pointer to a linked list of the transaction history.
*/
account_transaction_history_id_type most_recent_op;
@ -110,7 +106,7 @@ namespace graphene { namespace chain {
* Accounts are the primary unit of authority on the graphene system. Users must have an account in order to use
* assets, trade in the markets, vote for committee_members, etc.
*/
class account_object : public graphene::db::annotated_object<account_object>
class account_object : public graphene::db::abstract_object<account_object>
{
public:
static const uint8_t space_id = protocol_ids;
@ -134,7 +130,7 @@ namespace graphene { namespace chain {
account_id_type lifetime_referrer;
/// Percentage of fee which should go to network.
uint16_t network_fee_percentage;
uint16_t network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;
/// Percentage of fee which should go to lifetime referrer.
uint16_t lifetime_referrer_fee_percentage = 0;
/// Percentage of referral rewards (leftover fee after paying network and lifetime referrer) which should go
@ -170,6 +166,21 @@ namespace graphene { namespace chain {
*/
flat_set<account_id_type> whitelisting_accounts;
/**
* Optionally track all of the accounts this account has whitelisted or blacklisted, these should
* be made Immutable so that when the account object is cloned no deep copy is required. This state is
* tracked for GUI display purposes.
*
* TODO: move white list tracking to its own multi-index container rather than having 4 fields on an
* account. This will scale better because under the current design if you whitelist 2000 accounts,
* then every time someone fetches this account object they will get the full list of 2000 accounts.
*/
///@{
set<account_id_type> whitelisted_accounts;
set<account_id_type> blacklisted_accounts;
///@}
/**
* This is a set of all accounts which have 'blacklisted' this account. Blacklisting is only used in core
* validation for the purpose of forbidding accounts from holding and transacting in whitelisted assets. This
@ -220,20 +231,6 @@ namespace graphene { namespace chain {
account_id_type get_id()const { return id; }
};
/**
* This object is attached as the meta annotation on the account object, this information is not relevant to
* validation.
*/
class meta_account_object : public graphene::db::abstract_object<meta_account_object>
{
public:
static const uint8_t space_id = implementation_ids;
static const uint8_t type_id = meta_account_object_type;
public_key_type memo_key;
committee_member_id_type committee_member_id; // optional
};
/**
* @brief This secondary index will allow a reverse lookup of all accounts that a particular key or account
* is an potential signing authority.
@ -264,6 +261,7 @@ namespace graphene { namespace chain {
set<address> before_address_members;
};
/**
* @brief This secondary index will allow a reverse lookup of all accounts that have been referred by
* a particular account.
@ -326,21 +324,19 @@ namespace graphene { namespace chain {
}}
FC_REFLECT_DERIVED( graphene::chain::account_object,
(graphene::db::annotated_object<graphene::chain::account_object>),
(graphene::db::object),
(membership_expiration_date)(registrar)(referrer)(lifetime_referrer)
(network_fee_percentage)(lifetime_referrer_fee_percentage)(referrer_rewards_percentage)
(name)(owner)(active)(options)(statistics)(whitelisting_accounts)(blacklisting_accounts)
(whitelisting_accounts)(blacklisted_accounts)
(cashback_vb) )
FC_REFLECT_DERIVED( graphene::chain::account_balance_object,
(graphene::db::object),
(owner)(asset_type)(balance) )
FC_REFLECT_DERIVED( graphene::chain::meta_account_object,
(graphene::db::object),
(memo_key)(committee_member_id) )
FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (graphene::chain::object),
FC_REFLECT_DERIVED( graphene::chain::account_statistics_object,
(graphene::chain::object),
(owner)
(most_recent_op)
(total_core_in_orders)

View file

@ -68,7 +68,7 @@ namespace graphene { namespace chain {
* All assets have a globally unique symbol name that controls how they are traded and an issuer who
* has authority over the parameters of the asset.
*/
class asset_object : public graphene::db::annotated_object<asset_object>
class asset_object : public graphene::db::abstract_object<asset_object>
{
public:
static const uint8_t space_id = protocol_ids;
@ -249,8 +249,7 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db::
(settlement_fund)
)
FC_REFLECT_DERIVED( graphene::chain::asset_object,
(graphene::db::annotated_object<graphene::chain::asset_object>),
FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object),
(symbol)
(precision)
(issuer)

View file

@ -0,0 +1,84 @@
/*
* 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 <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
namespace graphene { namespace chain {
struct budget_record
{
uint64_t time_since_last_budget = 0;
// sources of budget
share_type from_initial_reserve = 0;
share_type from_accumulated_fees = 0;
share_type from_unused_witness_budget = 0;
// witness budget requested by the committee
share_type requested_witness_budget = 0;
// funds that can be released from reserve at maximum rate
share_type total_budget = 0;
// sinks of budget, should sum up to total_budget
share_type witness_budget = 0;
share_type worker_budget = 0;
// unused budget
share_type leftover_worker_funds = 0;
// change in supply due to budget operations
share_type supply_delta = 0;
};
class budget_record_object;
class budget_record_object : public graphene::db::abstract_object<budget_record_object>
{
public:
static const uint8_t space_id = implementation_ids;
static const uint8_t type_id = impl_budget_record_object_type;
fc::time_point_sec time;
budget_record record;
};
} }
FC_REFLECT(
graphene::chain::budget_record,
(time_since_last_budget)
(from_initial_reserve)
(from_accumulated_fees)
(from_unused_witness_budget)
(requested_witness_budget)
(total_budget)
(witness_budget)
(worker_budget)
(leftover_worker_funds)
(supply_delta)
)
FC_REFLECT_DERIVED(
graphene::chain::budget_record_object,
(graphene::db::object),
(time)
(record)
)

View file

@ -26,8 +26,6 @@
#define GRAPHENE_MIN_ASSET_SYMBOL_LENGTH 3
#define GRAPHENE_MAX_ASSET_SYMBOL_LENGTH 16
#define GRAPHENE_MAX_ASSET_NAME_LENGTH 127
#define GRAPHENE_MAX_SHARE_SUPPLY int64_t(1000000000000000ll)
#define GRAPHENE_MAX_PAY_RATE 10000 /* 100% */
#define GRAPHENE_MAX_SIG_CHECK_DEPTH 2
@ -137,6 +135,10 @@
#define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4
#define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3
#define GRAPHENE_CURRENT_DB_VERSION "test5b"
#define GRAPHENE_IRREVERSIBLE_THRESHOLD (70 * GRAPHENE_1_PERCENT)
/**
* Reserved Account IDs with special meaning
*/

View file

@ -40,6 +40,8 @@ namespace graphene { namespace chain {
using graphene::db::abstract_object;
using graphene::db::object;
struct budget_record;
/**
* @class database
* @brief tracks the blockchain state in an extensible manner
@ -122,6 +124,7 @@ namespace graphene { namespace chain {
void add_checkpoints( const flat_map<uint32_t,block_id_type>& checkpts );
const flat_map<uint32_t,block_id_type> get_checkpoints()const { return _checkpoints; }
bool before_last_checkpoint()const;
bool push_block( const signed_block& b, uint32_t skip = skip_nothing );
processed_transaction push_transaction( const signed_transaction& trx, uint32_t skip = skip_nothing );
@ -140,8 +143,7 @@ namespace graphene { namespace chain {
signed_block _generate_block(
const fc::time_point_sec when,
witness_id_type witness_id,
const fc::ecc::private_key& block_signing_private_key,
bool retry_on_failure
const fc::ecc::private_key& block_signing_private_key
);
void pop_block();
@ -420,19 +422,21 @@ namespace graphene { namespace chain {
//////////////////// db_update.cpp ////////////////////
void update_global_dynamic_data( const signed_block& b );
void update_signing_witness(const witness_object& signing_witness, const signed_block& new_block);
void update_last_irreversible_block();
void clear_expired_transactions();
void clear_expired_proposals();
void clear_expired_orders();
void update_expired_feeds();
void update_maintenance_flag( bool new_maintenance_flag );
void update_withdraw_permissions();
bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true );
///Steps performed only at maintenance intervals
///@{
//////////////////// db_maint.cpp ////////////////////
share_type get_max_budget( fc::time_point_sec now )const;
void initialize_budget_record( fc::time_point_sec now, budget_record& rec )const;
void process_budget();
void pay_workers( share_type& budget );
void perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props);

View file

@ -50,7 +50,7 @@ struct skip_flags_restorer
}
node_property_object& _npo;
uint32_t _old_skip_flags;
uint32_t _old_skip_flags; // initialized in ctor
};
/**

View file

@ -69,7 +69,8 @@ namespace graphene { namespace chain {
FC_DECLARE_DERIVED_EXCEPTION( operation_evaluate_exception, graphene::chain::chain_exception, 3050000, "operation evaluation exception" )
FC_DECLARE_DERIVED_EXCEPTION( utility_exception, graphene::chain::chain_exception, 3060000, "utility method exception" )
FC_DECLARE_DERIVED_EXCEPTION( undo_database_exception, graphene::chain::chain_exception, 3070000, "undo database exception" )
FC_DECLARE_DERIVED_EXCEPTION( unlinkable_block_exception, graphene::chain::chain_exception, 3080000, "unlinkable block" )
FC_DECLARE_DERIVED_EXCEPTION( unlinkable_block_exception, graphene::chain::chain_exception, 3080000, "unlinkable block" )
FC_DECLARE_DERIVED_EXCEPTION( black_swan_exception, graphene::chain::chain_exception, 3090000, "black swan" )
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_active_auth, graphene::chain::transaction_exception, 3030001, "missing required active authority" )
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_owner_auth, graphene::chain::transaction_exception, 3030002, "missing required owner authority" )

View file

@ -37,7 +37,7 @@ namespace graphene { namespace chain {
block_id_type previous_id()const { return data.previous; }
weak_ptr< fork_item > prev;
uint32_t num;
uint32_t num; // initialized in ctor
/**
* Used to flag a block as invalid and prevent other blocks from
* building on top of it.

View file

@ -40,7 +40,7 @@ struct genesis_state_type {
string issuer_name;
string description;
uint8_t precision;
uint8_t precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS;
share_type max_supply;
share_type accumulated_fees;
@ -84,7 +84,7 @@ struct genesis_state_type {
vector<initial_asset_type> initial_assets;
vector<initial_balance_type> initial_balances;
vector<initial_vesting_balance_type> initial_vesting_balances;
uint64_t initial_active_witnesses;
uint64_t initial_active_witnesses = GRAPHENE_DEFAULT_MIN_WITNESS_COUNT;
vector<initial_witness_type> initial_witness_candidates;
vector<initial_committee_member_type> initial_committee_candidates;
vector<initial_worker_type> initial_worker_candidates;

View file

@ -101,6 +101,8 @@ namespace graphene { namespace chain {
*/
uint32_t dynamic_flags = 0;
uint32_t last_irreversible_block_num = 0;
enum dynamic_flag_bits
{
/**
@ -129,6 +131,7 @@ FC_REFLECT_DERIVED( graphene::chain::dynamic_global_property_object, (graphene::
(current_aslot)
(recent_slots_filled)
(dynamic_flags)
(last_irreversible_block_num)
)
FC_REFLECT_DERIVED( graphene::chain::global_property_object, (graphene::db::object),

View file

@ -92,7 +92,7 @@ namespace graphene { namespace chain {
* On the @ref settlement_date the @ref balance will be converted to the collateral asset
* and paid to @ref owner and then this object will be deleted.
*/
class force_settlement_object : public graphene::db::annotated_object<force_settlement_object>
class force_settlement_object : public abstract_object<force_settlement_object>
{
public:
static const uint8_t space_id = protocol_ids;

View file

@ -33,9 +33,9 @@ namespace graphene { namespace chain {
class node_property_object
{
public:
node_property_object() : skip_flags(0) {}
node_property_object(){}
~node_property_object(){}
uint32_t skip_flags;
uint32_t skip_flags = 0;
};
} } // graphene::chain

View file

@ -141,7 +141,7 @@ namespace graphene { namespace chain {
account_id_type account_to_list;
/// The new white and blacklist status of account_to_list, as determined by authorizing_account
/// This is a bitfield using values defined in the account_listing enum
uint8_t new_listing;
uint8_t new_listing = no_listing;
extensions_type extensions;
account_id_type fee_payer()const { return authorizing_account; }

View file

@ -83,6 +83,15 @@ namespace graphene { namespace chain {
result.push_back(k.first);
return result;
}
vector<address> get_addresses() const
{
vector<address> result;
result.reserve( address_auths.size() );
for( const auto& k : address_auths )
result.push_back(k.first);
return result;
}
friend bool operator == ( const authority& a, const authority& b )
{

View file

@ -21,7 +21,7 @@ namespace graphene { namespace chain {
asset fee;
account_id_type payer;
flat_set<account_id_type> required_auths;
uint16_t id;
uint16_t id = 0;
vector<char> data;
account_id_type fee_payer()const { return payer; }

View file

@ -29,7 +29,7 @@ namespace graphene { namespace chain {
* be unique with high probability as long as the generating host has a high-resolution clock OR a strong source
* of entropy for generating private keys.
*/
uint64_t nonce;
uint64_t nonce = 0;
/**
* This field contains the AES encrypted packed @ref memo_message
*/

View file

@ -132,7 +132,7 @@ namespace graphene { namespace chain {
{
impl_global_property_object_type,
impl_dynamic_global_property_object_type,
impl_index_meta_object_type,
impl_reserved0_object_type, // formerly index_meta_object_type, TODO: delete me
impl_asset_dynamic_data_type,
impl_asset_bitasset_data_type,
impl_account_balance_object_type,
@ -142,13 +142,8 @@ namespace graphene { namespace chain {
impl_account_transaction_history_object_type,
impl_blinded_balance_object_type,
impl_chain_property_object_type,
impl_witness_schedule_object_type
};
enum meta_info_object_type
{
meta_asset_object_type,
meta_account_object_type
impl_witness_schedule_object_type,
impl_budget_record_object_type
};
//typedef fc::unsigned_int object_id_type;
@ -186,7 +181,6 @@ namespace graphene { namespace chain {
// implementation types
class global_property_object;
class dynamic_global_property_object;
class index_meta_object;
class asset_dynamic_data_object;
class asset_bitasset_data_object;
class account_balance_object;
@ -196,6 +190,7 @@ namespace graphene { namespace chain {
class account_transaction_history_object;
class chain_property_object;
class witness_schedule_object;
class budget_record_object;
typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type;
typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type;
@ -211,6 +206,7 @@ namespace graphene { namespace chain {
account_transaction_history_object> account_transaction_history_id_type;
typedef object_id< implementation_ids, impl_chain_property_object_type, chain_property_object> chain_property_id_type;
typedef object_id< implementation_ids, impl_witness_schedule_object_type, witness_schedule_object> witness_schedule_id_type;
typedef object_id< implementation_ids, impl_budget_record_object_type, budget_record_object > budget_record_id_type;
typedef fc::array<char, GRAPHENE_MAX_ASSET_SYMBOL_LENGTH> symbol_type;
typedef fc::ripemd160 block_id_type;
@ -225,8 +221,8 @@ namespace graphene { namespace chain {
{
struct binary_key
{
binary_key():check(0){}
uint32_t check;
binary_key() {}
uint32_t check = 0;
fc::ecc::public_key_data data;
};
fc::ecc::public_key_data key_data;
@ -277,7 +273,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type,
FC_REFLECT_ENUM( graphene::chain::impl_object_type,
(impl_global_property_object_type)
(impl_dynamic_global_property_object_type)
(impl_index_meta_object_type)
(impl_reserved0_object_type)
(impl_asset_dynamic_data_type)
(impl_asset_bitasset_data_type)
(impl_account_balance_object_type)
@ -288,10 +284,9 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type,
(impl_blinded_balance_object_type)
(impl_chain_property_object_type)
(impl_witness_schedule_object_type)
(impl_budget_record_object_type)
)
FC_REFLECT_ENUM( graphene::chain::meta_info_object_type, (meta_account_object_type)(meta_asset_object_type) )
FC_REFLECT_TYPENAME( graphene::chain::share_type )
FC_REFLECT_TYPENAME( graphene::chain::account_id_type )
@ -316,6 +311,7 @@ FC_REFLECT_TYPENAME( graphene::chain::account_statistics_id_type )
FC_REFLECT_TYPENAME( graphene::chain::transaction_obj_id_type )
FC_REFLECT_TYPENAME( graphene::chain::block_summary_id_type )
FC_REFLECT_TYPENAME( graphene::chain::account_transaction_history_id_type )
FC_REFLECT_TYPENAME( graphene::chain::budget_record_id_type )
FC_REFLECT( graphene::chain::void_t, )
FC_REFLECT_ENUM( graphene::chain::asset_issuer_permission_flags, (charge_market_fee)(white_list)(transfer_restricted)(override_authority)(disable_force_settle)(global_settle)(disable_confidential) )

View file

@ -35,9 +35,9 @@ namespace graphene { namespace chain {
/// The maximum amount authorized_account is allowed to withdraw in a given withdrawal period
asset withdrawal_limit;
/// Length of the withdrawal period in seconds
uint32_t withdrawal_period_sec;
uint32_t withdrawal_period_sec = 0;
/// The number of withdrawal periods this permission is valid for
uint32_t periods_until_expiration;
uint32_t periods_until_expiration = 0;
/// Time at which the first withdrawal period begins; must be in the future
time_point_sec period_start_time;
@ -70,11 +70,11 @@ namespace graphene { namespace chain {
/// New maximum amount the withdrawer is allowed to charge per withdrawal period
asset withdrawal_limit;
/// New length of the period between withdrawals
uint32_t withdrawal_period_sec;
uint32_t withdrawal_period_sec = 0;
/// New beginning of the next withdrawal period; must be in the future
time_point_sec period_start_time;
/// The new number of withdrawal periods for which this permission will be valid
uint32_t periods_until_expiration;
uint32_t periods_until_expiration = 0;
account_id_type fee_payer()const { return withdraw_from_account; }
void validate()const;

View file

@ -38,7 +38,8 @@ namespace graphene { namespace chain {
vote_id_type vote_id;
uint64_t total_votes = 0;
string url;
int64_t total_missed = 0;
int64_t total_missed = 0;
uint32_t last_confirmed_block_num = 0;
witness_object() : vote_id(vote_id_type::witness) {}
};
@ -72,4 +73,5 @@ FC_REFLECT_DERIVED( graphene::chain::witness_object, (graphene::db::object),
(total_votes)
(url)
(total_missed)
(last_confirmed_block_num)
)

View file

@ -183,6 +183,8 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
call.debt = o.delta_debt.amount;
call.call_price = price::call_price(o.delta_debt, o.delta_collateral,
_bitasset_data->current_feed.maintenance_collateral_ratio);
auto swan_price = call.get_debt()/ call.get_collateral();
});
}
else
@ -194,6 +196,7 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
call.debt += o.delta_debt.amount;
if( call.debt > 0 )
{
auto swan_price = call.get_debt()/ call.get_collateral();
call.call_price = price::call_price(call.get_debt(), call.get_collateral(),
_bitasset_data->current_feed.maintenance_collateral_ratio);
}
@ -229,6 +232,7 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
}
else
{
//edump( (~call_obj->call_price) ("<")( _bitasset_data->current_feed.settlement_price) );
// We didn't fill any call orders. This may be because we
// aren't in margin call territory, or it may be because there
// were no matching orders. In the latter case, we throw.

View file

@ -104,7 +104,13 @@ namespace graphene { namespace chain {
auto scaled = fc::uint128(base_value) * scale;
scaled /= GRAPHENE_100_PERCENT;
FC_ASSERT( scaled <= GRAPHENE_MAX_SHARE_SUPPLY );
//idump( (base_value)(scaled)(core_exchange_rate) );
auto result = asset( scaled.to_uint64(), 0 ) * core_exchange_rate;
//FC_ASSERT( result * core_exchange_rate >= asset( scaled.to_uint64()) );
while( result * core_exchange_rate < asset( scaled.to_uint64()) )
result.amount++;
FC_ASSERT( result.amount <= GRAPHENE_MAX_SHARE_SUPPLY );
return result;
}

View file

@ -7,6 +7,7 @@ namespace graphene { namespace chain {
proposal_create_operation proposal_create_operation::committee_proposal(const chain_parameters& global_params, fc::time_point_sec head_block_time )
{
// TODO move this method to unit tests as it is not useful
proposal_create_operation op;
op.expiration_time = head_block_time + global_params.maximum_proposal_lifetime;
op.review_period_seconds = global_params.committee_proposal_review_period;

View file

@ -114,6 +114,42 @@ struct sign_state
return itr->second = true;
}
optional<map<address,public_key_type>> available_address_sigs;
optional<map<address,public_key_type>> provided_address_sigs;
bool signed_by( const address& a ) {
if( !available_address_sigs ) {
available_address_sigs = std::map<address,public_key_type>();
provided_address_sigs = std::map<address,public_key_type>();
for( auto& item : available_keys ) {
(*available_address_sigs)[ address(pts_address(item, false, 56) ) ] = item;
(*available_address_sigs)[ address(pts_address(item, true, 56) ) ] = item;
(*available_address_sigs)[ address(pts_address(item, false, 0) ) ] = item;
(*available_address_sigs)[ address(pts_address(item, true, 0) ) ] = item;
(*available_address_sigs)[ address(item) ] = item;
}
for( auto& item : provided_signatures ) {
(*provided_address_sigs)[ address(pts_address(item.first, false, 56) ) ] = item.first;
(*provided_address_sigs)[ address(pts_address(item.first, true, 56) ) ] = item.first;
(*provided_address_sigs)[ address(pts_address(item.first, false, 0) ) ] = item.first;
(*provided_address_sigs)[ address(pts_address(item.first, true, 0) ) ] = item.first;
(*provided_address_sigs)[ address(item.first) ] = item.first;
}
}
auto itr = provided_address_sigs->find(a);
if( itr == provided_address_sigs->end() )
{
auto aitr = available_address_sigs->find(a);
if( aitr != available_address_sigs->end() ) {
auto pk = available_keys.find(aitr->second);
if( pk != available_keys.end() )
return provided_signatures[aitr->second] = true;
return false;
}
}
return provided_signatures[itr->second] = true;
}
bool check_authority( account_id_type id )
{
if( approved_by.find(id) != approved_by.end() ) return true;
@ -138,6 +174,14 @@ struct sign_state
return true;
}
for( const auto& k : auth.address_auths )
if( signed_by( k.first ) )
{
total_weight += k.second;
if( total_weight >= auth.weight_threshold )
return true;
}
for( const auto& a : auth.account_auths )
{
if( approved_by.find(a.first) == approved_by.end() )

View file

@ -67,6 +67,19 @@ namespace graphene { namespace db {
return a.number < b.number;
}
template< typename T >
bool is() const
{
return (number >> 48) == ((T::space_id << 8) | (T::type_id));
}
template< typename T >
T as() const
{
FC_ASSERT( is<T>() );
return T( *this );
}
uint64_t number;
};

View file

@ -62,32 +62,34 @@ namespace graphene { namespace db {
elog( "${e}", ("e",e.to_detail_string() ) );
throw; // maybe crash..
}
if( _disable_on_exit ) _db.disable();
}
void commit() { _apply_undo = false; _db.commit(); }
void undo() { if( _apply_undo ) _db.undo(); _apply_undo = false; }
void merge() { if( _apply_undo ) _db.merge(); _apply_undo = false; }
session& operator = ( session&& mv )
{
{ try {
if( this == &mv ) return *this;
if( _apply_undo ) _db.undo();
_apply_undo = mv._apply_undo;
mv._apply_undo = false;
return *this;
}
} FC_CAPTURE_AND_RETHROW() }
private:
friend class undo_database;
session(undo_database& db): _db(db) {}
session(undo_database& db, bool disable_on_exit = false): _db(db),_disable_on_exit(disable_on_exit) {}
undo_database& _db;
bool _apply_undo = true;
bool _disable_on_exit = false;
};
void disable();
void enable();
bool enabled()const { return !_disabled; }
session start_undo_session();
session start_undo_session( bool force_enable = false );
/**
* This should be called just after an object is created
*/

View file

@ -48,7 +48,6 @@ const object& object_database::get_object( object_id_type id )const
const index& object_database::get_index(uint8_t space_id, uint8_t type_id)const
{
FC_ASSERT( _index.size() > space_id, "", ("space_id",space_id)("type_id",type_id)("index.size",_index.size()) );
assert( _index[space_id].size() > type_id ); //, "", ("space_id",space_id)("type_id",type_id)("index[space_id].size",_index[space_id].size()) );
FC_ASSERT( _index[space_id].size() > type_id, "", ("space_id",space_id)("type_id",type_id)("index[space_id].size",_index[space_id].size()) );
const auto& tmp = _index[space_id][type_id];
FC_ASSERT( tmp );

View file

@ -24,16 +24,19 @@ namespace graphene { namespace db {
void undo_database::enable() { _disabled = false; }
void undo_database::disable() { _disabled = true; }
undo_database::session undo_database::start_undo_session()
undo_database::session undo_database::start_undo_session( bool force_enable )
{
if( _disabled ) return session(*this);
if( _disabled && !force_enable ) return session(*this);
bool disable_on_exit = _disabled && force_enable;
if( force_enable )
_disabled = false;
while( size() > max_size() )
_stack.pop_front();
_stack.emplace_back();
++_active_sessions;
return session(*this);
return session(*this, disable_on_exit );
}
void undo_database::on_create( const object& obj )
{

View file

@ -56,8 +56,11 @@
* our peers and save a copy in a cache were we will find it if
* a peer requests it. We expire out old items out of the cache
* after this number of blocks go by.
*
* Recently lowered from 30 to match the default expiration time
* the web wallet imposes on transactions.
*/
#define GRAPHENE_NET_MESSAGE_CACHE_DURATION_IN_BLOCKS 30
#define GRAPHENE_NET_MESSAGE_CACHE_DURATION_IN_BLOCKS 5
/**
* We prevent a peer from offering us a list of blocks which, if we fetched them
@ -68,11 +71,22 @@
*/
#define GRAPHENE_NET_FUTURE_SYNC_BLOCKS_GRACE_PERIOD_SEC (60 * 60)
#define GRAPHENE_NET_INSUFFICIENT_RELAY_FEE_PENALTY_SEC 15
#define GRAPHENE_NET_MAX_INVENTORY_SIZE_IN_MINUTES 2
#define GRAPHENE_NET_MAX_BLOCKS_PER_PEER_DURING_SYNCING 100
#define GRAPHENE_NET_MAX_BLOCKS_PER_PEER_DURING_SYNCING 200
/**
* During normal operation, how many items will be fetched from each
* peer at a time. This will only come into play when the network
* is being flooded -- typically transactions will be fetched as soon
* as we find out about them, so only one item will be requested
* at a time.
*
* No tests have been done to find the optimal value for this
* parameter, so consider increasing or decreasing it if performance
* during flooding is lacking.
*/
#define GRAPHENE_NET_MAX_ITEMS_PER_PEER_DURING_NORMAL_OPERATION 1
/**
* Instead of fetching all item IDs from a peer, then fetching all blocks

View file

@ -1108,39 +1108,81 @@ namespace graphene { namespace net { namespace detail {
fc::time_point next_peer_unblocked_time = fc::time_point::maximum();
std::forward_list<std::pair<peer_connection_ptr, item_id> > fetch_messages_to_send;
std::vector<fc::future<void> > write_ops;
for (auto iter = _items_to_fetch.begin(); iter != _items_to_fetch.end();)
// we need to construct a list of items to request from each peer first,
// then send the messages (in two steps, to avoid yielding while iterating)
// we want to evenly distribute our requests among our peers.
struct requested_item_count_index {};
struct peer_and_items_to_fetch
{
peer_connection_ptr peer;
std::vector<item_id> item_ids;
peer_and_items_to_fetch(const peer_connection_ptr& peer) : peer(peer) {}
bool operator<(const peer_and_items_to_fetch& rhs) const { return peer < rhs.peer; }
size_t number_of_items() const { return item_ids.size(); }
};
typedef boost::multi_index_container<peer_and_items_to_fetch,
boost::multi_index::indexed_by<boost::multi_index::ordered_unique<boost::multi_index::member<peer_and_items_to_fetch, peer_connection_ptr, &peer_and_items_to_fetch::peer> >,
boost::multi_index::ordered_non_unique<boost::multi_index::tag<requested_item_count_index>,
boost::multi_index::const_mem_fun<peer_and_items_to_fetch, size_t, &peer_and_items_to_fetch::number_of_items> > > > fetch_messages_to_send_set;
fetch_messages_to_send_set items_by_peer;
// initialize the fetch_messages_to_send with an empty set of items for all idle peers
for (const peer_connection_ptr& peer : _active_connections)
if (peer->idle())
items_by_peer.insert(peer_and_items_to_fetch(peer));
// now loop over all items we want to fetch
for (auto item_iter = _items_to_fetch.begin(); item_iter != _items_to_fetch.end();)
{
// and find a peer that has it, we'll use the one who has the least requests going to it to load balance
bool item_fetched = false;
for (const peer_connection_ptr& peer : _active_connections)
for (auto peer_iter = items_by_peer.get<requested_item_count_index>().begin(); peer_iter != items_by_peer.get<requested_item_count_index>().end(); ++peer_iter)
{
if (peer->idle() &&
peer->inventory_peer_advertised_to_us.find(iter->item) != peer->inventory_peer_advertised_to_us.end())
const peer_connection_ptr& peer = peer_iter->peer;
// if they have the item and we haven't already decided to ask them for too many other items
if (peer_iter->item_ids.size() < GRAPHENE_NET_MAX_ITEMS_PER_PEER_DURING_NORMAL_OPERATION &&
peer->inventory_peer_advertised_to_us.find(item_iter->item) != peer->inventory_peer_advertised_to_us.end())
{
if (peer->is_transaction_fetching_inhibited() && iter->item.item_type == graphene::net::trx_message_type)
if (item_iter->item.item_type == graphene::net::trx_message_type && peer->is_transaction_fetching_inhibited())
next_peer_unblocked_time = std::min(peer->transaction_fetching_inhibited_until, next_peer_unblocked_time);
else
{
dlog("requesting item ${hash} from peer ${endpoint}",
("hash", iter->item.item_hash)("endpoint", peer->get_remote_endpoint()));
peer->items_requested_from_peer.insert(peer_connection::item_to_time_map_type::value_type(iter->item, fc::time_point::now()));
item_id item_id_to_fetch = iter->item;
iter = _items_to_fetch.erase(iter);
//dlog("requesting item ${hash} from peer ${endpoint}",
// ("hash", iter->item.item_hash)("endpoint", peer->get_remote_endpoint()));
item_id item_id_to_fetch = item_iter->item;
peer->items_requested_from_peer.insert(peer_connection::item_to_time_map_type::value_type(item_id_to_fetch, fc::time_point::now()));
item_iter = _items_to_fetch.erase(item_iter);
item_fetched = true;
fetch_messages_to_send.emplace_front(std::make_pair(peer, item_id_to_fetch));
items_by_peer.get<requested_item_count_index>().modify(peer_iter, [&item_id_to_fetch](peer_and_items_to_fetch& peer_and_items) {
peer_and_items.item_ids.push_back(item_id_to_fetch);
});
break;
}
}
}
}
if (!item_fetched)
++iter;
++item_iter;
}
for (const auto& peer_and_item : fetch_messages_to_send)
peer_and_item.first->send_message(fetch_items_message(peer_and_item.second.item_type,
std::vector<item_hash_t>{peer_and_item.second.item_hash}));
fetch_messages_to_send.clear();
// we've figured out which peer will be providing each item, now send the messages.
for (const peer_and_items_to_fetch& peer_and_items : items_by_peer)
{
// the item lists are heterogenous and
// the fetch_items_message can only deal with one item type at a time.
std::map<uint32_t, std::vector<item_hash_t> > items_to_fetch_by_type;
for (const item_id& item : peer_and_items.item_ids)
items_to_fetch_by_type[item.item_type].push_back(item.item_hash);
for (auto& items_by_type : items_to_fetch_by_type)
{
dlog("requesting ${count} items of type ${type} from peer ${endpoint}: ${hashes}",
("count", items_by_type.second.size())("type", (uint32_t)items_by_type.first)
("endpoint", peer_and_items.peer->get_remote_endpoint())
("hashes", items_by_type.second));
peer_and_items.peer->send_message(fetch_items_message(items_by_type.first,
items_by_type.second));
}
}
items_by_peer.clear();
if (!_items_to_fetch_updated)
{
@ -3742,14 +3784,6 @@ namespace graphene { namespace net { namespace detail {
_delegate->handle_message( message_to_process );
message_validated_time = fc::time_point::now();
}
catch ( const insufficient_relay_fee& )
{
// flooding control. The message was valid but we can't handle it now.
assert(message_to_process.msg_type == graphene::net::trx_message_type); // we only support throttling transactions.
if (message_to_process.msg_type == graphene::net::trx_message_type)
originating_peer->transaction_fetching_inhibited_until = fc::time_point::now() + fc::seconds(GRAPHENE_NET_INSUFFICIENT_RELAY_FEE_PENALTY_SEC);
return;
}
catch ( const fc::canceled_exception& )
{
throw;

View file

@ -17,8 +17,8 @@
*/
#include <graphene/delayed_node/delayed_node_plugin.hpp>
#include <graphene/chain/protocol/types.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/worker_evaluator.hpp>
#include <graphene/app/api.hpp>
#include <fc/network/http/websocket.hpp>
@ -33,12 +33,12 @@ namespace bpo = boost::program_options;
namespace detail {
struct delayed_node_plugin_impl {
std::string remote_endpoint;
int delay_blocks;
fc::http::websocket_client client;
std::shared_ptr<fc::rpc::websocket_api_connection> client_connection;
fc::api<graphene::app::database_api> database_api;
boost::signals2::scoped_connection client_connection_closed;
bool currently_fetching = false;
graphene::chain::block_id_type last_received_remote_head;
graphene::chain::block_id_type last_processed_remote_head;
};
}
@ -49,12 +49,12 @@ delayed_node_plugin::delayed_node_plugin()
delayed_node_plugin::~delayed_node_plugin()
{}
void delayed_node_plugin::plugin_set_program_options(bpo::options_description&, bpo::options_description& cfg)
void delayed_node_plugin::plugin_set_program_options(bpo::options_description& cli, bpo::options_description& cfg)
{
cfg.add_options()
cli.add_options()
("trusted-node", boost::program_options::value<std::string>()->required(), "RPC endpoint of a trusted validating node (required)")
("delay-block-count", boost::program_options::value<int>()->required(), "Number of blocks to delay before advancing chain state (required)")
;
cfg.add(cli);
}
void delayed_node_plugin::connect()
@ -69,58 +69,79 @@ void delayed_node_plugin::connect()
void delayed_node_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{
my->remote_endpoint = "ws://" + options.at("trusted-node").as<std::string>();
my->delay_blocks = options.at("delay-block-count").as<int>();
}
void delayed_node_plugin::sync_with_trusted_node(uint32_t remote_head_block_num)
void delayed_node_plugin::sync_with_trusted_node()
{
struct raii {
bool* target;
~raii() {
*target = false;
auto& db = database();
uint32_t synced_blocks = 0;
uint32_t pass_count = 0;
while( true )
{
graphene::chain::dynamic_global_property_object remote_dpo = my->database_api->get_dynamic_global_properties();
if( remote_dpo.last_irreversible_block_num <= db.head_block_num() )
{
if( remote_dpo.last_irreversible_block_num < db.head_block_num() )
{
wlog( "Trusted node seems to be behind delayed node" );
}
if( synced_blocks > 1 )
{
ilog( "Delayed node finished syncing ${n} blocks in ${k} passes", ("n", synced_blocks)("k", pass_count) );
}
break;
}
};
pass_count++;
while( remote_dpo.last_irreversible_block_num > db.head_block_num() )
{
fc::optional<graphene::chain::signed_block> block = my->database_api->get_block( db.head_block_num()+1 );
FC_ASSERT(block, "Trusted node claims it has blocks it doesn't actually have.");
ilog("Pushing block #${n}", ("n", block->block_num()));
db.push_block(*block);
synced_blocks++;
}
}
}
if (my->currently_fetching) return;
raii releaser{&my->currently_fetching};
my->currently_fetching = true;
void delayed_node_plugin::mainloop()
{
while( true )
{
try
{
fc::usleep( fc::microseconds( 296645 ) ); // wake up a little over 3Hz
auto head_block = database().head_block_num();
while (remote_head_block_num - head_block > my->delay_blocks) {
fc::optional<graphene::chain::signed_block> block = my->database_api->get_block(++head_block);
FC_ASSERT(block, "Trusted node claims it has blocks it doesn't actually have.");
ilog("Pushing block #${n}", ("n", block->block_num()));
database().push_block(*block);
if( my->last_received_remote_head == my->last_processed_remote_head )
continue;
sync_with_trusted_node();
my->last_processed_remote_head = my->last_received_remote_head;
}
catch( const fc::exception& e )
{
elog("Error during connection: ${e}", ("e", e.to_detail_string()));
}
}
}
void delayed_node_plugin::plugin_startup()
{
try {
fc::async([this]()
{
mainloop();
});
try
{
connect();
my->database_api->set_subscribe_callback([this] (const fc::variant& v) {
auto& updates = v.get_array();
for( const auto& v : updates )
{
if( v.is_object() )
{
auto& obj = v.get_object();
if( obj["id"].as<graphene::chain::object_id_type>() == graphene::chain::dynamic_global_property_id_type() )
{
auto props = v.as<graphene::chain::dynamic_global_property_object>();
sync_with_trusted_node(props.head_block_number);
}
}
}
}, true);
// Go ahead and get in sync now, before subscribing
chain::dynamic_global_property_object props = my->database_api->get_dynamic_global_properties();
sync_with_trusted_node(props.head_block_number);
my->database_api->set_block_applied_callback([this]( const fc::variant& block_id )
{
fc::from_variant( block_id, my->last_received_remote_head );
} );
return;
} catch (const fc::exception& e) {
}
catch (const fc::exception& e)
{
elog("Error during connection: ${e}", ("e", e.to_detail_string()));
}
fc::async([this]{connection_failed();});
@ -129,7 +150,7 @@ void delayed_node_plugin::plugin_startup()
void delayed_node_plugin::connection_failed()
{
elog("Connection to trusted node failed; retrying in 5 seconds...");
fc::schedule([this]{plugin_startup();}, fc::time_point::now() + fc::seconds(5));
fc::schedule([this]{connect();}, fc::time_point::now() + fc::seconds(5));
}
} }

View file

@ -34,11 +34,12 @@ public:
boost::program_options::options_description& cfg) override;
virtual void plugin_initialize(const boost::program_options::variables_map& options) override;
virtual void plugin_startup() override;
void mainloop();
protected:
void connection_failed();
void connect();
void sync_with_trusted_node(uint32_t remote_head_block_num);
void sync_with_trusted_node();
};
} } //graphene::account_history

View file

@ -107,7 +107,8 @@ struct operation_process_fill_order
auto itr = by_key_idx.find( key );
if( itr == by_key_idx.end() )
{ // create new bucket
const auto& obj = db.create<bucket_object>( [&]( bucket_object& b ){
/* const auto& obj = */
db.create<bucket_object>( [&]( bucket_object& b ){
b.key = key;
b.quote_volume += trade_price.quote.amount;
b.base_volume += trade_price.base.amount;
@ -120,7 +121,7 @@ struct operation_process_fill_order
b.low_base = b.close_base;
b.low_quote = b.close_quote;
});
wlog( " creating bucket ${b}", ("b",obj) );
//wlog( " creating bucket ${b}", ("b",obj) );
}
else
{ // update existing bucket

View file

@ -0,0 +1,80 @@
/*
* 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
// This file contains various reflection methods that are used to
// support the wallet, e.g. allow specifying operations by name
// instead of ID.
namespace graphene { namespace wallet {
struct static_variant_map
{
flat_map< string, int > name_to_which;
vector< string > which_to_name;
};
namespace impl {
struct static_variant_map_visitor
{
static_variant_map_visitor() {}
typedef void result_type;
template< typename T >
result_type operator()( const T& dummy )
{
assert( which == m.which_to_name.size() );
std::string name = js_name<T>::name();
m.name_to_which[ name ] = which;
m.which_to_name.push_back( name );
}
static_variant_map m;
int which;
};
} // namespace impl
template< typename T >
T from_which_variant( int which, const variant& v )
{
// Parse a variant for a known which()
T result;
result.set_which( which );
from_variant( v, result );
return result;
}
template<typename T>
static_variant_map create_static_variant_map()
{
T dummy;
int n = dummy.count();
impl::static_variant_map_visitor vtor;
for( int i=0; i<n; i++ )
{
dummy.set_which(i);
vtor.which = i;
dummy.visit( vtor );
}
return vtor.m;
}
} } // namespace graphene::wallet

View file

@ -1269,22 +1269,29 @@ class wallet_api
* desired.
*
* @param proposing_account The account paying the fee to propose the tx
* @param expiration_time Timestamp specifying when the proposal will either take effect or expire.
* @param changed_values The values to change; all other chain parameters are filled in with default values
* @param broadcast true if you wish to broadcast the transaction
* @return the signed version of the transaction
*/
signed_transaction propose_parameter_change(
const string& proposing_account,
fc::time_point_sec expiration_time,
const variant_object& changed_values,
bool broadcast = false);
/** Propose a fee change.
*
* Not implemented.
*
* @param proposing_account The account paying the fee to propose the tx
* @param expiration_time Timestamp specifying when the proposal will either take effect or expire.
* @param changed_values Map of operation type to new fee. Operations may be specified by name or ID.
* The "scale" key changes the scale. All other operations will maintain current values.
* @param broadcast true if you wish to broadcast the transaction
* @return the signed version of the transaction
*/
signed_transaction propose_fee_change(
const string& proposing_account,
fc::time_point_sec expiration_time,
const variant_object& changed_values,
bool broadcast = false);

View file

@ -51,10 +51,12 @@
#include <graphene/app/api.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
#include <graphene/utilities/key_conversion.hpp>
#include <graphene/utilities/words.hpp>
#include <graphene/wallet/wallet.hpp>
#include <graphene/wallet/api_documentation.hpp>
#include <graphene/wallet/reflect_util.hpp>
#include <fc/smart_ref_impl.hpp>
#ifndef WIN32
@ -1965,6 +1967,7 @@ public:
signed_transaction propose_parameter_change(
const string& proposing_account,
fc::time_point_sec expiration_time,
const variant_object& changed_values,
bool broadcast = false)
{
@ -1979,8 +1982,10 @@ public:
committee_member_update_global_parameters_operation update_op;
update_op.new_parameters = new_params;
proposal_create_operation prop_op = proposal_create_operation::committee_proposal(
current_params, get_dynamic_global_properties().time );
proposal_create_operation prop_op;
prop_op.expiration_time = expiration_time;
prop_op.review_period_seconds = current_params.committee_proposal_review_period;
prop_op.fee_paying_account = get_account(proposing_account).id;
prop_op.proposed_ops.emplace_back( update_op );
@ -1996,10 +2001,84 @@ public:
signed_transaction propose_fee_change(
const string& proposing_account,
const variant_object& changed_values,
fc::time_point_sec expiration_time,
const variant_object& changed_fees,
bool broadcast = false)
{
FC_ASSERT( false, "not implemented" );
const chain_parameters& current_params = get_global_properties().parameters;
const fee_schedule_type& current_fees = *(current_params.current_fees);
flat_map< int, fee_parameters > fee_map;
fee_map.reserve( current_fees.parameters.size() );
for( const fee_parameters& op_fee : current_fees.parameters )
fee_map[ op_fee.which() ] = op_fee;
uint32_t scale = current_fees.scale;
for( const auto& item : changed_fees )
{
const string& key = item.key();
if( key == "scale" )
{
int64_t _scale = item.value().as_int64();
FC_ASSERT( _scale >= 0 );
FC_ASSERT( _scale <= std::numeric_limits<uint32_t>::max() );
scale = uint32_t( _scale );
continue;
}
// is key a number?
auto is_numeric = [&]() -> bool
{
size_t n = key.size();
for( size_t i=0; i<n; i++ )
{
if( !isdigit( key[i] ) )
return false;
}
return true;
};
int which;
if( is_numeric() )
which = std::stoi( key );
else
{
const auto& n2w = _operation_which_map.name_to_which;
auto it = n2w.find( key );
FC_ASSERT( it != n2w.end(), "unknown operation" );
which = it->second;
}
fee_parameters fp = from_which_variant< fee_parameters >( which, item.value() );
fee_map[ which ] = fp;
}
fee_schedule_type new_fees;
for( const std::pair< int, fee_parameters >& item : fee_map )
new_fees.parameters.insert( item.second );
new_fees.scale = scale;
chain_parameters new_params = current_params;
new_params.current_fees = new_fees;
committee_member_update_global_parameters_operation update_op;
update_op.new_parameters = new_params;
proposal_create_operation prop_op;
prop_op.expiration_time = expiration_time;
prop_op.review_period_seconds = current_params.committee_proposal_review_period;
prop_op.fee_paying_account = get_account(proposing_account).id;
prop_op.proposed_ops.emplace_back( update_op );
current_params.current_fees->set_fee( prop_op.proposed_ops.back().op );
signed_transaction tx;
tx.operations.push_back(prop_op);
set_operation_fees(tx, current_params.current_fees);
tx.validate();
return sign_transaction(tx, broadcast);
}
signed_transaction approve_proposal(
@ -2168,6 +2247,8 @@ public:
flat_map<string, operation> _prototype_ops;
static_variant_map _operation_which_map = create_static_variant_map< operation >();
#ifdef __unix__
mode_t _old_umask;
#endif
@ -2863,20 +2944,22 @@ void wallet_api::flood_network(string prefix, uint32_t number_of_transactions)
signed_transaction wallet_api::propose_parameter_change(
const string& proposing_account,
fc::time_point_sec expiration_time,
const variant_object& changed_values,
bool broadcast /* = false */
)
{
return my->propose_parameter_change( proposing_account, changed_values, broadcast );
return my->propose_parameter_change( proposing_account, expiration_time, changed_values, broadcast );
}
signed_transaction wallet_api::propose_fee_change(
const string& proposing_account,
const variant_object& changed_values,
fc::time_point_sec expiration_time,
const variant_object& changed_fees,
bool broadcast /* = false */
)
{
return my->propose_fee_change( proposing_account, changed_values, broadcast );
return my->propose_fee_change( proposing_account, expiration_time, changed_fees, broadcast );
}
signed_transaction wallet_api::approve_proposal(

71
programs/genesis_util/remove.py Executable file
View file

@ -0,0 +1,71 @@
#!/usr/bin/env python3
import argparse
import json
import sys
def dump_json(obj, out, pretty):
if pretty:
json.dump(obj, out, indent=2, sort_keys=True)
else:
json.dump(obj, out, separators=(",", ":"), sort_keys=True)
return
def main():
parser = argparse.ArgumentParser(description="Remove entities from snapshot")
parser.add_argument("-o", "--output", metavar="OUT", default="-", help="output filename (default: stdout)")
parser.add_argument("-i", "--input", metavar="IN", default="-", help="input filename (default: stdin)")
parser.add_argument("-a", "--asset", metavar="ASSETS", nargs="+", help="list of asset(s) to delete")
parser.add_argument("-p", "--pretty", action="store_true", default=False, help="pretty print output")
opts = parser.parse_args()
if opts.input == "-":
genesis = json.load(sys.stdin)
else:
with open(opts.input, "r") as f:
genesis = json.load(f)
if opts.asset is None:
opts.asset = []
rm_asset_set = set(opts.asset)
removed_asset_entries = {aname : 0 for aname in opts.asset}
new_initial_assets = []
for asset in genesis["initial_assets"]:
symbol = asset["symbol"]
if symbol not in rm_asset_set:
new_initial_assets.append(asset)
else:
removed_asset_entries[symbol] += 1
genesis["initial_assets"] = new_initial_assets
removed_balance_entries = {aname : [] for aname in opts.asset}
new_initial_balances = []
for balance in genesis["initial_balances"]:
symbol = balance["asset_symbol"]
if symbol not in rm_asset_set:
new_initial_balances.append(balance)
else:
removed_balance_entries[symbol].append(balance)
genesis["initial_balances"] = new_initial_balances
# TODO: Remove from initial_vesting_balances
for aname in opts.asset:
sys.stderr.write(
"Asset {sym} removed {acount} initial_assets, {bcount} initial_balances totaling {btotal}\n".format(
sym=aname,
acount=removed_asset_entries[aname],
bcount=len(removed_balance_entries[aname]),
btotal=sum(int(e["amount"]) for e in removed_balance_entries[aname]),
))
if opts.output == "-":
dump_json( genesis, sys.stdout, opts.pretty )
sys.stdout.flush()
else:
with open(opts.output, "w") as f:
dump_json( genesis, f, opts.pretty )
return
if __name__ == "__main__":
main()

View file

@ -68,12 +68,6 @@ database_fixture::database_fixture()
boost::program_options::variables_map options;
// app.initialize();
ahplugin->plugin_set_app(&app);
ahplugin->plugin_initialize(options);
mhplugin->plugin_set_app(&app);
mhplugin->plugin_initialize(options);
genesis_state.initial_timestamp = time_point_sec( GRAPHENE_TESTING_GENESIS_TIMESTAMP );
genesis_state.initial_active_witnesses = 10;
@ -88,7 +82,14 @@ database_fixture::database_fixture()
genesis_state.initial_witness_candidates.push_back({name, init_account_priv_key.get_public_key()});
}
genesis_state.initial_parameters.current_fees->zero_all_fees();
db.init_genesis(genesis_state);
open_database();
// app.initialize();
ahplugin->plugin_set_app(&app);
ahplugin->plugin_initialize(options);
mhplugin->plugin_set_app(&app);
mhplugin->plugin_initialize(options);
ahplugin->plugin_startup();
mhplugin->plugin_startup();
@ -292,8 +293,6 @@ void database_fixture::open_database()
signed_block database_fixture::generate_block(uint32_t skip, const fc::ecc::private_key& key, int miss_blocks)
{
open_database();
skip |= database::skip_undo_history_check;
// skip == ~0 will skip checks specified in database::validation_steps
auto block = db.generate_block(db.get_slot_time(miss_blocks + 1),

View file

@ -1035,7 +1035,7 @@ BOOST_FIXTURE_TEST_CASE( transaction_invalidated_in_cache, database_fixture )
{
ACTORS( (alice)(bob) );
auto generate_block = [&]( database& d, uint32_t skip = database::skip_nothing ) -> signed_block
auto generate_block = [&]( database& d, uint32_t skip ) -> signed_block
{
return d.generate_block(d.get_slot_time(1), d.get_scheduled_witness(1), init_account_priv_key, skip);
};
@ -1058,7 +1058,7 @@ BOOST_FIXTURE_TEST_CASE( transaction_invalidated_in_cache, database_fixture )
BOOST_CHECK( db2.get( alice_id ).name == "alice" );
BOOST_CHECK( db2.get( bob_id ).name == "bob" );
db2.push_block(generate_block(db));
db2.push_block(generate_block(db, database::skip_nothing));
transfer( account_id_type(), alice_id, asset( 1000 ) );
transfer( account_id_type(), bob_id, asset( 1000 ) );
// need to skip authority check here as well for same reason as above
@ -1073,7 +1073,7 @@ BOOST_FIXTURE_TEST_CASE( transaction_invalidated_in_cache, database_fixture )
{
for( int i=0; i<n; i++ )
{
signed_block b = generate_block(db2);
signed_block b = generate_block(db2, database::skip_nothing);
PUSH_BLOCK( db, b );
}
};
@ -1112,7 +1112,7 @@ BOOST_FIXTURE_TEST_CASE( transaction_invalidated_in_cache, database_fixture )
BOOST_CHECK_EQUAL(db.get_balance( bob_id, asset_id_type()).amount.value, 1000);
// generate a block with db and ensure we don't somehow apply it
PUSH_BLOCK(db2, generate_block(db));
PUSH_BLOCK(db2, generate_block(db, database::skip_nothing));
BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value, 1000);
BOOST_CHECK_EQUAL(db.get_balance( bob_id, asset_id_type()).amount.value, 1000);
@ -1133,7 +1133,7 @@ BOOST_FIXTURE_TEST_CASE( transaction_invalidated_in_cache, database_fixture )
signed_transaction tx_b = generate_xfer_tx( alice_id, bob_id, 2000, 10 );
signed_transaction tx_c = generate_xfer_tx( alice_id, bob_id, 500, 10 );
generate_block( db );
generate_block( db, database::skip_nothing );
PUSH_TX( db, tx_a );
BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value, 2000);

View file

@ -262,6 +262,117 @@ BOOST_AUTO_TEST_CASE( black_swan )
}
}
/**
* Black swan occurs when price feed falls, triggered by settlement
* order.
*/
BOOST_AUTO_TEST_CASE( black_swan_issue_346 )
{ try {
ACTORS((buyer)(seller)(borrower)(borrower2)(settler)(feeder));
const asset_object& core = asset_id_type()(db);
int trial = 0;
const int64_t init_balance(1000000);
vector< const account_object* > actors{ &buyer, &seller, &borrower, &borrower2, &settler, &feeder };
auto top_up = [&]()
{
for( const account_object* actor : actors )
{
int64_t bal = get_balance( *actor, core );
if( bal < init_balance )
transfer( committee_account, actor->id, asset(init_balance - bal) );
else if( bal > init_balance )
transfer( actor->id, committee_account, asset(bal - init_balance) );
}
};
auto setup_asset = [&]() -> const asset_object&
{
const asset_object& bitusd = create_bitasset("BITUSD"+fc::to_string(trial), feeder_id);
update_feed_producers( bitusd, {feeder.id} );
BOOST_CHECK( !bitusd.bitasset_data(db).has_settlement() );
trial++;
return bitusd;
};
/*
* GRAPHENE_COLLATERAL_RATIO_DENOM
uint16_t maintenance_collateral_ratio = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO;
uint16_t maximum_short_squeeze_ratio = GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO;
*/
// situations to test:
// 1. minus short squeeze protection would be black swan, otherwise no
// 2. issue 346 (price feed drops followed by force settle, drop should trigger BS)
// 3. feed price < D/C of least collateralized short < call price < highest bid
auto set_price = [&](
const asset_object& bitusd,
const price& settlement_price
)
{
price_feed feed;
feed.settlement_price = settlement_price;
wdump( (feed.max_short_squeeze_price()) );
publish_feed( bitusd, feeder, feed );
};
auto wait_for_settlement = [&]()
{
const auto& idx = db.get_index_type<force_settlement_index>().indices().get<by_expiration>();
const auto& itr = idx.rbegin();
if( itr == idx.rend() )
return;
generate_blocks( itr->settlement_date );
BOOST_CHECK( !idx.empty() );
generate_block();
BOOST_CHECK( idx.empty() );
};
{
const asset_object& bitusd = setup_asset();
top_up();
set_price( bitusd, bitusd.amount(1) / core.amount(5) ); // $0.20
borrow(borrower, bitusd.amount(100), asset(1000)); // 2x collat
transfer( borrower, settler, bitusd.amount(100) );
// drop to $0.02 and settle
BOOST_CHECK( !bitusd.bitasset_data(db).has_settlement() );
set_price( bitusd, bitusd.amount(1) / core.amount(50) ); // $0.02
BOOST_CHECK( bitusd.bitasset_data(db).has_settlement() );
GRAPHENE_REQUIRE_THROW( borrow( borrower2, bitusd.amount(100), asset(10000) ), fc::exception );
force_settle( settler, bitusd.amount(100) );
// wait for forced settlement to execute
// this would throw on Sep.18 testnet, see #346
wait_for_settlement();
}
// issue 350
{
// ok, new asset
const asset_object& bitusd = setup_asset();
top_up();
set_price( bitusd, bitusd.amount(40) / core.amount(1000) ); // $0.04
borrow( borrower, bitusd.amount(100), asset(5000) ); // 2x collat
transfer( borrower, seller, bitusd.amount(100) );
limit_order_id_type oid_019 = create_sell_order( seller, bitusd.amount(39), core.amount(2000) )->id; // this order is at $0.019, we should not be able to match against it
limit_order_id_type oid_020 = create_sell_order( seller, bitusd.amount(40), core.amount(2000) )->id; // this order is at $0.020, we should be able to match against it
set_price( bitusd, bitusd.amount(21) / core.amount(1000) ); // $0.021
BOOST_CHECK( !bitusd.bitasset_data(db).has_settlement() );
BOOST_CHECK( db.find_object( oid_019 ) != nullptr );
BOOST_CHECK( db.find_object( oid_020 ) == nullptr );
}
} catch( const fc::exception& e) {
edump((e.to_detail_string()));
throw;
}
}
BOOST_AUTO_TEST_CASE( prediction_market )
{ try {
ACTORS((judge)(dan)(nathan));