diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index cf5239af..4ec4f5b8 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -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: { } } } diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 9dcbda94..a3c73eee 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -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 application::get_plugin(const string& name) const diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 3d371bae..5554f5ab 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -92,6 +92,7 @@ class database_api_impl : public std::enable_shared_from_this std::string get_transaction_hex(const signed_transaction& trx)const; set get_required_signatures( const signed_transaction& trx, const flat_set& available_keys )const; set get_potential_signatures( const signed_transaction& trx )const; + set
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& signers )const; processed_transaction validate_transaction( const signed_transaction& trx )const; @@ -1213,6 +1214,10 @@ set database_api::get_potential_signatures( const signed_transa { return my->get_potential_signatures( trx ); } +set
database_api::get_potential_address_signatures( const signed_transaction& trx )const +{ + return my->get_potential_address_signatures( trx ); +} set database_api_impl::get_potential_signatures( const signed_transaction& trx )const { @@ -1242,6 +1247,31 @@ set database_api_impl::get_potential_signatures( const signed_t return result; } +set
database_api_impl::get_potential_address_signatures( const signed_transaction& trx )const +{ + set
result; + trx.get_required_signatures( + _db.get_chain_id(), + flat_set(), + [&]( 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 ); diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 4aedd0ed..a0503fd6 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -416,6 +416,7 @@ class database_api * to get the minimum subset. */ set get_potential_signatures( const signed_transaction& trx )const; + set
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) diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index a7714376..9b7f6fbd 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -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) ) } diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 78533129..e19c218d 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -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& checkpts _checkpoints[i.first] = i.second; } +bool database::before_last_checkpoint()const +{ + return (_checkpoints.size() > 0) && (_checkpoints.rbegin()->first >= head_block_num()); +} + } } diff --git a/libraries/chain/db_debug.cpp b/libraries/chain/db_debug.cpp index a792b741..9e5fc58f 100644 --- a/libraries/chain/db_debug.cpp +++ b/libraries/chain/db_debug.cpp @@ -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().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().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>(); for( const auto& s : vbidx ) { - idump(("vesting_balance")(s)); +// idump(("vesting_balance")(s)); } + */ } } } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index eb01d668..1bd467f8 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -194,6 +195,7 @@ void database::initialize_indexes() add_index< primary_index> >(); add_index< primary_index > >(); add_index< primary_index > >(); + add_index< primary_index > >(); } 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(); + modify( owner_account_id(*this).statistics(*this), [&]( account_statistics_object& o ) { + o.total_core_in_orders = collateral_rec.collateral; + }); + create([&](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() } diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index b6a4ae98..c5e108f2 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -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 @@ -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(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().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. } diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 5193eee5..6d26984d 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -21,6 +21,9 @@ #include #include +#include + +#include #include #include @@ -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(); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 54fa666a..f6b87393 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -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 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); } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 86e14da4..c67a6b6b 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -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(); + const auto& call_price_index = call_index.indices().get(); + + const limit_order_index& limit_index = get_index_type(); + const auto& limit_price_index = limit_index.indices().get(); + + // 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; diff --git a/libraries/chain/get_config.cpp b/libraries/chain/get_config.cpp index 1d81ecfb..8f72bf28 100644 --- a/libraries/chain/get_config.cpp +++ b/libraries/chain/get_config.cpp @@ -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; diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 675f11b8..27c80523 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -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 + class account_object : public graphene::db::abstract_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 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 whitelisted_accounts; + set 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 - { - 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
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::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) diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index f34fa849..6586c5a7 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -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 + class asset_object : public graphene::db::abstract_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), +FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object), (symbol) (precision) (issuer) diff --git a/libraries/chain/include/graphene/chain/budget_record_object.hpp b/libraries/chain/include/graphene/chain/budget_record_object.hpp new file mode 100644 index 00000000..679889f7 --- /dev/null +++ b/libraries/chain/include/graphene/chain/budget_record_object.hpp @@ -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 +#include +#include + +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 +{ + 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) +) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 3ad559ea..1f1bda1c 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -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 */ diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 59d3c4c5..be39b48a 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -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& checkpts ); const flat_map 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); diff --git a/libraries/chain/include/graphene/chain/db_with.hpp b/libraries/chain/include/graphene/chain/db_with.hpp index d8748ea9..5a300e34 100644 --- a/libraries/chain/include/graphene/chain/db_with.hpp +++ b/libraries/chain/include/graphene/chain/db_with.hpp @@ -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 }; /** diff --git a/libraries/chain/include/graphene/chain/exceptions.hpp b/libraries/chain/include/graphene/chain/exceptions.hpp index 7bdc7ca5..3244236e 100644 --- a/libraries/chain/include/graphene/chain/exceptions.hpp +++ b/libraries/chain/include/graphene/chain/exceptions.hpp @@ -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" ) diff --git a/libraries/chain/include/graphene/chain/fork_database.hpp b/libraries/chain/include/graphene/chain/fork_database.hpp index 2d65ccc4..39f513dc 100644 --- a/libraries/chain/include/graphene/chain/fork_database.hpp +++ b/libraries/chain/include/graphene/chain/fork_database.hpp @@ -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. diff --git a/libraries/chain/include/graphene/chain/genesis_state.hpp b/libraries/chain/include/graphene/chain/genesis_state.hpp index 5780acdc..7cd710b6 100644 --- a/libraries/chain/include/graphene/chain/genesis_state.hpp +++ b/libraries/chain/include/graphene/chain/genesis_state.hpp @@ -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_assets; vector initial_balances; vector initial_vesting_balances; - uint64_t initial_active_witnesses; + uint64_t initial_active_witnesses = GRAPHENE_DEFAULT_MIN_WITNESS_COUNT; vector initial_witness_candidates; vector initial_committee_candidates; vector initial_worker_candidates; diff --git a/libraries/chain/include/graphene/chain/global_property_object.hpp b/libraries/chain/include/graphene/chain/global_property_object.hpp index 568ded67..ac272156 100644 --- a/libraries/chain/include/graphene/chain/global_property_object.hpp +++ b/libraries/chain/include/graphene/chain/global_property_object.hpp @@ -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), diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index 48f4d5f6..5f60c12f 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -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 + class force_settlement_object : public abstract_object { public: static const uint8_t space_id = protocol_ids; diff --git a/libraries/chain/include/graphene/chain/node_property_object.hpp b/libraries/chain/include/graphene/chain/node_property_object.hpp index 92addff9..559fc354 100644 --- a/libraries/chain/include/graphene/chain/node_property_object.hpp +++ b/libraries/chain/include/graphene/chain/node_property_object.hpp @@ -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 diff --git a/libraries/chain/include/graphene/chain/protocol/account.hpp b/libraries/chain/include/graphene/chain/protocol/account.hpp index c68334d6..f1da673d 100644 --- a/libraries/chain/include/graphene/chain/protocol/account.hpp +++ b/libraries/chain/include/graphene/chain/protocol/account.hpp @@ -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; } diff --git a/libraries/chain/include/graphene/chain/protocol/authority.hpp b/libraries/chain/include/graphene/chain/protocol/authority.hpp index 0c7d1ac0..b297fb6b 100644 --- a/libraries/chain/include/graphene/chain/protocol/authority.hpp +++ b/libraries/chain/include/graphene/chain/protocol/authority.hpp @@ -83,6 +83,15 @@ namespace graphene { namespace chain { result.push_back(k.first); return result; } + vector
get_addresses() const + { + vector
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 ) { diff --git a/libraries/chain/include/graphene/chain/protocol/custom.hpp b/libraries/chain/include/graphene/chain/protocol/custom.hpp index 5d3f8b2a..3c74dc62 100644 --- a/libraries/chain/include/graphene/chain/protocol/custom.hpp +++ b/libraries/chain/include/graphene/chain/protocol/custom.hpp @@ -21,7 +21,7 @@ namespace graphene { namespace chain { asset fee; account_id_type payer; flat_set required_auths; - uint16_t id; + uint16_t id = 0; vector data; account_id_type fee_payer()const { return payer; } diff --git a/libraries/chain/include/graphene/chain/protocol/memo.hpp b/libraries/chain/include/graphene/chain/protocol/memo.hpp index b5bec350..84c6bb14 100644 --- a/libraries/chain/include/graphene/chain/protocol/memo.hpp +++ b/libraries/chain/include/graphene/chain/protocol/memo.hpp @@ -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 */ diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 0030c1f1..e06f7600 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -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 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) ) diff --git a/libraries/chain/include/graphene/chain/protocol/withdraw_permission.hpp b/libraries/chain/include/graphene/chain/protocol/withdraw_permission.hpp index 2ae4ca33..397dc2fc 100644 --- a/libraries/chain/include/graphene/chain/protocol/withdraw_permission.hpp +++ b/libraries/chain/include/graphene/chain/protocol/withdraw_permission.hpp @@ -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; diff --git a/libraries/chain/include/graphene/chain/witness_object.hpp b/libraries/chain/include/graphene/chain/witness_object.hpp index 20df80fe..e3ac8fba 100644 --- a/libraries/chain/include/graphene/chain/witness_object.hpp +++ b/libraries/chain/include/graphene/chain/witness_object.hpp @@ -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) ) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 786a56ad..77f15cf6 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -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. diff --git a/libraries/chain/protocol/fee_schedule.cpp b/libraries/chain/protocol/fee_schedule.cpp index 05d9df99..93f454a1 100644 --- a/libraries/chain/protocol/fee_schedule.cpp +++ b/libraries/chain/protocol/fee_schedule.cpp @@ -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; } diff --git a/libraries/chain/protocol/proposal.cpp b/libraries/chain/protocol/proposal.cpp index 46ca683d..e4cc71a3 100644 --- a/libraries/chain/protocol/proposal.cpp +++ b/libraries/chain/protocol/proposal.cpp @@ -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; diff --git a/libraries/chain/protocol/transaction.cpp b/libraries/chain/protocol/transaction.cpp index 86c8a210..c0740d2e 100644 --- a/libraries/chain/protocol/transaction.cpp +++ b/libraries/chain/protocol/transaction.cpp @@ -114,6 +114,42 @@ struct sign_state return itr->second = true; } + optional> available_address_sigs; + optional> provided_address_sigs; + + bool signed_by( const address& a ) { + if( !available_address_sigs ) { + available_address_sigs = std::map(); + provided_address_sigs = std::map(); + 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() ) diff --git a/libraries/db/include/graphene/db/object_id.hpp b/libraries/db/include/graphene/db/object_id.hpp index eb784f90..00a616e6 100644 --- a/libraries/db/include/graphene/db/object_id.hpp +++ b/libraries/db/include/graphene/db/object_id.hpp @@ -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() ); + return T( *this ); + } + uint64_t number; }; diff --git a/libraries/db/include/graphene/db/undo_database.hpp b/libraries/db/include/graphene/db/undo_database.hpp index 4cf206ee..ab87998d 100644 --- a/libraries/db/include/graphene/db/undo_database.hpp +++ b/libraries/db/include/graphene/db/undo_database.hpp @@ -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 */ diff --git a/libraries/db/object_database.cpp b/libraries/db/object_database.cpp index a5b9f530..d7d81a25 100644 --- a/libraries/db/object_database.cpp +++ b/libraries/db/object_database.cpp @@ -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 ); diff --git a/libraries/db/undo_database.cpp b/libraries/db/undo_database.cpp index 9534f683..945163fe 100644 --- a/libraries/db/undo_database.cpp +++ b/libraries/db/undo_database.cpp @@ -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 ) { diff --git a/libraries/net/include/graphene/net/config.hpp b/libraries/net/include/graphene/net/config.hpp index 5273be0f..6894680f 100644 --- a/libraries/net/include/graphene/net/config.hpp +++ b/libraries/net/include/graphene/net/config.hpp @@ -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 diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index fc49f0f3..1eda204d 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -1108,39 +1108,81 @@ namespace graphene { namespace net { namespace detail { fc::time_point next_peer_unblocked_time = fc::time_point::maximum(); - std::forward_list > fetch_messages_to_send; - std::vector > 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_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 >, + boost::multi_index::ordered_non_unique, + boost::multi_index::const_mem_fun > > > 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().begin(); peer_iter != items_by_peer.get().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().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{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 > 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; diff --git a/libraries/plugins/delayed_node/delayed_node_plugin.cpp b/libraries/plugins/delayed_node/delayed_node_plugin.cpp index 208fa566..5e9e1bc8 100644 --- a/libraries/plugins/delayed_node/delayed_node_plugin.cpp +++ b/libraries/plugins/delayed_node/delayed_node_plugin.cpp @@ -17,8 +17,8 @@ */ #include +#include #include -#include #include #include @@ -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 client_connection; fc::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()->required(), "RPC endpoint of a trusted validating node (required)") - ("delay-block-count", boost::program_options::value()->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(); - my->delay_blocks = options.at("delay-block-count").as(); } -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 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 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::dynamic_global_property_id_type() ) - { - auto props = v.as(); - 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)); } } } diff --git a/libraries/plugins/delayed_node/include/graphene/delayed_node/delayed_node_plugin.hpp b/libraries/plugins/delayed_node/include/graphene/delayed_node/delayed_node_plugin.hpp index c25b1203..3865411e 100644 --- a/libraries/plugins/delayed_node/include/graphene/delayed_node/delayed_node_plugin.hpp +++ b/libraries/plugins/delayed_node/include/graphene/delayed_node/delayed_node_plugin.hpp @@ -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 diff --git a/libraries/plugins/market_history/market_history_plugin.cpp b/libraries/plugins/market_history/market_history_plugin.cpp index ec317d86..67f3423f 100644 --- a/libraries/plugins/market_history/market_history_plugin.cpp +++ b/libraries/plugins/market_history/market_history_plugin.cpp @@ -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& b ){ + /* const auto& obj = */ + db.create( [&]( 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 diff --git a/libraries/wallet/include/graphene/wallet/reflect_util.hpp b/libraries/wallet/include/graphene/wallet/reflect_util.hpp new file mode 100644 index 00000000..d32c96ef --- /dev/null +++ b/libraries/wallet/include/graphene/wallet/reflect_util.hpp @@ -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::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 +static_variant_map create_static_variant_map() +{ + T dummy; + int n = dummy.count(); + impl::static_variant_map_visitor vtor; + for( int i=0; i #include +#include #include #include #include #include +#include #include #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::max() ); + scale = uint32_t( _scale ); + continue; + } + // is key a number? + auto is_numeric = [&]() -> bool + { + size_t n = key.size(); + for( size_t i=0; isecond; + } + + 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 _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( diff --git a/programs/genesis_util/remove.py b/programs/genesis_util/remove.py new file mode 100755 index 00000000..ec464258 --- /dev/null +++ b/programs/genesis_util/remove.py @@ -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() diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index e3c69605..ad408fcb 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -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), diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index f3755452..0ef0eeaf 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -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 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().indices().get(); + 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));